-
-
Notifications
You must be signed in to change notification settings - Fork 6
feat: Implement Tool Coordinator (Fresh PR with Build Fixes) #378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f2bc7da
75e01df
0311b79
924bebd
612479d
9e2a308
37290d5
e4dbb38
328a80f
fa50fc1
0bc9475
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,7 +12,7 @@ import type { FeatureCollection } from 'geojson' | |||||||||||||||||
| import { Spinner } from '@/components/ui/spinner' | ||||||||||||||||||
| import { Section } from '@/components/section' | ||||||||||||||||||
| import { FollowupPanel } from '@/components/followup-panel' | ||||||||||||||||||
| import { inquire, researcher, taskManager, querySuggestor, resolutionSearch } from '@/lib/agents' | ||||||||||||||||||
| import { inquire, researcher, taskManager, querySuggestor, resolutionSearch, toolCoordinator, executeToolPlan, aggregateToolResults } from '@/lib/agents' | ||||||||||||||||||
| // Removed import of useGeospatialToolMcp as it no longer exists and was incorrectly used here. | ||||||||||||||||||
| // The geospatialTool (if used by agents like researcher) now manages its own MCP client. | ||||||||||||||||||
| import { writer } from '@/lib/agents/writer' | ||||||||||||||||||
|
|
@@ -125,6 +125,7 @@ async function submit(formData?: FormData, skip?: boolean) { | |||||||||||||||||
|
|
||||||||||||||||||
| const groupeId = nanoid() | ||||||||||||||||||
| const useSpecificAPI = process.env.USE_SPECIFIC_API_FOR_WRITER === 'true' | ||||||||||||||||||
| const useToolCoordinator = process.env.USE_TOOL_COORDINATOR === 'true' | ||||||||||||||||||
| const maxMessages = useSpecificAPI ? 5 : 10 | ||||||||||||||||||
| messages.splice(0, Math.max(messages.length - maxMessages, 0)) | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -319,17 +320,55 @@ async function submit(formData?: FormData, skip?: boolean) { | |||||||||||||||||
| const streamText = createStreamableValue<string>() | ||||||||||||||||||
| uiStream.update(<Spinner />) | ||||||||||||||||||
|
|
||||||||||||||||||
| let finalMessages = messages | ||||||||||||||||||
|
|
||||||||||||||||||
| if (useToolCoordinator) { | ||||||||||||||||||
| uiStream.update(<div><Spinner /> Planning tool execution...</div>) | ||||||||||||||||||
| try { | ||||||||||||||||||
| const plan = await toolCoordinator(messages) | ||||||||||||||||||
| uiStream.update(<div><Spinner /> Executing tool plan...</div>) | ||||||||||||||||||
| const results = await executeToolPlan(plan, { uiStream, fullResponse: answer }) | ||||||||||||||||||
| toolOutputs = results | ||||||||||||||||||
| const summary = aggregateToolResults(results, plan) | ||||||||||||||||||
|
|
||||||||||||||||||
| // Add the summary to the messages for the final synthesis agent | ||||||||||||||||||
| finalMessages = [ | ||||||||||||||||||
| ...messages, | ||||||||||||||||||
| { | ||||||||||||||||||
| id: nanoid(), | ||||||||||||||||||
| role: 'tool', | ||||||||||||||||||
| content: summary, | ||||||||||||||||||
| type: 'tool_coordinator_summary' | ||||||||||||||||||
| } as any // Cast to any to satisfy CoreMessage type for custom type | ||||||||||||||||||
| ] | ||||||||||||||||||
|
|
||||||||||||||||||
| // Stream a message to the user about the tool execution completion | ||||||||||||||||||
| uiStream.append( | ||||||||||||||||||
| <BotMessage content={createStreamableValue("Tool execution complete. Synthesizing final answer...").value} /> | ||||||||||||||||||
| ) | ||||||||||||||||||
|
Comment on lines
+346
to
+348
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Streamable value is never closed. The - uiStream.append(
- <BotMessage content={createStreamableValue("Tool execution complete. Synthesizing final answer...").value} />
- )
+ const completionMessage = createStreamableValue<string>()
+ completionMessage.done("Tool execution complete. Synthesizing final answer...")
+ uiStream.append(
+ <BotMessage content={completionMessage.value} />
+ )📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| } catch (e) { | ||||||||||||||||||
| console.error('Tool Coordinator failed:', e) | ||||||||||||||||||
| uiStream.append( | ||||||||||||||||||
| <BotMessage content="Tool Coordinator failed. Falling back to streaming researcher." /> | ||||||||||||||||||
| ) | ||||||||||||||||||
| // Fallback: continue with the original messages and let the researcher handle it | ||||||||||||||||||
| finalMessages = messages | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| while ( | ||||||||||||||||||
| useSpecificAPI | ||||||||||||||||||
| ? answer.length === 0 | ||||||||||||||||||
| : answer.length === 0 && !errorOccurred | ||||||||||||||||||
| ) { | ||||||||||||||||||
| // If coordinator was used, pass finalMessages and disable tools for researcher | ||||||||||||||||||
| const { fullResponse, hasError, toolResponses } = await researcher( | ||||||||||||||||||
| currentSystemPrompt, | ||||||||||||||||||
| uiStream, | ||||||||||||||||||
| streamText, | ||||||||||||||||||
| messages, | ||||||||||||||||||
| useSpecificAPI | ||||||||||||||||||
| finalMessages, | ||||||||||||||||||
| useSpecificAPI, | ||||||||||||||||||
| !useToolCoordinator // Pass a flag to disable tools if coordinator was used | ||||||||||||||||||
| ) | ||||||||||||||||||
| answer = fullResponse | ||||||||||||||||||
| toolOutputs = toolResponses | ||||||||||||||||||
|
|
||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,197 @@ | ||||||||||||||||||||||||||||||||||
| import { generateObject } from 'ai' | ||||||||||||||||||||||||||||||||||
| import { z } from 'zod' | ||||||||||||||||||||||||||||||||||
| import { CoreMessage } from 'ai' | ||||||||||||||||||||||||||||||||||
| import { getTools } from '@/lib/agents/tools' | ||||||||||||||||||||||||||||||||||
| import { ToolResultPart } from '@/lib/types' | ||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify
🏁 Script executed: #!/bin/bash
# Check if ToolResultPart is defined in lib/types and compare with ai package usage
echo "=== Checking lib/types for ToolResultPart ==="
fd -t f -e ts -e tsx 'types' lib --exec grep -l 'ToolResultPart' {} \;
echo "=== Checking ToolResultPart definition ==="
rg -n 'export.*ToolResultPart|interface ToolResultPart|type ToolResultPart' lib/
echo "=== Checking ai package import in actions.tsx ==="
rg -n 'ToolResultPart' app/actions.tsxLength of output: 366 I need to verify what's actually exported from Now let me check what's actually in Let me directly check what's actually exported from I need to actually inspect the codebase. Let me correct that: 🏁 Script executed: #!/bin/bash
# Find lib/types files and check their content
echo "=== Finding lib/types file(s) ==="
fd -t f 'types\.' lib/
echo "=== Content of lib/types file ==="
fd -t f 'types\.' lib/ --exec cat {} \;Length of output: 67 🏁 Script executed: #!/bin/bash
# Check if lib/types.ts or lib/types.tsx or similar exists
echo "=== Searching for lib/types files ==="
find lib -name "*types*" -type f 2>/dev/null | head -20
echo "=== Checking lib/types.ts/tsx directly ==="
cat lib/types.ts lib/types.tsx 2>/dev/null || echo "No lib/types found in standard locations"
echo "=== Checking lib/index.ts if it exists ==="
cat lib/index.ts 2>/dev/null || echo "No lib/index.ts"Length of output: 180 Let me verify the exact import in tool-coordinator.tsx and explore the lib directory structure: 🏁 Script executed: #!/bin/bash
# Check the actual imports in tool-coordinator.tsx
echo "=== First 15 lines of lib/agents/tool-coordinator.tsx ==="
head -15 lib/agents/tool-coordinator.tsx
echo ""
echo "=== All files in lib/ directory (recursive) ==="
find lib -type f | sort
echo ""
echo "=== Checking what '@/lib' path alias resolves to ==="
grep -r "alias.*@/lib\|paths.*@/lib" . --include="*.json" --include="*.config.*" 2>/dev/null | head -5Length of output: 1370 Now let me check what 🏁 Script executed: #!/bin/bash
echo "=== Contents of lib/types/index.ts ==="
cat lib/types/index.tsLength of output: 1801 Fix import source: Verification confirms the issue. The file Change line 5 from: to: 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // —————————————————————————————————————— | ||||||||||||||||||||||||||||||||||
| // Fallbacks if the original files don't exist yet | ||||||||||||||||||||||||||||||||||
| // —————————————————————————————————————— | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let getModel: () => any | ||||||||||||||||||||||||||||||||||
| let createStreamableUI: () => any | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| // Try the most common real locations first | ||||||||||||||||||||||||||||||||||
| const models = require('@/lib/models') | ||||||||||||||||||||||||||||||||||
| getModel = models.getModel || models.default || (() => null) | ||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| const mod = require('@/lib/ai/models') | ||||||||||||||||||||||||||||||||||
| getModel = mod.getModel || mod.default | ||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||
| getModel = () => { | ||||||||||||||||||||||||||||||||||
| throw new Error('getModel not available — check your @/lib/models setup') | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+14
to
+27
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fragile dynamic The fallback chain tries Consider importing directly from the known location: -let getModel: () => any
-let createStreamableUI: () => any
-
-try {
- // Try the most common real locations first
- const models = require('@/lib/models')
- getModel = models.getModel || models.default || (() => null)
-} catch {
- try {
- const mod = require('@/lib/ai/models')
- getModel = mod.getModel || mod.default
- } catch {
- getModel = () => {
- throw new Error('getModel not available — check your @/lib/models setup')
- }
- }
-}
+import { getModel } from '@/lib/utils'
+import { createStreamableUI } from 'ai/rsc'This also applies to 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| const streamable = require('@/lib/streamable') | ||||||||||||||||||||||||||||||||||
| createStreamableUI = streamable.createStreamableUI || streamable.default | ||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| const s = require('@/lib/ui/streamable') | ||||||||||||||||||||||||||||||||||
| createStreamableUI = s.createStreamableUI | ||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||
| // Minimal no-op version that won't break tool calling | ||||||||||||||||||||||||||||||||||
| createStreamableUI = () => ({ | ||||||||||||||||||||||||||||||||||
| append: () => {}, | ||||||||||||||||||||||||||||||||||
| update: () => {}, | ||||||||||||||||||||||||||||||||||
| done: () => {}, | ||||||||||||||||||||||||||||||||||
| value: null | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // —————————————————————————————————————— | ||||||||||||||||||||||||||||||||||
| // Schemas | ||||||||||||||||||||||||||||||||||
| // —————————————————————————————————————— | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const toolStepSchema = z.object({ | ||||||||||||||||||||||||||||||||||
| toolName: z.string(), | ||||||||||||||||||||||||||||||||||
| toolArgs: z.record(z.any()), | ||||||||||||||||||||||||||||||||||
| dependencyIndices: z.array(z.number()).optional(), | ||||||||||||||||||||||||||||||||||
| purpose: z.string() | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const toolPlanSchema = z.object({ | ||||||||||||||||||||||||||||||||||
| reasoning: z.string(), | ||||||||||||||||||||||||||||||||||
| steps: z.array(toolStepSchema) | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| export type ToolPlan = z.infer<typeof toolPlanSchema> | ||||||||||||||||||||||||||||||||||
| export type ToolStep = z.infer<typeof toolStepSchema> | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // —————————————————————————————————————— | ||||||||||||||||||||||||||||||||||
| // 1. Plan Generation | ||||||||||||||||||||||||||||||||||
| // —————————————————————————————————————— | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| export async function toolCoordinator(messages: CoreMessage[]): Promise<ToolPlan> { | ||||||||||||||||||||||||||||||||||
| const model = getModel() | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const toolsObj = getTools({ | ||||||||||||||||||||||||||||||||||
| uiStream: createStreamableUI(), | ||||||||||||||||||||||||||||||||||
| fullResponse: '' | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const toolDescriptions = Object.values(toolsObj).map(tool => ({ | ||||||||||||||||||||||||||||||||||
| name: tool.toolName, | ||||||||||||||||||||||||||||||||||
| description: tool.description, | ||||||||||||||||||||||||||||||||||
| parameters: tool.parameters | ||||||||||||||||||||||||||||||||||
| })) | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+78
to
+82
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tool objects don't have a Based on Use - const toolDescriptions = Object.values(toolsObj).map(tool => ({
- name: tool.toolName,
- description: tool.description,
- parameters: tool.parameters
- }))
+ const toolDescriptions = Object.entries(toolsObj).map(([name, tool]) => ({
+ name,
+ description: tool.description,
+ parameters: tool.parameters
+ }))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const systemPrompt = `You are an expert Tool Coordinator. Create a precise multi-step plan using only these tools. | ||||||||||||||||||||||||||||||||||
| Rules: | ||||||||||||||||||||||||||||||||||
| - Use exact toolName from the list. | ||||||||||||||||||||||||||||||||||
| - Use dependencyIndices (0-based) when a step needs prior results. | ||||||||||||||||||||||||||||||||||
| - Output must be valid JSON matching the schema. | ||||||||||||||||||||||||||||||||||
| Available Tools: | ||||||||||||||||||||||||||||||||||
| ${JSON.stringify(toolDescriptions, null, 2)} | ||||||||||||||||||||||||||||||||||
| ` | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const { object } = await generateObject({ | ||||||||||||||||||||||||||||||||||
| model, | ||||||||||||||||||||||||||||||||||
| system: systemPrompt, | ||||||||||||||||||||||||||||||||||
| messages, | ||||||||||||||||||||||||||||||||||
| schema: toolPlanSchema | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return object | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // —————————————————————————————————————— | ||||||||||||||||||||||||||||||||||
| // 2. Execution | ||||||||||||||||||||||||||||||||||
| // —————————————————————————————————————— | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| interface ExecutionContext { | ||||||||||||||||||||||||||||||||||
| uiStream: any | ||||||||||||||||||||||||||||||||||
| fullResponse: string | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| export async function executeToolPlan( | ||||||||||||||||||||||||||||||||||
| plan: ToolPlan, | ||||||||||||||||||||||||||||||||||
| context: ExecutionContext | ||||||||||||||||||||||||||||||||||
| ): Promise<ToolResultPart[]> { | ||||||||||||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
| const { uiStream, fullResponse } = context | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const toolsObj = getTools({ uiStream, fullResponse }) | ||||||||||||||||||||||||||||||||||
| const toolMap = new Map(Object.values(toolsObj).map(t => [t.toolName, t])) | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+120
to
+121
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same issue: This has the same bug as the plan generation. The tool map will be keyed by - const toolMap = new Map(Object.values(toolsObj).map(t => [t.toolName, t]))
+ const toolMap = new Map(Object.entries(toolsObj).map(([name, t]) => [name, t]))📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const results = new Map<number, any>() | ||||||||||||||||||||||||||||||||||
| const toolResults: ToolResultPart[] = [] | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const resolveDeps = (indices: number[] = []) => | ||||||||||||||||||||||||||||||||||
| indices.map(i => { | ||||||||||||||||||||||||||||||||||
| if (!results.has(i)) throw new Error(`Dependency step ${i} missing`) | ||||||||||||||||||||||||||||||||||
| return results.get(i) | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+126
to
+130
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider validating that dependency indices reference prior steps only. The const resolveDeps = (indices: number[] = []) =>
- indices.map(i => {
- if (!results.has(i)) throw new Error(`Dependency step ${i} missing`)
+ indices.map((depIndex, _, __, currentStepIndex = i) => {
+ if (depIndex >= currentStepIndex) throw new Error(`Invalid forward dependency: step ${currentStepIndex} cannot depend on step ${depIndex}`)
+ if (!results.has(depIndex)) throw new Error(`Dependency step ${depIndex} missing`)
return results.get(i)
})Alternatively, validate the plan structure upfront before execution.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| for (let i = 0; i < plan.steps.length; i++) { | ||||||||||||||||||||||||||||||||||
| const step = plan.steps[i] | ||||||||||||||||||||||||||||||||||
| const tool = toolMap.get(step.toolName) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| let result: any = { error: `Tool "${step.toolName}" not found` } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| if (!tool) throw new Error(`Tool not found: ${step.toolName}`) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const deps = step.dependencyIndices ? resolveDeps(step.dependencyIndices) : [] | ||||||||||||||||||||||||||||||||||
| const args = { | ||||||||||||||||||||||||||||||||||
| ...step.toolArgs, | ||||||||||||||||||||||||||||||||||
| ...(deps.length > 0 && { _dependencyResults: deps }) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| console.log(`[ToolCoordinator] Step ${i}: ${step.toolName}`) | ||||||||||||||||||||||||||||||||||
| result = await tool.execute(args) | ||||||||||||||||||||||||||||||||||
| } catch (err: any) { | ||||||||||||||||||||||||||||||||||
| const msg = err?.message || String(err) | ||||||||||||||||||||||||||||||||||
| console.error(`[ToolCoordinator] Step ${i} failed:`, msg) | ||||||||||||||||||||||||||||||||||
| result = { error: msg } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| results.set(i, result) | ||||||||||||||||||||||||||||||||||
| toolResults.push({ | ||||||||||||||||||||||||||||||||||
| toolName: step.toolName, | ||||||||||||||||||||||||||||||||||
| toolCallId: `coord-${i}`, | ||||||||||||||||||||||||||||||||||
| result | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return toolResults | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| // —————————————————————————————————————— | ||||||||||||||||||||||||||||||||||
| // 3. Aggregation | ||||||||||||||||||||||||||||||||||
| // —————————————————————————————————————— | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| export function aggregateToolResults(toolResults: ToolResultPart[], plan: ToolPlan): string { | ||||||||||||||||||||||||||||||||||
| let out = `# Tool Coordinator Results | ||||||||||||||||||||||||||||||||||
| ### Plan | ||||||||||||||||||||||||||||||||||
| ${plan.reasoning} | ||||||||||||||||||||||||||||||||||
| ### Steps | ||||||||||||||||||||||||||||||||||
| ` | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| toolResults.forEach((tr, i) => { | ||||||||||||||||||||||||||||||||||
| const step = plan.steps[i] | ||||||||||||||||||||||||||||||||||
| const hasError = tr.result && typeof tr.result === 'object' && 'error' in tr.result | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| out += `\n#### Step ${i + 1}: ${step.purpose} (\`${step.toolName}\`)` | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (hasError) { | ||||||||||||||||||||||||||||||||||
| out += `\n**Status:** Failed\n**Error:** ${tr.result.error}` | ||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||
| const json = JSON.stringify(tr.result, null, 2) | ||||||||||||||||||||||||||||||||||
| const truncated = json.length > 600 ? json.slice(0, 600) + '...' : json | ||||||||||||||||||||||||||||||||||
| out += `\n**Status:** Success\n**Result:**\n\`\`\`json\n${truncated}\n\`\`\`` | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| out += `\n\n---\n**INSTRUCTION:** Write a natural, helpful final answer using only the information above. Do not mention tools, steps, or internal process.` | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return out | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -217,10 +217,21 @@ Uses the Mapbox Search Box Text Search API endpoint to power searching for and g | |
|
|
||
| , | ||
| parameters: geospatialQuerySchema, | ||
| execute: async (params: z.infer<typeof geospatialQuerySchema>) => { | ||
| const { queryType, includeMap = true } = params; | ||
| execute: async (params: z.infer<typeof geospatialQuerySchema> & { _dependencyResults?: any[] }) => { | ||
| const { queryType, includeMap = true, _dependencyResults } = params; | ||
| console.log('[GeospatialTool] Execute called with:', params); | ||
|
|
||
| if (_dependencyResults && _dependencyResults.length > 0) { | ||
| console.log('[GeospatialTool] Processing dependency results:', _dependencyResults); | ||
| // Logic to process dependency results can be added here. | ||
| // For example, if a previous step was a search, the result might contain coordinates | ||
| // that can be used as input for a subsequent directions query. | ||
| // Since the full logic for dependency injection is complex and depends on the | ||
| // specific tool schema, we will log it for now and ensure the tool can handle it. | ||
| // The LLM planning step is responsible for generating the correct 'params' | ||
| // based on the dependency results. The tool only needs to be aware of them. | ||
| } | ||
|
Comment on lines
+220
to
+233
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Dependency results are accepted but not utilized. The Is this intentional scaffolding for a future implementation? If the LLM planning step is expected to pre-populate Would you like me to generate a sample implementation that extracts coordinates from geocode results for use in directions queries? 🤖 Prompt for AI Agents |
||
|
|
||
| const uiFeedbackStream = createStreamableValue<string>(); | ||
| uiStream.append(<BotMessage content={uiFeedbackStream.value} />); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type mismatch:
CoreMessagedoesn't support customtypefield.The object constructed here uses
role: 'tool'with a customtype: 'tool_coordinator_summary'field. TheCoreMessagetype from theaipackage expectscontentto beToolResultPart[]for tool messages, not a string. Theas anycast masks this incompatibility.Consider using
role: 'assistant'instead, since this is a summary text for the synthesis agent:finalMessages = [ ...messages, { - id: nanoid(), - role: 'tool', + role: 'assistant', content: summary, - type: 'tool_coordinator_summary' - } as any + } ]📝 Committable suggestion
🤖 Prompt for AI Agents