@librechat/agents 3.1.88 → 3.1.90
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/dist/cjs/graphs/Graph.cjs +25 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/hooks/executeHooks.cjs +14 -7
- package/dist/cjs/hooks/executeHooks.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/index.cjs +8 -2
- package/dist/cjs/llm/anthropic/index.cjs.map +1 -1
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs +34 -0
- package/dist/cjs/llm/anthropic/utils/message_inputs.cjs.map +1 -1
- package/dist/cjs/main.cjs +9 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/stream.cjs +115 -8
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BashExecutor.cjs +10 -9
- package/dist/cjs/tools/BashExecutor.cjs.map +1 -1
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs +12 -8
- package/dist/cjs/tools/BashProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/CodeExecutor.cjs +35 -11
- package/dist/cjs/tools/CodeExecutor.cjs.map +1 -1
- package/dist/cjs/tools/CodeSessionFileSummary.cjs +63 -0
- package/dist/cjs/tools/CodeSessionFileSummary.cjs.map +1 -0
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs +16 -12
- package/dist/cjs/tools/ProgrammaticToolCalling.cjs.map +1 -1
- package/dist/cjs/tools/ToolNode.cjs +32 -12
- package/dist/cjs/tools/ToolNode.cjs.map +1 -1
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs +319 -29
- package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
- package/dist/cjs/tools/toolOutputReferences.cjs +8 -0
- package/dist/cjs/tools/toolOutputReferences.cjs.map +1 -1
- package/dist/cjs/utils/events.cjs +3 -1
- package/dist/cjs/utils/events.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +25 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/hooks/executeHooks.mjs +14 -7
- package/dist/esm/hooks/executeHooks.mjs.map +1 -1
- package/dist/esm/llm/anthropic/index.mjs +9 -3
- package/dist/esm/llm/anthropic/index.mjs.map +1 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs +33 -1
- package/dist/esm/llm/anthropic/utils/message_inputs.mjs.map +1 -1
- package/dist/esm/main.mjs +2 -1
- package/dist/esm/main.mjs.map +1 -1
- package/dist/esm/stream.mjs +115 -8
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BashExecutor.mjs +11 -10
- package/dist/esm/tools/BashExecutor.mjs.map +1 -1
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs +13 -9
- package/dist/esm/tools/BashProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/CodeExecutor.mjs +29 -12
- package/dist/esm/tools/CodeExecutor.mjs.map +1 -1
- package/dist/esm/tools/CodeSessionFileSummary.mjs +60 -0
- package/dist/esm/tools/CodeSessionFileSummary.mjs.map +1 -0
- package/dist/esm/tools/ProgrammaticToolCalling.mjs +17 -13
- package/dist/esm/tools/ProgrammaticToolCalling.mjs.map +1 -1
- package/dist/esm/tools/ToolNode.mjs +32 -12
- package/dist/esm/tools/ToolNode.mjs.map +1 -1
- package/dist/esm/tools/subagent/SubagentExecutor.mjs +320 -31
- package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
- package/dist/esm/tools/toolOutputReferences.mjs +8 -1
- package/dist/esm/tools/toolOutputReferences.mjs.map +1 -1
- package/dist/esm/utils/events.mjs +3 -1
- package/dist/esm/utils/events.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +8 -0
- package/dist/types/llm/anthropic/index.d.ts +3 -1
- package/dist/types/llm/anthropic/utils/message_inputs.d.ts +4 -0
- package/dist/types/tools/BashExecutor.d.ts +3 -3
- package/dist/types/tools/CodeExecutor.d.ts +10 -3
- package/dist/types/tools/CodeSessionFileSummary.d.ts +3 -0
- package/dist/types/tools/ProgrammaticToolCalling.d.ts +4 -4
- package/dist/types/tools/subagent/SubagentExecutor.d.ts +8 -5
- package/dist/types/types/tools.d.ts +11 -3
- package/dist/types/utils/events.d.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/stream.eagerEventExecution.test.ts +1073 -221
- package/src/graphs/Graph.ts +27 -5
- package/src/hooks/__tests__/executeHooks.test.ts +38 -0
- package/src/hooks/executeHooks.ts +27 -7
- package/src/llm/anthropic/index.ts +27 -3
- package/src/llm/anthropic/llm.spec.ts +60 -1
- package/src/llm/anthropic/utils/message_inputs.ts +46 -0
- package/src/specs/subagent.test.ts +87 -1
- package/src/stream.ts +163 -12
- package/src/tools/BashExecutor.ts +21 -10
- package/src/tools/BashProgrammaticToolCalling.ts +21 -9
- package/src/tools/CodeExecutor.ts +55 -12
- package/src/tools/CodeSessionFileSummary.ts +80 -0
- package/src/tools/ProgrammaticToolCalling.ts +25 -12
- package/src/tools/ToolNode.ts +142 -116
- package/src/tools/__tests__/BashExecutor.test.ts +9 -0
- package/src/tools/__tests__/CodeApiAuthHeaders.test.ts +43 -0
- package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +100 -16
- package/src/tools/__tests__/SubagentExecutor.test.ts +540 -6
- package/src/tools/__tests__/ToolNode.eagerEventExecution.test.ts +278 -14
- package/src/tools/__tests__/ToolNode.outputReferences.test.ts +52 -0
- package/src/tools/__tests__/subagentHooks.test.ts +237 -0
- package/src/tools/subagent/SubagentExecutor.ts +514 -36
- package/src/types/tools.ts +11 -3
- package/src/utils/events.ts +4 -2
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from '@/common';
|
|
12
12
|
import { HandlerRegistry } from '@/events';
|
|
13
13
|
import * as events from '@/utils/events';
|
|
14
|
-
import { ChatModelStreamHandler } from '@/stream';
|
|
14
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
15
15
|
import {
|
|
16
16
|
STREAMED_TOOL_CALL_SEAL_METADATA_KEY,
|
|
17
17
|
STREAMED_TOOL_CALL_ADAPTER_METADATA_KEY,
|
|
@@ -71,7 +71,8 @@ function createGraph(overrides: Partial<StandardGraph> = {}): StandardGraph {
|
|
|
71
71
|
(details as t.StepDetails).type === StepTypes.TOOL_CALLS &&
|
|
72
72
|
Array.isArray((details as t.ToolCallsDetails).tool_calls)
|
|
73
73
|
) {
|
|
74
|
-
for (const toolCall of (details as t.ToolCallsDetails).tool_calls ??
|
|
74
|
+
for (const toolCall of (details as t.ToolCallsDetails).tool_calls ??
|
|
75
|
+
[]) {
|
|
75
76
|
if (toolCall.id != null && toolCall.id !== '') {
|
|
76
77
|
graph.toolCallStepIds.set(toolCall.id, id);
|
|
77
78
|
}
|
|
@@ -110,8 +111,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
110
111
|
it('prestarts a complete event-driven tool call from the stream', async () => {
|
|
111
112
|
const graph = createGraph();
|
|
112
113
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
113
|
-
jest
|
|
114
|
-
|
|
114
|
+
jest
|
|
115
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
116
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
115
117
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
116
118
|
return;
|
|
117
119
|
}
|
|
@@ -124,8 +126,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
124
126
|
content: 'sunny',
|
|
125
127
|
},
|
|
126
128
|
]);
|
|
127
|
-
}
|
|
128
|
-
);
|
|
129
|
+
});
|
|
129
130
|
|
|
130
131
|
await new ChatModelStreamHandler().handle(
|
|
131
132
|
GraphEvents.CHAT_MODEL_STREAM,
|
|
@@ -162,11 +163,65 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
162
163
|
expect(graph.toolCallStepIds.has('call_weather')).toBe(true);
|
|
163
164
|
});
|
|
164
165
|
|
|
166
|
+
it('prestarts when subagent callback forwarding can execute tools without a handler registry', async () => {
|
|
167
|
+
const graph = createGraph({
|
|
168
|
+
handlerRegistry: undefined,
|
|
169
|
+
eventToolExecutionAvailable: true,
|
|
170
|
+
});
|
|
171
|
+
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
172
|
+
jest
|
|
173
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
174
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
175
|
+
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const batch = data as t.ToolExecuteBatchRequest;
|
|
179
|
+
toolExecuteCalls.push(batch);
|
|
180
|
+
batch.resolve([
|
|
181
|
+
{
|
|
182
|
+
toolCallId: 'call_weather',
|
|
183
|
+
status: 'success',
|
|
184
|
+
content: 'sunny',
|
|
185
|
+
},
|
|
186
|
+
]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await new ChatModelStreamHandler().handle(
|
|
190
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
191
|
+
{
|
|
192
|
+
chunk: {
|
|
193
|
+
content: '',
|
|
194
|
+
tool_calls: [
|
|
195
|
+
{
|
|
196
|
+
id: 'call_weather',
|
|
197
|
+
name: 'weather',
|
|
198
|
+
args: { city: 'NYC' },
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
response_metadata: finalToolCallResponseMetadata,
|
|
202
|
+
} as unknown as t.StreamChunk,
|
|
203
|
+
},
|
|
204
|
+
{ langgraph_node: 'agent' },
|
|
205
|
+
graph
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(toolExecuteCalls).toHaveLength(1);
|
|
209
|
+
expect(toolExecuteCalls[0].toolCalls[0]).toMatchObject({
|
|
210
|
+
id: 'call_weather',
|
|
211
|
+
name: 'weather',
|
|
212
|
+
args: { city: 'NYC' },
|
|
213
|
+
stepId: expect.stringMatching(/^step_/),
|
|
214
|
+
turn: 0,
|
|
215
|
+
});
|
|
216
|
+
expect(graph.eagerEventToolExecutions.has('call_weather')).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
|
|
165
219
|
it('does not prestart parseable tool calls before a final tool-call signal', async () => {
|
|
166
220
|
const graph = createGraph();
|
|
167
221
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
168
|
-
jest
|
|
169
|
-
|
|
222
|
+
jest
|
|
223
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
224
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
170
225
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
171
226
|
return;
|
|
172
227
|
}
|
|
@@ -179,8 +234,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
179
234
|
content: 'sunny',
|
|
180
235
|
},
|
|
181
236
|
]);
|
|
182
|
-
}
|
|
183
|
-
);
|
|
237
|
+
});
|
|
184
238
|
|
|
185
239
|
const handler = new ChatModelStreamHandler();
|
|
186
240
|
const metadata = { langgraph_node: 'agent' };
|
|
@@ -248,8 +302,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
248
302
|
) as unknown as StandardGraph['getAgentContext'],
|
|
249
303
|
});
|
|
250
304
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
251
|
-
jest
|
|
252
|
-
|
|
305
|
+
jest
|
|
306
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
307
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
253
308
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
254
309
|
return;
|
|
255
310
|
}
|
|
@@ -262,8 +317,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
262
317
|
content: `${request.name} result`,
|
|
263
318
|
}))
|
|
264
319
|
);
|
|
265
|
-
}
|
|
266
|
-
);
|
|
320
|
+
});
|
|
267
321
|
|
|
268
322
|
await new ChatModelStreamHandler().handle(
|
|
269
323
|
GraphEvents.CHAT_MODEL_STREAM,
|
|
@@ -308,7 +362,8 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
308
362
|
}),
|
|
309
363
|
]);
|
|
310
364
|
const weatherExecution = graph.eagerEventToolExecutions.get('call_weather');
|
|
311
|
-
const calendarExecution =
|
|
365
|
+
const calendarExecution =
|
|
366
|
+
graph.eagerEventToolExecutions.get('call_calendar');
|
|
312
367
|
expect(weatherExecution).toMatchObject({
|
|
313
368
|
toolCallId: 'call_weather',
|
|
314
369
|
toolName: 'weather',
|
|
@@ -325,8 +380,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
325
380
|
it('assigns same-tool eager turns in model emission order', async () => {
|
|
326
381
|
const graph = createGraph();
|
|
327
382
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
328
|
-
jest
|
|
329
|
-
|
|
383
|
+
jest
|
|
384
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
385
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
330
386
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
331
387
|
return;
|
|
332
388
|
}
|
|
@@ -339,8 +395,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
339
395
|
content: `${request.args.city} weather`,
|
|
340
396
|
}))
|
|
341
397
|
);
|
|
342
|
-
}
|
|
343
|
-
);
|
|
398
|
+
});
|
|
344
399
|
|
|
345
400
|
await new ChatModelStreamHandler().handle(
|
|
346
401
|
GraphEvents.CHAT_MODEL_STREAM,
|
|
@@ -371,10 +426,12 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
371
426
|
0, 1,
|
|
372
427
|
]);
|
|
373
428
|
expect(graph.eagerEventToolUsageCount.get('weather')).toBe(2);
|
|
374
|
-
expect(
|
|
375
|
-
.
|
|
376
|
-
|
|
377
|
-
|
|
429
|
+
expect(
|
|
430
|
+
graph.eagerEventToolExecutions.get('call_weather_1')?.request.turn
|
|
431
|
+
).toBe(0);
|
|
432
|
+
expect(
|
|
433
|
+
graph.eagerEventToolExecutions.get('call_weather_2')?.request.turn
|
|
434
|
+
).toBe(1);
|
|
378
435
|
});
|
|
379
436
|
|
|
380
437
|
it('scopes eager turn reservation by agent', async () => {
|
|
@@ -391,19 +448,21 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
391
448
|
const graph = createGraph({
|
|
392
449
|
getEagerEventToolUsageCount: jest.fn(getUsageCount),
|
|
393
450
|
getAgentContext: jest.fn(
|
|
394
|
-
(metadata?: Record<string, unknown>): AgentContext =>
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
451
|
+
(metadata?: Record<string, unknown>): AgentContext =>
|
|
452
|
+
({
|
|
453
|
+
provider: Providers.ANTHROPIC,
|
|
454
|
+
reasoningKey: 'reasoning',
|
|
455
|
+
toolDefinitions: [{ name: 'weather' }],
|
|
456
|
+
graphTools: [],
|
|
457
|
+
agentId:
|
|
458
|
+
metadata?.langgraph_node === 'agent_2' ? 'agent_2' : 'agent_1',
|
|
459
|
+
}) as unknown as AgentContext
|
|
402
460
|
),
|
|
403
461
|
});
|
|
404
462
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
405
|
-
jest
|
|
406
|
-
|
|
463
|
+
jest
|
|
464
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
465
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
407
466
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
408
467
|
return;
|
|
409
468
|
}
|
|
@@ -416,8 +475,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
416
475
|
content: 'ok',
|
|
417
476
|
}))
|
|
418
477
|
);
|
|
419
|
-
}
|
|
420
|
-
);
|
|
478
|
+
});
|
|
421
479
|
|
|
422
480
|
const handler = new ChatModelStreamHandler();
|
|
423
481
|
await handler.handle(
|
|
@@ -463,25 +521,27 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
463
521
|
]);
|
|
464
522
|
expect(usageByAgent.get('agent_1')?.get('weather')).toBe(1);
|
|
465
523
|
expect(usageByAgent.get('agent_2')?.get('weather')).toBe(1);
|
|
466
|
-
expect(
|
|
467
|
-
.
|
|
468
|
-
|
|
469
|
-
|
|
524
|
+
expect(
|
|
525
|
+
graph.eagerEventToolExecutions.get('call_agent_1_weather')?.request.turn
|
|
526
|
+
).toBe(0);
|
|
527
|
+
expect(
|
|
528
|
+
graph.eagerEventToolExecutions.get('call_agent_2_weather')?.request.turn
|
|
529
|
+
).toBe(0);
|
|
470
530
|
});
|
|
471
531
|
|
|
472
532
|
it('skips eager for the whole batch if any call is not request-plannable', async () => {
|
|
473
533
|
const graph = createGraph();
|
|
474
534
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
475
|
-
jest
|
|
476
|
-
|
|
535
|
+
jest
|
|
536
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
537
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
477
538
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
478
539
|
return;
|
|
479
540
|
}
|
|
480
541
|
const batch = data as t.ToolExecuteBatchRequest;
|
|
481
542
|
toolExecuteCalls.push(batch);
|
|
482
543
|
batch.resolve([]);
|
|
483
|
-
}
|
|
484
|
-
);
|
|
544
|
+
});
|
|
485
545
|
|
|
486
546
|
await new ChatModelStreamHandler().handle(
|
|
487
547
|
GraphEvents.CHAT_MODEL_STREAM,
|
|
@@ -515,8 +575,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
515
575
|
it('records complete chunk-only tool calls after creating a tool step', async () => {
|
|
516
576
|
const graph = createGraph();
|
|
517
577
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
518
|
-
jest
|
|
519
|
-
|
|
578
|
+
jest
|
|
579
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
580
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
520
581
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
521
582
|
return;
|
|
522
583
|
}
|
|
@@ -529,8 +590,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
529
590
|
content: 'sunny',
|
|
530
591
|
},
|
|
531
592
|
]);
|
|
532
|
-
}
|
|
533
|
-
);
|
|
593
|
+
});
|
|
534
594
|
|
|
535
595
|
await new ChatModelStreamHandler().handle(
|
|
536
596
|
GraphEvents.CHAT_MODEL_STREAM,
|
|
@@ -554,8 +614,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
554
614
|
expect(toolExecuteCalls).toHaveLength(0);
|
|
555
615
|
expect(graph.toolCallStepIds.has('call_weather')).toBe(true);
|
|
556
616
|
expect(
|
|
557
|
-
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))
|
|
558
|
-
?.argsText
|
|
617
|
+
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))?.argsText
|
|
559
618
|
).toBe('{"city":"NYC"}');
|
|
560
619
|
});
|
|
561
620
|
|
|
@@ -572,8 +631,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
572
631
|
) as unknown as StandardGraph['getAgentContext'],
|
|
573
632
|
});
|
|
574
633
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
575
|
-
jest
|
|
576
|
-
|
|
634
|
+
jest
|
|
635
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
636
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
577
637
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
578
638
|
return;
|
|
579
639
|
}
|
|
@@ -586,8 +646,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
586
646
|
content: 'sunny',
|
|
587
647
|
},
|
|
588
648
|
]);
|
|
589
|
-
}
|
|
590
|
-
);
|
|
649
|
+
});
|
|
591
650
|
|
|
592
651
|
const handler = new ChatModelStreamHandler();
|
|
593
652
|
const metadata = { langgraph_node: 'agent' };
|
|
@@ -666,9 +725,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
666
725
|
stepId: expect.stringMatching(/^step_/),
|
|
667
726
|
turn: 0,
|
|
668
727
|
});
|
|
669
|
-
expect(
|
|
670
|
-
|
|
671
|
-
);
|
|
728
|
+
expect(
|
|
729
|
+
graph.eagerEventToolCallChunks.has(chunkStateKey('step-key', 0))
|
|
730
|
+
).toBe(false);
|
|
672
731
|
});
|
|
673
732
|
|
|
674
733
|
it('keeps OpenAI Chat Completions streamed chunks on the final tool_calls path', async () => {
|
|
@@ -684,8 +743,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
684
743
|
) as unknown as StandardGraph['getAgentContext'],
|
|
685
744
|
});
|
|
686
745
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
687
|
-
jest
|
|
688
|
-
|
|
746
|
+
jest
|
|
747
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
748
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
689
749
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
690
750
|
return;
|
|
691
751
|
}
|
|
@@ -698,8 +758,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
698
758
|
content: `ok ${call.name}`,
|
|
699
759
|
}))
|
|
700
760
|
);
|
|
701
|
-
}
|
|
702
|
-
);
|
|
761
|
+
});
|
|
703
762
|
|
|
704
763
|
const handler = new ChatModelStreamHandler();
|
|
705
764
|
const metadata = { langgraph_node: 'agent' };
|
|
@@ -786,8 +845,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
786
845
|
it('prestarts final tool calls even when the final chunk also has tool-call chunks', async () => {
|
|
787
846
|
const graph = createGraph();
|
|
788
847
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
789
|
-
jest
|
|
790
|
-
|
|
848
|
+
jest
|
|
849
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
850
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
791
851
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
792
852
|
return;
|
|
793
853
|
}
|
|
@@ -800,8 +860,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
800
860
|
content: 'sunny',
|
|
801
861
|
},
|
|
802
862
|
]);
|
|
803
|
-
}
|
|
804
|
-
);
|
|
863
|
+
});
|
|
805
864
|
|
|
806
865
|
await new ChatModelStreamHandler().handle(
|
|
807
866
|
GraphEvents.CHAT_MODEL_STREAM,
|
|
@@ -843,8 +902,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
843
902
|
it('waits for final tool calls before prestarting streamed chunk calls', async () => {
|
|
844
903
|
const graph = createGraph();
|
|
845
904
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
846
|
-
jest
|
|
847
|
-
|
|
905
|
+
jest
|
|
906
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
907
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
848
908
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
849
909
|
return;
|
|
850
910
|
}
|
|
@@ -857,8 +917,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
857
917
|
content: 'sunny',
|
|
858
918
|
},
|
|
859
919
|
]);
|
|
860
|
-
}
|
|
861
|
-
);
|
|
920
|
+
});
|
|
862
921
|
|
|
863
922
|
const handler = new ChatModelStreamHandler();
|
|
864
923
|
|
|
@@ -1033,8 +1092,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1033
1092
|
it('preserves repeated adjacent argument deltas', async () => {
|
|
1034
1093
|
const graph = createGraph();
|
|
1035
1094
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
1036
|
-
jest
|
|
1037
|
-
|
|
1095
|
+
jest
|
|
1096
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
1097
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
1038
1098
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
1039
1099
|
return;
|
|
1040
1100
|
}
|
|
@@ -1047,8 +1107,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1047
1107
|
content: 'ok',
|
|
1048
1108
|
},
|
|
1049
1109
|
]);
|
|
1050
|
-
}
|
|
1051
|
-
);
|
|
1110
|
+
});
|
|
1052
1111
|
|
|
1053
1112
|
const handler = new ChatModelStreamHandler();
|
|
1054
1113
|
const metadata = { langgraph_node: 'agent' };
|
|
@@ -1127,8 +1186,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1127
1186
|
|
|
1128
1187
|
expect(toolExecuteCalls).toHaveLength(0);
|
|
1129
1188
|
expect(
|
|
1130
|
-
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))
|
|
1131
|
-
?.argsText
|
|
1189
|
+
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))?.argsText
|
|
1132
1190
|
).toBe('{"word":"book"}');
|
|
1133
1191
|
|
|
1134
1192
|
await handler.handle(
|
|
@@ -1202,8 +1260,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1202
1260
|
);
|
|
1203
1261
|
|
|
1204
1262
|
expect(
|
|
1205
|
-
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))
|
|
1206
|
-
?.argsText
|
|
1263
|
+
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))?.argsText
|
|
1207
1264
|
).toBe('oo');
|
|
1208
1265
|
});
|
|
1209
1266
|
|
|
@@ -1249,16 +1306,16 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1249
1306
|
);
|
|
1250
1307
|
|
|
1251
1308
|
expect(
|
|
1252
|
-
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))
|
|
1253
|
-
?.argsText
|
|
1309
|
+
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))?.argsText
|
|
1254
1310
|
).toBe('{"titl');
|
|
1255
1311
|
});
|
|
1256
1312
|
|
|
1257
1313
|
it('does not prestart from cumulative streamed args before final tool calls', async () => {
|
|
1258
1314
|
const graph = createGraph();
|
|
1259
1315
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
1260
|
-
jest
|
|
1261
|
-
|
|
1316
|
+
jest
|
|
1317
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
1318
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
1262
1319
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
1263
1320
|
return;
|
|
1264
1321
|
}
|
|
@@ -1271,8 +1328,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1271
1328
|
content: 'sunny',
|
|
1272
1329
|
},
|
|
1273
1330
|
]);
|
|
1274
|
-
}
|
|
1275
|
-
);
|
|
1331
|
+
});
|
|
1276
1332
|
|
|
1277
1333
|
const handler = new ChatModelStreamHandler();
|
|
1278
1334
|
const metadata = { langgraph_node: 'agent' };
|
|
@@ -1353,8 +1409,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1353
1409
|
|
|
1354
1410
|
expect(toolExecuteCalls).toHaveLength(0);
|
|
1355
1411
|
expect(
|
|
1356
|
-
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))
|
|
1357
|
-
?.argsText
|
|
1412
|
+
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))?.argsText
|
|
1358
1413
|
).toBe('{"city":"NYC","unit":"C"}');
|
|
1359
1414
|
|
|
1360
1415
|
await handler.handle(
|
|
@@ -1445,8 +1500,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1445
1500
|
);
|
|
1446
1501
|
|
|
1447
1502
|
expect(
|
|
1448
|
-
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))
|
|
1449
|
-
?.argsText
|
|
1503
|
+
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))?.argsText
|
|
1450
1504
|
).toBe('{"title":"alpha","city":"NYC"}');
|
|
1451
1505
|
});
|
|
1452
1506
|
|
|
@@ -1504,8 +1558,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1504
1558
|
);
|
|
1505
1559
|
|
|
1506
1560
|
expect(
|
|
1507
|
-
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))
|
|
1508
|
-
?.argsText
|
|
1561
|
+
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))?.argsText
|
|
1509
1562
|
).toBe('{"word":"book"}');
|
|
1510
1563
|
});
|
|
1511
1564
|
|
|
@@ -1590,8 +1643,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1590
1643
|
);
|
|
1591
1644
|
|
|
1592
1645
|
expect(
|
|
1593
|
-
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))
|
|
1594
|
-
?.argsText
|
|
1646
|
+
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 0))?.argsText
|
|
1595
1647
|
).toBe('{"city":"NYC"}');
|
|
1596
1648
|
});
|
|
1597
1649
|
|
|
@@ -1599,8 +1651,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1599
1651
|
const graphA = createGraph();
|
|
1600
1652
|
const graphB = createGraph();
|
|
1601
1653
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
1602
|
-
jest
|
|
1603
|
-
|
|
1654
|
+
jest
|
|
1655
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
1656
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
1604
1657
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
1605
1658
|
return;
|
|
1606
1659
|
}
|
|
@@ -1613,8 +1666,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1613
1666
|
content: `${request.id} result`,
|
|
1614
1667
|
}))
|
|
1615
1668
|
);
|
|
1616
|
-
}
|
|
1617
|
-
);
|
|
1669
|
+
});
|
|
1618
1670
|
|
|
1619
1671
|
const handler = new ChatModelStreamHandler();
|
|
1620
1672
|
const sharedChunk = {
|
|
@@ -1660,8 +1712,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1660
1712
|
) as unknown as StandardGraph['getAgentContext'],
|
|
1661
1713
|
});
|
|
1662
1714
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
1663
|
-
jest
|
|
1664
|
-
|
|
1715
|
+
jest
|
|
1716
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
1717
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
1665
1718
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
1666
1719
|
return;
|
|
1667
1720
|
}
|
|
@@ -1674,8 +1727,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1674
1727
|
content: `ok ${call.name}`,
|
|
1675
1728
|
}))
|
|
1676
1729
|
);
|
|
1677
|
-
}
|
|
1678
|
-
);
|
|
1730
|
+
});
|
|
1679
1731
|
|
|
1680
1732
|
const handler = new ChatModelStreamHandler();
|
|
1681
1733
|
const metadata = { langgraph_node: 'agent' };
|
|
@@ -1732,9 +1784,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1732
1784
|
]);
|
|
1733
1785
|
expect(graph.eagerEventToolExecutions.has('call_weather')).toBe(true);
|
|
1734
1786
|
expect(graph.eagerEventToolExecutions.has('call_stock')).toBe(false);
|
|
1735
|
-
expect(
|
|
1736
|
-
|
|
1737
|
-
);
|
|
1787
|
+
expect(
|
|
1788
|
+
graph.eagerEventToolCallChunks.has(chunkStateKey('step-key', 0))
|
|
1789
|
+
).toBe(false);
|
|
1738
1790
|
expect(
|
|
1739
1791
|
graph.eagerEventToolCallChunks.get(chunkStateKey('step-key', 1))?.argsText
|
|
1740
1792
|
).toBe('{"ticker":"C');
|
|
@@ -1793,11 +1845,11 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1793
1845
|
expect(graph.eagerEventToolCallChunks.size).toBe(0);
|
|
1794
1846
|
});
|
|
1795
1847
|
|
|
1796
|
-
it('
|
|
1848
|
+
it('emits a completed event when an eager streamed tool result settles', async () => {
|
|
1797
1849
|
const graph = createGraph({
|
|
1798
1850
|
getAgentContext: jest.fn(
|
|
1799
1851
|
(): Partial<AgentContext> => ({
|
|
1800
|
-
provider: Providers.
|
|
1852
|
+
provider: Providers.ANTHROPIC,
|
|
1801
1853
|
reasoningKey: 'reasoning',
|
|
1802
1854
|
toolDefinitions: [{ name: 'weather' }, { name: 'stock' }],
|
|
1803
1855
|
graphTools: [],
|
|
@@ -1805,14 +1857,18 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1805
1857
|
})
|
|
1806
1858
|
) as unknown as StandardGraph['getAgentContext'],
|
|
1807
1859
|
});
|
|
1808
|
-
const
|
|
1809
|
-
jest
|
|
1810
|
-
|
|
1860
|
+
const completedEvents: Array<{ result: t.ToolEndEvent }> = [];
|
|
1861
|
+
jest
|
|
1862
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
1863
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
1864
|
+
if (event === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
1865
|
+
completedEvents.push(data as { result: t.ToolEndEvent });
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1811
1868
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
1812
1869
|
return;
|
|
1813
1870
|
}
|
|
1814
1871
|
const batch = data as t.ToolExecuteBatchRequest;
|
|
1815
|
-
toolExecuteCalls.push(batch);
|
|
1816
1872
|
batch.resolve(
|
|
1817
1873
|
batch.toolCalls.map((call) => ({
|
|
1818
1874
|
toolCallId: call.id,
|
|
@@ -1820,8 +1876,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1820
1876
|
content: `ok ${call.name}`,
|
|
1821
1877
|
}))
|
|
1822
1878
|
);
|
|
1823
|
-
}
|
|
1824
|
-
);
|
|
1879
|
+
});
|
|
1825
1880
|
|
|
1826
1881
|
const handler = new ChatModelStreamHandler();
|
|
1827
1882
|
const metadata = { langgraph_node: 'agent' };
|
|
@@ -1844,6 +1899,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1844
1899
|
metadata,
|
|
1845
1900
|
graph
|
|
1846
1901
|
);
|
|
1902
|
+
|
|
1847
1903
|
await handler.handle(
|
|
1848
1904
|
GraphEvents.CHAT_MODEL_STREAM,
|
|
1849
1905
|
{
|
|
@@ -1863,50 +1919,111 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1863
1919
|
graph
|
|
1864
1920
|
);
|
|
1865
1921
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
expect(
|
|
1922
|
+
await graph.eagerEventToolExecutions.get('call_weather')?.promise;
|
|
1923
|
+
|
|
1924
|
+
expect(completedEvents).toHaveLength(1);
|
|
1925
|
+
expect(completedEvents[0].result).toMatchObject({
|
|
1926
|
+
id: expect.stringMatching(/^step_/),
|
|
1927
|
+
type: 'tool_call',
|
|
1928
|
+
eager: true,
|
|
1929
|
+
tool_call: {
|
|
1930
|
+
id: 'call_weather',
|
|
1931
|
+
name: 'weather',
|
|
1932
|
+
args: '{"city":"NYC"}',
|
|
1933
|
+
output: 'ok weather',
|
|
1934
|
+
progress: 1,
|
|
1935
|
+
},
|
|
1936
|
+
});
|
|
1937
|
+
expect(
|
|
1938
|
+
graph.eagerEventToolExecutions.get('call_weather')?.completionDispatched
|
|
1939
|
+
).toBe(true);
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
it('does not emit a stale eager completion after the active execution is invalidated', async () => {
|
|
1943
|
+
const graph = createGraph({
|
|
1944
|
+
getAgentContext: jest.fn(
|
|
1945
|
+
(): Partial<AgentContext> => ({
|
|
1946
|
+
provider: Providers.ANTHROPIC,
|
|
1947
|
+
reasoningKey: 'reasoning',
|
|
1948
|
+
toolDefinitions: [{ name: 'weather' }, { name: 'stock' }],
|
|
1949
|
+
graphTools: [],
|
|
1950
|
+
agentId: 'agent_1',
|
|
1951
|
+
})
|
|
1952
|
+
) as unknown as StandardGraph['getAgentContext'],
|
|
1953
|
+
});
|
|
1954
|
+
const completedEvents: Array<{ result: t.ToolEndEvent }> = [];
|
|
1955
|
+
let pendingBatch: t.ToolExecuteBatchRequest | undefined;
|
|
1956
|
+
jest
|
|
1957
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
1958
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
1959
|
+
if (event === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
1960
|
+
completedEvents.push(data as { result: t.ToolEndEvent });
|
|
1961
|
+
return;
|
|
1962
|
+
}
|
|
1963
|
+
if (event === GraphEvents.ON_TOOL_EXECUTE) {
|
|
1964
|
+
pendingBatch = data as t.ToolExecuteBatchRequest;
|
|
1965
|
+
}
|
|
1966
|
+
});
|
|
1967
|
+
|
|
1968
|
+
const handler = new ChatModelStreamHandler();
|
|
1969
|
+
const metadata = { langgraph_node: 'agent' };
|
|
1869
1970
|
|
|
1870
1971
|
await handler.handle(
|
|
1871
1972
|
GraphEvents.CHAT_MODEL_STREAM,
|
|
1872
1973
|
{
|
|
1873
1974
|
chunk: {
|
|
1874
1975
|
content: '',
|
|
1875
|
-
|
|
1976
|
+
tool_call_chunks: [
|
|
1876
1977
|
{
|
|
1877
1978
|
id: 'call_weather',
|
|
1878
1979
|
name: 'weather',
|
|
1879
|
-
args: {
|
|
1980
|
+
args: '{"city":"NYC"}',
|
|
1981
|
+
index: 0,
|
|
1880
1982
|
},
|
|
1983
|
+
],
|
|
1984
|
+
} as unknown as t.StreamChunk,
|
|
1985
|
+
},
|
|
1986
|
+
metadata,
|
|
1987
|
+
graph
|
|
1988
|
+
);
|
|
1989
|
+
await handler.handle(
|
|
1990
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
1991
|
+
{
|
|
1992
|
+
chunk: {
|
|
1993
|
+
content: '',
|
|
1994
|
+
tool_call_chunks: [
|
|
1881
1995
|
{
|
|
1882
1996
|
id: 'call_stock',
|
|
1883
1997
|
name: 'stock',
|
|
1884
|
-
args: {
|
|
1998
|
+
args: '{"ticker":"C',
|
|
1999
|
+
index: 1,
|
|
1885
2000
|
},
|
|
1886
2001
|
],
|
|
1887
|
-
response_metadata: finalToolCallResponseMetadata,
|
|
1888
2002
|
} as unknown as t.StreamChunk,
|
|
1889
2003
|
},
|
|
1890
2004
|
metadata,
|
|
1891
2005
|
graph
|
|
1892
2006
|
);
|
|
1893
2007
|
|
|
1894
|
-
|
|
1895
|
-
expect(
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
}),
|
|
2008
|
+
const staleRecord = graph.eagerEventToolExecutions.get('call_weather');
|
|
2009
|
+
expect(staleRecord).toBeDefined();
|
|
2010
|
+
expect(pendingBatch?.toolCalls).toHaveLength(1);
|
|
2011
|
+
|
|
2012
|
+
graph.eagerEventToolExecutions.delete('call_weather');
|
|
2013
|
+
pendingBatch?.resolve([
|
|
2014
|
+
{
|
|
2015
|
+
toolCallId: 'call_weather',
|
|
2016
|
+
status: 'success',
|
|
2017
|
+
content: 'stale weather',
|
|
2018
|
+
},
|
|
1906
2019
|
]);
|
|
2020
|
+
await staleRecord?.promise;
|
|
2021
|
+
|
|
2022
|
+
expect(completedEvents).toHaveLength(0);
|
|
2023
|
+
expect(staleRecord?.completionDispatched).toBeUndefined();
|
|
1907
2024
|
});
|
|
1908
2025
|
|
|
1909
|
-
it('does not
|
|
2026
|
+
it('does not mark eager completion dispatched when event delivery is swallowed', async () => {
|
|
1910
2027
|
const graph = createGraph({
|
|
1911
2028
|
getAgentContext: jest.fn(
|
|
1912
2029
|
(): Partial<AgentContext> => ({
|
|
@@ -1918,23 +2035,23 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1918
2035
|
})
|
|
1919
2036
|
) as unknown as StandardGraph['getAgentContext'],
|
|
1920
2037
|
});
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
async (event, data): Promise<void> => {
|
|
1924
|
-
if (event
|
|
1925
|
-
return;
|
|
2038
|
+
jest
|
|
2039
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
2040
|
+
.mockImplementation(async (event, data): Promise<boolean | void> => {
|
|
2041
|
+
if (event === GraphEvents.ON_RUN_STEP_COMPLETED) {
|
|
2042
|
+
return false;
|
|
1926
2043
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
2044
|
+
if (event === GraphEvents.ON_TOOL_EXECUTE) {
|
|
2045
|
+
const batch = data as t.ToolExecuteBatchRequest;
|
|
2046
|
+
batch.resolve([
|
|
2047
|
+
{
|
|
2048
|
+
toolCallId: 'call_weather',
|
|
2049
|
+
status: 'success',
|
|
2050
|
+
content: 'weather result',
|
|
2051
|
+
},
|
|
2052
|
+
]);
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
1938
2055
|
|
|
1939
2056
|
const handler = new ChatModelStreamHandler();
|
|
1940
2057
|
const metadata = { langgraph_node: 'agent' };
|
|
@@ -1957,17 +2074,12 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1957
2074
|
metadata,
|
|
1958
2075
|
graph
|
|
1959
2076
|
);
|
|
1960
|
-
|
|
1961
2077
|
await handler.handle(
|
|
1962
2078
|
GraphEvents.CHAT_MODEL_STREAM,
|
|
1963
2079
|
{
|
|
1964
2080
|
chunk: {
|
|
1965
2081
|
content: '',
|
|
1966
2082
|
tool_call_chunks: [
|
|
1967
|
-
{
|
|
1968
|
-
args: '',
|
|
1969
|
-
index: 0,
|
|
1970
|
-
},
|
|
1971
2083
|
{
|
|
1972
2084
|
id: 'call_stock',
|
|
1973
2085
|
name: 'stock',
|
|
@@ -1981,38 +2093,96 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
1981
2093
|
graph
|
|
1982
2094
|
);
|
|
1983
2095
|
|
|
1984
|
-
|
|
2096
|
+
const record = graph.eagerEventToolExecutions.get('call_weather');
|
|
2097
|
+
await record?.promise;
|
|
1985
2098
|
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2099
|
+
expect(record?.completionDispatched).toBeUndefined();
|
|
2100
|
+
});
|
|
2101
|
+
|
|
2102
|
+
it('does not overwrite a completed tool output with later streamed deltas', () => {
|
|
2103
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
2104
|
+
|
|
2105
|
+
aggregateContent({
|
|
2106
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
2107
|
+
data: {
|
|
2108
|
+
id: 'step_weather',
|
|
2109
|
+
type: StepTypes.TOOL_CALLS,
|
|
2110
|
+
index: 0,
|
|
2111
|
+
stepDetails: {
|
|
2112
|
+
type: StepTypes.TOOL_CALLS,
|
|
2113
|
+
tool_calls: [
|
|
1992
2114
|
{
|
|
1993
|
-
|
|
1994
|
-
|
|
2115
|
+
id: 'call_weather',
|
|
2116
|
+
name: 'weather',
|
|
2117
|
+
args: {},
|
|
1995
2118
|
},
|
|
1996
2119
|
],
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2120
|
+
},
|
|
2121
|
+
usage: null,
|
|
2122
|
+
} as t.RunStep,
|
|
2123
|
+
});
|
|
2124
|
+
aggregateContent({
|
|
2125
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
2126
|
+
data: {
|
|
2127
|
+
result: {
|
|
2128
|
+
id: 'step_weather',
|
|
2129
|
+
index: 0,
|
|
2130
|
+
type: 'tool_call',
|
|
2131
|
+
tool_call: {
|
|
2132
|
+
id: 'call_weather',
|
|
2133
|
+
name: 'weather',
|
|
2134
|
+
args: '{"city":"NYC"}',
|
|
2135
|
+
output: 'sunny',
|
|
2136
|
+
progress: 1,
|
|
2137
|
+
} as t.ProcessedToolCall,
|
|
2138
|
+
},
|
|
2139
|
+
} as { result: t.ToolEndEvent },
|
|
2140
|
+
});
|
|
2141
|
+
aggregateContent({
|
|
2142
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
2143
|
+
data: {
|
|
2144
|
+
id: 'step_weather',
|
|
2145
|
+
delta: {
|
|
2146
|
+
type: StepTypes.TOOL_CALLS,
|
|
2147
|
+
tool_calls: [
|
|
2148
|
+
{
|
|
2149
|
+
id: 'call_weather',
|
|
2150
|
+
name: 'weather',
|
|
2151
|
+
args: '{"city":"NYC revised"}',
|
|
2152
|
+
},
|
|
2153
|
+
],
|
|
2154
|
+
},
|
|
2155
|
+
} as t.RunStepDeltaEvent,
|
|
2156
|
+
});
|
|
2002
2157
|
|
|
2003
|
-
expect(
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2158
|
+
expect(contentParts[0]).toMatchObject({
|
|
2159
|
+
type: ContentTypes.TOOL_CALL,
|
|
2160
|
+
tool_call: {
|
|
2161
|
+
id: 'call_weather',
|
|
2162
|
+
name: 'weather',
|
|
2163
|
+
args: '{"city":"NYC"}',
|
|
2164
|
+
output: 'sunny',
|
|
2165
|
+
progress: 1,
|
|
2166
|
+
},
|
|
2008
2167
|
});
|
|
2009
2168
|
});
|
|
2010
2169
|
|
|
2011
|
-
it('
|
|
2012
|
-
const graph = createGraph(
|
|
2170
|
+
it('does not use next-index sealing for Moonshot streamed tool chunks', async () => {
|
|
2171
|
+
const graph = createGraph({
|
|
2172
|
+
getAgentContext: jest.fn(
|
|
2173
|
+
(): Partial<AgentContext> => ({
|
|
2174
|
+
provider: Providers.MOONSHOT,
|
|
2175
|
+
reasoningKey: 'reasoning',
|
|
2176
|
+
toolDefinitions: [{ name: 'weather' }, { name: 'stock' }],
|
|
2177
|
+
graphTools: [],
|
|
2178
|
+
agentId: 'agent_1',
|
|
2179
|
+
})
|
|
2180
|
+
) as unknown as StandardGraph['getAgentContext'],
|
|
2181
|
+
});
|
|
2013
2182
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
2014
|
-
jest
|
|
2015
|
-
|
|
2183
|
+
jest
|
|
2184
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
2185
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
2016
2186
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
2017
2187
|
return;
|
|
2018
2188
|
}
|
|
@@ -2022,11 +2192,10 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2022
2192
|
batch.toolCalls.map((call) => ({
|
|
2023
2193
|
toolCallId: call.id,
|
|
2024
2194
|
status: 'success',
|
|
2025
|
-
content: `ok ${call.
|
|
2195
|
+
content: `ok ${call.name}`,
|
|
2026
2196
|
}))
|
|
2027
2197
|
);
|
|
2028
|
-
}
|
|
2029
|
-
);
|
|
2198
|
+
});
|
|
2030
2199
|
|
|
2031
2200
|
const handler = new ChatModelStreamHandler();
|
|
2032
2201
|
const metadata = { langgraph_node: 'agent' };
|
|
@@ -2038,7 +2207,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2038
2207
|
content: '',
|
|
2039
2208
|
tool_call_chunks: [
|
|
2040
2209
|
{
|
|
2041
|
-
id: '
|
|
2210
|
+
id: 'call_weather',
|
|
2042
2211
|
name: 'weather',
|
|
2043
2212
|
args: '{"city":"NYC"}',
|
|
2044
2213
|
index: 0,
|
|
@@ -2049,7 +2218,6 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2049
2218
|
metadata,
|
|
2050
2219
|
graph
|
|
2051
2220
|
);
|
|
2052
|
-
|
|
2053
2221
|
await handler.handle(
|
|
2054
2222
|
GraphEvents.CHAT_MODEL_STREAM,
|
|
2055
2223
|
{
|
|
@@ -2057,9 +2225,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2057
2225
|
content: '',
|
|
2058
2226
|
tool_call_chunks: [
|
|
2059
2227
|
{
|
|
2060
|
-
id: '
|
|
2061
|
-
name: '
|
|
2062
|
-
args: '{"
|
|
2228
|
+
id: 'call_stock',
|
|
2229
|
+
name: 'stock',
|
|
2230
|
+
args: '{"ticker":"C',
|
|
2063
2231
|
index: 1,
|
|
2064
2232
|
},
|
|
2065
2233
|
],
|
|
@@ -2069,9 +2237,215 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2069
2237
|
graph
|
|
2070
2238
|
);
|
|
2071
2239
|
|
|
2072
|
-
expect(toolExecuteCalls).toHaveLength(
|
|
2073
|
-
expect(
|
|
2074
|
-
|
|
2240
|
+
expect(toolExecuteCalls).toHaveLength(0);
|
|
2241
|
+
expect(graph.eagerEventToolExecutions.size).toBe(0);
|
|
2242
|
+
expect(graph.eagerEventToolCallChunks.size).toBe(0);
|
|
2243
|
+
|
|
2244
|
+
await handler.handle(
|
|
2245
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
2246
|
+
{
|
|
2247
|
+
chunk: {
|
|
2248
|
+
content: '',
|
|
2249
|
+
tool_calls: [
|
|
2250
|
+
{
|
|
2251
|
+
id: 'call_weather',
|
|
2252
|
+
name: 'weather',
|
|
2253
|
+
args: { city: 'NYC revised' },
|
|
2254
|
+
},
|
|
2255
|
+
{
|
|
2256
|
+
id: 'call_stock',
|
|
2257
|
+
name: 'stock',
|
|
2258
|
+
args: { ticker: 'CH' },
|
|
2259
|
+
},
|
|
2260
|
+
],
|
|
2261
|
+
response_metadata: finalToolCallResponseMetadata,
|
|
2262
|
+
} as unknown as t.StreamChunk,
|
|
2263
|
+
},
|
|
2264
|
+
metadata,
|
|
2265
|
+
graph
|
|
2266
|
+
);
|
|
2267
|
+
|
|
2268
|
+
expect(toolExecuteCalls).toHaveLength(1);
|
|
2269
|
+
expect(toolExecuteCalls[0].toolCalls).toEqual([
|
|
2270
|
+
expect.objectContaining({
|
|
2271
|
+
id: 'call_weather',
|
|
2272
|
+
name: 'weather',
|
|
2273
|
+
args: { city: 'NYC revised' },
|
|
2274
|
+
}),
|
|
2275
|
+
expect.objectContaining({
|
|
2276
|
+
id: 'call_stock',
|
|
2277
|
+
name: 'stock',
|
|
2278
|
+
args: { ticker: 'CH' },
|
|
2279
|
+
}),
|
|
2280
|
+
]);
|
|
2281
|
+
});
|
|
2282
|
+
|
|
2283
|
+
it('does not seal a streamed tool when the same chunk also carries its own index', async () => {
|
|
2284
|
+
const graph = createGraph({
|
|
2285
|
+
getAgentContext: jest.fn(
|
|
2286
|
+
(): Partial<AgentContext> => ({
|
|
2287
|
+
provider: Providers.ANTHROPIC,
|
|
2288
|
+
reasoningKey: 'reasoning',
|
|
2289
|
+
toolDefinitions: [{ name: 'weather' }, { name: 'stock' }],
|
|
2290
|
+
graphTools: [],
|
|
2291
|
+
agentId: 'agent_1',
|
|
2292
|
+
})
|
|
2293
|
+
) as unknown as StandardGraph['getAgentContext'],
|
|
2294
|
+
});
|
|
2295
|
+
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
2296
|
+
jest
|
|
2297
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
2298
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
2299
|
+
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
const batch = data as t.ToolExecuteBatchRequest;
|
|
2303
|
+
toolExecuteCalls.push(batch);
|
|
2304
|
+
batch.resolve(
|
|
2305
|
+
batch.toolCalls.map((call) => ({
|
|
2306
|
+
toolCallId: call.id,
|
|
2307
|
+
status: 'success',
|
|
2308
|
+
content: `ok ${call.name}`,
|
|
2309
|
+
}))
|
|
2310
|
+
);
|
|
2311
|
+
});
|
|
2312
|
+
|
|
2313
|
+
const handler = new ChatModelStreamHandler();
|
|
2314
|
+
const metadata = { langgraph_node: 'agent' };
|
|
2315
|
+
|
|
2316
|
+
await handler.handle(
|
|
2317
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
2318
|
+
{
|
|
2319
|
+
chunk: {
|
|
2320
|
+
content: '',
|
|
2321
|
+
tool_call_chunks: [
|
|
2322
|
+
{
|
|
2323
|
+
id: 'call_weather',
|
|
2324
|
+
name: 'weather',
|
|
2325
|
+
args: '{"city":"NYC"}',
|
|
2326
|
+
index: 0,
|
|
2327
|
+
},
|
|
2328
|
+
],
|
|
2329
|
+
} as unknown as t.StreamChunk,
|
|
2330
|
+
},
|
|
2331
|
+
metadata,
|
|
2332
|
+
graph
|
|
2333
|
+
);
|
|
2334
|
+
|
|
2335
|
+
await handler.handle(
|
|
2336
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
2337
|
+
{
|
|
2338
|
+
chunk: {
|
|
2339
|
+
content: '',
|
|
2340
|
+
tool_call_chunks: [
|
|
2341
|
+
{
|
|
2342
|
+
args: '',
|
|
2343
|
+
index: 0,
|
|
2344
|
+
},
|
|
2345
|
+
{
|
|
2346
|
+
id: 'call_stock',
|
|
2347
|
+
name: 'stock',
|
|
2348
|
+
args: '{"ticker":"C',
|
|
2349
|
+
index: 1,
|
|
2350
|
+
},
|
|
2351
|
+
],
|
|
2352
|
+
} as unknown as t.StreamChunk,
|
|
2353
|
+
},
|
|
2354
|
+
metadata,
|
|
2355
|
+
graph
|
|
2356
|
+
);
|
|
2357
|
+
|
|
2358
|
+
expect(toolExecuteCalls).toHaveLength(0);
|
|
2359
|
+
|
|
2360
|
+
await handler.handle(
|
|
2361
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
2362
|
+
{
|
|
2363
|
+
chunk: {
|
|
2364
|
+
content: '',
|
|
2365
|
+
tool_call_chunks: [
|
|
2366
|
+
{
|
|
2367
|
+
args: 'H"}',
|
|
2368
|
+
index: 1,
|
|
2369
|
+
},
|
|
2370
|
+
],
|
|
2371
|
+
} as unknown as t.StreamChunk,
|
|
2372
|
+
},
|
|
2373
|
+
metadata,
|
|
2374
|
+
graph
|
|
2375
|
+
);
|
|
2376
|
+
|
|
2377
|
+
expect(toolExecuteCalls).toHaveLength(1);
|
|
2378
|
+
expect(toolExecuteCalls[0].toolCalls[0]).toMatchObject({
|
|
2379
|
+
id: 'call_weather',
|
|
2380
|
+
name: 'weather',
|
|
2381
|
+
args: { city: 'NYC' },
|
|
2382
|
+
});
|
|
2383
|
+
});
|
|
2384
|
+
|
|
2385
|
+
it('preserves same-tool turns across per-call streamed eager starts', async () => {
|
|
2386
|
+
const graph = createGraph();
|
|
2387
|
+
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
2388
|
+
jest
|
|
2389
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
2390
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
2391
|
+
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
const batch = data as t.ToolExecuteBatchRequest;
|
|
2395
|
+
toolExecuteCalls.push(batch);
|
|
2396
|
+
batch.resolve(
|
|
2397
|
+
batch.toolCalls.map((call) => ({
|
|
2398
|
+
toolCallId: call.id,
|
|
2399
|
+
status: 'success',
|
|
2400
|
+
content: `ok ${call.args.city}`,
|
|
2401
|
+
}))
|
|
2402
|
+
);
|
|
2403
|
+
});
|
|
2404
|
+
|
|
2405
|
+
const handler = new ChatModelStreamHandler();
|
|
2406
|
+
const metadata = { langgraph_node: 'agent' };
|
|
2407
|
+
|
|
2408
|
+
await handler.handle(
|
|
2409
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
2410
|
+
{
|
|
2411
|
+
chunk: {
|
|
2412
|
+
content: '',
|
|
2413
|
+
tool_call_chunks: [
|
|
2414
|
+
{
|
|
2415
|
+
id: 'call_weather_1',
|
|
2416
|
+
name: 'weather',
|
|
2417
|
+
args: '{"city":"NYC"}',
|
|
2418
|
+
index: 0,
|
|
2419
|
+
},
|
|
2420
|
+
],
|
|
2421
|
+
} as unknown as t.StreamChunk,
|
|
2422
|
+
},
|
|
2423
|
+
metadata,
|
|
2424
|
+
graph
|
|
2425
|
+
);
|
|
2426
|
+
|
|
2427
|
+
await handler.handle(
|
|
2428
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
2429
|
+
{
|
|
2430
|
+
chunk: {
|
|
2431
|
+
content: '',
|
|
2432
|
+
tool_call_chunks: [
|
|
2433
|
+
{
|
|
2434
|
+
id: 'call_weather_2',
|
|
2435
|
+
name: 'weather',
|
|
2436
|
+
args: '{"city":"B',
|
|
2437
|
+
index: 1,
|
|
2438
|
+
},
|
|
2439
|
+
],
|
|
2440
|
+
} as unknown as t.StreamChunk,
|
|
2441
|
+
},
|
|
2442
|
+
metadata,
|
|
2443
|
+
graph
|
|
2444
|
+
);
|
|
2445
|
+
|
|
2446
|
+
expect(toolExecuteCalls).toHaveLength(1);
|
|
2447
|
+
expect(toolExecuteCalls[0].toolCalls[0]).toMatchObject({
|
|
2448
|
+
id: 'call_weather_1',
|
|
2075
2449
|
name: 'weather',
|
|
2076
2450
|
args: { city: 'NYC' },
|
|
2077
2451
|
turn: 0,
|
|
@@ -2135,8 +2509,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2135
2509
|
),
|
|
2136
2510
|
});
|
|
2137
2511
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
2138
|
-
jest
|
|
2139
|
-
|
|
2512
|
+
jest
|
|
2513
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
2514
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
2140
2515
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
2141
2516
|
return;
|
|
2142
2517
|
}
|
|
@@ -2149,8 +2524,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2149
2524
|
content: `ok ${call.name}`,
|
|
2150
2525
|
}))
|
|
2151
2526
|
);
|
|
2152
|
-
}
|
|
2153
|
-
);
|
|
2527
|
+
});
|
|
2154
2528
|
|
|
2155
2529
|
const handler = new ChatModelStreamHandler();
|
|
2156
2530
|
|
|
@@ -2255,9 +2629,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2255
2629
|
graph
|
|
2256
2630
|
);
|
|
2257
2631
|
|
|
2258
|
-
expect(
|
|
2259
|
-
|
|
2260
|
-
);
|
|
2632
|
+
expect(
|
|
2633
|
+
graph.eagerEventToolCallChunks.has(chunkStateKey('agent_b', 0))
|
|
2634
|
+
).toBe(false);
|
|
2261
2635
|
expect(
|
|
2262
2636
|
graph.eagerEventToolCallChunks.get(chunkStateKey('agent_a', 0))?.argsText
|
|
2263
2637
|
).toBe('{"city":"NYC"}');
|
|
@@ -2296,7 +2670,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2296
2670
|
});
|
|
2297
2671
|
|
|
2298
2672
|
it('does not prestart when batch-sensitive hooks are configured', async () => {
|
|
2299
|
-
const graph = createGraph({
|
|
2673
|
+
const graph = createGraph({
|
|
2674
|
+
hookRegistry: {} as StandardGraph['hookRegistry'],
|
|
2675
|
+
});
|
|
2300
2676
|
const dispatchSpy = jest.spyOn(events, 'safeDispatchCustomEvent');
|
|
2301
2677
|
|
|
2302
2678
|
await new ChatModelStreamHandler().handle(
|
|
@@ -2412,7 +2788,10 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2412
2788
|
(): Partial<AgentContext> => ({
|
|
2413
2789
|
provider: Providers.OPENAI,
|
|
2414
2790
|
reasoningKey: 'reasoning_content',
|
|
2415
|
-
toolDefinitions: [
|
|
2791
|
+
toolDefinitions: [
|
|
2792
|
+
{ name: Constants.EXECUTE_CODE },
|
|
2793
|
+
{ name: 'weather' },
|
|
2794
|
+
],
|
|
2416
2795
|
graphTools: [],
|
|
2417
2796
|
agentId: 'agent_1',
|
|
2418
2797
|
})
|
|
@@ -2468,53 +2847,526 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2468
2847
|
expect(graph.eagerEventToolCallChunks.size).toBe(0);
|
|
2469
2848
|
});
|
|
2470
2849
|
|
|
2471
|
-
it('
|
|
2850
|
+
it('prestarts streamed remote bash tools when the next Anthropic tool call begins', async () => {
|
|
2472
2851
|
const graph = createGraph({
|
|
2473
|
-
toolExecution: {
|
|
2474
|
-
engine: 'local',
|
|
2475
|
-
} as StandardGraph['toolExecution'],
|
|
2476
2852
|
getAgentContext: jest.fn(
|
|
2477
2853
|
(): Partial<AgentContext> => ({
|
|
2478
|
-
provider: Providers.
|
|
2479
|
-
reasoningKey: '
|
|
2854
|
+
provider: Providers.ANTHROPIC,
|
|
2855
|
+
reasoningKey: 'reasoning',
|
|
2480
2856
|
toolDefinitions: [
|
|
2481
|
-
{ name: Constants.
|
|
2482
|
-
{ name:
|
|
2857
|
+
{ name: Constants.BASH_TOOL },
|
|
2858
|
+
{ name: Constants.READ_FILE },
|
|
2483
2859
|
],
|
|
2484
2860
|
graphTools: [],
|
|
2485
2861
|
agentId: 'agent_1',
|
|
2486
2862
|
})
|
|
2487
2863
|
) as unknown as StandardGraph['getAgentContext'],
|
|
2488
2864
|
});
|
|
2489
|
-
const
|
|
2865
|
+
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
2866
|
+
jest
|
|
2867
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
2868
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
2869
|
+
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
2870
|
+
return;
|
|
2871
|
+
}
|
|
2872
|
+
const batch = data as t.ToolExecuteBatchRequest;
|
|
2873
|
+
toolExecuteCalls.push(batch);
|
|
2874
|
+
batch.resolve(
|
|
2875
|
+
batch.toolCalls.map((call) => ({
|
|
2876
|
+
toolCallId: call.id,
|
|
2877
|
+
status: 'success',
|
|
2878
|
+
content: `ok ${call.name}`,
|
|
2879
|
+
}))
|
|
2880
|
+
);
|
|
2881
|
+
});
|
|
2490
2882
|
|
|
2491
|
-
|
|
2883
|
+
const handler = new ChatModelStreamHandler();
|
|
2884
|
+
const metadata = { langgraph_node: 'agent' };
|
|
2885
|
+
|
|
2886
|
+
await handler.handle(
|
|
2492
2887
|
GraphEvents.CHAT_MODEL_STREAM,
|
|
2493
2888
|
{
|
|
2494
2889
|
chunk: {
|
|
2495
2890
|
content: '',
|
|
2496
|
-
|
|
2497
|
-
{
|
|
2498
|
-
id: 'call_code',
|
|
2499
|
-
name: Constants.EXECUTE_CODE,
|
|
2500
|
-
args: { code: 'print(1)' },
|
|
2501
|
-
},
|
|
2891
|
+
tool_call_chunks: [
|
|
2502
2892
|
{
|
|
2503
|
-
id: '
|
|
2504
|
-
name:
|
|
2505
|
-
args: {
|
|
2893
|
+
id: 'toolu_env',
|
|
2894
|
+
name: Constants.BASH_TOOL,
|
|
2895
|
+
args: '{"command":"echo env"}',
|
|
2896
|
+
index: 2,
|
|
2506
2897
|
},
|
|
2507
2898
|
],
|
|
2508
|
-
response_metadata: finalToolCallResponseMetadata,
|
|
2509
2899
|
} as unknown as t.StreamChunk,
|
|
2510
2900
|
},
|
|
2511
|
-
|
|
2901
|
+
metadata,
|
|
2512
2902
|
graph
|
|
2513
2903
|
);
|
|
2514
2904
|
|
|
2515
|
-
expect(
|
|
2516
|
-
|
|
2517
|
-
|
|
2905
|
+
expect(toolExecuteCalls).toHaveLength(0);
|
|
2906
|
+
|
|
2907
|
+
await handler.handle(
|
|
2908
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
2909
|
+
{
|
|
2910
|
+
chunk: {
|
|
2911
|
+
content: '',
|
|
2912
|
+
tool_call_chunks: [
|
|
2913
|
+
{
|
|
2914
|
+
id: 'toolu_net',
|
|
2915
|
+
name: Constants.BASH_TOOL,
|
|
2916
|
+
args: '{"command":"echo net"}',
|
|
2917
|
+
index: 3,
|
|
2918
|
+
},
|
|
2919
|
+
],
|
|
2920
|
+
} as unknown as t.StreamChunk,
|
|
2921
|
+
},
|
|
2922
|
+
metadata,
|
|
2923
|
+
graph
|
|
2924
|
+
);
|
|
2925
|
+
|
|
2926
|
+
expect(toolExecuteCalls).toHaveLength(1);
|
|
2927
|
+
expect(toolExecuteCalls[0].toolCalls).toEqual([
|
|
2928
|
+
expect.objectContaining({
|
|
2929
|
+
id: 'toolu_env',
|
|
2930
|
+
name: Constants.BASH_TOOL,
|
|
2931
|
+
args: { command: 'echo env' },
|
|
2932
|
+
stepId: expect.stringMatching(/^step_/),
|
|
2933
|
+
turn: 0,
|
|
2934
|
+
}),
|
|
2935
|
+
]);
|
|
2936
|
+
expect(graph.eagerEventToolExecutions.has('toolu_env')).toBe(true);
|
|
2937
|
+
expect(graph.eagerEventToolExecutions.has('toolu_net')).toBe(false);
|
|
2938
|
+
});
|
|
2939
|
+
|
|
2940
|
+
it('does not prestart streamed remote tools when graph tools may appear later', async () => {
|
|
2941
|
+
const graph = createGraph({
|
|
2942
|
+
getAgentContext: jest.fn(
|
|
2943
|
+
(): Partial<AgentContext> => ({
|
|
2944
|
+
provider: Providers.ANTHROPIC,
|
|
2945
|
+
reasoningKey: 'reasoning',
|
|
2946
|
+
toolDefinitions: [
|
|
2947
|
+
{ name: Constants.BASH_TOOL },
|
|
2948
|
+
{ name: Constants.READ_FILE },
|
|
2949
|
+
],
|
|
2950
|
+
graphTools: [
|
|
2951
|
+
{ name: 'transfer_to_researcher' } as unknown as t.GenericTool,
|
|
2952
|
+
],
|
|
2953
|
+
agentId: 'agent_1',
|
|
2954
|
+
})
|
|
2955
|
+
) as unknown as StandardGraph['getAgentContext'],
|
|
2956
|
+
});
|
|
2957
|
+
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
2958
|
+
jest
|
|
2959
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
2960
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
2961
|
+
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
const batch = data as t.ToolExecuteBatchRequest;
|
|
2965
|
+
toolExecuteCalls.push(batch);
|
|
2966
|
+
batch.resolve(
|
|
2967
|
+
batch.toolCalls.map((call) => ({
|
|
2968
|
+
toolCallId: call.id,
|
|
2969
|
+
status: 'success',
|
|
2970
|
+
content: `ok ${call.name}`,
|
|
2971
|
+
}))
|
|
2972
|
+
);
|
|
2973
|
+
});
|
|
2974
|
+
|
|
2975
|
+
const handler = new ChatModelStreamHandler();
|
|
2976
|
+
const metadata = { langgraph_node: 'agent' };
|
|
2977
|
+
|
|
2978
|
+
await handler.handle(
|
|
2979
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
2980
|
+
{
|
|
2981
|
+
chunk: {
|
|
2982
|
+
content: '',
|
|
2983
|
+
tool_call_chunks: [
|
|
2984
|
+
{
|
|
2985
|
+
id: 'toolu_env',
|
|
2986
|
+
name: Constants.BASH_TOOL,
|
|
2987
|
+
args: '{"command":"echo env"}',
|
|
2988
|
+
index: 2,
|
|
2989
|
+
},
|
|
2990
|
+
],
|
|
2991
|
+
} as unknown as t.StreamChunk,
|
|
2992
|
+
},
|
|
2993
|
+
metadata,
|
|
2994
|
+
graph
|
|
2995
|
+
);
|
|
2996
|
+
|
|
2997
|
+
await handler.handle(
|
|
2998
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
2999
|
+
{
|
|
3000
|
+
chunk: {
|
|
3001
|
+
content: '',
|
|
3002
|
+
tool_call_chunks: [
|
|
3003
|
+
{
|
|
3004
|
+
id: 'toolu_net',
|
|
3005
|
+
name: Constants.BASH_TOOL,
|
|
3006
|
+
args: '{"command":"echo net"}',
|
|
3007
|
+
index: 3,
|
|
3008
|
+
},
|
|
3009
|
+
],
|
|
3010
|
+
} as unknown as t.StreamChunk,
|
|
3011
|
+
},
|
|
3012
|
+
metadata,
|
|
3013
|
+
graph
|
|
3014
|
+
);
|
|
3015
|
+
|
|
3016
|
+
expect(toolExecuteCalls).toHaveLength(0);
|
|
3017
|
+
expect(graph.eagerEventToolExecutions.size).toBe(0);
|
|
3018
|
+
expect(graph.eagerEventToolCallChunks.size).toBe(0);
|
|
3019
|
+
});
|
|
3020
|
+
|
|
3021
|
+
it('prestarts streamed remote bash tools when code output references are enabled', async () => {
|
|
3022
|
+
const graph = createGraph({
|
|
3023
|
+
toolOutputReferences: { enabled: true },
|
|
3024
|
+
getAgentContext: jest.fn(
|
|
3025
|
+
(): Partial<AgentContext> => ({
|
|
3026
|
+
provider: Providers.ANTHROPIC,
|
|
3027
|
+
reasoningKey: 'reasoning',
|
|
3028
|
+
toolDefinitions: [
|
|
3029
|
+
{ name: Constants.BASH_TOOL },
|
|
3030
|
+
{ name: Constants.READ_FILE },
|
|
3031
|
+
],
|
|
3032
|
+
graphTools: [],
|
|
3033
|
+
agentId: 'agent_1',
|
|
3034
|
+
})
|
|
3035
|
+
) as unknown as StandardGraph['getAgentContext'],
|
|
3036
|
+
});
|
|
3037
|
+
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
3038
|
+
jest
|
|
3039
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
3040
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
3041
|
+
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
const batch = data as t.ToolExecuteBatchRequest;
|
|
3045
|
+
toolExecuteCalls.push(batch);
|
|
3046
|
+
batch.resolve(
|
|
3047
|
+
batch.toolCalls.map((call) => ({
|
|
3048
|
+
toolCallId: call.id,
|
|
3049
|
+
status: 'success',
|
|
3050
|
+
content: `ok ${call.name}`,
|
|
3051
|
+
}))
|
|
3052
|
+
);
|
|
3053
|
+
});
|
|
3054
|
+
|
|
3055
|
+
const handler = new ChatModelStreamHandler();
|
|
3056
|
+
const metadata = { langgraph_node: 'agent' };
|
|
3057
|
+
|
|
3058
|
+
for (const args of ['{"command":"echo ', 'env && ', 'pwd"}']) {
|
|
3059
|
+
await handler.handle(
|
|
3060
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
3061
|
+
{
|
|
3062
|
+
chunk: {
|
|
3063
|
+
content: '',
|
|
3064
|
+
tool_call_chunks: [
|
|
3065
|
+
{
|
|
3066
|
+
id: 'toolu_env',
|
|
3067
|
+
name: Constants.BASH_TOOL,
|
|
3068
|
+
args,
|
|
3069
|
+
index: 2,
|
|
3070
|
+
},
|
|
3071
|
+
],
|
|
3072
|
+
} as unknown as t.StreamChunk,
|
|
3073
|
+
},
|
|
3074
|
+
metadata,
|
|
3075
|
+
graph
|
|
3076
|
+
);
|
|
3077
|
+
}
|
|
3078
|
+
|
|
3079
|
+
expect(toolExecuteCalls).toHaveLength(0);
|
|
3080
|
+
|
|
3081
|
+
await handler.handle(
|
|
3082
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
3083
|
+
{
|
|
3084
|
+
chunk: {
|
|
3085
|
+
content: '',
|
|
3086
|
+
tool_call_chunks: [
|
|
3087
|
+
{
|
|
3088
|
+
id: 'toolu_net',
|
|
3089
|
+
name: Constants.BASH_TOOL,
|
|
3090
|
+
args: '{"command":"echo net"}',
|
|
3091
|
+
index: 3,
|
|
3092
|
+
},
|
|
3093
|
+
],
|
|
3094
|
+
} as unknown as t.StreamChunk,
|
|
3095
|
+
},
|
|
3096
|
+
metadata,
|
|
3097
|
+
graph
|
|
3098
|
+
);
|
|
3099
|
+
|
|
3100
|
+
expect(toolExecuteCalls).toHaveLength(1);
|
|
3101
|
+
expect(toolExecuteCalls[0].toolCalls).toEqual([
|
|
3102
|
+
expect.objectContaining({
|
|
3103
|
+
id: 'toolu_env',
|
|
3104
|
+
name: Constants.BASH_TOOL,
|
|
3105
|
+
args: { command: 'echo env && pwd' },
|
|
3106
|
+
}),
|
|
3107
|
+
]);
|
|
3108
|
+
});
|
|
3109
|
+
|
|
3110
|
+
it('does not prestart streamed code tools whose args contain output references', async () => {
|
|
3111
|
+
const graph = createGraph({
|
|
3112
|
+
toolOutputReferences: { enabled: true },
|
|
3113
|
+
getAgentContext: jest.fn(
|
|
3114
|
+
(): Partial<AgentContext> => ({
|
|
3115
|
+
provider: Providers.ANTHROPIC,
|
|
3116
|
+
reasoningKey: 'reasoning',
|
|
3117
|
+
toolDefinitions: [
|
|
3118
|
+
{ name: Constants.BASH_TOOL },
|
|
3119
|
+
{ name: Constants.READ_FILE },
|
|
3120
|
+
],
|
|
3121
|
+
graphTools: [],
|
|
3122
|
+
agentId: 'agent_1',
|
|
3123
|
+
})
|
|
3124
|
+
) as unknown as StandardGraph['getAgentContext'],
|
|
3125
|
+
});
|
|
3126
|
+
const dispatchSpy = jest.spyOn(events, 'safeDispatchCustomEvent');
|
|
3127
|
+
const handler = new ChatModelStreamHandler();
|
|
3128
|
+
const metadata = { langgraph_node: 'agent' };
|
|
3129
|
+
|
|
3130
|
+
await handler.handle(
|
|
3131
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
3132
|
+
{
|
|
3133
|
+
chunk: {
|
|
3134
|
+
content: '',
|
|
3135
|
+
tool_call_chunks: [
|
|
3136
|
+
{
|
|
3137
|
+
id: 'toolu_ref',
|
|
3138
|
+
name: Constants.BASH_TOOL,
|
|
3139
|
+
args: '{"command":"cat <<EOF\\n{{tool0turn0}}\\nEOF"}',
|
|
3140
|
+
index: 2,
|
|
3141
|
+
},
|
|
3142
|
+
],
|
|
3143
|
+
} as unknown as t.StreamChunk,
|
|
3144
|
+
},
|
|
3145
|
+
metadata,
|
|
3146
|
+
graph
|
|
3147
|
+
);
|
|
3148
|
+
await handler.handle(
|
|
3149
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
3150
|
+
{
|
|
3151
|
+
chunk: {
|
|
3152
|
+
content: '',
|
|
3153
|
+
tool_call_chunks: [
|
|
3154
|
+
{
|
|
3155
|
+
id: 'toolu_next',
|
|
3156
|
+
name: Constants.BASH_TOOL,
|
|
3157
|
+
args: '{"command":"echo next"}',
|
|
3158
|
+
index: 3,
|
|
3159
|
+
},
|
|
3160
|
+
],
|
|
3161
|
+
} as unknown as t.StreamChunk,
|
|
3162
|
+
},
|
|
3163
|
+
metadata,
|
|
3164
|
+
graph
|
|
3165
|
+
);
|
|
3166
|
+
|
|
3167
|
+
expect(dispatchSpy).not.toHaveBeenCalledWith(
|
|
3168
|
+
GraphEvents.ON_TOOL_EXECUTE,
|
|
3169
|
+
expect.anything(),
|
|
3170
|
+
expect.anything()
|
|
3171
|
+
);
|
|
3172
|
+
});
|
|
3173
|
+
|
|
3174
|
+
it('does not prestart streamed tools when the next Anthropic tool call is a graph tool', async () => {
|
|
3175
|
+
const handoffToolName = `${Constants.LC_TRANSFER_TO_}researcher`;
|
|
3176
|
+
const graph = createGraph({
|
|
3177
|
+
getAgentContext: jest.fn(
|
|
3178
|
+
(): Partial<AgentContext> => ({
|
|
3179
|
+
provider: Providers.ANTHROPIC,
|
|
3180
|
+
reasoningKey: 'reasoning',
|
|
3181
|
+
toolDefinitions: [
|
|
3182
|
+
{ name: Constants.BASH_TOOL },
|
|
3183
|
+
{ name: handoffToolName },
|
|
3184
|
+
],
|
|
3185
|
+
graphTools: [{ name: handoffToolName } as unknown as t.GenericTool],
|
|
3186
|
+
agentId: 'agent_1',
|
|
3187
|
+
})
|
|
3188
|
+
) as unknown as StandardGraph['getAgentContext'],
|
|
3189
|
+
});
|
|
3190
|
+
const dispatchSpy = jest.spyOn(events, 'safeDispatchCustomEvent');
|
|
3191
|
+
const handler = new ChatModelStreamHandler();
|
|
3192
|
+
const metadata = { langgraph_node: 'agent' };
|
|
3193
|
+
|
|
3194
|
+
await handler.handle(
|
|
3195
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
3196
|
+
{
|
|
3197
|
+
chunk: {
|
|
3198
|
+
content: '',
|
|
3199
|
+
tool_call_chunks: [
|
|
3200
|
+
{
|
|
3201
|
+
id: 'toolu_env',
|
|
3202
|
+
name: Constants.BASH_TOOL,
|
|
3203
|
+
args: '{"command":"echo env"}',
|
|
3204
|
+
index: 2,
|
|
3205
|
+
},
|
|
3206
|
+
],
|
|
3207
|
+
} as unknown as t.StreamChunk,
|
|
3208
|
+
},
|
|
3209
|
+
metadata,
|
|
3210
|
+
graph
|
|
3211
|
+
);
|
|
3212
|
+
|
|
3213
|
+
await handler.handle(
|
|
3214
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
3215
|
+
{
|
|
3216
|
+
chunk: {
|
|
3217
|
+
content: '',
|
|
3218
|
+
tool_call_chunks: [
|
|
3219
|
+
{
|
|
3220
|
+
id: 'toolu_handoff',
|
|
3221
|
+
name: handoffToolName,
|
|
3222
|
+
args: '{"message":"check this"}',
|
|
3223
|
+
index: 3,
|
|
3224
|
+
},
|
|
3225
|
+
],
|
|
3226
|
+
} as unknown as t.StreamChunk,
|
|
3227
|
+
},
|
|
3228
|
+
metadata,
|
|
3229
|
+
graph
|
|
3230
|
+
);
|
|
3231
|
+
|
|
3232
|
+
expect(dispatchSpy).not.toHaveBeenCalledWith(
|
|
3233
|
+
GraphEvents.ON_TOOL_EXECUTE,
|
|
3234
|
+
expect.anything(),
|
|
3235
|
+
expect.anything()
|
|
3236
|
+
);
|
|
3237
|
+
expect(graph.eagerEventToolExecutions.size).toBe(0);
|
|
3238
|
+
});
|
|
3239
|
+
|
|
3240
|
+
it('does not prestart streamed tools after a graph tool appeared earlier in the same step', async () => {
|
|
3241
|
+
const handoffToolName = `${Constants.LC_TRANSFER_TO_}researcher`;
|
|
3242
|
+
const graph = createGraph({
|
|
3243
|
+
getAgentContext: jest.fn(
|
|
3244
|
+
(): Partial<AgentContext> => ({
|
|
3245
|
+
provider: Providers.ANTHROPIC,
|
|
3246
|
+
reasoningKey: 'reasoning',
|
|
3247
|
+
toolDefinitions: [
|
|
3248
|
+
{ name: Constants.BASH_TOOL },
|
|
3249
|
+
{ name: handoffToolName },
|
|
3250
|
+
],
|
|
3251
|
+
graphTools: [{ name: handoffToolName } as unknown as t.GenericTool],
|
|
3252
|
+
agentId: 'agent_1',
|
|
3253
|
+
})
|
|
3254
|
+
) as unknown as StandardGraph['getAgentContext'],
|
|
3255
|
+
});
|
|
3256
|
+
const dispatchSpy = jest.spyOn(events, 'safeDispatchCustomEvent');
|
|
3257
|
+
const handler = new ChatModelStreamHandler();
|
|
3258
|
+
const metadata = { langgraph_node: 'agent' };
|
|
3259
|
+
|
|
3260
|
+
await handler.handle(
|
|
3261
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
3262
|
+
{
|
|
3263
|
+
chunk: {
|
|
3264
|
+
content: '',
|
|
3265
|
+
tool_call_chunks: [
|
|
3266
|
+
{
|
|
3267
|
+
id: 'toolu_env',
|
|
3268
|
+
name: Constants.BASH_TOOL,
|
|
3269
|
+
args: '{"command":"echo env"}',
|
|
3270
|
+
index: 2,
|
|
3271
|
+
},
|
|
3272
|
+
],
|
|
3273
|
+
} as unknown as t.StreamChunk,
|
|
3274
|
+
},
|
|
3275
|
+
metadata,
|
|
3276
|
+
graph
|
|
3277
|
+
);
|
|
3278
|
+
await handler.handle(
|
|
3279
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
3280
|
+
{
|
|
3281
|
+
chunk: {
|
|
3282
|
+
content: '',
|
|
3283
|
+
tool_call_chunks: [
|
|
3284
|
+
{
|
|
3285
|
+
id: 'toolu_handoff',
|
|
3286
|
+
name: handoffToolName,
|
|
3287
|
+
args: '{"message":"partial',
|
|
3288
|
+
index: 3,
|
|
3289
|
+
},
|
|
3290
|
+
],
|
|
3291
|
+
} as unknown as t.StreamChunk,
|
|
3292
|
+
},
|
|
3293
|
+
metadata,
|
|
3294
|
+
graph
|
|
3295
|
+
);
|
|
3296
|
+
await handler.handle(
|
|
3297
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
3298
|
+
{
|
|
3299
|
+
chunk: {
|
|
3300
|
+
content: '',
|
|
3301
|
+
tool_call_chunks: [
|
|
3302
|
+
{
|
|
3303
|
+
id: 'toolu_next',
|
|
3304
|
+
name: Constants.BASH_TOOL,
|
|
3305
|
+
args: '{"command":"echo next"}',
|
|
3306
|
+
index: 4,
|
|
3307
|
+
},
|
|
3308
|
+
],
|
|
3309
|
+
} as unknown as t.StreamChunk,
|
|
3310
|
+
},
|
|
3311
|
+
metadata,
|
|
3312
|
+
graph
|
|
3313
|
+
);
|
|
3314
|
+
|
|
3315
|
+
expect(dispatchSpy).not.toHaveBeenCalledWith(
|
|
3316
|
+
GraphEvents.ON_TOOL_EXECUTE,
|
|
3317
|
+
expect.anything(),
|
|
3318
|
+
expect.anything()
|
|
3319
|
+
);
|
|
3320
|
+
expect(graph.eagerEventToolExecutions.size).toBe(0);
|
|
3321
|
+
});
|
|
3322
|
+
|
|
3323
|
+
it('does not prestart event tools in a mixed direct-tool batch', async () => {
|
|
3324
|
+
const graph = createGraph({
|
|
3325
|
+
toolExecution: {
|
|
3326
|
+
engine: 'local',
|
|
3327
|
+
} as StandardGraph['toolExecution'],
|
|
3328
|
+
getAgentContext: jest.fn(
|
|
3329
|
+
(): Partial<AgentContext> => ({
|
|
3330
|
+
provider: Providers.OPENAI,
|
|
3331
|
+
reasoningKey: 'reasoning_content',
|
|
3332
|
+
toolDefinitions: [
|
|
3333
|
+
{ name: Constants.EXECUTE_CODE },
|
|
3334
|
+
{ name: 'weather' },
|
|
3335
|
+
],
|
|
3336
|
+
graphTools: [],
|
|
3337
|
+
agentId: 'agent_1',
|
|
3338
|
+
})
|
|
3339
|
+
) as unknown as StandardGraph['getAgentContext'],
|
|
3340
|
+
});
|
|
3341
|
+
const dispatchSpy = jest.spyOn(events, 'safeDispatchCustomEvent');
|
|
3342
|
+
|
|
3343
|
+
await new ChatModelStreamHandler().handle(
|
|
3344
|
+
GraphEvents.CHAT_MODEL_STREAM,
|
|
3345
|
+
{
|
|
3346
|
+
chunk: {
|
|
3347
|
+
content: '',
|
|
3348
|
+
tool_calls: [
|
|
3349
|
+
{
|
|
3350
|
+
id: 'call_code',
|
|
3351
|
+
name: Constants.EXECUTE_CODE,
|
|
3352
|
+
args: { code: 'print(1)' },
|
|
3353
|
+
},
|
|
3354
|
+
{
|
|
3355
|
+
id: 'call_weather',
|
|
3356
|
+
name: 'weather',
|
|
3357
|
+
args: { city: 'NYC' },
|
|
3358
|
+
},
|
|
3359
|
+
],
|
|
3360
|
+
response_metadata: finalToolCallResponseMetadata,
|
|
3361
|
+
} as unknown as t.StreamChunk,
|
|
3362
|
+
},
|
|
3363
|
+
{ langgraph_node: 'agent' },
|
|
3364
|
+
graph
|
|
3365
|
+
);
|
|
3366
|
+
|
|
3367
|
+
expect(dispatchSpy).not.toHaveBeenCalledWith(
|
|
3368
|
+
GraphEvents.ON_TOOL_EXECUTE,
|
|
3369
|
+
expect.anything(),
|
|
2518
3370
|
expect.anything()
|
|
2519
3371
|
);
|
|
2520
3372
|
expect(graph.eagerEventToolExecutions.size).toBe(0);
|
|
@@ -2524,8 +3376,9 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2524
3376
|
const graph = createGraph();
|
|
2525
3377
|
graph.eagerEventToolUsageCount.set('weather', 1);
|
|
2526
3378
|
const toolExecuteCalls: t.ToolExecuteBatchRequest[] = [];
|
|
2527
|
-
jest
|
|
2528
|
-
|
|
3379
|
+
jest
|
|
3380
|
+
.spyOn(events, 'safeDispatchCustomEvent')
|
|
3381
|
+
.mockImplementation(async (event, data): Promise<void> => {
|
|
2529
3382
|
if (event !== GraphEvents.ON_TOOL_EXECUTE) {
|
|
2530
3383
|
return;
|
|
2531
3384
|
}
|
|
@@ -2538,8 +3391,7 @@ describe('ChatModelStreamHandler eager event tool execution', () => {
|
|
|
2538
3391
|
content: 'sunny',
|
|
2539
3392
|
},
|
|
2540
3393
|
]);
|
|
2541
|
-
}
|
|
2542
|
-
);
|
|
3394
|
+
});
|
|
2543
3395
|
|
|
2544
3396
|
await new ChatModelStreamHandler().handle(
|
|
2545
3397
|
GraphEvents.CHAT_MODEL_STREAM,
|