@illuma-ai/agents 1.1.22 → 1.1.24
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.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +34 -8
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/utils/llm.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +34 -8
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/utils/llm.mjs.map +1 -1
- package/package.json +1 -1
- package/src/graphs/Graph.ts +1 -0
- package/src/graphs/MultiAgentGraph.ts +42 -13
- package/src/graphs/__tests__/multi-agent-delegate.test.ts +41 -27
- package/src/utils/llm.ts +1 -0
package/src/graphs/Graph.ts
CHANGED
|
@@ -678,6 +678,7 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
678
678
|
];
|
|
679
679
|
}
|
|
680
680
|
|
|
681
|
+
|
|
681
682
|
const childState: t.BaseGraphState = {
|
|
682
683
|
messages: childMessages,
|
|
683
684
|
};
|
|
@@ -688,6 +689,25 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
688
689
|
);
|
|
689
690
|
|
|
690
691
|
try {
|
|
692
|
+
/**
|
|
693
|
+
* Dispatch transition BEFORE invoking the child subgraph so that
|
|
694
|
+
* callbacks.js sets multiAgentTrace.isMultiAgent = true before the
|
|
695
|
+
* child's ON_RUN_STEP events fire. This ensures child tool calls
|
|
696
|
+
* are attributed to the correct agent in admin traces.
|
|
697
|
+
*/
|
|
698
|
+
await safeDispatchCustomEvent(
|
|
699
|
+
GraphEvents.ON_AGENT_TRANSITION,
|
|
700
|
+
{
|
|
701
|
+
sourceAgentId: sourceAgentId,
|
|
702
|
+
sourceAgentName: this.agentContexts.get(sourceAgentId)?.name ?? sourceAgentId,
|
|
703
|
+
destinationAgentId: destination,
|
|
704
|
+
destinationAgentName: destContext?.name ?? destination,
|
|
705
|
+
edgeType: EdgeType.HANDOFF,
|
|
706
|
+
timestamp: Date.now(),
|
|
707
|
+
},
|
|
708
|
+
config
|
|
709
|
+
);
|
|
710
|
+
|
|
691
711
|
/**
|
|
692
712
|
* Invoke the child subgraph with config propagation.
|
|
693
713
|
* Config carries callbacks (for SSE streaming), abort signal,
|
|
@@ -710,19 +730,6 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
710
730
|
`${truncatedResult.length < resultText.length ? `, truncated to ${truncatedResult.length}` : ''})`
|
|
711
731
|
);
|
|
712
732
|
|
|
713
|
-
await safeDispatchCustomEvent(
|
|
714
|
-
GraphEvents.ON_AGENT_TRANSITION,
|
|
715
|
-
{
|
|
716
|
-
sourceAgentId: sourceAgentId,
|
|
717
|
-
sourceAgentName: this.agentContexts.get(sourceAgentId)?.name ?? sourceAgentId,
|
|
718
|
-
destinationAgentId: destination,
|
|
719
|
-
destinationAgentName: destContext?.name ?? destination,
|
|
720
|
-
edgeType: EdgeType.HANDOFF,
|
|
721
|
-
timestamp: Date.now(),
|
|
722
|
-
},
|
|
723
|
-
config
|
|
724
|
-
);
|
|
725
|
-
|
|
726
733
|
return truncatedResult;
|
|
727
734
|
} catch (err) {
|
|
728
735
|
const errorMessage =
|
|
@@ -970,6 +977,28 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
970
977
|
}
|
|
971
978
|
}
|
|
972
979
|
|
|
980
|
+
/**
|
|
981
|
+
* Ensure messages end with a HumanMessage.
|
|
982
|
+
* After stripping tool artifacts, the last message may be an AIMessage
|
|
983
|
+
* (orchestrator's reasoning before the handoff). Some providers (Bedrock,
|
|
984
|
+
* Google/VertexAI) reject conversations ending with an assistant message.
|
|
985
|
+
* Convert the trailing AIMessage to a HumanMessage to preserve any useful
|
|
986
|
+
* context (e.g., compacted tool summaries) while satisfying the API requirement.
|
|
987
|
+
*/
|
|
988
|
+
if (cleaned.length > 0 && cleaned[cleaned.length - 1].getType() === 'ai') {
|
|
989
|
+
const lastAI = cleaned[cleaned.length - 1];
|
|
990
|
+
const content = typeof lastAI.content === 'string'
|
|
991
|
+
? lastAI.content
|
|
992
|
+
: '';
|
|
993
|
+
if (content.trim()) {
|
|
994
|
+
cleaned[cleaned.length - 1] = new HumanMessage(
|
|
995
|
+
`[Context from orchestrator]: ${content}`
|
|
996
|
+
);
|
|
997
|
+
} else {
|
|
998
|
+
cleaned.pop();
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
973
1002
|
return cleaned;
|
|
974
1003
|
}
|
|
975
1004
|
|
|
@@ -215,20 +215,35 @@ describe('prepareHandoffMessages', () => {
|
|
|
215
215
|
expect(MultiAgentGraph.prepareHandoffMessages([])).toEqual([]);
|
|
216
216
|
});
|
|
217
217
|
|
|
218
|
-
it('
|
|
218
|
+
it('converts trailing AIMessage to HumanMessage context', () => {
|
|
219
219
|
const messages: BaseMessage[] = [
|
|
220
220
|
new HumanMessage('Hello'),
|
|
221
221
|
new AIMessage('Hi there'),
|
|
222
222
|
];
|
|
223
223
|
const result = MultiAgentGraph.prepareHandoffMessages(messages);
|
|
224
|
+
// Trailing AI converted to HumanMessage so conversation ends with user message
|
|
224
225
|
expect(result).toHaveLength(2);
|
|
225
226
|
expect(result[0].getType()).toBe('human');
|
|
226
227
|
expect(result[0].content).toBe('Hello');
|
|
228
|
+
expect(result[1].getType()).toBe('human');
|
|
229
|
+
expect(result[1].content).toContain('Hi there');
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('preserves AI messages that are not trailing', () => {
|
|
233
|
+
const messages: BaseMessage[] = [
|
|
234
|
+
new HumanMessage('Hello'),
|
|
235
|
+
new AIMessage('Hi there'),
|
|
236
|
+
new HumanMessage('Follow up'),
|
|
237
|
+
];
|
|
238
|
+
const result = MultiAgentGraph.prepareHandoffMessages(messages);
|
|
239
|
+
expect(result).toHaveLength(3);
|
|
240
|
+
expect(result[0].content).toBe('Hello');
|
|
227
241
|
expect(result[1].getType()).toBe('ai');
|
|
228
242
|
expect(result[1].content).toBe('Hi there');
|
|
243
|
+
expect(result[2].content).toBe('Follow up');
|
|
229
244
|
});
|
|
230
245
|
|
|
231
|
-
it('strips orphaned tool_use
|
|
246
|
+
it('strips orphaned tool_use, converts trailing AI to context', () => {
|
|
232
247
|
const messages: BaseMessage[] = [
|
|
233
248
|
new HumanMessage('Do something'),
|
|
234
249
|
new AIMessage({
|
|
@@ -242,12 +257,9 @@ describe('prepareHandoffMessages', () => {
|
|
|
242
257
|
const result = MultiAgentGraph.prepareHandoffMessages(messages);
|
|
243
258
|
expect(result).toHaveLength(2);
|
|
244
259
|
expect(result[0].content).toBe('Do something');
|
|
245
|
-
// AI
|
|
246
|
-
expect(result[1].getType()).toBe('
|
|
260
|
+
// Trailing AI converted to HumanMessage context
|
|
261
|
+
expect(result[1].getType()).toBe('human');
|
|
247
262
|
expect(result[1].content).toContain('Let me call a tool');
|
|
248
|
-
// Should NOT contain tool_calls
|
|
249
|
-
const aiMsg = result[1] as AIMessage;
|
|
250
|
-
expect(aiMsg.tool_calls ?? []).toHaveLength(0);
|
|
251
263
|
});
|
|
252
264
|
|
|
253
265
|
it('compacts paired tool_use/tool_result into text summaries', () => {
|
|
@@ -264,17 +276,20 @@ describe('prepareHandoffMessages', () => {
|
|
|
264
276
|
];
|
|
265
277
|
const result = MultiAgentGraph.prepareHandoffMessages(messages);
|
|
266
278
|
|
|
267
|
-
//
|
|
279
|
+
// HumanMessage + compacted AI + trailing AI converted to HumanMessage
|
|
268
280
|
expect(result).toHaveLength(3);
|
|
269
281
|
|
|
270
282
|
// ToolMessage should be removed
|
|
271
283
|
expect(result.every((m) => m.getType() !== 'tool')).toBe(true);
|
|
272
284
|
|
|
273
285
|
// Compacted AI message should contain tool summary
|
|
274
|
-
|
|
275
|
-
expect(
|
|
276
|
-
expect(
|
|
277
|
-
|
|
286
|
+
expect(result[1].content).toContain('I will search for you');
|
|
287
|
+
expect(result[1].content).toContain('web_search');
|
|
288
|
+
expect(result[1].content).toContain('Found 3 results');
|
|
289
|
+
|
|
290
|
+
// Trailing AI converted to HumanMessage
|
|
291
|
+
expect(result[2].getType()).toBe('human');
|
|
292
|
+
expect(result[2].content).toContain('Based on the search');
|
|
278
293
|
});
|
|
279
294
|
|
|
280
295
|
it('removes ALL ToolMessages from output', () => {
|
|
@@ -326,25 +341,20 @@ describe('prepareHandoffMessages', () => {
|
|
|
326
341
|
// No ToolMessages
|
|
327
342
|
expect(result.filter((m) => m.getType() === 'tool')).toHaveLength(0);
|
|
328
343
|
|
|
329
|
-
//
|
|
330
|
-
for (const msg of result) {
|
|
331
|
-
if (msg.getType() === 'ai') {
|
|
332
|
-
expect((msg as AIMessage).tool_calls ?? []).toHaveLength(0);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Should have: HumanMessage + 2 compacted/cleaned AI messages
|
|
344
|
+
// Should have: HumanMessage + compacted AI (with tool summary) + trailing AI converted to HumanMessage
|
|
337
345
|
expect(result).toHaveLength(3);
|
|
338
346
|
|
|
339
347
|
// First AI message should have researcher tool summary (paired)
|
|
348
|
+
expect(result[1].getType()).toBe('ai');
|
|
340
349
|
expect(result[1].content).toContain('lc_handoff_to_researcher');
|
|
341
350
|
expect(result[1].content).toContain('AI adoption');
|
|
342
351
|
|
|
343
|
-
//
|
|
352
|
+
// Trailing AI converted to HumanMessage context
|
|
353
|
+
expect(result[2].getType()).toBe('human');
|
|
344
354
|
expect(result[2].content).toContain('Now I will delegate to the writer');
|
|
345
355
|
});
|
|
346
356
|
|
|
347
|
-
it('preserves message order', () => {
|
|
357
|
+
it('preserves message order, converts only trailing AI', () => {
|
|
348
358
|
const messages: BaseMessage[] = [
|
|
349
359
|
new HumanMessage('Step 1'),
|
|
350
360
|
new AIMessage('Response 1'),
|
|
@@ -355,8 +365,10 @@ describe('prepareHandoffMessages', () => {
|
|
|
355
365
|
expect(result).toHaveLength(4);
|
|
356
366
|
expect(result[0].content).toBe('Step 1');
|
|
357
367
|
expect(result[1].content).toBe('Response 1');
|
|
368
|
+
expect(result[1].getType()).toBe('ai'); // Not trailing — preserved as AI
|
|
358
369
|
expect(result[2].content).toBe('Step 2');
|
|
359
|
-
expect(result[3].
|
|
370
|
+
expect(result[3].getType()).toBe('human'); // Trailing — converted
|
|
371
|
+
expect(result[3].content).toContain('Response 2');
|
|
360
372
|
});
|
|
361
373
|
|
|
362
374
|
it('drops AI messages that have only tool_calls and no text content', () => {
|
|
@@ -390,10 +402,12 @@ describe('prepareHandoffMessages', () => {
|
|
|
390
402
|
new ToolMessage({ content: 'tool output', tool_call_id: 'tc1', name: 'some_tool' }),
|
|
391
403
|
];
|
|
392
404
|
const result = MultiAgentGraph.prepareHandoffMessages(messages);
|
|
405
|
+
// Compacted AI is trailing → converted to HumanMessage
|
|
393
406
|
expect(result).toHaveLength(2);
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
expect(
|
|
397
|
-
expect(
|
|
407
|
+
expect(result[1].getType()).toBe('human');
|
|
408
|
+
const content = result[1].content as string;
|
|
409
|
+
expect(content).toContain('Part one.');
|
|
410
|
+
expect(content).toContain('Part two.');
|
|
411
|
+
expect(content).toContain('some_tool');
|
|
398
412
|
});
|
|
399
413
|
});
|
package/src/utils/llm.ts
CHANGED