illuma-agents 1.0.10 → 1.0.11
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.
- package/LICENSE +1 -1
- package/dist/cjs/agents/AgentContext.cjs +228 -27
- package/dist/cjs/agents/AgentContext.cjs.map +1 -1
- package/dist/cjs/common/enum.cjs +2 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/events.cjs +3 -11
- package/dist/cjs/events.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +27 -18
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/instrumentation.cjs +1 -3
- package/dist/cjs/instrumentation.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +1 -1
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/bedrock/index.cjs +122 -7
- package/dist/cjs/llm/bedrock/index.cjs.map +1 -1
- package/dist/cjs/llm/google/index.cjs +1 -1
- package/dist/cjs/llm/google/index.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +6 -6
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/llm/openrouter/index.cjs +1 -1
- package/dist/cjs/llm/openrouter/index.cjs.map +1 -1
- package/dist/cjs/main.cjs +18 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/cache.cjs +149 -54
- package/dist/cjs/messages/cache.cjs.map +1 -1
- package/dist/cjs/messages/tools.cjs +85 -0
- package/dist/cjs/messages/tools.cjs.map +1 -0
- package/dist/cjs/run.cjs +0 -8
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +4 -0
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +438 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -0
- package/dist/cjs/tools/ToolNode.cjs +53 -15
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/ToolSearchRegex.cjs +455 -0
- package/dist/cjs/tools/ToolSearchRegex.cjs.map +1 -0
- package/dist/cjs/tools/search/schema.cjs +7 -9
- package/dist/cjs/tools/search/schema.cjs.map +1 -1
- package/dist/cjs/utils/run.cjs +5 -1
- package/dist/cjs/utils/run.cjs.map +1 -1
- package/dist/esm/agents/AgentContext.mjs +228 -27
- package/dist/esm/agents/AgentContext.mjs.map +1 -1
- package/dist/esm/common/enum.mjs +2 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/events.mjs +4 -12
- package/dist/esm/events.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +27 -18
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/instrumentation.mjs +1 -3
- package/dist/esm/instrumentation.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +1 -1
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/bedrock/index.mjs +122 -7
- package/dist/esm/llm/bedrock/index.mjs.map +1 -1
- package/dist/esm/llm/google/index.mjs +1 -1
- package/dist/esm/llm/google/index.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +6 -6
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/llm/openrouter/index.mjs +1 -1
- package/dist/esm/llm/openrouter/index.mjs.map +1 -1
- package/dist/esm/main.mjs +3 -0
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/messages/cache.mjs +149 -54
- package/dist/esm/messages/cache.mjs.map +1 -1
- package/dist/esm/messages/tools.mjs +82 -0
- package/dist/esm/messages/tools.mjs.map +1 -0
- package/dist/esm/run.mjs +0 -8
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +4 -0
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +430 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -0
- package/dist/esm/tools/ToolNode.mjs +53 -15
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/ToolSearchRegex.mjs +448 -0
- package/dist/esm/tools/ToolSearchRegex.mjs.map +1 -0
- package/dist/esm/tools/search/schema.mjs +7 -9
- package/dist/esm/tools/search/schema.mjs.map +1 -1
- package/dist/esm/utils/run.mjs +5 -1
- package/dist/esm/utils/run.mjs.map +1 -1
- package/dist/types/agents/AgentContext.d.ts +65 -5
- package/dist/types/common/enum.d.ts +2 -0
- package/dist/types/graphs/Graph.d.ts +3 -2
- package/dist/types/index.d.ts +2 -0
- package/dist/types/llm/anthropic/index.d.ts +1 -1
- package/dist/types/llm/bedrock/index.d.ts +31 -4
- package/dist/types/llm/google/index.d.ts +1 -1
- package/dist/types/llm/openai/index.d.ts +3 -3
- package/dist/types/llm/openrouter/index.d.ts +1 -1
- package/dist/types/messages/cache.d.ts +23 -8
- package/dist/types/messages/index.d.ts +1 -0
- package/dist/types/messages/tools.d.ts +17 -0
- package/dist/types/test/mockTools.d.ts +28 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +91 -0
- package/dist/types/tools/ToolNode.d.ts +10 -2
- package/dist/types/tools/ToolSearchRegex.d.ts +80 -0
- package/dist/types/types/graph.d.ts +7 -1
- package/dist/types/types/tools.d.ts +138 -0
- package/package.json +7 -2
- package/src/agents/AgentContext.ts +267 -27
- package/src/agents/__tests__/AgentContext.test.ts +805 -0
- package/src/common/enum.ts +2 -0
- package/src/events.ts +5 -12
- package/src/graphs/Graph.ts +33 -19
- package/src/index.ts +2 -0
- package/src/instrumentation.ts +1 -4
- package/src/llm/anthropic/index.ts +2 -2
- package/src/llm/bedrock/__tests__/bedrock-caching.test.ts +473 -0
- package/src/llm/bedrock/index.ts +150 -13
- package/src/llm/google/index.ts +2 -2
- package/src/llm/openai/index.ts +9 -9
- package/src/llm/openrouter/index.ts +2 -2
- package/src/messages/__tests__/tools.test.ts +473 -0
- package/src/messages/cache.ts +163 -61
- package/src/messages/index.ts +1 -0
- package/src/messages/tools.ts +99 -0
- package/src/run.ts +0 -9
- package/src/scripts/code_exec_ptc.ts +334 -0
- package/src/scripts/image.ts +178 -0
- package/src/scripts/programmatic_exec.ts +396 -0
- package/src/scripts/programmatic_exec_agent.ts +231 -0
- package/src/scripts/test-tools-before-handoff.ts +5 -1
- package/src/scripts/tool_search_regex.ts +162 -0
- package/src/scripts/tools.ts +4 -1
- package/src/specs/thinking-prune.test.ts +52 -118
- package/src/test/mockTools.ts +366 -0
- package/src/tools/CodeExecutor.ts +4 -0
- package/src/tools/ProgrammaticToolCalling.ts +558 -0
- package/src/tools/ToolNode.ts +59 -18
- package/src/tools/ToolSearchRegex.ts +535 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.integration.test.ts +318 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +853 -0
- package/src/tools/__tests__/ToolSearchRegex.integration.test.ts +161 -0
- package/src/tools/__tests__/ToolSearchRegex.test.ts +232 -0
- package/src/tools/search/jina-reranker.test.ts +16 -16
- package/src/tools/search/schema.ts +7 -9
- package/src/types/graph.ts +7 -1
- package/src/types/tools.ts +166 -0
- package/src/utils/run.ts +5 -1
- package/src/tools/search/direct-url.test.ts +0 -530
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { config } from 'dotenv';
|
|
3
|
+
import fetch from 'node-fetch';
|
|
4
|
+
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
5
|
+
import { getEnvironmentVariable } from '@langchain/core/utils/env';
|
|
6
|
+
import { tool } from '@langchain/core/tools';
|
|
7
|
+
import { imageExtRegex, getCodeBaseURL } from './CodeExecutor.mjs';
|
|
8
|
+
import { Constants, EnvVar } from '../common/enum.mjs';
|
|
9
|
+
|
|
10
|
+
// src/tools/ProgrammaticToolCalling.ts
|
|
11
|
+
config();
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Constants
|
|
14
|
+
// ============================================================================
|
|
15
|
+
const imageMessage = 'Image is already displayed to the user';
|
|
16
|
+
const otherMessage = 'File is already downloaded by the user';
|
|
17
|
+
const accessMessage = 'Note: Files are READ-ONLY. Save changes to NEW filenames. To access these files in future executions, provide the `session_id` as a parameter (not in your code).';
|
|
18
|
+
const emptyOutputMessage = 'stdout: Empty. Ensure you\'re writing output explicitly.\n';
|
|
19
|
+
/** Default max round-trips to prevent infinite loops */
|
|
20
|
+
const DEFAULT_MAX_ROUND_TRIPS = 20;
|
|
21
|
+
/** Default execution timeout in milliseconds */
|
|
22
|
+
const DEFAULT_TIMEOUT = 60000;
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Schema
|
|
25
|
+
// ============================================================================
|
|
26
|
+
const ProgrammaticToolCallingSchema = z.object({
|
|
27
|
+
code: z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1)
|
|
30
|
+
.describe(`Python code that calls tools programmatically. Tools are automatically available as async Python functions - DO NOT define them yourself.
|
|
31
|
+
|
|
32
|
+
The Code API generates async function stubs from the tool definitions. Just call them directly:
|
|
33
|
+
|
|
34
|
+
Example (Simple call):
|
|
35
|
+
result = await get_weather(city="San Francisco")
|
|
36
|
+
print(result)
|
|
37
|
+
|
|
38
|
+
Example (Parallel - Fastest):
|
|
39
|
+
results = await asyncio.gather(
|
|
40
|
+
get_weather(city="SF"),
|
|
41
|
+
get_weather(city="NYC"),
|
|
42
|
+
get_weather(city="London")
|
|
43
|
+
)
|
|
44
|
+
for city, weather in zip(["SF", "NYC", "London"], results):
|
|
45
|
+
print(f"{city}: {weather['temperature']}°F")
|
|
46
|
+
|
|
47
|
+
Example (Loop with processing):
|
|
48
|
+
team = await get_team_members()
|
|
49
|
+
for member in team:
|
|
50
|
+
expenses = await get_expenses(user_id=member['id'])
|
|
51
|
+
total = sum(e['amount'] for e in expenses)
|
|
52
|
+
print(f"{member['name']}: \${total:.2f}")
|
|
53
|
+
|
|
54
|
+
Example (Conditional logic):
|
|
55
|
+
data = await fetch_data(source="primary")
|
|
56
|
+
if not data:
|
|
57
|
+
data = await fetch_data(source="backup")
|
|
58
|
+
print(f"Got {len(data)} records")
|
|
59
|
+
|
|
60
|
+
Requirements:
|
|
61
|
+
- Tools are pre-defined as async functions - DO NOT write function definitions
|
|
62
|
+
- Use await for all tool calls
|
|
63
|
+
- Use asyncio.gather() for parallel execution of independent calls
|
|
64
|
+
- Only print() output flows back to the context window
|
|
65
|
+
- Tool results from programmatic calls do NOT consume context tokens`),
|
|
66
|
+
session_id: z
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe('Session ID for file access (same as regular code execution). Files load into /mnt/data/ and are READ-ONLY.'),
|
|
70
|
+
timeout: z
|
|
71
|
+
.number()
|
|
72
|
+
.int()
|
|
73
|
+
.min(1000)
|
|
74
|
+
.max(300000)
|
|
75
|
+
.optional()
|
|
76
|
+
.default(DEFAULT_TIMEOUT)
|
|
77
|
+
.describe('Maximum execution time in milliseconds. Default: 60 seconds. Max: 5 minutes.'),
|
|
78
|
+
});
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Helper Functions
|
|
81
|
+
// ============================================================================
|
|
82
|
+
/** Python reserved keywords that get `_tool` suffix in Code API */
|
|
83
|
+
const PYTHON_KEYWORDS = new Set([
|
|
84
|
+
'False',
|
|
85
|
+
'None',
|
|
86
|
+
'True',
|
|
87
|
+
'and',
|
|
88
|
+
'as',
|
|
89
|
+
'assert',
|
|
90
|
+
'async',
|
|
91
|
+
'await',
|
|
92
|
+
'break',
|
|
93
|
+
'class',
|
|
94
|
+
'continue',
|
|
95
|
+
'def',
|
|
96
|
+
'del',
|
|
97
|
+
'elif',
|
|
98
|
+
'else',
|
|
99
|
+
'except',
|
|
100
|
+
'finally',
|
|
101
|
+
'for',
|
|
102
|
+
'from',
|
|
103
|
+
'global',
|
|
104
|
+
'if',
|
|
105
|
+
'import',
|
|
106
|
+
'in',
|
|
107
|
+
'is',
|
|
108
|
+
'lambda',
|
|
109
|
+
'nonlocal',
|
|
110
|
+
'not',
|
|
111
|
+
'or',
|
|
112
|
+
'pass',
|
|
113
|
+
'raise',
|
|
114
|
+
'return',
|
|
115
|
+
'try',
|
|
116
|
+
'while',
|
|
117
|
+
'with',
|
|
118
|
+
'yield',
|
|
119
|
+
]);
|
|
120
|
+
/**
|
|
121
|
+
* Normalizes a tool name to Python identifier format.
|
|
122
|
+
* Must match the Code API's `normalizePythonFunctionName` exactly:
|
|
123
|
+
* 1. Replace hyphens and spaces with underscores
|
|
124
|
+
* 2. Remove any other invalid characters
|
|
125
|
+
* 3. Prefix with underscore if starts with number
|
|
126
|
+
* 4. Append `_tool` if it's a Python keyword
|
|
127
|
+
* @param name - The tool name to normalize
|
|
128
|
+
* @returns Normalized Python-safe identifier
|
|
129
|
+
*/
|
|
130
|
+
function normalizeToPythonIdentifier(name) {
|
|
131
|
+
let normalized = name.replace(/[-\s]/g, '_');
|
|
132
|
+
normalized = normalized.replace(/[^a-zA-Z0-9_]/g, '');
|
|
133
|
+
if (/^[0-9]/.test(normalized)) {
|
|
134
|
+
normalized = '_' + normalized;
|
|
135
|
+
}
|
|
136
|
+
if (PYTHON_KEYWORDS.has(normalized)) {
|
|
137
|
+
normalized = normalized + '_tool';
|
|
138
|
+
}
|
|
139
|
+
return normalized;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Extracts tool names that are actually called in the Python code.
|
|
143
|
+
* Handles hyphen/underscore conversion since Python identifiers use underscores.
|
|
144
|
+
* @param code - The Python code to analyze
|
|
145
|
+
* @param toolNameMap - Map from normalized Python name to original tool name
|
|
146
|
+
* @returns Set of original tool names found in the code
|
|
147
|
+
*/
|
|
148
|
+
function extractUsedToolNames(code, toolNameMap) {
|
|
149
|
+
const usedTools = new Set();
|
|
150
|
+
for (const [pythonName, originalName] of toolNameMap) {
|
|
151
|
+
const escapedName = pythonName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
152
|
+
const pattern = new RegExp(`\\b${escapedName}\\s*\\(`, 'g');
|
|
153
|
+
if (pattern.test(code)) {
|
|
154
|
+
usedTools.add(originalName);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return usedTools;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Filters tool definitions to only include tools actually used in the code.
|
|
161
|
+
* Handles the hyphen-to-underscore conversion for Python compatibility.
|
|
162
|
+
* @param toolDefs - All available tool definitions
|
|
163
|
+
* @param code - The Python code to analyze
|
|
164
|
+
* @param debug - Enable debug logging
|
|
165
|
+
* @returns Filtered array of tool definitions
|
|
166
|
+
*/
|
|
167
|
+
function filterToolsByUsage(toolDefs, code, debug = false) {
|
|
168
|
+
const toolNameMap = new Map();
|
|
169
|
+
for (const tool of toolDefs) {
|
|
170
|
+
const pythonName = normalizeToPythonIdentifier(tool.name);
|
|
171
|
+
toolNameMap.set(pythonName, tool.name);
|
|
172
|
+
}
|
|
173
|
+
const usedToolNames = extractUsedToolNames(code, toolNameMap);
|
|
174
|
+
if (debug) {
|
|
175
|
+
// eslint-disable-next-line no-console
|
|
176
|
+
console.log(`[PTC Debug] Tool filtering: found ${usedToolNames.size}/${toolDefs.length} tools in code`);
|
|
177
|
+
if (usedToolNames.size > 0) {
|
|
178
|
+
// eslint-disable-next-line no-console
|
|
179
|
+
console.log(`[PTC Debug] Matched tools: ${Array.from(usedToolNames).join(', ')}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (usedToolNames.size === 0) {
|
|
183
|
+
if (debug) {
|
|
184
|
+
// eslint-disable-next-line no-console
|
|
185
|
+
console.log('[PTC Debug] No tools detected in code - sending all tools as fallback');
|
|
186
|
+
}
|
|
187
|
+
return toolDefs;
|
|
188
|
+
}
|
|
189
|
+
return toolDefs.filter((tool) => usedToolNames.has(tool.name));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Makes an HTTP request to the Code API.
|
|
193
|
+
* @param endpoint - The API endpoint URL
|
|
194
|
+
* @param apiKey - The API key for authentication
|
|
195
|
+
* @param body - The request body
|
|
196
|
+
* @param proxy - Optional HTTP proxy URL
|
|
197
|
+
* @returns The parsed API response
|
|
198
|
+
*/
|
|
199
|
+
async function makeRequest(endpoint, apiKey, body, proxy) {
|
|
200
|
+
const fetchOptions = {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
headers: {
|
|
203
|
+
'Content-Type': 'application/json',
|
|
204
|
+
'User-Agent': 'Illuma/1.0',
|
|
205
|
+
'X-API-Key': apiKey,
|
|
206
|
+
},
|
|
207
|
+
body: JSON.stringify(body),
|
|
208
|
+
};
|
|
209
|
+
if (proxy != null && proxy !== '') {
|
|
210
|
+
fetchOptions.agent = new HttpsProxyAgent(proxy);
|
|
211
|
+
}
|
|
212
|
+
const response = await fetch(endpoint, fetchOptions);
|
|
213
|
+
if (!response.ok) {
|
|
214
|
+
const errorText = await response.text();
|
|
215
|
+
throw new Error(`HTTP error! status: ${response.status}, body: ${errorText}`);
|
|
216
|
+
}
|
|
217
|
+
return (await response.json());
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Executes tools in parallel when requested by the API.
|
|
221
|
+
* Uses Promise.all for parallel execution, catching individual errors.
|
|
222
|
+
* @param toolCalls - Array of tool calls from the API
|
|
223
|
+
* @param toolMap - Map of tool names to executable tools
|
|
224
|
+
* @returns Array of tool results
|
|
225
|
+
*/
|
|
226
|
+
async function executeTools(toolCalls, toolMap) {
|
|
227
|
+
const executions = toolCalls.map(async (call) => {
|
|
228
|
+
const tool = toolMap.get(call.name);
|
|
229
|
+
if (!tool) {
|
|
230
|
+
return {
|
|
231
|
+
call_id: call.id,
|
|
232
|
+
result: null,
|
|
233
|
+
is_error: true,
|
|
234
|
+
error_message: `Tool '${call.name}' not found. Available tools: ${Array.from(toolMap.keys()).join(', ')}`,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
const result = await tool.invoke(call.input, {
|
|
239
|
+
metadata: { [Constants.PROGRAMMATIC_TOOL_CALLING]: true },
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
call_id: call.id,
|
|
243
|
+
result,
|
|
244
|
+
is_error: false,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
return {
|
|
249
|
+
call_id: call.id,
|
|
250
|
+
result: null,
|
|
251
|
+
is_error: true,
|
|
252
|
+
error_message: error.message || 'Tool execution failed',
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
return await Promise.all(executions);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Formats the completed response for the agent.
|
|
260
|
+
* @param response - The completed API response
|
|
261
|
+
* @returns Tuple of [formatted string, artifact]
|
|
262
|
+
*/
|
|
263
|
+
function formatCompletedResponse(response) {
|
|
264
|
+
let formatted = '';
|
|
265
|
+
if (response.stdout != null && response.stdout !== '') {
|
|
266
|
+
formatted += `stdout:\n${response.stdout}\n`;
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
formatted += emptyOutputMessage;
|
|
270
|
+
}
|
|
271
|
+
if (response.stderr != null && response.stderr !== '') {
|
|
272
|
+
formatted += `stderr:\n${response.stderr}\n`;
|
|
273
|
+
}
|
|
274
|
+
if (response.files && response.files.length > 0) {
|
|
275
|
+
formatted += 'Generated files:\n';
|
|
276
|
+
const fileCount = response.files.length;
|
|
277
|
+
for (let i = 0; i < fileCount; i++) {
|
|
278
|
+
const file = response.files[i];
|
|
279
|
+
const isImage = imageExtRegex.test(file.name);
|
|
280
|
+
formatted += `- /mnt/data/${file.name} | ${isImage ? imageMessage : otherMessage}`;
|
|
281
|
+
if (i < fileCount - 1) {
|
|
282
|
+
formatted += fileCount <= 3 ? ', ' : ',\n';
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
formatted += `\nsession_id: ${response.session_id}\n\n${accessMessage}`;
|
|
286
|
+
}
|
|
287
|
+
return [
|
|
288
|
+
formatted.trim(),
|
|
289
|
+
{
|
|
290
|
+
session_id: response.session_id,
|
|
291
|
+
files: response.files,
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
}
|
|
295
|
+
// ============================================================================
|
|
296
|
+
// Tool Factory
|
|
297
|
+
// ============================================================================
|
|
298
|
+
/**
|
|
299
|
+
* Creates a Programmatic Tool Calling tool for complex multi-tool workflows.
|
|
300
|
+
*
|
|
301
|
+
* This tool enables AI agents to write Python code that orchestrates multiple
|
|
302
|
+
* tool calls programmatically, reducing LLM round-trips and token usage.
|
|
303
|
+
*
|
|
304
|
+
* The tool map must be provided at runtime via config.configurable.toolMap.
|
|
305
|
+
*
|
|
306
|
+
* @param params - Configuration parameters (apiKey, baseUrl, maxRoundTrips, proxy)
|
|
307
|
+
* @returns A LangChain DynamicStructuredTool for programmatic tool calling
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
* const ptcTool = createProgrammaticToolCallingTool({
|
|
311
|
+
* apiKey: process.env.CODE_API_KEY,
|
|
312
|
+
* maxRoundTrips: 20
|
|
313
|
+
* });
|
|
314
|
+
*
|
|
315
|
+
* const [output, artifact] = await ptcTool.invoke(
|
|
316
|
+
* { code, tools },
|
|
317
|
+
* { configurable: { toolMap } }
|
|
318
|
+
* );
|
|
319
|
+
*/
|
|
320
|
+
function createProgrammaticToolCallingTool(initParams = {}) {
|
|
321
|
+
const apiKey = initParams[EnvVar.CODE_API_KEY] ??
|
|
322
|
+
initParams.apiKey ??
|
|
323
|
+
getEnvironmentVariable(EnvVar.CODE_API_KEY) ??
|
|
324
|
+
'';
|
|
325
|
+
if (!apiKey) {
|
|
326
|
+
throw new Error('No API key provided for programmatic tool calling. ' +
|
|
327
|
+
'Set CODE_API_KEY environment variable or pass apiKey in initParams.');
|
|
328
|
+
}
|
|
329
|
+
const baseUrl = initParams.baseUrl ?? getCodeBaseURL();
|
|
330
|
+
const maxRoundTrips = initParams.maxRoundTrips ?? DEFAULT_MAX_ROUND_TRIPS;
|
|
331
|
+
const proxy = initParams.proxy ?? process.env.PROXY;
|
|
332
|
+
const debug = initParams.debug ?? process.env.PTC_DEBUG === 'true';
|
|
333
|
+
const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;
|
|
334
|
+
const description = `
|
|
335
|
+
Run tools by writing Python code. Tools are available as async functions - just call them with await.
|
|
336
|
+
|
|
337
|
+
This is different from execute_code: here you can call your tools (like get_weather, get_expenses, etc.) directly in Python code.
|
|
338
|
+
|
|
339
|
+
Usage:
|
|
340
|
+
- Tools are pre-defined as async functions - call them with await
|
|
341
|
+
- Use asyncio.gather() to run multiple tools in parallel
|
|
342
|
+
- Only print() output is returned - tool results stay in Python
|
|
343
|
+
|
|
344
|
+
Examples:
|
|
345
|
+
- Simple: result = await get_weather(city="NYC")
|
|
346
|
+
- Loop: for user in users: data = await get_expenses(user_id=user['id'])
|
|
347
|
+
- Parallel: sf, ny = await asyncio.gather(get_weather(city="SF"), get_weather(city="NY"))
|
|
348
|
+
|
|
349
|
+
When to use this instead of calling tools directly:
|
|
350
|
+
- You need to call tools in a loop (process many items)
|
|
351
|
+
- You want parallel execution (asyncio.gather)
|
|
352
|
+
- You need conditionals based on tool results
|
|
353
|
+
- You want to aggregate/filter data before returning
|
|
354
|
+
`.trim();
|
|
355
|
+
return tool(async (params, config) => {
|
|
356
|
+
const { code, session_id, timeout = DEFAULT_TIMEOUT } = params;
|
|
357
|
+
// Extra params injected by ToolNode (follows web_search pattern)
|
|
358
|
+
const { toolMap, toolDefs } = (config.toolCall ?? {});
|
|
359
|
+
if (toolMap == null || toolMap.size === 0) {
|
|
360
|
+
throw new Error('No toolMap provided. ' +
|
|
361
|
+
'ToolNode should inject this from AgentContext when invoked through the graph.');
|
|
362
|
+
}
|
|
363
|
+
if (toolDefs == null || toolDefs.length === 0) {
|
|
364
|
+
throw new Error('No tool definitions provided. ' +
|
|
365
|
+
'Either pass tools in the input or ensure ToolNode injects toolDefs.');
|
|
366
|
+
}
|
|
367
|
+
let roundTrip = 0;
|
|
368
|
+
try {
|
|
369
|
+
// ====================================================================
|
|
370
|
+
// Phase 1: Filter tools and make initial request
|
|
371
|
+
// ====================================================================
|
|
372
|
+
const effectiveTools = filterToolsByUsage(toolDefs, code, debug);
|
|
373
|
+
if (debug) {
|
|
374
|
+
// eslint-disable-next-line no-console
|
|
375
|
+
console.log(`[PTC Debug] Sending ${effectiveTools.length} tools to API ` +
|
|
376
|
+
`(filtered from ${toolDefs.length})`);
|
|
377
|
+
}
|
|
378
|
+
let response = await makeRequest(EXEC_ENDPOINT, apiKey, {
|
|
379
|
+
code,
|
|
380
|
+
tools: effectiveTools,
|
|
381
|
+
session_id,
|
|
382
|
+
timeout,
|
|
383
|
+
}, proxy);
|
|
384
|
+
// ====================================================================
|
|
385
|
+
// Phase 2: Handle response loop
|
|
386
|
+
// ====================================================================
|
|
387
|
+
while (response.status === 'tool_call_required') {
|
|
388
|
+
roundTrip++;
|
|
389
|
+
if (roundTrip > maxRoundTrips) {
|
|
390
|
+
throw new Error(`Exceeded maximum round trips (${maxRoundTrips}). ` +
|
|
391
|
+
'This may indicate an infinite loop, excessive tool calls, ' +
|
|
392
|
+
'or a logic error in your code.');
|
|
393
|
+
}
|
|
394
|
+
if (debug) {
|
|
395
|
+
// eslint-disable-next-line no-console
|
|
396
|
+
console.log(`[PTC Debug] Round trip ${roundTrip}: ${response.tool_calls?.length ?? 0} tool(s) to execute`);
|
|
397
|
+
}
|
|
398
|
+
const toolResults = await executeTools(response.tool_calls ?? [], toolMap);
|
|
399
|
+
response = await makeRequest(EXEC_ENDPOINT, apiKey, {
|
|
400
|
+
continuation_token: response.continuation_token,
|
|
401
|
+
tool_results: toolResults,
|
|
402
|
+
}, proxy);
|
|
403
|
+
}
|
|
404
|
+
// ====================================================================
|
|
405
|
+
// Phase 3: Handle final state
|
|
406
|
+
// ====================================================================
|
|
407
|
+
if (response.status === 'completed') {
|
|
408
|
+
return formatCompletedResponse(response);
|
|
409
|
+
}
|
|
410
|
+
if (response.status === 'error') {
|
|
411
|
+
throw new Error(`Execution error: ${response.error}` +
|
|
412
|
+
(response.stderr != null && response.stderr !== ''
|
|
413
|
+
? `\n\nStderr:\n${response.stderr}`
|
|
414
|
+
: ''));
|
|
415
|
+
}
|
|
416
|
+
throw new Error(`Unexpected response status: ${response.status}`);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
throw new Error(`Programmatic execution failed: ${error.message}`);
|
|
420
|
+
}
|
|
421
|
+
}, {
|
|
422
|
+
name: Constants.PROGRAMMATIC_TOOL_CALLING,
|
|
423
|
+
description,
|
|
424
|
+
schema: ProgrammaticToolCallingSchema,
|
|
425
|
+
responseFormat: Constants.CONTENT_AND_ARTIFACT,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export { createProgrammaticToolCallingTool, executeTools, extractUsedToolNames, filterToolsByUsage, formatCompletedResponse, makeRequest, normalizeToPythonIdentifier };
|
|
430
|
+
//# sourceMappingURL=ProgrammaticToolCalling.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProgrammaticToolCalling.mjs","sources":["../../../src/tools/ProgrammaticToolCalling.ts"],"sourcesContent":["// src/tools/ProgrammaticToolCalling.ts\nimport { z } from 'zod';\nimport { config } from 'dotenv';\nimport fetch, { RequestInit } from 'node-fetch';\nimport { HttpsProxyAgent } from 'https-proxy-agent';\nimport { getEnvironmentVariable } from '@langchain/core/utils/env';\nimport { tool, DynamicStructuredTool } from '@langchain/core/tools';\nimport type { ToolCall } from '@langchain/core/messages/tool';\nimport type * as t from '@/types';\nimport { imageExtRegex, getCodeBaseURL } from './CodeExecutor';\nimport { EnvVar, Constants } from '@/common';\n\nconfig();\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst imageMessage = 'Image is already displayed to the user';\nconst otherMessage = 'File is already downloaded by the user';\nconst accessMessage =\n 'Note: Files are READ-ONLY. Save changes to NEW filenames. To access these files in future executions, provide the `session_id` as a parameter (not in your code).';\nconst emptyOutputMessage =\n 'stdout: Empty. Ensure you\\'re writing output explicitly.\\n';\n\n/** Default max round-trips to prevent infinite loops */\nconst DEFAULT_MAX_ROUND_TRIPS = 20;\n\n/** Default execution timeout in milliseconds */\nconst DEFAULT_TIMEOUT = 60000;\n\n// ============================================================================\n// Schema\n// ============================================================================\n\nconst ProgrammaticToolCallingSchema = z.object({\n code: z\n .string()\n .min(1)\n .describe(\n `Python code that calls tools programmatically. Tools are automatically available as async Python functions - DO NOT define them yourself.\n\nThe Code API generates async function stubs from the tool definitions. Just call them directly:\n\nExample (Simple call):\n result = await get_weather(city=\"San Francisco\")\n print(result)\n\nExample (Parallel - Fastest):\n results = await asyncio.gather(\n get_weather(city=\"SF\"),\n get_weather(city=\"NYC\"),\n get_weather(city=\"London\")\n )\n for city, weather in zip([\"SF\", \"NYC\", \"London\"], results):\n print(f\"{city}: {weather['temperature']}°F\")\n\nExample (Loop with processing):\n team = await get_team_members()\n for member in team:\n expenses = await get_expenses(user_id=member['id'])\n total = sum(e['amount'] for e in expenses)\n print(f\"{member['name']}: \\${total:.2f}\")\n\nExample (Conditional logic):\n data = await fetch_data(source=\"primary\")\n if not data:\n data = await fetch_data(source=\"backup\")\n print(f\"Got {len(data)} records\")\n\nRequirements:\n- Tools are pre-defined as async functions - DO NOT write function definitions\n- Use await for all tool calls\n- Use asyncio.gather() for parallel execution of independent calls\n- Only print() output flows back to the context window\n- Tool results from programmatic calls do NOT consume context tokens`\n ),\n session_id: z\n .string()\n .optional()\n .describe(\n 'Session ID for file access (same as regular code execution). Files load into /mnt/data/ and are READ-ONLY.'\n ),\n timeout: z\n .number()\n .int()\n .min(1000)\n .max(300000)\n .optional()\n .default(DEFAULT_TIMEOUT)\n .describe(\n 'Maximum execution time in milliseconds. Default: 60 seconds. Max: 5 minutes.'\n ),\n});\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/** Python reserved keywords that get `_tool` suffix in Code API */\nconst PYTHON_KEYWORDS = new Set([\n 'False',\n 'None',\n 'True',\n 'and',\n 'as',\n 'assert',\n 'async',\n 'await',\n 'break',\n 'class',\n 'continue',\n 'def',\n 'del',\n 'elif',\n 'else',\n 'except',\n 'finally',\n 'for',\n 'from',\n 'global',\n 'if',\n 'import',\n 'in',\n 'is',\n 'lambda',\n 'nonlocal',\n 'not',\n 'or',\n 'pass',\n 'raise',\n 'return',\n 'try',\n 'while',\n 'with',\n 'yield',\n]);\n\n/**\n * Normalizes a tool name to Python identifier format.\n * Must match the Code API's `normalizePythonFunctionName` exactly:\n * 1. Replace hyphens and spaces with underscores\n * 2. Remove any other invalid characters\n * 3. Prefix with underscore if starts with number\n * 4. Append `_tool` if it's a Python keyword\n * @param name - The tool name to normalize\n * @returns Normalized Python-safe identifier\n */\nexport function normalizeToPythonIdentifier(name: string): string {\n let normalized = name.replace(/[-\\s]/g, '_');\n\n normalized = normalized.replace(/[^a-zA-Z0-9_]/g, '');\n\n if (/^[0-9]/.test(normalized)) {\n normalized = '_' + normalized;\n }\n\n if (PYTHON_KEYWORDS.has(normalized)) {\n normalized = normalized + '_tool';\n }\n\n return normalized;\n}\n\n/**\n * Extracts tool names that are actually called in the Python code.\n * Handles hyphen/underscore conversion since Python identifiers use underscores.\n * @param code - The Python code to analyze\n * @param toolNameMap - Map from normalized Python name to original tool name\n * @returns Set of original tool names found in the code\n */\nexport function extractUsedToolNames(\n code: string,\n toolNameMap: Map<string, string>\n): Set<string> {\n const usedTools = new Set<string>();\n\n for (const [pythonName, originalName] of toolNameMap) {\n const escapedName = pythonName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`\\\\b${escapedName}\\\\s*\\\\(`, 'g');\n\n if (pattern.test(code)) {\n usedTools.add(originalName);\n }\n }\n\n return usedTools;\n}\n\n/**\n * Filters tool definitions to only include tools actually used in the code.\n * Handles the hyphen-to-underscore conversion for Python compatibility.\n * @param toolDefs - All available tool definitions\n * @param code - The Python code to analyze\n * @param debug - Enable debug logging\n * @returns Filtered array of tool definitions\n */\nexport function filterToolsByUsage(\n toolDefs: t.LCTool[],\n code: string,\n debug = false\n): t.LCTool[] {\n const toolNameMap = new Map<string, string>();\n for (const tool of toolDefs) {\n const pythonName = normalizeToPythonIdentifier(tool.name);\n toolNameMap.set(pythonName, tool.name);\n }\n\n const usedToolNames = extractUsedToolNames(code, toolNameMap);\n\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Tool filtering: found ${usedToolNames.size}/${toolDefs.length} tools in code`\n );\n if (usedToolNames.size > 0) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Matched tools: ${Array.from(usedToolNames).join(', ')}`\n );\n }\n }\n\n if (usedToolNames.size === 0) {\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n '[PTC Debug] No tools detected in code - sending all tools as fallback'\n );\n }\n return toolDefs;\n }\n\n return toolDefs.filter((tool) => usedToolNames.has(tool.name));\n}\n\n/**\n * Makes an HTTP request to the Code API.\n * @param endpoint - The API endpoint URL\n * @param apiKey - The API key for authentication\n * @param body - The request body\n * @param proxy - Optional HTTP proxy URL\n * @returns The parsed API response\n */\nexport async function makeRequest(\n endpoint: string,\n apiKey: string,\n body: Record<string, unknown>,\n proxy?: string\n): Promise<t.ProgrammaticExecutionResponse> {\n const fetchOptions: RequestInit = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'User-Agent': 'Illuma/1.0',\n 'X-API-Key': apiKey,\n },\n body: JSON.stringify(body),\n };\n\n if (proxy != null && proxy !== '') {\n fetchOptions.agent = new HttpsProxyAgent(proxy);\n }\n\n const response = await fetch(endpoint, fetchOptions);\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(\n `HTTP error! status: ${response.status}, body: ${errorText}`\n );\n }\n\n return (await response.json()) as t.ProgrammaticExecutionResponse;\n}\n\n/**\n * Executes tools in parallel when requested by the API.\n * Uses Promise.all for parallel execution, catching individual errors.\n * @param toolCalls - Array of tool calls from the API\n * @param toolMap - Map of tool names to executable tools\n * @returns Array of tool results\n */\nexport async function executeTools(\n toolCalls: t.PTCToolCall[],\n toolMap: t.ToolMap\n): Promise<t.PTCToolResult[]> {\n const executions = toolCalls.map(async (call): Promise<t.PTCToolResult> => {\n const tool = toolMap.get(call.name);\n\n if (!tool) {\n return {\n call_id: call.id,\n result: null,\n is_error: true,\n error_message: `Tool '${call.name}' not found. Available tools: ${Array.from(toolMap.keys()).join(', ')}`,\n };\n }\n\n try {\n const result = await tool.invoke(call.input, {\n metadata: { [Constants.PROGRAMMATIC_TOOL_CALLING]: true },\n });\n return {\n call_id: call.id,\n result,\n is_error: false,\n };\n } catch (error) {\n return {\n call_id: call.id,\n result: null,\n is_error: true,\n error_message: (error as Error).message || 'Tool execution failed',\n };\n }\n });\n\n return await Promise.all(executions);\n}\n\n/**\n * Formats the completed response for the agent.\n * @param response - The completed API response\n * @returns Tuple of [formatted string, artifact]\n */\nexport function formatCompletedResponse(\n response: t.ProgrammaticExecutionResponse\n): [string, t.ProgrammaticExecutionArtifact] {\n let formatted = '';\n\n if (response.stdout != null && response.stdout !== '') {\n formatted += `stdout:\\n${response.stdout}\\n`;\n } else {\n formatted += emptyOutputMessage;\n }\n\n if (response.stderr != null && response.stderr !== '') {\n formatted += `stderr:\\n${response.stderr}\\n`;\n }\n\n if (response.files && response.files.length > 0) {\n formatted += 'Generated files:\\n';\n\n const fileCount = response.files.length;\n for (let i = 0; i < fileCount; i++) {\n const file = response.files[i];\n const isImage = imageExtRegex.test(file.name);\n formatted += `- /mnt/data/${file.name} | ${isImage ? imageMessage : otherMessage}`;\n\n if (i < fileCount - 1) {\n formatted += fileCount <= 3 ? ', ' : ',\\n';\n }\n }\n\n formatted += `\\nsession_id: ${response.session_id}\\n\\n${accessMessage}`;\n }\n\n return [\n formatted.trim(),\n {\n session_id: response.session_id,\n files: response.files,\n },\n ];\n}\n\n// ============================================================================\n// Tool Factory\n// ============================================================================\n\n/**\n * Creates a Programmatic Tool Calling tool for complex multi-tool workflows.\n *\n * This tool enables AI agents to write Python code that orchestrates multiple\n * tool calls programmatically, reducing LLM round-trips and token usage.\n *\n * The tool map must be provided at runtime via config.configurable.toolMap.\n *\n * @param params - Configuration parameters (apiKey, baseUrl, maxRoundTrips, proxy)\n * @returns A LangChain DynamicStructuredTool for programmatic tool calling\n *\n * @example\n * const ptcTool = createProgrammaticToolCallingTool({\n * apiKey: process.env.CODE_API_KEY,\n * maxRoundTrips: 20\n * });\n *\n * const [output, artifact] = await ptcTool.invoke(\n * { code, tools },\n * { configurable: { toolMap } }\n * );\n */\nexport function createProgrammaticToolCallingTool(\n initParams: t.ProgrammaticToolCallingParams = {}\n): DynamicStructuredTool<typeof ProgrammaticToolCallingSchema> {\n const apiKey =\n (initParams[EnvVar.CODE_API_KEY] as string | undefined) ??\n initParams.apiKey ??\n getEnvironmentVariable(EnvVar.CODE_API_KEY) ??\n '';\n\n if (!apiKey) {\n throw new Error(\n 'No API key provided for programmatic tool calling. ' +\n 'Set CODE_API_KEY environment variable or pass apiKey in initParams.'\n );\n }\n\n const baseUrl = initParams.baseUrl ?? getCodeBaseURL();\n const maxRoundTrips = initParams.maxRoundTrips ?? DEFAULT_MAX_ROUND_TRIPS;\n const proxy = initParams.proxy ?? process.env.PROXY;\n const debug = initParams.debug ?? process.env.PTC_DEBUG === 'true';\n const EXEC_ENDPOINT = `${baseUrl}/exec/programmatic`;\n\n const description = `\nRun tools by writing Python code. Tools are available as async functions - just call them with await.\n\nThis is different from execute_code: here you can call your tools (like get_weather, get_expenses, etc.) directly in Python code.\n\nUsage:\n- Tools are pre-defined as async functions - call them with await\n- Use asyncio.gather() to run multiple tools in parallel\n- Only print() output is returned - tool results stay in Python\n\nExamples:\n- Simple: result = await get_weather(city=\"NYC\")\n- Loop: for user in users: data = await get_expenses(user_id=user['id'])\n- Parallel: sf, ny = await asyncio.gather(get_weather(city=\"SF\"), get_weather(city=\"NY\"))\n\nWhen to use this instead of calling tools directly:\n- You need to call tools in a loop (process many items)\n- You want parallel execution (asyncio.gather)\n- You need conditionals based on tool results\n- You want to aggregate/filter data before returning\n`.trim();\n\n return tool<typeof ProgrammaticToolCallingSchema>(\n async (params, config) => {\n const { code, session_id, timeout = DEFAULT_TIMEOUT } = params;\n\n // Extra params injected by ToolNode (follows web_search pattern)\n const { toolMap, toolDefs } = (config.toolCall ?? {}) as ToolCall &\n Partial<t.ProgrammaticCache>;\n\n if (toolMap == null || toolMap.size === 0) {\n throw new Error(\n 'No toolMap provided. ' +\n 'ToolNode should inject this from AgentContext when invoked through the graph.'\n );\n }\n\n if (toolDefs == null || toolDefs.length === 0) {\n throw new Error(\n 'No tool definitions provided. ' +\n 'Either pass tools in the input or ensure ToolNode injects toolDefs.'\n );\n }\n\n let roundTrip = 0;\n\n try {\n // ====================================================================\n // Phase 1: Filter tools and make initial request\n // ====================================================================\n\n const effectiveTools = filterToolsByUsage(toolDefs, code, debug);\n\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Sending ${effectiveTools.length} tools to API ` +\n `(filtered from ${toolDefs.length})`\n );\n }\n\n let response = await makeRequest(\n EXEC_ENDPOINT,\n apiKey,\n {\n code,\n tools: effectiveTools,\n session_id,\n timeout,\n },\n proxy\n );\n\n // ====================================================================\n // Phase 2: Handle response loop\n // ====================================================================\n\n while (response.status === 'tool_call_required') {\n roundTrip++;\n\n if (roundTrip > maxRoundTrips) {\n throw new Error(\n `Exceeded maximum round trips (${maxRoundTrips}). ` +\n 'This may indicate an infinite loop, excessive tool calls, ' +\n 'or a logic error in your code.'\n );\n }\n\n if (debug) {\n // eslint-disable-next-line no-console\n console.log(\n `[PTC Debug] Round trip ${roundTrip}: ${response.tool_calls?.length ?? 0} tool(s) to execute`\n );\n }\n\n const toolResults = await executeTools(\n response.tool_calls ?? [],\n toolMap\n );\n\n response = await makeRequest(\n EXEC_ENDPOINT,\n apiKey,\n {\n continuation_token: response.continuation_token,\n tool_results: toolResults,\n },\n proxy\n );\n }\n\n // ====================================================================\n // Phase 3: Handle final state\n // ====================================================================\n\n if (response.status === 'completed') {\n return formatCompletedResponse(response);\n }\n\n if (response.status === 'error') {\n throw new Error(\n `Execution error: ${response.error}` +\n (response.stderr != null && response.stderr !== ''\n ? `\\n\\nStderr:\\n${response.stderr}`\n : '')\n );\n }\n\n throw new Error(`Unexpected response status: ${response.status}`);\n } catch (error) {\n throw new Error(\n `Programmatic execution failed: ${(error as Error).message}`\n );\n }\n },\n {\n name: Constants.PROGRAMMATIC_TOOL_CALLING,\n description,\n schema: ProgrammaticToolCallingSchema,\n responseFormat: Constants.CONTENT_AND_ARTIFACT,\n }\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isBaseMessage, ToolMessage, isAIMessage } from '@langchain/core/messages';
|
|
2
2
|
import { isCommand, isGraphInterrupt, Command, END, Send } from '@langchain/langgraph';
|
|
3
|
-
import '../common/enum.mjs';
|
|
3
|
+
import { Constants } from '../common/enum.mjs';
|
|
4
4
|
import 'nanoid';
|
|
5
5
|
import '../messages/core.mjs';
|
|
6
6
|
import { RunnableCallable } from '../utils/run.mjs';
|
|
@@ -14,7 +14,6 @@ function isSend(value) {
|
|
|
14
14
|
}
|
|
15
15
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
16
16
|
class ToolNode extends RunnableCallable {
|
|
17
|
-
tools;
|
|
18
17
|
toolMap;
|
|
19
18
|
loadRuntimeTools;
|
|
20
19
|
handleToolErrors = true;
|
|
@@ -22,15 +21,41 @@ class ToolNode extends RunnableCallable {
|
|
|
22
21
|
toolCallStepIds;
|
|
23
22
|
errorHandler;
|
|
24
23
|
toolUsageCount;
|
|
25
|
-
|
|
24
|
+
/** Tool registry for filtering (lazy computation of programmatic maps) */
|
|
25
|
+
toolRegistry;
|
|
26
|
+
/** Cached programmatic tools (computed once on first PTC call) */
|
|
27
|
+
programmaticCache;
|
|
28
|
+
constructor({ tools, toolMap, name, tags, errorHandler, toolCallStepIds, handleToolErrors, loadRuntimeTools, toolRegistry, }) {
|
|
26
29
|
super({ name, tags, func: (input, config) => this.run(input, config) });
|
|
27
|
-
this.tools = tools;
|
|
28
30
|
this.toolMap = toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
|
|
29
31
|
this.toolCallStepIds = toolCallStepIds;
|
|
30
32
|
this.handleToolErrors = handleToolErrors ?? this.handleToolErrors;
|
|
31
33
|
this.loadRuntimeTools = loadRuntimeTools;
|
|
32
34
|
this.errorHandler = errorHandler;
|
|
33
35
|
this.toolUsageCount = new Map();
|
|
36
|
+
this.toolRegistry = toolRegistry;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Returns cached programmatic tools, computing once on first access.
|
|
40
|
+
* Single iteration builds both toolMap and toolDefs simultaneously.
|
|
41
|
+
*/
|
|
42
|
+
getProgrammaticTools() {
|
|
43
|
+
if (this.programmaticCache)
|
|
44
|
+
return this.programmaticCache;
|
|
45
|
+
const toolMap = new Map();
|
|
46
|
+
const toolDefs = [];
|
|
47
|
+
if (this.toolRegistry) {
|
|
48
|
+
for (const [name, toolDef] of this.toolRegistry) {
|
|
49
|
+
if ((toolDef.allowed_callers ?? ['direct']).includes('code_execution')) {
|
|
50
|
+
toolDefs.push(toolDef);
|
|
51
|
+
const tool = this.toolMap.get(name);
|
|
52
|
+
if (tool)
|
|
53
|
+
toolMap.set(name, tool);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
this.programmaticCache = { toolMap, toolDefs };
|
|
58
|
+
return this.programmaticCache;
|
|
34
59
|
}
|
|
35
60
|
/**
|
|
36
61
|
* Returns a snapshot of the current tool usage counts.
|
|
@@ -52,17 +77,30 @@ class ToolNode extends RunnableCallable {
|
|
|
52
77
|
this.toolUsageCount.set(call.name, turn + 1);
|
|
53
78
|
const args = call.args;
|
|
54
79
|
const stepId = this.toolCallStepIds?.get(call.id);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
80
|
+
// Build invoke params - LangChain extracts non-schema fields to config.toolCall
|
|
81
|
+
let invokeParams = {
|
|
82
|
+
...call,
|
|
83
|
+
args,
|
|
84
|
+
type: 'tool_call',
|
|
85
|
+
stepId,
|
|
86
|
+
turn,
|
|
87
|
+
};
|
|
88
|
+
// Inject runtime data for special tools (becomes available at config.toolCall)
|
|
89
|
+
if (call.name === Constants.PROGRAMMATIC_TOOL_CALLING) {
|
|
90
|
+
const { toolMap, toolDefs } = this.getProgrammaticTools();
|
|
91
|
+
invokeParams = {
|
|
92
|
+
...invokeParams,
|
|
93
|
+
toolMap,
|
|
94
|
+
toolDefs,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
else if (call.name === Constants.TOOL_SEARCH_REGEX) {
|
|
98
|
+
invokeParams = {
|
|
99
|
+
...invokeParams,
|
|
100
|
+
toolRegistry: this.toolRegistry,
|
|
101
|
+
};
|
|
65
102
|
}
|
|
103
|
+
const output = await tool.invoke(invokeParams, config);
|
|
66
104
|
if ((isBaseMessage(output) && output._getType() === 'tool') ||
|
|
67
105
|
isCommand(output)) {
|
|
68
106
|
return output;
|
|
@@ -158,9 +196,9 @@ class ToolNode extends RunnableCallable {
|
|
|
158
196
|
}
|
|
159
197
|
if (this.loadRuntimeTools) {
|
|
160
198
|
const { tools, toolMap } = this.loadRuntimeTools(aiMessage.tool_calls ?? []);
|
|
161
|
-
this.tools = tools;
|
|
162
199
|
this.toolMap =
|
|
163
200
|
toolMap ?? new Map(tools.map((tool) => [tool.name, tool]));
|
|
201
|
+
this.programmaticCache = undefined; // Invalidate cache on toolMap change
|
|
164
202
|
}
|
|
165
203
|
outputs = await Promise.all(aiMessage.tool_calls
|
|
166
204
|
?.filter((call) => {
|