@librechat/agents 3.0.42 → 3.0.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/dist/cjs/agents/AgentContext.cjs +134 -70
  2. package/dist/cjs/agents/AgentContext.cjs.map +1 -1
  3. package/dist/cjs/common/enum.cjs +1 -1
  4. package/dist/cjs/common/enum.cjs.map +1 -1
  5. package/dist/cjs/graphs/Graph.cjs +7 -13
  6. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  7. package/dist/cjs/main.cjs +5 -0
  8. package/dist/cjs/main.cjs.map +1 -1
  9. package/dist/cjs/messages/tools.cjs +85 -0
  10. package/dist/cjs/messages/tools.cjs.map +1 -0
  11. package/dist/cjs/tools/ProgrammaticToolCalling.cjs +55 -32
  12. package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
  13. package/dist/cjs/tools/ToolNode.cjs +30 -13
  14. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  15. package/dist/esm/agents/AgentContext.mjs +134 -70
  16. package/dist/esm/agents/AgentContext.mjs.map +1 -1
  17. package/dist/esm/common/enum.mjs +1 -1
  18. package/dist/esm/common/enum.mjs.map +1 -1
  19. package/dist/esm/graphs/Graph.mjs +8 -14
  20. package/dist/esm/graphs/Graph.mjs.map +1 -1
  21. package/dist/esm/main.mjs +2 -1
  22. package/dist/esm/main.mjs.map +1 -1
  23. package/dist/esm/messages/tools.mjs +82 -0
  24. package/dist/esm/messages/tools.mjs.map +1 -0
  25. package/dist/esm/tools/ProgrammaticToolCalling.mjs +54 -33
  26. package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
  27. package/dist/esm/tools/ToolNode.mjs +30 -13
  28. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  29. package/dist/types/agents/AgentContext.d.ts +37 -17
  30. package/dist/types/common/enum.d.ts +1 -1
  31. package/dist/types/messages/index.d.ts +1 -0
  32. package/dist/types/messages/tools.d.ts +17 -0
  33. package/dist/types/tools/ProgrammaticToolCalling.d.ts +15 -23
  34. package/dist/types/tools/ToolNode.d.ts +9 -7
  35. package/dist/types/types/tools.d.ts +5 -5
  36. package/package.json +1 -1
  37. package/src/agents/AgentContext.ts +157 -85
  38. package/src/agents/__tests__/AgentContext.test.ts +805 -0
  39. package/src/common/enum.ts +1 -1
  40. package/src/graphs/Graph.ts +9 -21
  41. package/src/messages/__tests__/tools.test.ts +473 -0
  42. package/src/messages/index.ts +1 -0
  43. package/src/messages/tools.ts +99 -0
  44. package/src/scripts/code_exec_ptc.ts +78 -21
  45. package/src/scripts/programmatic_exec.ts +3 -3
  46. package/src/scripts/programmatic_exec_agent.ts +4 -4
  47. package/src/tools/ProgrammaticToolCalling.ts +71 -39
  48. package/src/tools/ToolNode.ts +33 -14
  49. package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +9 -9
  50. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +180 -5
  51. package/src/types/tools.ts +3 -5
@@ -18,25 +18,79 @@ import type { RunnableConfig } from '@langchain/core/runnables';
18
18
  import type * as t from '@/types';
19
19
  import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
20
20
  import {
21
- ToolEndHandler,
22
- ModelEndHandler,
21
+ // createProgrammaticToolRegistry,
22
+ createGetTeamMembersTool,
23
+ createGetExpensesTool,
24
+ createGetWeatherTool,
25
+ } from '@/test/mockTools';
26
+ import {
23
27
  createMetadataAggregator,
28
+ ModelEndHandler,
29
+ ToolEndHandler,
24
30
  } from '@/events';
31
+ import { createProgrammaticToolCallingTool } from '@/tools/ProgrammaticToolCalling';
32
+ import { createCodeExecutionTool } from '@/tools/CodeExecutor';
25
33
  import { getLLMConfig } from '@/utils/llmConfig';
26
34
  import { getArgs } from '@/scripts/args';
27
35
  import { GraphEvents } from '@/common';
28
36
  import { Run } from '@/run';
