diff --git a/apps/server/src/services/llm/constants/llm_prompt_constants.ts b/apps/server/src/services/llm/constants/llm_prompt_constants.ts index 9c9a2e0ae..63dc72987 100644 --- a/apps/server/src/services/llm/constants/llm_prompt_constants.ts +++ b/apps/server/src/services/llm/constants/llm_prompt_constants.ts @@ -213,7 +213,23 @@ Be concise and informative in your responses. ${context} -Based on this information, please answer: ${query}` +Based on this information, please answer: ${query}`, + + // Tool instructions for Ollama + TOOL_INSTRUCTIONS: ` +CRITICAL INSTRUCTIONS FOR TOOL USAGE: +1. YOU MUST TRY MULTIPLE TOOLS AND SEARCH VARIATIONS before concluding information isn't available +2. ALWAYS PERFORM AT LEAST 3 DIFFERENT SEARCHES with different parameters before giving up on finding information +3. If a search returns no results, IMMEDIATELY TRY ANOTHER SEARCH with different parameters: + - Use broader terms: If "Kubernetes deployment" fails, try just "Kubernetes" or "container orchestration" + - Try synonyms: If "meeting notes" fails, try "conference", "discussion", or "conversation" + - Remove specific qualifiers: If "quarterly financial report 2024" fails, try just "financial report" + - Try semantic variations: If keyword_search fails, use vector_search which finds conceptually related content +4. CHAIN TOOLS TOGETHER: Use the results of one tool to inform parameters for the next tool +5. NEVER respond with "there are no notes about X" until you've tried at least 3 different search variations +6. DO NOT ask the user what to do next when searches fail - AUTOMATICALLY try different approaches +7. ALWAYS EXPLAIN what you're doing: "I didn't find results for X, so I'm now searching for Y instead" +8. If all reasonable search variations fail (minimum 3 attempts), THEN you may inform the user that the information might not be in their notes` }, // Common prompts across providers diff --git a/apps/server/src/services/llm/formatters/ollama_formatter.ts b/apps/server/src/services/llm/formatters/ollama_formatter.ts index 34a422a19..eb780f760 100644 --- a/apps/server/src/services/llm/formatters/ollama_formatter.ts +++ b/apps/server/src/services/llm/formatters/ollama_formatter.ts @@ -1,7 +1,7 @@ import type { Message } from '../ai_interface.js'; import { BaseMessageFormatter } from './base_formatter.js'; import sanitizeHtml from 'sanitize-html'; -import { PROVIDER_PROMPTS, FORMATTING_PROMPTS } from '../constants/llm_prompt_constants.js'; +import { PROVIDER_PROMPTS } from '../constants/llm_prompt_constants.js'; import { LLM_CONSTANTS } from '../constants/provider_constants.js'; import { HTML_ALLOWED_TAGS, @@ -29,7 +29,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter { * @param context Optional context to include * @param preserveSystemPrompt When true, preserves existing system messages rather than replacing them */ - formatMessages(messages: Message[], systemPrompt?: string, context?: string, preserveSystemPrompt?: boolean): Message[] { + formatMessages(messages: Message[], systemPrompt?: string, context?: string, preserveSystemPrompt?: boolean, useTools?: boolean): Message[] { const formattedMessages: Message[] = []; // Log the input messages with all their properties @@ -37,7 +37,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter { messages.forEach((msg, index) => { const msgKeys = Object.keys(msg); log.info(`Message ${index} - role: ${msg.role}, keys: ${msgKeys.join(', ')}, content length: ${msg.content.length}`); - + // Log special properties if present if (msg.tool_calls) { log.info(`Message ${index} has ${msg.tool_calls.length} tool_calls`); @@ -61,7 +61,19 @@ export class OllamaMessageFormatter extends BaseMessageFormatter { log.info(`Preserving existing system message: ${systemMessages[0].content.substring(0, 50)}...`); } else { // Use provided systemPrompt or default - const basePrompt = systemPrompt || PROVIDER_PROMPTS.COMMON.DEFAULT_ASSISTANT_INTRO; + let basePrompt = systemPrompt || PROVIDER_PROMPTS.COMMON.DEFAULT_ASSISTANT_INTRO; + + // Check if any message has tool_calls or if useTools flag is set, indicating this is a tool-using conversation + const hasPreviousToolCalls = messages.some(msg => msg.tool_calls && msg.tool_calls.length > 0); + const hasToolResults = messages.some(msg => msg.role === 'tool'); + const isToolUsingConversation = useTools || hasPreviousToolCalls || hasToolResults; + + // Add tool instructions for Ollama when tools are being used + if (isToolUsingConversation && PROVIDER_PROMPTS.OLLAMA.TOOL_INSTRUCTIONS) { + log.info('Adding tool instructions to system prompt for Ollama'); + basePrompt = `${basePrompt}\n\n${PROVIDER_PROMPTS.OLLAMA.TOOL_INSTRUCTIONS}`; + } + formattedMessages.push({ role: 'system', content: basePrompt @@ -96,7 +108,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter { ...msg, // Copy all properties content: formattedContext // Override content with injected context }; - + formattedMessages.push(newMessage); log.info(`Created user message with context, final keys: ${Object.keys(newMessage).join(', ')}`); @@ -104,7 +116,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter { } else { // For other messages, preserve all properties including any tool-related ones log.info(`Preserving message with role ${msg.role}, keys: ${Object.keys(msg).join(', ')}`); - + formattedMessages.push({ ...msg // Copy all properties }); @@ -126,7 +138,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter { formattedMessages.forEach((msg, index) => { const msgKeys = Object.keys(msg); log.info(`Formatted message ${index} - role: ${msg.role}, keys: ${msgKeys.join(', ')}, content length: ${msg.content.length}`); - + // Log special properties if present if (msg.tool_calls) { log.info(`Formatted message ${index} has ${msg.tool_calls.length} tool_calls`); @@ -151,13 +163,11 @@ export class OllamaMessageFormatter extends BaseMessageFormatter { if (!content) return ''; try { - // Store our XML tags so we can restore them after cleaning - const noteTagsRegex = /<\/?note>/g; + // Define regexes for identifying and preserving tagged content const notesTagsRegex = /<\/?notes>/g; - const queryTagsRegex = /<\/?query>[^<]*<\/query>/g; + // const queryTagsRegex = /<\/?query>/g; // Commenting out unused variable // Capture tags to restore later - const noteTags = content.match(noteTagsRegex) || []; const noteTagPositions: number[] = []; let match; const regex = /<\/?note>/g; @@ -166,17 +176,15 @@ export class OllamaMessageFormatter extends BaseMessageFormatter { } // Remember the notes tags - const notesTagsMatch = content.match(notesTagsRegex) || []; const notesTagPositions: number[] = []; while ((match = notesTagsRegex.exec(content)) !== null) { notesTagPositions.push(match.index); } - // Remember the query tags - const queryTagsMatch = content.match(queryTagsRegex) || []; + // Remember the query tag // Temporarily replace XML tags with markers that won't be affected by sanitization - let modified = content + const modified = content .replace(//g, '[NOTE_START]') .replace(/<\/note>/g, '[NOTE_END]') .replace(//g, '[NOTES_START]') @@ -184,7 +192,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter { .replace(/(.*?)<\/query>/g, '[QUERY]$1[/QUERY]'); // First use the parent class to do standard cleaning - let sanitized = super.cleanContextContent(modified); + const sanitized = super.cleanContextContent(modified); // Then apply Ollama-specific aggressive cleaning // Remove any remaining HTML using sanitizeHtml while keeping our markers diff --git a/apps/server/src/services/llm/providers/ollama_service.ts b/apps/server/src/services/llm/providers/ollama_service.ts index cb7f3cd74..ce392b6f1 100644 --- a/apps/server/src/services/llm/providers/ollama_service.ts +++ b/apps/server/src/services/llm/providers/ollama_service.ts @@ -144,14 +144,19 @@ export class OllamaService extends BaseAIService { messagesToSend = [...messages]; log.info(`Bypassing formatter for Ollama request with ${messages.length} messages`); } else { + // Determine if tools will be used in this request + const willUseTools = providerOptions.enableTools !== false; + // Use the formatter to prepare messages messagesToSend = this.formatter.formatMessages( messages, systemPrompt, undefined, // context - providerOptions.preserveSystemPrompt + providerOptions.preserveSystemPrompt, + willUseTools // Pass flag indicating if tools will be used ); - log.info(`Sending to Ollama with formatted messages: ${messagesToSend.length}`); + + log.info(`Sending to Ollama with formatted messages: ${messagesToSend.length}${willUseTools ? ' (with tool instructions)' : ''}`); } // Get tools if enabled