@wundr.io/langgraph-orchestrator 1.0.3
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/README.md +842 -0
- package/dist/checkpointing.d.ts +265 -0
- package/dist/checkpointing.d.ts.map +1 -0
- package/dist/checkpointing.js +577 -0
- package/dist/checkpointing.js.map +1 -0
- package/dist/edges/conditional-edge.d.ts +230 -0
- package/dist/edges/conditional-edge.d.ts.map +1 -0
- package/dist/edges/conditional-edge.js +439 -0
- package/dist/edges/conditional-edge.js.map +1 -0
- package/dist/edges/loop-edge.d.ts +290 -0
- package/dist/edges/loop-edge.d.ts.map +1 -0
- package/dist/edges/loop-edge.js +503 -0
- package/dist/edges/loop-edge.js.map +1 -0
- package/dist/index.d.ts +125 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +269 -0
- package/dist/index.js.map +1 -0
- package/dist/nodes/decision-node.d.ts +276 -0
- package/dist/nodes/decision-node.d.ts.map +1 -0
- package/dist/nodes/decision-node.js +403 -0
- package/dist/nodes/decision-node.js.map +1 -0
- package/dist/nodes/human-node.d.ts +272 -0
- package/dist/nodes/human-node.d.ts.map +1 -0
- package/dist/nodes/human-node.js +394 -0
- package/dist/nodes/human-node.js.map +1 -0
- package/dist/nodes/llm-node.d.ts +173 -0
- package/dist/nodes/llm-node.d.ts.map +1 -0
- package/dist/nodes/llm-node.js +325 -0
- package/dist/nodes/llm-node.js.map +1 -0
- package/dist/nodes/tool-node.d.ts +151 -0
- package/dist/nodes/tool-node.d.ts.map +1 -0
- package/dist/nodes/tool-node.js +373 -0
- package/dist/nodes/tool-node.js.map +1 -0
- package/dist/prebuilt-graphs/plan-execute-refine.d.ts +149 -0
- package/dist/prebuilt-graphs/plan-execute-refine.d.ts.map +1 -0
- package/dist/prebuilt-graphs/plan-execute-refine.js +600 -0
- package/dist/prebuilt-graphs/plan-execute-refine.js.map +1 -0
- package/dist/state-graph.d.ts +158 -0
- package/dist/state-graph.d.ts.map +1 -0
- package/dist/state-graph.js +756 -0
- package/dist/state-graph.js.map +1 -0
- package/dist/types.d.ts +762 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +73 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
- package/src/checkpointing.ts +702 -0
- package/src/edges/conditional-edge.ts +518 -0
- package/src/edges/loop-edge.ts +623 -0
- package/src/index.ts +416 -0
- package/src/nodes/decision-node.ts +538 -0
- package/src/nodes/human-node.ts +572 -0
- package/src/nodes/llm-node.ts +448 -0
- package/src/nodes/tool-node.ts +525 -0
- package/src/prebuilt-graphs/plan-execute-refine.ts +769 -0
- package/src/state-graph.ts +990 -0
- package/src/types.ts +729 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Node - Tool execution node for agent workflows
|
|
3
|
+
* @module @wundr.io/langgraph-orchestrator
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
AgentState,
|
|
11
|
+
NodeDefinition,
|
|
12
|
+
NodeContext,
|
|
13
|
+
NodeResult,
|
|
14
|
+
Message,
|
|
15
|
+
Tool,
|
|
16
|
+
ToolCall,
|
|
17
|
+
ToolResult,
|
|
18
|
+
ToolRegistry,
|
|
19
|
+
} from '../types';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Configuration for tool node
|
|
23
|
+
*/
|
|
24
|
+
export interface ToolNodeConfig {
|
|
25
|
+
/** Available tools (if not using registry) */
|
|
26
|
+
readonly tools?: Tool[];
|
|
27
|
+
/** Whether to execute tools in parallel */
|
|
28
|
+
readonly parallel?: boolean;
|
|
29
|
+
/** Maximum concurrent tool executions */
|
|
30
|
+
readonly maxConcurrency?: number;
|
|
31
|
+
/** Timeout per tool execution in milliseconds */
|
|
32
|
+
readonly toolTimeout?: number;
|
|
33
|
+
/** Whether to continue on tool error */
|
|
34
|
+
readonly continueOnError?: boolean;
|
|
35
|
+
/** Custom error handler */
|
|
36
|
+
readonly onError?: (error: Error, toolCall: ToolCall) => ToolResult;
|
|
37
|
+
/** Post-processing for tool results */
|
|
38
|
+
readonly postProcess?: (
|
|
39
|
+
results: ToolResult[],
|
|
40
|
+
state: AgentState
|
|
41
|
+
) => Partial<AgentState['data']>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Schema for tool node configuration validation
|
|
46
|
+
*/
|
|
47
|
+
export const ToolNodeConfigSchema = z.object({
|
|
48
|
+
parallel: z.boolean().optional(),
|
|
49
|
+
maxConcurrency: z.number().min(1).optional(),
|
|
50
|
+
toolTimeout: z.number().min(0).optional(),
|
|
51
|
+
continueOnError: z.boolean().optional(),
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a tool execution node
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* const toolNode = createToolNode({
|
|
60
|
+
* id: 'tools',
|
|
61
|
+
* name: 'Tool Executor',
|
|
62
|
+
* config: {
|
|
63
|
+
* parallel: true,
|
|
64
|
+
* maxConcurrency: 3,
|
|
65
|
+
* continueOnError: true
|
|
66
|
+
* }
|
|
67
|
+
* });
|
|
68
|
+
*
|
|
69
|
+
* graph.addNode('tools', toolNode);
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* @param options - Node creation options
|
|
73
|
+
* @returns NodeDefinition for use in StateGraph
|
|
74
|
+
*/
|
|
75
|
+
export function createToolNode<
|
|
76
|
+
TState extends AgentState = AgentState,
|
|
77
|
+
>(options: {
|
|
78
|
+
id: string;
|
|
79
|
+
name: string;
|
|
80
|
+
config?: ToolNodeConfig;
|
|
81
|
+
nodeConfig?: NodeDefinition<TState>['config'];
|
|
82
|
+
}): NodeDefinition<TState> {
|
|
83
|
+
const { id, name, config = {}, nodeConfig = {} } = options;
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
id,
|
|
87
|
+
name,
|
|
88
|
+
type: 'tool',
|
|
89
|
+
config: nodeConfig,
|
|
90
|
+
execute: async (
|
|
91
|
+
state: TState,
|
|
92
|
+
context: NodeContext,
|
|
93
|
+
): Promise<NodeResult<TState>> => {
|
|
94
|
+
// Get pending tool calls from state
|
|
95
|
+
const pendingToolCalls = state.data['pendingToolCalls'] as
|
|
96
|
+
| ToolCall[]
|
|
97
|
+
| undefined;
|
|
98
|
+
|
|
99
|
+
if (!pendingToolCalls || pendingToolCalls.length === 0) {
|
|
100
|
+
context.services.logger.warn(
|
|
101
|
+
'Tool node executed with no pending tool calls',
|
|
102
|
+
);
|
|
103
|
+
return { state };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
context.services.logger.debug('Executing tool calls', {
|
|
107
|
+
count: pendingToolCalls.length,
|
|
108
|
+
tools: pendingToolCalls.map(tc => tc.name),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Get tools from config or registry
|
|
112
|
+
const tools =
|
|
113
|
+
config.tools ?? getToolsFromRegistry(context.services.toolRegistry);
|
|
114
|
+
const toolMap = new Map(tools.map(t => [t.name, t]));
|
|
115
|
+
|
|
116
|
+
// Execute tool calls
|
|
117
|
+
const results: ToolResult[] = await executeToolCalls(
|
|
118
|
+
pendingToolCalls,
|
|
119
|
+
toolMap,
|
|
120
|
+
config,
|
|
121
|
+
context,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Build tool result messages
|
|
125
|
+
const toolMessages: Message[] = results.map(result => ({
|
|
126
|
+
id: uuidv4(),
|
|
127
|
+
role: 'tool' as const,
|
|
128
|
+
content: result.content,
|
|
129
|
+
toolResult: result,
|
|
130
|
+
timestamp: new Date(),
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
// Build updated state
|
|
134
|
+
let newData = { ...state.data };
|
|
135
|
+
delete newData['pendingToolCalls']; // Clear pending calls
|
|
136
|
+
|
|
137
|
+
// Store results in state
|
|
138
|
+
newData['lastToolResults'] = results;
|
|
139
|
+
|
|
140
|
+
// Apply post-processing if configured
|
|
141
|
+
if (config.postProcess) {
|
|
142
|
+
const processed = config.postProcess(results, state);
|
|
143
|
+
newData = { ...newData, ...processed };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const newState: TState = {
|
|
147
|
+
...state,
|
|
148
|
+
messages: [...state.messages, ...toolMessages],
|
|
149
|
+
data: newData,
|
|
150
|
+
} as TState;
|
|
151
|
+
|
|
152
|
+
// Check if all tools succeeded
|
|
153
|
+
const _allSucceeded = results.every(r => r.success);
|
|
154
|
+
const hasErrors = results.some(r => !r.success);
|
|
155
|
+
|
|
156
|
+
context.services.logger.debug('Tool execution complete', {
|
|
157
|
+
total: results.length,
|
|
158
|
+
succeeded: results.filter(r => r.success).length,
|
|
159
|
+
failed: results.filter(r => !r.success).length,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
state: newState,
|
|
164
|
+
// If there were errors and we're not continuing, route to error handler
|
|
165
|
+
next: hasErrors && !config.continueOnError ? 'error' : undefined,
|
|
166
|
+
metadata: {
|
|
167
|
+
duration: 0,
|
|
168
|
+
toolCalls: pendingToolCalls,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Execute tool calls with concurrency control
|
|
177
|
+
*/
|
|
178
|
+
async function executeToolCalls(
|
|
179
|
+
toolCalls: ToolCall[],
|
|
180
|
+
toolMap: Map<string, Tool>,
|
|
181
|
+
config: ToolNodeConfig,
|
|
182
|
+
context: NodeContext,
|
|
183
|
+
): Promise<ToolResult[]> {
|
|
184
|
+
if (config.parallel) {
|
|
185
|
+
return executeParallel(toolCalls, toolMap, config, context);
|
|
186
|
+
}
|
|
187
|
+
return executeSequential(toolCalls, toolMap, config, context);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Execute tool calls sequentially
|
|
192
|
+
*/
|
|
193
|
+
async function executeSequential(
|
|
194
|
+
toolCalls: ToolCall[],
|
|
195
|
+
toolMap: Map<string, Tool>,
|
|
196
|
+
config: ToolNodeConfig,
|
|
197
|
+
context: NodeContext,
|
|
198
|
+
): Promise<ToolResult[]> {
|
|
199
|
+
const results: ToolResult[] = [];
|
|
200
|
+
|
|
201
|
+
for (const toolCall of toolCalls) {
|
|
202
|
+
const result = await executeSingleTool(toolCall, toolMap, config, context);
|
|
203
|
+
results.push(result);
|
|
204
|
+
|
|
205
|
+
// Stop if there's an error and we're not continuing on error
|
|
206
|
+
if (!result.success && !config.continueOnError) {
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return results;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Execute tool calls in parallel with concurrency limit
|
|
216
|
+
*/
|
|
217
|
+
async function executeParallel(
|
|
218
|
+
toolCalls: ToolCall[],
|
|
219
|
+
toolMap: Map<string, Tool>,
|
|
220
|
+
config: ToolNodeConfig,
|
|
221
|
+
context: NodeContext,
|
|
222
|
+
): Promise<ToolResult[]> {
|
|
223
|
+
const maxConcurrency = config.maxConcurrency ?? 5;
|
|
224
|
+
const results: ToolResult[] = [];
|
|
225
|
+
const executing: Promise<ToolResult>[] = [];
|
|
226
|
+
|
|
227
|
+
for (const toolCall of toolCalls) {
|
|
228
|
+
const promise = executeSingleTool(toolCall, toolMap, config, context);
|
|
229
|
+
executing.push(promise);
|
|
230
|
+
|
|
231
|
+
if (executing.length >= maxConcurrency) {
|
|
232
|
+
const result = await Promise.race(executing);
|
|
233
|
+
results.push(result);
|
|
234
|
+
const index = executing.indexOf(promise);
|
|
235
|
+
if (index > -1) {
|
|
236
|
+
executing.splice(index, 1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Wait for remaining executions
|
|
242
|
+
const remaining = await Promise.all(executing);
|
|
243
|
+
results.push(...remaining);
|
|
244
|
+
|
|
245
|
+
return results;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Execute a single tool call
|
|
250
|
+
*/
|
|
251
|
+
async function executeSingleTool(
|
|
252
|
+
toolCall: ToolCall,
|
|
253
|
+
toolMap: Map<string, Tool>,
|
|
254
|
+
config: ToolNodeConfig,
|
|
255
|
+
context: NodeContext,
|
|
256
|
+
): Promise<ToolResult> {
|
|
257
|
+
const tool = toolMap.get(toolCall.name);
|
|
258
|
+
|
|
259
|
+
if (!tool) {
|
|
260
|
+
const error = new Error(`Tool "${toolCall.name}" not found`);
|
|
261
|
+
if (config.onError) {
|
|
262
|
+
return config.onError(error, toolCall);
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
toolCallId: toolCall.id,
|
|
266
|
+
content: `Error: Tool "${toolCall.name}" not found`,
|
|
267
|
+
success: false,
|
|
268
|
+
error: error.message,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
// Validate input if schema provided
|
|
274
|
+
if (tool.inputSchema) {
|
|
275
|
+
tool.inputSchema.parse(toolCall.arguments);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Execute with timeout if configured
|
|
279
|
+
const result = config.toolTimeout
|
|
280
|
+
? await executeWithTimeout(tool, toolCall.arguments, config.toolTimeout)
|
|
281
|
+
: await tool.execute(toolCall.arguments);
|
|
282
|
+
|
|
283
|
+
// Validate output if schema provided
|
|
284
|
+
if (tool.outputSchema) {
|
|
285
|
+
tool.outputSchema.parse(result);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
context.services.logger.debug('Tool executed successfully', {
|
|
289
|
+
tool: toolCall.name,
|
|
290
|
+
callId: toolCall.id,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
toolCallId: toolCall.id,
|
|
295
|
+
content: typeof result === 'string' ? result : JSON.stringify(result),
|
|
296
|
+
success: true,
|
|
297
|
+
};
|
|
298
|
+
} catch (error) {
|
|
299
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
300
|
+
|
|
301
|
+
context.services.logger.error('Tool execution failed', {
|
|
302
|
+
tool: toolCall.name,
|
|
303
|
+
callId: toolCall.id,
|
|
304
|
+
error: err.message,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
if (config.onError) {
|
|
308
|
+
return config.onError(err, toolCall);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
toolCallId: toolCall.id,
|
|
313
|
+
content: `Error executing tool "${toolCall.name}": ${err.message}`,
|
|
314
|
+
success: false,
|
|
315
|
+
error: err.message,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Execute tool with timeout
|
|
322
|
+
*/
|
|
323
|
+
async function executeWithTimeout(
|
|
324
|
+
tool: Tool,
|
|
325
|
+
args: Record<string, unknown>,
|
|
326
|
+
timeout: number,
|
|
327
|
+
): Promise<unknown> {
|
|
328
|
+
return Promise.race([
|
|
329
|
+
tool.execute(args),
|
|
330
|
+
new Promise((_, reject) =>
|
|
331
|
+
setTimeout(
|
|
332
|
+
() => reject(new Error(`Tool execution timed out after ${timeout}ms`)),
|
|
333
|
+
timeout,
|
|
334
|
+
),
|
|
335
|
+
),
|
|
336
|
+
]);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Get tools from registry
|
|
341
|
+
*/
|
|
342
|
+
function getToolsFromRegistry(registry?: ToolRegistry): Tool[] {
|
|
343
|
+
if (!registry) {
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
return registry.list();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Create a simple in-memory tool registry
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* const registry = createToolRegistry();
|
|
355
|
+
* registry.register({
|
|
356
|
+
* name: 'search',
|
|
357
|
+
* description: 'Search the web',
|
|
358
|
+
* inputSchema: z.object({ query: z.string() }),
|
|
359
|
+
* execute: async ({ query }) => searchWeb(query)
|
|
360
|
+
* });
|
|
361
|
+
*
|
|
362
|
+
* graph.setServices({ toolRegistry: registry });
|
|
363
|
+
* ```
|
|
364
|
+
*
|
|
365
|
+
* @returns ToolRegistry implementation
|
|
366
|
+
*/
|
|
367
|
+
export function createToolRegistry(): ToolRegistry {
|
|
368
|
+
const tools = new Map<string, Tool>();
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
get(name: string): Tool | undefined {
|
|
372
|
+
return tools.get(name);
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
list(): Tool[] {
|
|
376
|
+
return Array.from(tools.values());
|
|
377
|
+
},
|
|
378
|
+
|
|
379
|
+
register(tool: Tool): void {
|
|
380
|
+
if (tools.has(tool.name)) {
|
|
381
|
+
throw new Error(`Tool "${tool.name}" is already registered`);
|
|
382
|
+
}
|
|
383
|
+
tools.set(tool.name, tool);
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
unregister(name: string): void {
|
|
387
|
+
tools.delete(name);
|
|
388
|
+
},
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Create a tool definition with type-safe input/output
|
|
394
|
+
*
|
|
395
|
+
* @example
|
|
396
|
+
* ```typescript
|
|
397
|
+
* const searchTool = createTool({
|
|
398
|
+
* name: 'search',
|
|
399
|
+
* description: 'Search the web for information',
|
|
400
|
+
* inputSchema: z.object({
|
|
401
|
+
* query: z.string(),
|
|
402
|
+
* maxResults: z.number().optional()
|
|
403
|
+
* }),
|
|
404
|
+
* outputSchema: z.array(z.object({
|
|
405
|
+
* title: z.string(),
|
|
406
|
+
* url: z.string(),
|
|
407
|
+
* snippet: z.string()
|
|
408
|
+
* })),
|
|
409
|
+
* execute: async ({ query, maxResults = 10 }) => {
|
|
410
|
+
* return await searchWeb(query, maxResults);
|
|
411
|
+
* }
|
|
412
|
+
* });
|
|
413
|
+
* ```
|
|
414
|
+
*
|
|
415
|
+
* @param options - Tool definition options
|
|
416
|
+
* @returns Tool definition
|
|
417
|
+
*/
|
|
418
|
+
export function createTool<TInput, TOutput = unknown>(options: {
|
|
419
|
+
name: string;
|
|
420
|
+
description: string;
|
|
421
|
+
inputSchema: z.ZodSchema<TInput>;
|
|
422
|
+
outputSchema?: z.ZodSchema<TOutput>;
|
|
423
|
+
execute: (input: TInput) => Promise<TOutput>;
|
|
424
|
+
}): Tool {
|
|
425
|
+
return {
|
|
426
|
+
name: options.name,
|
|
427
|
+
description: options.description,
|
|
428
|
+
inputSchema: options.inputSchema,
|
|
429
|
+
outputSchema: options.outputSchema,
|
|
430
|
+
execute: async (input: unknown): Promise<unknown> => {
|
|
431
|
+
const parsed = options.inputSchema.parse(input);
|
|
432
|
+
return options.execute(parsed);
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Create a batch tool node that groups tool calls
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* ```typescript
|
|
442
|
+
* const batchToolNode = createBatchToolNode({
|
|
443
|
+
* id: 'batch-tools',
|
|
444
|
+
* name: 'Batch Tool Executor',
|
|
445
|
+
* config: {
|
|
446
|
+
* batchSize: 5,
|
|
447
|
+
* batchTimeout: 1000
|
|
448
|
+
* }
|
|
449
|
+
* });
|
|
450
|
+
* ```
|
|
451
|
+
*
|
|
452
|
+
* @param options - Node creation options
|
|
453
|
+
* @returns NodeDefinition for use in StateGraph
|
|
454
|
+
*/
|
|
455
|
+
export function createBatchToolNode<
|
|
456
|
+
TState extends AgentState = AgentState,
|
|
457
|
+
>(options: {
|
|
458
|
+
id: string;
|
|
459
|
+
name: string;
|
|
460
|
+
config?: ToolNodeConfig & {
|
|
461
|
+
batchSize?: number;
|
|
462
|
+
batchTimeout?: number;
|
|
463
|
+
};
|
|
464
|
+
nodeConfig?: NodeDefinition<TState>['config'];
|
|
465
|
+
}): NodeDefinition<TState> {
|
|
466
|
+
const { id, name, config = {}, nodeConfig = {} } = options;
|
|
467
|
+
const batchSize = config.batchSize ?? 10;
|
|
468
|
+
|
|
469
|
+
return {
|
|
470
|
+
id,
|
|
471
|
+
name,
|
|
472
|
+
type: 'tool',
|
|
473
|
+
config: nodeConfig,
|
|
474
|
+
execute: async (
|
|
475
|
+
state: TState,
|
|
476
|
+
context: NodeContext,
|
|
477
|
+
): Promise<NodeResult<TState>> => {
|
|
478
|
+
const pendingToolCalls = state.data['pendingToolCalls'] as
|
|
479
|
+
| ToolCall[]
|
|
480
|
+
| undefined;
|
|
481
|
+
|
|
482
|
+
if (!pendingToolCalls || pendingToolCalls.length === 0) {
|
|
483
|
+
return { state };
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const tools =
|
|
487
|
+
config.tools ?? getToolsFromRegistry(context.services.toolRegistry);
|
|
488
|
+
const toolMap = new Map(tools.map(t => [t.name, t]));
|
|
489
|
+
|
|
490
|
+
// Process in batches
|
|
491
|
+
const batches: ToolCall[][] = [];
|
|
492
|
+
for (let i = 0; i < pendingToolCalls.length; i += batchSize) {
|
|
493
|
+
batches.push(pendingToolCalls.slice(i, i + batchSize));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const allResults: ToolResult[] = [];
|
|
497
|
+
|
|
498
|
+
for (const batch of batches) {
|
|
499
|
+
const results = await executeParallel(batch, toolMap, config, context);
|
|
500
|
+
allResults.push(...results);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Build tool result messages
|
|
504
|
+
const toolMessages: Message[] = allResults.map(result => ({
|
|
505
|
+
id: uuidv4(),
|
|
506
|
+
role: 'tool' as const,
|
|
507
|
+
content: result.content,
|
|
508
|
+
toolResult: result,
|
|
509
|
+
timestamp: new Date(),
|
|
510
|
+
}));
|
|
511
|
+
|
|
512
|
+
const newData = { ...state.data };
|
|
513
|
+
delete newData['pendingToolCalls'];
|
|
514
|
+
newData['lastToolResults'] = allResults;
|
|
515
|
+
|
|
516
|
+
const newState: TState = {
|
|
517
|
+
...state,
|
|
518
|
+
messages: [...state.messages, ...toolMessages],
|
|
519
|
+
data: newData,
|
|
520
|
+
} as TState;
|
|
521
|
+
|
|
522
|
+
return { state: newState };
|
|
523
|
+
},
|
|
524
|
+
};
|
|
525
|
+
}
|