29
- import { createCodeExecutionTool } from '@/tools/CodeExecutor';
30
- import { createProgrammaticToolCallingTool } from '@/tools/ProgrammaticToolCalling';
31
- import {
32
- createGetTeamMembersTool,
33
- createGetExpensesTool,
34
- createGetWeatherTool,
35
- createProgrammaticToolRegistry,
36
- } from '@/test/mockTools';
37
37
 
38
38
  const conversationHistory: BaseMessage[] = [];
39
39
 
40
+ /**
41
+ * Creates a tool registry where ALL business tools are code_execution ONLY.
42
+ * This forces the LLM to use PTC - it cannot call these tools directly.
43
+ */
44
+ function createPTCOnlyToolRegistry(): t.LCToolRegistry {
45
+ const toolDefs: t.LCTool[] = [
46
+ {
47
+ name: 'get_team_members',
48
+ description:
49
+ 'Get list of team members. Returns array of objects with id, name, and department fields.',
50
+ parameters: {
51
+ type: 'object',
52
+ properties: {},
53
+ required: [],
54
+ },
55
+ allowed_callers: ['code_execution'], // PTC ONLY - not direct
56
+ },
57
+ {
58
+ name: 'get_expenses',
59
+ description:
60
+ 'Get expense records for a user. Returns array of objects with amount and category fields.',
61
+ parameters: {
62
+ type: 'object',
63
+ properties: {
64
+ user_id: {
65
+ type: 'string',
66
+ description: 'The user ID to fetch expenses for',
67
+ },
68
+ },
69
+ required: ['user_id'],
70
+ },
71
+ allowed_callers: ['code_execution'], // PTC ONLY - not direct
72
+ },
73
+ {
74
+ name: 'get_weather',
75
+ description:
76
+ 'Get current weather for a city. Returns object with temperature (number) and condition (string) fields.',
77
+ parameters: {
78
+ type: 'object',
79
+ properties: {
80
+ city: {
81
+ type: 'string',
82
+ description: 'City name',
83
+ },
84
+ },
85
+ required: ['city'],
86
+ },
87
+ allowed_callers: ['code_execution'], // PTC ONLY - not direct (changed from ['direct', 'code_execution'])
88
+ },
89
+ ];
90
+
91
+ return new Map(toolDefs.map((def) => [def.name, def]));
92
+ }
93
+
40
94
  async function testProgrammaticToolCalling(): Promise<void> {
41
95
  const { userName, location, provider, currentDate } = await getArgs();
42
96
  const { contentParts, aggregateContent } = createContentAggregator();
@@ -111,10 +165,10 @@ async function testProgrammaticToolCalling(): Promise<void> {
111
165
  const allTools = [teamTool, expensesTool, weatherTool, codeExecTool, ptcTool];
112
166
  const toolMap = new Map(allTools.map((t) => [t.name, t]));
113
167
 
114
- // Create tool registry with allowed_callers configuration
115
- // Only includes business logic tools (not special tools)
116
- // Special tools (execute_code, PTC) are always bound directly to LLM
117
- const toolRegistry = createProgrammaticToolRegistry();
168
+ // Create tool registry where ALL business tools are PTC-only
169
+ // This means the LLM CANNOT call get_team_members, get_expenses, get_weather directly
170
+ // It MUST use run_tools_with_code to invoke them
171
+ const toolRegistry = createPTCOnlyToolRegistry();
118
172
 
119
173
  console.log('\n' + '='.repeat(70));
120
174
  console.log('Tool Configuration Summary:');
@@ -150,11 +204,14 @@ async function testProgrammaticToolCalling(): Promise<void> {
150
204
  toolMap,
151
205
  toolRegistry,
152
206
  instructions:
153
- 'You are a friendly AI assistant with advanced coding capabilities. ' +
154
- 'You have access to team and expense management tools, but ONLY through programmatic code execution. ' +
155
- 'When you need to analyze expenses or process team data, use the programmatic_code_execution tool ' +
156
- 'to write Python code that calls get_team_members(), get_expenses(), and get_weather() functions. ' +
157
- 'These functions are async - use await. Use asyncio.gather() for parallel execution.',
207
+ 'You are a friendly AI assistant with advanced coding capabilities.\n\n' +
208
+ 'IMPORTANT: The tools get_team_members(), get_expenses(), and get_weather() are NOT available ' +
209
+ 'for direct function calling. You MUST use the run_tools_with_code tool to invoke them.\n\n' +
210
+ 'When you need to use these tools, write Python code using run_tools_with_code that calls:\n' +
211
+ '- await get_team_members() - returns list of team members\n' +
212
+ '- await get_expenses(user_id="...") - returns expenses for a user\n' +
213
+ '- await get_weather(city="...") - returns weather data\n\n' +
214
+ 'Use asyncio.gather() for parallel execution when calling multiple tools.',
158
215
  additional_instructions: `The user's name is ${userName} and they are located in ${location}. Today is ${currentDate}.`,
159
216
  },
160
217
  ],
@@ -187,7 +244,7 @@ async function testProgrammaticToolCalling(): Promise<void> {
187
244
  4. Identify anyone who spent more than $500
188
245
  5. Show me a summary report
189
246
 
190
- IMPORTANT: Use the programmatic_code_execution tool to do this efficiently.
247
+ IMPORTANT: Use the run_tools_with_code tool to do this efficiently.
191
248
  Don't call each tool separately - write Python code that orchestrates all the calls!`;
192
249
 
193
250
  conversationHistory.push(new HumanMessage(userMessage1));
@@ -217,7 +274,7 @@ Don't call each tool separately - write Python code that orchestrates all the ca
217
274
  3. For the Engineering team members only, calculate their travel expenses
218
275
  4. Show me the results
219
276
 
220
- Again, use programmatic_code_execution for maximum efficiency. Use asyncio.gather()
277
+ Again, use run_tools_with_code for maximum efficiency. Use asyncio.gather()
221
278
  to check both cities' weather at the same time!`;
222
279
 
223
280
  conversationHistory.push(new HumanMessage(userMessage2));
@@ -353,8 +353,8 @@ print(f"Temperature difference: {difference}°F")
353
353
  console.log('='.repeat(70));
354
354
  console.log(
355
355
  '\nWhen PTC is invoked through ToolNode in a real agent:\n' +
356
- '- ToolNode detects call.name === "programmatic_code_execution"\n' +
357
- '- ToolNode injects: { ...invokeParams, toolMap, programmaticToolDefs }\n' +
356
+ '- ToolNode detects call.name === "run_tools_with_code"\n' +
357
+ '- ToolNode injects: { ...invokeParams, toolMap, toolDefs }\n' +
358
358
  '- PTC tool extracts these from params (not from config)\n' +
359
359
  '- No explicit tools parameter needed in schema\n\n' +
360
360
  'This test demonstrates param injection with explicit tools:\n'
@@ -364,7 +364,7 @@ print(f"Temperature difference: {difference}°F")
364
364
  ptcTool,
365
365
  'Runtime injection with explicit tools',
366
366
  `
367
- # ToolNode would inject toolMap+programmaticToolDefs
367
+ # ToolNode would inject toolMap+toolDefs
368
368
  # For this test, we pass tools explicitly (same effect)
369
369
  team = await get_team_members()
370
370
  print(f"Team size: {len(team)}")
@@ -40,7 +40,7 @@ import {
40
40
 
41
41
  /**
42
42
  * Tool registry only needs business logic tools that require filtering.
43
- * Special tools (execute_code, programmatic_code_execution, tool_search_regex)
43
+ * Special tools (execute_code, run_tools_with_code, tool_search_regex)
44
44
  * are always bound directly to the LLM and don't need registry entries.
45
45
  */
46
46
  function createAgentToolRegistry(): t.LCToolRegistry {
@@ -133,7 +133,7 @@ async function main(): Promise<void> {
133
133
  instructions:
134
134
  'You are an AI assistant with access to programmatic tool calling. ' +
135
135
  'When you need to process multiple items or perform complex data operations, ' +
136
- 'use the programmatic_code_execution tool to write Python code that calls tools efficiently.',
136
+ 'use the run_tools_with_code tool to write Python code that calls tools efficiently.',
137
137
  },
138
138
  ],
139
139
  },
@@ -164,7 +164,7 @@ async function main(): Promise<void> {
164
164
  4. Identify anyone who spent more than $300
165
165
  5. Show me the results in a nice format
166
166
 
167
- Use the programmatic_code_execution tool to do this efficiently - don't call each tool separately!`
167
+ Use the run_tools_with_code tool to do this efficiently - don't call each tool separately!`
168
168
  );
169
169
 
170
170
  conversationHistory.push(userMessage);
@@ -199,7 +199,7 @@ Use the programmatic_code_execution tool to do this efficiently - don't call eac
199
199
  console.log('='.repeat(70));
200
200
  console.log('\nKey observations:');
201
201
  console.log(
202
- '1. LLM only sees tools with allowed_callers including "direct" (get_weather, execute_code, programmatic_code_execution, tool_search_regex)'
202
+ '1. LLM only sees tools with allowed_callers including "direct" (get_weather, execute_code, run_tools_with_code, tool_search_regex)'
203
203
  );
204
204
  console.log(
205
205
  '2. When PTC is invoked, ToolNode automatically injects programmatic tools (get_team_members, get_expenses, get_weather)'
@@ -5,6 +5,7 @@ import fetch, { RequestInit } from 'node-fetch';
5
5
  import { HttpsProxyAgent } from 'https-proxy-agent';
6
6
  import { getEnvironmentVariable } from '@langchain/core/utils/env';
7
7
  import { tool, DynamicStructuredTool } from '@langchain/core/tools';
8
+ import type { ToolCall } from '@langchain/core/messages/tool';
8
9
  import type * as t from '@/types';
9
10
  import { imageExtRegex, getCodeBaseURL } from './CodeExecutor';
10
11
  import { EnvVar, Constants } from '@/common';
@@ -74,18 +75,6 @@ Requirements:
74
75
  - Only print() output flows back to the context window
75
76
  - Tool results from programmatic calls do NOT consume context tokens`
76
77
  ),
77
- tools: z
78
- .array(
79
- z.object({
80
- name: z.string(),
81
- description: z.string().optional(),
82
- parameters: z.any(), // JsonSchemaType
83
- })
84
- )
85
- .optional()
86
- .describe(
87
- 'Optional array of tool definitions that can be called from the code. If not provided, uses programmatic tools configured in the agent context. Tool names must match tools available in the toolMap.'
88
- ),
89
78
  session_id: z
90
79
  .string()
91
80
  .optional()
@@ -108,6 +97,51 @@ Requirements:
108
97
  // Helper Functions
109
98
  // ============================================================================
110
99
 
100
+ /**
101
+ * Extracts tool names that are actually called in the Python code.
102
+ * Matches patterns like `await tool_name(`, `tool_name(`, and asyncio.gather calls.
103
+ * @param code - The Python code to analyze
104
+ * @param availableToolNames - Set of available tool names to match against
105
+ * @returns Set of tool names found in the code
106
+ */
107
+ export function extractUsedToolNames(
108
+ code: string,
109
+ availableToolNames: Set<string>
110
+ ): Set<string> {
111
+ const usedTools = new Set<string>();
112
+
113
+ for (const toolName of availableToolNames) {
114
+ const escapedName = toolName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
115
+ const pattern = new RegExp(`\\b${escapedName}\\s*\\(`, 'g');
116
+
117
+ if (pattern.test(code)) {
118
+ usedTools.add(toolName);
119
+ }
120
+ }
121
+
122
+ return usedTools;
123
+ }
124
+
125
+ /**
126
+ * Filters tool definitions to only include tools actually used in the code.
127
+ * @param toolDefs - All available tool definitions
128
+ * @param code - The Python code to analyze
129
+ * @returns Filtered array of tool definitions
130
+ */
131
+ export function filterToolsByUsage(
132
+ toolDefs: t.LCTool[],
133
+ code: string
134
+ ): t.LCTool[] {
135
+ const availableToolNames = new Set(toolDefs.map((tool) => tool.name));
136
+ const usedToolNames = extractUsedToolNames(code, availableToolNames);
137
+
138
+ if (usedToolNames.size === 0) {
139
+ return toolDefs;
140
+ }
141
+
142
+ return toolDefs.filter((tool) => usedToolNames.has(tool.name));
143
+ }
144
+
111
145
  /**
112
146
  * Makes an HTTP request to the Code API.
113
147
  * @param endpoint - The API endpoint URL
@@ -287,35 +321,34 @@ export function createProgrammaticToolCallingTool(
287
321
  const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;
288
322
 
289
323
  const description = `
290
- Executes Python code with programmatic tool calling. Tools are automatically generated as async Python functions from the tool definitions - DO NOT define them in your code.
324
+ Run tools by writing Python code. Tools are available as async functions - just call them with await.
325
+
326
+ This is different from execute_code: here you can call your tools (like get_weather, get_expenses, etc.) directly in Python code.
291
327
 
292
328
  Usage:
293
- - Write Python code that calls tools using await: result = await get_data()
294
- - Tools are pre-defined as async functions - just call them
295
- - Use asyncio.gather() for parallel execution (single round-trip!)
296
- - Only print() output flows through the context window
297
- - Tool results from programmatic calls do NOT consume context tokens
298
-
299
- When to use:
300
- - Processing multiple records with tool calls (10+ items)
301
- - Loops, conditionals, or aggregation based on tool results
302
- - Any workflow requiring 3+ sequential tool calls
303
- - Parallel execution of independent tool calls
304
- - Filtering/summarizing large data before returning to context
305
-
306
- Patterns:
307
- - Simple: result = await get_data()
308
- - Loop: for item in items: data = await fetch(item)
309
- - Parallel: results = await asyncio.gather(t1(), t2(), t3())
310
- - Conditional: if x: await tool_a() else: await tool_b()
329
+ - Tools are pre-defined as async functions - call them with await
330
+ - Use asyncio.gather() to run multiple tools in parallel
331
+ - Only print() output is returned - tool results stay in Python
332
+
333
+ Examples:
334
+ - Simple: result = await get_weather(city="NYC")
335
+ - Loop: for user in users: data = await get_expenses(user_id=user['id'])
336
+ - Parallel: sf, ny = await asyncio.gather(get_weather(city="SF"), get_weather(city="NY"))
337
+
338
+ When to use this instead of calling tools directly:
339
+ - You need to call tools in a loop (process many items)
340
+ - You want parallel execution (asyncio.gather)
341
+ - You need conditionals based on tool results
342
+ - You want to aggregate/filter data before returning
311
343
  `.trim();
312
344
 
313
345
  return tool<typeof ProgrammaticToolCallingSchema>(
314
346
  async (params, config) => {
315
- const { code, tools, session_id, timeout = DEFAULT_TIMEOUT } = params;
347
+ const { code, session_id, timeout = DEFAULT_TIMEOUT } = params;
316
348
 
317
349
  // Extra params injected by ToolNode (follows web_search pattern)
318
- const { toolMap, programmaticToolDefs } = config.toolCall ?? {};
350
+ const { toolMap, toolDefs } = (config.toolCall ?? {}) as ToolCall &
351
+ Partial<t.ProgrammaticCache>;
319
352
 
320
353
  if (toolMap == null || toolMap.size === 0) {
321
354
  throw new Error(
@@ -324,13 +357,10 @@ Patterns:
324
357
  );
325
358
  }
326
359
 
327
- // Use provided tools or fall back to programmaticToolDefs from ToolNode
328
- const effectiveTools = tools ?? programmaticToolDefs;
329
-
330
- if (effectiveTools == null || effectiveTools.length === 0) {
360
+ if (toolDefs == null || toolDefs.length === 0) {
331
361
  throw new Error(
332
362
  'No tool definitions provided. ' +
333
- 'Either pass tools in the input or ensure ToolNode injects programmaticToolDefs.'
363
+ 'Either pass tools in the input or ensure ToolNode injects toolDefs.'
334
364
  );
335
365
  }
336
366
 
@@ -338,9 +368,11 @@ Patterns:
338
368
 
339
369
  try {
340
370
  // ====================================================================
341
- // Phase 1: Initial request
371
+ // Phase 1: Filter tools and make initial request
342
372
  // ====================================================================
343
373
 
374
+ const effectiveTools = filterToolsByUsage(toolDefs, code);
375
+
344
376
  let response = await makeRequest(
345
377
  EXEC_ENDPOINT,
346
378
  apiKey,
@@ -31,7 +31,6 @@ function isSend(value: unknown): value is Send {
31
31
 
32
32
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
33
  export class ToolNode<T = any> extends RunnableCallable<T, T> {
34
- tools: t.GenericTool[];
35
34
  private toolMap: Map<string, StructuredToolInterface | RunnableToolLike>;
36
35
  private loadRuntimeTools?: t.ToolRefGenerator;
37
36
  handleToolErrors = true;
@@ -39,12 +38,10 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
39
38
  toolCallStepIds?: Map<string, string>;
40
39
  errorHandler?: t.ToolNodeConstructorParams['errorHandler'];
41
40
  private toolUsageCount: Map<string, number>;
42
- /** Tools available for programmatic code execution */
43
- private programmaticToolMap?: t.ToolMap;
44
- /** Tool definitions for programmatic code execution (sent to Code API) */
45
- private programmaticToolDefs?: t.LCTool[];
46
- /** Tool registry for tool search (deferred tools) */
41
+ /** Tool registry for filtering (lazy computation of programmatic maps) */
47
42
  private toolRegistry?: t.LCToolRegistry;
43
+ /** Cached programmatic tools (computed once on first PTC call) */
44
+ private programmaticCache?: t.ProgrammaticCache;
48
45
 
49
46
  constructor({
50
47
  tools,
@@ -55,23 +52,44 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
55
52
  toolCallStepIds,
56
53
  handleToolErrors,
57
54
  loadRuntimeTools,
58
- programmaticToolMap,
59
- programmaticToolDefs,
60
55
  toolRegistry,
61
56
  }: t.ToolNodeConstructorParams) {
62
57
  super({ name, tags, func: (input, config) => this.run(input, config) });
63
- this.tools = tools;
64
58
  this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
65
59
  this.toolCallStepIds = toolCallStepIds;
66
60
  this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;
67
61
  this.loadRuntimeTools = loadRuntimeTools;
68
62
  this.errorHandler = errorHandler;
69
63
  this.toolUsageCount = new Map<string, number>();
70
- this.programmaticToolMap = programmaticToolMap;
71
- this.programmaticToolDefs = programmaticToolDefs;
72
64
  this.toolRegistry = toolRegistry;
73
65
  }
74
66
 
67
+ /**
68
+ * Returns cached programmatic tools, computing once on first access.
69
+ * Single iteration builds both toolMap and toolDefs simultaneously.
70
+ */
71
+ private getProgrammaticTools(): { toolMap: t.ToolMap; toolDefs: t.LCTool[] } {
72
+ if (this.programmaticCache) return this.programmaticCache;
73
+
74
+ const toolMap: t.ToolMap = new Map();
75
+ const toolDefs: t.LCTool[] = [];
76
+
77
+ if (this.toolRegistry) {
78
+ for (const [name, toolDef] of this.toolRegistry) {
79
+ if (
80
+ (toolDef.allowed_callers ?? ['direct']).includes('code_execution')
81
+ ) {
82
+ toolDefs.push(toolDef);
83
+ const tool = this.toolMap.get(name);
84
+ if (tool) toolMap.set(name, tool);
85
+ }
86
+ }
87
+ }
88
+
89
+ this.programmaticCache = { toolMap, toolDefs };
90
+ return this.programmaticCache;
91
+ }
92
+
75
93
  /**
76
94
  * Returns a snapshot of the current tool usage counts.
77
95
  * @returns A ReadonlyMap where keys are tool names and values are their usage counts.
@@ -108,10 +126,11 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
108
126
 
109
127
  // Inject runtime data for special tools (becomes available at config.toolCall)
110
128
  if (call.name === Constants.PROGRAMMATIC_TOOL_CALLING) {
129
+ const { toolMap, toolDefs } = this.getProgrammaticTools();
111
130
  invokeParams = {
112
131
  ...invokeParams,
113
- toolMap: this.programmaticToolMap,
114
- programmaticToolDefs: this.programmaticToolDefs,
132
+ toolMap,
133
+ toolDefs,
115
134
  };
116
135
  } else if (call.name === Constants.TOOL_SEARCH_REGEX) {
117
136
  invokeParams = {
@@ -228,9 +247,9 @@ export class ToolNode<T = any> extends RunnableCallable<T, T> {
228
247
  const { tools, toolMap } = this.loadRuntimeTools(
229
248
  aiMessage.tool_calls ?? []
230
249
  );
231
- this.tools = tools;
232
250
  this.toolMap =
233
251
  toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
252
+ this.programmaticCache = undefined; // Invalidate cache on toolMap change
234
253
  }
235
254
 
236
255
  outputs = await Promise.all(
@@ -52,12 +52,12 @@ result = await get_weather(city="San Francisco")
52
52
  print(f"Temperature: {result['temperature']}°F")
53
53
  print(f"Condition: {result['condition']}")
54
54
  `,
55
- tools: toolDefinitions,
56
55
  };
57
56
  const toolCall = {
58
57
  name: 'programmatic_code_execution',
59
58
  args,
60
59
  toolMap,
60
+ toolDefs: toolDefinitions,
61
61
  };
62
62
 
63
63
  const output = await ptcTool.invoke(args, { toolCall });
@@ -85,12 +85,12 @@ for member in team:
85
85
 
86
86
  print(f"Grand total: \${total:.2f}")
87
87
  `,
88
- tools: toolDefinitions,
89
88
  };
90
89
  const toolCall = {
91
90
  name: 'programmatic_code_execution',
92
91
  args,
93
92
  toolMap,
93
+ toolDefs: toolDefinitions,
94
94
  };
95
95
 
96
96
  const output = await ptcTool.invoke(args, { toolCall });
@@ -116,12 +116,12 @@ results = await asyncio.gather(*[
116
116
  for city, weather in zip(cities, results):
117
117
  print(f"{city}: {weather['temperature']}°F, {weather['condition']}")
118
118
  `,
119
- tools: toolDefinitions,
120
119
  };
121
120
  const toolCall = {
122
121
  name: 'programmatic_code_execution',
123
122
  args,
124
123
  toolMap,
124
+ toolDefs: toolDefinitions,
125
125
  };
126
126
 
127
127
  const output = await ptcTool.invoke(args, { toolCall });
@@ -150,12 +150,12 @@ if high_spenders:
150
150
  else:
151
151
  print("No high spenders found.")
152
152
  `,
153
- tools: toolDefinitions,
154
153
  };
155
154
  const toolCall = {
156
155
  name: 'programmatic_code_execution',
157
156
  args,
158
157
  toolMap,
158
+ toolDefs: toolDefinitions,
159
159
  };
160
160
 
161
161
  const output = await ptcTool.invoke(args, { toolCall });
@@ -180,12 +180,12 @@ for member in team:
180
180
  else:
181
181
  print("No equipment expenses found")
182
182
  `,
183
- tools: toolDefinitions,
184
183
  };
185
184
  const toolCall = {
186
185
  name: 'programmatic_code_execution',
187
186
  args,
188
187
  toolMap,
188
+ toolDefs: toolDefinitions,
189
189
  };
190
190
 
191
191
  const output = await ptcTool.invoke(args, { toolCall });
@@ -206,12 +206,12 @@ for city in cities:
206
206
  except Exception as e:
207
207
  print(f"{city}: Error - {e}")
208
208
  `,
209
- tools: toolDefinitions,
210
209
  };
211
210
  const toolCall = {
212
211
  name: 'programmatic_code_execution',
213
212
  args,
214
213
  toolMap,
214
+ toolDefs: toolDefinitions,
215
215
  };
216
216
 
217
217
  const output = await ptcTool.invoke(args, { toolCall });
@@ -230,12 +230,12 @@ result2 = await calculator(expression="(10 + 5) / 3")
230
230
  print(f"2 + 2 * 3 = {result1['result']}")
231
231
  print(f"(10 + 5) / 3 = {result2['result']:.2f}")
232
232
  `,
233
- tools: toolDefinitions,
234
233
  };
235
234
  const toolCall = {
236
235
  name: 'programmatic_code_execution',
237
236
  args,
238
237
  toolMap,
238
+ toolDefs: toolDefinitions,
239
239
  };
240
240
 
241
241
  const output = await ptcTool.invoke(args, { toolCall });
@@ -265,12 +265,12 @@ for member, expenses in zip(team, all_expenses):
265
265
  total = sum(e['amount'] for e in expenses)
266
266
  print(f" {member['name']}: \${total:.2f} ({len(expenses)} items)")
267
267
  `,
268
- tools: toolDefinitions,
269
268
  };
270
269
  const toolCall = {
271
270
  name: 'programmatic_code_execution',
272
271
  args,
273
272
  toolMap,
273
+ toolDefs: toolDefinitions,
274
274
  };
275
275
 
276
276
  const output = await ptcTool.invoke(args, { toolCall });
@@ -302,12 +302,12 @@ print(f"SF: {sf['temperature']}°F vs NYC: {nyc['temperature']}°F")
302
302
  difference = abs(sf['temperature'] - nyc['temperature'])
303
303
  print(f"Temperature difference: {difference}°F")
304
304
  `,
305
- tools: [weatherToolDef!],
306
305
  };
307
306
  const toolCall = {
308
307
  name: 'programmatic_code_execution',
309
308
  args,
310
309
  toolMap,
310
+ toolDefs: [weatherToolDef!],
311
311
  };
312
312
 
313
313
  const output = await ptcTool.invoke(args, { toolCall });