feat(llm): really try to coax ollama to run tools

This commit is contained in:
perf3ct 2025-05-29 21:24:04 +00:00
parent 87859aec1c
commit 8f8b9d9e3b
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
3 changed files with 48 additions and 19 deletions

View File

@ -213,7 +213,23 @@ Be concise and informative in your responses.
${context} ${context}
Based on this information, please answer: <query>${query}</query>` Based on this information, please answer: <query>${query}</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 // Common prompts across providers

View File

@ -1,7 +1,7 @@
import type { Message } from '../ai_interface.js'; import type { Message } from '../ai_interface.js';
import { BaseMessageFormatter } from './base_formatter.js'; import { BaseMessageFormatter } from './base_formatter.js';
import sanitizeHtml from 'sanitize-html'; 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 { LLM_CONSTANTS } from '../constants/provider_constants.js';
import { import {
HTML_ALLOWED_TAGS, HTML_ALLOWED_TAGS,
@ -29,7 +29,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter {
* @param context Optional context to include * @param context Optional context to include
* @param preserveSystemPrompt When true, preserves existing system messages rather than replacing them * @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[] = []; const formattedMessages: Message[] = [];
// Log the input messages with all their properties // Log the input messages with all their properties
@ -37,7 +37,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter {
messages.forEach((msg, index) => { messages.forEach((msg, index) => {
const msgKeys = Object.keys(msg); const msgKeys = Object.keys(msg);
log.info(`Message ${index} - role: ${msg.role}, keys: ${msgKeys.join(', ')}, content length: ${msg.content.length}`); log.info(`Message ${index} - role: ${msg.role}, keys: ${msgKeys.join(', ')}, content length: ${msg.content.length}`);
// Log special properties if present // Log special properties if present
if (msg.tool_calls) { if (msg.tool_calls) {
log.info(`Message ${index} has ${msg.tool_calls.length} 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)}...`); log.info(`Preserving existing system message: ${systemMessages[0].content.substring(0, 50)}...`);
} else { } else {
// Use provided systemPrompt or default // 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({ formattedMessages.push({
role: 'system', role: 'system',
content: basePrompt content: basePrompt
@ -96,7 +108,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter {
...msg, // Copy all properties ...msg, // Copy all properties
content: formattedContext // Override content with injected context content: formattedContext // Override content with injected context
}; };
formattedMessages.push(newMessage); formattedMessages.push(newMessage);
log.info(`Created user message with context, final keys: ${Object.keys(newMessage).join(', ')}`); log.info(`Created user message with context, final keys: ${Object.keys(newMessage).join(', ')}`);
@ -104,7 +116,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter {
} else { } else {
// For other messages, preserve all properties including any tool-related ones // For other messages, preserve all properties including any tool-related ones
log.info(`Preserving message with role ${msg.role}, keys: ${Object.keys(msg).join(', ')}`); log.info(`Preserving message with role ${msg.role}, keys: ${Object.keys(msg).join(', ')}`);
formattedMessages.push({ formattedMessages.push({
...msg // Copy all properties ...msg // Copy all properties
}); });
@ -126,7 +138,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter {
formattedMessages.forEach((msg, index) => { formattedMessages.forEach((msg, index) => {
const msgKeys = Object.keys(msg); const msgKeys = Object.keys(msg);
log.info(`Formatted message ${index} - role: ${msg.role}, keys: ${msgKeys.join(', ')}, content length: ${msg.content.length}`); log.info(`Formatted message ${index} - role: ${msg.role}, keys: ${msgKeys.join(', ')}, content length: ${msg.content.length}`);
// Log special properties if present // Log special properties if present
if (msg.tool_calls) { if (msg.tool_calls) {
log.info(`Formatted message ${index} has ${msg.tool_calls.length} 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 ''; if (!content) return '';
try { try {
// Store our XML tags so we can restore them after cleaning // Define regexes for identifying and preserving tagged content
const noteTagsRegex = /<\/?note>/g;
const notesTagsRegex = /<\/?notes>/g; const notesTagsRegex = /<\/?notes>/g;
const queryTagsRegex = /<\/?query>[^<]*<\/query>/g; // const queryTagsRegex = /<\/?query>/g; // Commenting out unused variable
// Capture tags to restore later // Capture tags to restore later
const noteTags = content.match(noteTagsRegex) || [];
const noteTagPositions: number[] = []; const noteTagPositions: number[] = [];
let match; let match;
const regex = /<\/?note>/g; const regex = /<\/?note>/g;
@ -166,17 +176,15 @@ export class OllamaMessageFormatter extends BaseMessageFormatter {
} }
// Remember the notes tags // Remember the notes tags
const notesTagsMatch = content.match(notesTagsRegex) || [];
const notesTagPositions: number[] = []; const notesTagPositions: number[] = [];
while ((match = notesTagsRegex.exec(content)) !== null) { while ((match = notesTagsRegex.exec(content)) !== null) {
notesTagPositions.push(match.index); notesTagPositions.push(match.index);
} }
// Remember the query tags // Remember the query tag
const queryTagsMatch = content.match(queryTagsRegex) || [];
// Temporarily replace XML tags with markers that won't be affected by sanitization // Temporarily replace XML tags with markers that won't be affected by sanitization
let modified = content const modified = content
.replace(/<note>/g, '[NOTE_START]') .replace(/<note>/g, '[NOTE_START]')
.replace(/<\/note>/g, '[NOTE_END]') .replace(/<\/note>/g, '[NOTE_END]')
.replace(/<notes>/g, '[NOTES_START]') .replace(/<notes>/g, '[NOTES_START]')
@ -184,7 +192,7 @@ export class OllamaMessageFormatter extends BaseMessageFormatter {
.replace(/<query>(.*?)<\/query>/g, '[QUERY]$1[/QUERY]'); .replace(/<query>(.*?)<\/query>/g, '[QUERY]$1[/QUERY]');
// First use the parent class to do standard cleaning // 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 // Then apply Ollama-specific aggressive cleaning
// Remove any remaining HTML using sanitizeHtml while keeping our markers // Remove any remaining HTML using sanitizeHtml while keeping our markers

View File

@ -144,14 +144,19 @@ export class OllamaService extends BaseAIService {
messagesToSend = [...messages]; messagesToSend = [...messages];
log.info(`Bypassing formatter for Ollama request with ${messages.length} messages`); log.info(`Bypassing formatter for Ollama request with ${messages.length} messages`);
} else { } else {
// Determine if tools will be used in this request
const willUseTools = providerOptions.enableTools !== false;
// Use the formatter to prepare messages // Use the formatter to prepare messages
messagesToSend = this.formatter.formatMessages( messagesToSend = this.formatter.formatMessages(
messages, messages,
systemPrompt, systemPrompt,
undefined, // context 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 // Get tools if enabled