From 940abb18e80ce5e244fa97a12fd3e15698a0a084 Mon Sep 17 00:00:00 2001 From: vikingowl Date: Wed, 31 Dec 2025 20:08:37 +0100 Subject: [PATCH] fix: improve tool descriptions and enable recursive tool calling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Simplify and clarify all tool descriptions for better model understanding - Enable recursive tool calling - model can now chain multiple tools - Pass tools on follow-up calls so model can call more tools after seeing results - Update tool result message to encourage calling additional tools if needed - Include suggestion in error messages so model knows what to do on failure - Fix StreamingIndicator visibility with explicit colors 🤖 Generated with [Claude Code](https://claude.ai/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../src/lib/components/chat/ChatWindow.svelte | 2 +- frontend/src/lib/tools/builtin.ts | 26 ++++++++--------- frontend/src/lib/tools/executor.ts | 7 ++++- frontend/src/routes/+page.svelte | 29 +++++++++++++++++-- 4 files changed, 46 insertions(+), 18 deletions(-) diff --git a/frontend/src/lib/components/chat/ChatWindow.svelte b/frontend/src/lib/components/chat/ChatWindow.svelte index 92da478..161e00a 100644 --- a/frontend/src/lib/components/chat/ChatWindow.svelte +++ b/frontend/src/lib/components/chat/ChatWindow.svelte @@ -432,7 +432,7 @@ // Add tool results as a system/tool message to context const toolMessageId = chatState.addMessage({ role: 'user', // Ollama expects tool results in a user-like message - content: `Tool execution results:\n${toolResultContent}\n\nPlease provide a response based on these results.` + content: `Tool execution results:\n${toolResultContent}\n\nBased on these results, either provide a helpful response OR call another tool if you need more information.` }); if (conversationId) { diff --git a/frontend/src/lib/tools/builtin.ts b/frontend/src/lib/tools/builtin.ts index 830d19a..8817e6d 100644 --- a/frontend/src/lib/tools/builtin.ts +++ b/frontend/src/lib/tools/builtin.ts @@ -17,17 +17,17 @@ const getTimeDefinition: ToolDefinition = { type: 'function', function: { name: 'get_current_time', - description: 'Get the current date and time. Can optionally specify timezone and format.', + description: 'Returns the current date and time. Use when you need to know what time or date it is now.', parameters: { type: 'object', properties: { timezone: { type: 'string', - description: 'IANA timezone name (e.g., "America/New_York", "Europe/London"). Defaults to local timezone.' + description: 'Optional IANA timezone (e.g., "America/New_York", "Europe/Berlin"). Defaults to local.' }, format: { type: 'string', - description: 'Output format: "iso" for ISO 8601, "locale" for localized string, "unix" for Unix timestamp.', + description: 'Output format: "iso" (default), "locale", or "unix" timestamp.', enum: ['iso', 'locale', 'unix'] } } @@ -93,17 +93,17 @@ const calculateDefinition: ToolDefinition = { type: 'function', function: { name: 'calculate', - description: 'Compute a mathematical expression. Supports basic arithmetic (+, -, *, /, ^), parentheses, and common functions (sqrt, sin, cos, tan, log, exp, abs, round, floor, ceil).', + description: 'Evaluates a math expression and returns the numeric result. Use for any calculations, unit conversions, or math problems.', parameters: { type: 'object', properties: { expression: { type: 'string', - description: 'The mathematical expression to compute (e.g., "2 + 2", "sqrt(16)", "sin(3.14159/2)")' + description: 'Math expression to evaluate. Examples: "2+2", "sqrt(16)", "100*1.19", "sin(3.14/2)"' }, precision: { type: 'number', - description: 'Number of decimal places for the result (default: 10)' + description: 'Decimal places in result (default: 10)' } }, required: ['expression'] @@ -297,22 +297,22 @@ const fetchUrlDefinition: ToolDefinition = { type: 'function', function: { name: 'fetch_url', - description: 'Fetch content from a URL and extract text, title, or links. Useful for retrieving web page content.', + description: 'Fetches and reads content from a specific URL. Use after web_search to read full content from a result URL, or when user provides a URL directly.', parameters: { type: 'object', properties: { url: { type: 'string', - description: 'The URL to fetch (must be a valid HTTP/HTTPS URL)' + description: 'The URL to fetch (e.g., "https://example.com/page")' }, extract: { type: 'string', - description: 'What to extract: "text" for main content, "title" for page title, "links" for all links, "all" for everything', + description: 'What to extract: "text" (default), "title", "links", or "all"', enum: ['text', 'title', 'links', 'all'] }, maxLength: { type: 'number', - description: 'Maximum length of extracted text (default: 5000 characters)' + description: 'Max text length (default: 5000)' } }, required: ['url'] @@ -459,13 +459,13 @@ const getLocationDefinition: ToolDefinition = { type: 'function', function: { name: 'get_location', - description: 'Get the user\'s current location (city, country, coordinates). Call this IMMEDIATELY when you need location for weather, local info, or nearby places. Do NOT ask the user where they are - use this tool instead.', + description: 'Gets the user\'s current city and country from their device GPS. Returns location that can be used with web_search. If this fails, ask user for their location.', parameters: { type: 'object', properties: { highAccuracy: { type: 'boolean', - description: 'Whether to request high accuracy GPS location (may take longer and use more battery). Default is false.' + description: 'Request high accuracy GPS (slower). Default: false' } } } @@ -569,7 +569,7 @@ const webSearchDefinition: ToolDefinition = { type: 'function', function: { name: 'web_search', - description: 'Search the web for current information. You MUST call this tool immediately when the user asks about weather, news, current events, sports, stocks, prices, or any real-time information. Do NOT ask the user for clarification - just search. If no location is specified for weather, call get_location first.', + description: 'Searches the internet and returns results with titles, URLs, and snippets. Use for weather, news, current events, facts, prices, or any real-time information. Include location in query for local results (e.g., "weather Munich" not just "weather"). Can call fetch_url on result URLs to get full content.', parameters: { type: 'object', properties: { diff --git a/frontend/src/lib/tools/executor.ts b/frontend/src/lib/tools/executor.ts index dbf9eb5..1347cbe 100644 --- a/frontend/src/lib/tools/executor.ts +++ b/frontend/src/lib/tools/executor.ts @@ -217,10 +217,15 @@ export async function runToolCall( // Check if result is an error object if (result && typeof result === 'object' && 'error' in result) { + const errorObj = result as { error: unknown; suggestion?: string }; + // Include suggestion in error message if present + const errorMsg = errorObj.suggestion + ? `${String(errorObj.error)}. ${errorObj.suggestion}` + : String(errorObj.error); return { toolCallId: id, success: false, - error: String((result as { error: unknown }).error) + error: errorMsg }; } diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 82b41b4..9066ecd 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -260,7 +260,7 @@ // Add tool results as a message const toolMessageId = chatState.addMessage({ role: 'user', - content: `Tool execution results:\n${toolResultContent}\n\nPlease provide a response based on these results.` + content: `Tool execution results:\n${toolResultContent}\n\nBased on these results, either provide a helpful response OR call another tool if you need more information.` }); await addStoredMessage( @@ -270,7 +270,7 @@ toolMessageId ); - // Stream final response using original model + // Stream final response using original model - WITH tools so it can call more if needed const finalMessageId = chatState.startStreaming(); const allMessages = chatState.visibleMessages.map(node => ({ @@ -279,14 +279,37 @@ images: node.message.images })) as OllamaMessage[]; + // Track if model wants to call more tools + let morePendingToolCalls: OllamaToolCall[] | null = null; + + // Pass tools again so model can chain tool calls + const tools = getToolsForApi(); + await ollamaClient.streamChatWithCallbacks( - { model, messages: allMessages }, + { model, messages: allMessages, tools }, { onToken: (token) => { chatState.appendToStreaming(token); }, + onToolCall: (newToolCalls) => { + morePendingToolCalls = newToolCalls; + console.log('[NewChat] Additional tool calls received:', newToolCalls); + }, onComplete: async () => { chatState.finishStreaming(); + + // If model wants to call more tools, recurse + if (morePendingToolCalls && morePendingToolCalls.length > 0) { + await executeToolsAndContinue( + model, + finalMessageId, + toolMessageId, + morePendingToolCalls, + conversationId + ); + return; + } + const node = chatState.messageTree.get(finalMessageId); if (node) { await addStoredMessage(