Skip to content

Commit 9a0fa3a

Browse files
committed
dynamic tool call works! with context injection (chat History not shared with tool call service, only 'tool call goal' and tool choice from build-test subagent)
1 parent bbba3e6 commit 9a0fa3a

4 files changed

Lines changed: 78 additions & 71 deletions

File tree

packages/core/src/agents/build-test-agent.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -178,13 +178,16 @@ You have access to these tools:
178178
\${directive}
179179
`,
180180
directive: `## Output Format
181+
If you decide to choose a tool to further the task, your response must *ONLY* contain the tool choice in JSON format.
181182
182-
If you decide to choose a tool to further the task, your response must *ONLY* contain a one-line explanation of your rationale, followed by the tool choice in JSON format.
183+
The \`goal\` you provide is critical, as it will be passed to a separate, specialized agent that executes the command. This agent has **no access to our conversation history**. Therefore, your \`goal\` **must be a fully self-contained and actionable instruction**. It needs to include not just *what* to do, but also *why*, so the other agent understands the context.
183184
184-
**Example:**
185-
I have chosen \`tool_name\` because [Your concise rationale and what you are trying to do and why it will help].
185+
**JSON Structure:**
186186
\`\`\`json
187-
{ "name": "tool_name" }
187+
{
188+
"name": "run_shell_command",
189+
"goal": "<Your fully self-contained goal>"
190+
}
188191
\`\`\`
189192
190193
If you decide the user's objective is met, your response must *ONLY* contain a five bullet point summary (e.g., test names, file names, pass/fail status), followed by the \`complete_task\` tool call in JSON format.
@@ -202,20 +205,24 @@ I am satisfied with the results. Here are the execution highlights:
202205
203206
`,
204207
reminder: `Remember! You are a **Build And Test Agent** whose purpose is to build and/or test code according to the user's objective.
205-
208+
206209
## Available Tools
207210
You have access to these tools:
208211
\${tool_code}
209212
210213
211214
## Output Format
215+
If you decide to choose a tool to further the task, your response must *ONLY* contain the tool choice in JSON format.
212216
213-
If you decide to choose a tool to further the task, your response must *ONLY* contain a one-line explanation of your rationale, followed by the tool choice in JSON format.
217+
The \`goal\` you provide is critical, as it will be passed to a separate, specialized agent that executes the command. This agent has **no access to our conversation history**. Therefore, your \`goal\` **must be a fully self-contained and actionable instruction**. It needs to include not just *what* to do, but also *why*, so the other agent understands the context.
214218
215-
**Example:**
216-
I have chosen \`tool_name\` because [Your concise rationale and what you are trying to do and why it will help].
219+
220+
**JSON Structure:**
217221
\`\`\`json
218-
{ "name": "tool_name" }
222+
{
223+
"name": "run_shell_command",
224+
"goal": "<Your fully self-contained goal>"
225+
}
219226
\`\`\`
220227
221228
If you decide the user's objective is met, your response must *ONLY* contain a five bullet point summary (e.g., test names, file names, pass/fail status), followed by the \`complete_task\` tool call in JSON format.

packages/core/src/agents/executor.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1058,10 +1058,16 @@ export class AgentExecutor<TOutput extends z.ZodTypeAny> {
10581058
if (functionCall.name) {
10591059
const tool = this.toolRegistry.getTool(functionCall.name);
10601060
if (tool) {
1061+
const goal = (functionCall.args as { goal?: string })?.goal ?? '';
1062+
if (!goal) {
1063+
throw new Error(
1064+
'The "goal" argument is missing from the tool call.',
1065+
);
1066+
}
10611067
const completeFunctionCall =
10621068
await this.toolCallService.generateToolCall(
10631069
functionCall.name,
1064-
chat,
1070+
goal,
10651071
this.definition.modelConfig,
10661072
);
10671073
completeFunctionCalls.push(completeFunctionCall);

packages/core/src/services/toolCallService.ts

Lines changed: 32 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
import type { FunctionCall, FunctionDeclaration, Schema } from '@google/genai';
88

9-
import type { GeminiChat } from '../core/geminiChat.js';
109
import { OllamaChat, StreamEventType } from '../core/ollamaChat.js';
1110
import { debugLogger } from '../utils/debugLogger.js';
1211
import type { ModelConfig, OllamaModelConfig } from '../agents/types.js';
@@ -83,7 +82,7 @@ export class ToolCallService {
8382
*/
8483
async generateToolCall(
8584
toolName: string,
86-
chat: GeminiChat | OllamaChat,
85+
goal: string,
8786
modelConfig: ModelConfig | OllamaModelConfig,
8887
): Promise<FunctionCall> {
8988
const tool = this.toolRegistry.getTool(toolName);
@@ -96,51 +95,46 @@ export class ToolCallService {
9695

9796
const formattedSchema = this._prepareGemmaToolCode(functionDeclarations);
9897

99-
const chatHistory = await chat.getHistory();
100-
const lastMessage = chatHistory[chatHistory.length - 1];
101-
102-
if (!lastMessage || !('parts' in lastMessage)) {
103-
throw new Error('Could not get last message from chat history');
104-
}
105-
106-
const lastMessageText = lastMessage
107-
.parts!.map((part) => ('text' in part ? part.text : ''))
108-
.join(' ');
98+
let fileContents = 'Tool Call Service Chat History\n\n';
10999

110100
const systemPrompt = `You are a tool call generator. Your task is to generate a valid tool call for the tool named "${toolName}".
111-
You will be provided with the schema of the tool and the chat history.
112-
Generate a tool call that best helps with the user's request.
101+
You will be provided with the schema of the tool and a goal.
102+
Generate a tool call that best helps with achieving the goal.
103+
104+
You must return a single JSON object with the following structure:
105+
{
106+
"name": "${toolName}",
107+
"parameters": { ... }
108+
}
113109
The tool schema is:
114110
${formattedSchema}
115111
116112
Respond with only the JSON for the tool call. Do not include any other text or markdown.
117-
118-
The
119113
`;
120-
121-
const reminder = `Remember!
122-
You must look at the preceding chat history to understand the user's objective and current step in the process.
123-
Only respond with the JSON for the tool call. Do not include any other text or markdown.`;
124-
125-
// TODO: add a user message which actually has the formatted schema and a reiteration of the user's objective and asks what command must be called. that will replace text: ''. maybe it's fine to have the schema and tool name in the system prompt.
114+
fileContents += `--- ROLE: system ---\n${systemPrompt}\n---\n\n`;
115+
await this._writeChatHistoryToFile(fileContents);
126116

127117
const tempChat = new OllamaChat(
128118
modelConfig as OllamaModelConfig,
129119
systemPrompt,
130-
// [],
131-
chatHistory,
120+
[],
132121
{
133122
query: '',
134123
systemPrompt,
135124
},
136125
);
137126

127+
const userMessage = `The goal is: ${goal}`;
128+
fileContents += `--- ROLE: user ---\n${userMessage}\n---\n\n`;
129+
await this._writeChatHistoryToFile(fileContents);
130+
138131
const responseStream = await tempChat.sendMessageStream(modelConfig.model, {
139-
// message: [{ text: lastMessageText }],
140-
message: [{ text: reminder }],
132+
message: [{ text: userMessage }],
141133
});
142134

143135
let textResponse = '';
136+
fileContents += `--- ROLE: model ---\n`;
137+
await this._writeChatHistoryToFile(fileContents);
144138
for await (const resp of responseStream) {
145139
if (resp.type === StreamEventType.CHUNK) {
146140
const chunk = resp.value;
@@ -153,6 +147,8 @@ Only respond with the JSON for the tool call. Do not include any other text or m
153147
}
154148
}
155149
}
150+
fileContents += `${textResponse}\n---\n\n`;
151+
await this._writeChatHistoryToFile(fileContents);
156152

157153
const responseText = textResponse;
158154
const functionCalls = parseToolCalls(responseText, 'tool-call-service');
@@ -166,34 +162,21 @@ Only respond with the JSON for the tool call. Do not include any other text or m
166162

167163
const parsedFunctionCall = functionCalls[0];
168164

169-
try {
170-
await this._writeChatHistoryToFile(
171-
systemPrompt,
172-
lastMessageText,
173-
textResponse,
174-
);
175-
} catch (error) {
176-
debugLogger.error(
177-
'[DEBUG] Failed to save summarized tool output to tool_call_chat_history.txt:',
178-
error,
179-
);
180-
}
181-
182165
return {
183166
name: parsedFunctionCall.name,
184167
args: parsedFunctionCall.args,
185168
};
186169
}
187170

188-
private async _writeChatHistoryToFile(
189-
systemPrompt: string,
190-
userMessage: string,
191-
modelResponse: string,
192-
): Promise<void> {
193-
let fileContent = 'Tool Call Service Chat History\n\n';
194-
fileContent += `--- ROLE: system ---\n${systemPrompt}\n---\n\n`;
195-
fileContent += `--- ROLE: user ---\n${userMessage}\n---\n\n`;
196-
fileContent += `--- ROLE: model ---\n${modelResponse}\n---\n\n`;
197-
await fs.writeFile(`tool_call_chat_history.txt`, fileContent);
171+
private async _writeChatHistoryToFile(content: string): Promise<void> {
172+
try {
173+
await fs.writeFile(`tool_call_chat_history.txt`, content);
174+
} catch (error) {
175+
debugLogger.error(
176+
'[DEBUG] Failed to write to tool_call_chat_history.txt:',
177+
178+
error,
179+
);
180+
}
198181
}
199182
}

packages/core/src/utils/toolCallParser.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,29 @@ export function parseToolCalls(text: string, promptId: string): FunctionCall[] {
3434
toolCall !== null &&
3535
'name' in toolCall
3636
) {
37-
const tc = toolCall as {
38-
name: string;
39-
parameters?: Record<string, unknown>;
40-
};
41-
if (typeof tc.name === 'string') {
42-
return {
43-
// id: `${promptId}-ollama-${index}`,
44-
name: tc.name,
45-
args: tc.parameters ?? {},
37+
// Handle dynamic tool calls with 'goal'
38+
if ('goal' in toolCall) {
39+
debugLogger.log(`[Debug] Processing dynamic tool call with goal.`);
40+
const tc = toolCall as { name: string; goal: string };
41+
if (typeof tc.name === 'string' && typeof tc.goal === 'string') {
42+
return {
43+
name: tc.name,
44+
args: { goal: tc.goal },
45+
};
46+
}
47+
} else {
48+
// Handle static tool calls with 'parameters'
49+
const tc = toolCall as {
50+
name: string;
51+
parameters?: Record<string, unknown>;
4652
};
53+
if (typeof tc.name === 'string') {
54+
return {
55+
// id: `${promptId}-ollama-${index}`,
56+
name: tc.name,
57+
args: tc.parameters ?? {},
58+
};
59+
}
4760
}
4861
}
4962
return null;
@@ -70,6 +83,7 @@ export function parseToolCalls(text: string, promptId: string): FunctionCall[] {
7083
// Not a valid JSON, proceed with regex parsing
7184
debugLogger.log(
7285
'[Debug] Failed to parse tool calls as JSON, falling back to regex.',
86+
e,
7387
);
7488
}
7589

@@ -123,8 +137,5 @@ export function parseToolCalls(text: string, promptId: string): FunctionCall[] {
123137
args,
124138
});
125139
}
126-
// debugLogger.log(
127-
// `[Debug] Parsed Ollama tool calls: ${JSON.stringify(functionCalls)}`,
128-
// );
129140
return functionCalls;
130141
}

0 commit comments

Comments
 (0)