illuma-agents 1.0.27 → 1.0.28
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 +28 -2
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +108 -0
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/stream.cjs +27 -0
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/BrowserTools.cjs +53 -32
- package/dist/cjs/tools/BrowserTools.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +28 -2
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +108 -0
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/stream.mjs +27 -0
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/BrowserTools.mjs +53 -32
- package/dist/esm/tools/BrowserTools.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +14 -0
- package/dist/types/graphs/MultiAgentGraph.d.ts +41 -0
- package/dist/types/messages/format.d.ts +1 -1
- package/dist/types/tools/BrowserTools.d.ts +1 -1
- package/dist/types/types/stream.d.ts +13 -0
- package/package.json +4 -2
- package/src/graphs/Graph.ts +30 -2
- package/src/graphs/MultiAgentGraph.ts +119 -0
- package/src/messages/format.ts +2 -2
- package/src/scripts/multi-agent-chain.ts +59 -6
- package/src/scripts/multi-agent-parallel-start.ts +265 -0
- package/src/scripts/multi-agent-parallel.ts +61 -10
- package/src/scripts/multi-agent-sequence.ts +6 -1
- package/src/scripts/parallel-asymmetric-tools-test.ts +274 -0
- package/src/scripts/parallel-full-metadata-test.ts +240 -0
- package/src/scripts/parallel-tools-test.ts +340 -0
- package/src/scripts/sequential-full-metadata-test.ts +197 -0
- package/src/scripts/single-agent-metadata-test.ts +198 -0
- package/src/scripts/test-thinking-handoff.ts +8 -0
- package/src/scripts/tools.ts +31 -11
- package/src/stream.ts +32 -0
- package/src/tools/BrowserTools.ts +356 -350
- package/src/tools/__tests__/BrowserTools.test.ts +263 -257
- package/src/types/stream.ts +15 -0
package/src/graphs/Graph.ts
CHANGED
|
@@ -1246,6 +1246,26 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
|
|
|
1246
1246
|
return workflow;
|
|
1247
1247
|
}
|
|
1248
1248
|
|
|
1249
|
+
/**
|
|
1250
|
+
* Indicates if this is a multi-agent graph.
|
|
1251
|
+
* Override in MultiAgentGraph to return true.
|
|
1252
|
+
* Used to conditionally include agentId in RunStep for frontend rendering.
|
|
1253
|
+
*/
|
|
1254
|
+
protected isMultiAgentGraph(): boolean {
|
|
1255
|
+
return false;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
/**
|
|
1259
|
+
* Get the parallel group ID for an agent, if any.
|
|
1260
|
+
* Override in MultiAgentGraph to provide actual group IDs.
|
|
1261
|
+
* Group IDs are incrementing numbers (1, 2, 3...) reflecting execution order.
|
|
1262
|
+
* @param _agentId - The agent ID to look up
|
|
1263
|
+
* @returns undefined for StandardGraph (no parallel groups), or group number for MultiAgentGraph
|
|
1264
|
+
*/
|
|
1265
|
+
protected getParallelGroupIdForAgent(_agentId: string): number | undefined {
|
|
1266
|
+
return undefined;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1249
1269
|
/* Dispatchers */
|
|
1250
1270
|
|
|
1251
1271
|
/**
|
|
@@ -1286,13 +1306,21 @@ If I seem to be missing something we discussed earlier, just give me a quick rem
|
|
|
1286
1306
|
}
|
|
1287
1307
|
|
|
1288
1308
|
/**
|
|
1289
|
-
* Extract and
|
|
1309
|
+
* Extract agentId and parallelGroupId from metadata
|
|
1310
|
+
* Only set agentId for MultiAgentGraph (so frontend knows when to show agent labels)
|
|
1290
1311
|
*/
|
|
1291
1312
|
if (metadata) {
|
|
1292
1313
|
try {
|
|
1293
1314
|
const agentContext = this.getAgentContext(metadata);
|
|
1294
|
-
if (agentContext.agentId) {
|
|
1315
|
+
if (this.isMultiAgentGraph() && agentContext.agentId) {
|
|
1316
|
+
// Only include agentId for MultiAgentGraph - enables frontend to show agent labels
|
|
1295
1317
|
runStep.agentId = agentContext.agentId;
|
|
1318
|
+
// Set group ID if this agent is part of a parallel group
|
|
1319
|
+
// Group IDs are incrementing numbers (1, 2, 3...) reflecting execution order
|
|
1320
|
+
const groupId = this.getParallelGroupIdForAgent(agentContext.agentId);
|
|
1321
|
+
if (groupId != null) {
|
|
1322
|
+
runStep.groupId = groupId;
|
|
1323
|
+
}
|
|
1296
1324
|
}
|
|
1297
1325
|
} catch (_e) {
|
|
1298
1326
|
/** If we can't get agent context, that's okay - agentId remains undefined */
|
|
@@ -40,6 +40,17 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
40
40
|
private startingNodes: Set<string> = new Set();
|
|
41
41
|
private directEdges: t.GraphEdge[] = [];
|
|
42
42
|
private handoffEdges: t.GraphEdge[] = [];
|
|
43
|
+
/**
|
|
44
|
+
* Map of agentId to parallel group info.
|
|
45
|
+
* Contains groupId (incrementing number reflecting execution order) for agents in parallel groups.
|
|
46
|
+
* Sequential agents (not in any parallel group) have undefined entry.
|
|
47
|
+
*
|
|
48
|
+
* Example for: researcher -> [analyst1, analyst2, analyst3] -> summarizer
|
|
49
|
+
* - researcher: undefined (sequential, order 0)
|
|
50
|
+
* - analyst1, analyst2, analyst3: { groupId: 1 } (parallel group, order 1)
|
|
51
|
+
* - summarizer: undefined (sequential, order 2)
|
|
52
|
+
*/
|
|
53
|
+
private agentParallelGroups: Map<string, number> = new Map();
|
|
43
54
|
|
|
44
55
|
constructor(input: t.MultiAgentGraphInput) {
|
|
45
56
|
super(input);
|
|
@@ -99,6 +110,114 @@ export class MultiAgentGraph extends StandardGraph {
|
|
|
99
110
|
if (this.startingNodes.size === 0 && this.agentContexts.size > 0) {
|
|
100
111
|
this.startingNodes.add(this.agentContexts.keys().next().value!);
|
|
101
112
|
}
|
|
113
|
+
|
|
114
|
+
// Determine if graph has parallel execution capability
|
|
115
|
+
this.computeParallelCapability();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Compute parallel groups by traversing the graph in execution order.
|
|
120
|
+
* Assigns incrementing group IDs that reflect the sequential order of execution.
|
|
121
|
+
*
|
|
122
|
+
* For: researcher -> [analyst1, analyst2, analyst3] -> summarizer
|
|
123
|
+
* - researcher: no group (first sequential node)
|
|
124
|
+
* - analyst1, analyst2, analyst3: groupId 1 (first parallel group)
|
|
125
|
+
* - summarizer: no group (next sequential node)
|
|
126
|
+
*
|
|
127
|
+
* This allows frontend to render in order:
|
|
128
|
+
* Row 0: researcher
|
|
129
|
+
* Row 1: [analyst1, analyst2, analyst3] (grouped)
|
|
130
|
+
* Row 2: summarizer
|
|
131
|
+
*/
|
|
132
|
+
private computeParallelCapability(): void {
|
|
133
|
+
let groupCounter = 1; // Start at 1, 0 reserved for "no group"
|
|
134
|
+
|
|
135
|
+
// Check 1: Multiple starting nodes means parallel from the start (group 1)
|
|
136
|
+
if (this.startingNodes.size > 1) {
|
|
137
|
+
for (const agentId of this.startingNodes) {
|
|
138
|
+
this.agentParallelGroups.set(agentId, groupCounter);
|
|
139
|
+
}
|
|
140
|
+
groupCounter++;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check 2: Traverse direct edges in order to find fan-out patterns
|
|
144
|
+
// Build a simple execution order by following edges from starting nodes
|
|
145
|
+
const visited = new Set<string>();
|
|
146
|
+
const queue: string[] = [...this.startingNodes];
|
|
147
|
+
|
|
148
|
+
while (queue.length > 0) {
|
|
149
|
+
const current = queue.shift()!;
|
|
150
|
+
if (visited.has(current)) continue;
|
|
151
|
+
visited.add(current);
|
|
152
|
+
|
|
153
|
+
// Find direct edges from this node
|
|
154
|
+
for (const edge of this.directEdges) {
|
|
155
|
+
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
156
|
+
if (!sources.includes(current)) continue;
|
|
157
|
+
|
|
158
|
+
const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
159
|
+
|
|
160
|
+
// Fan-out: multiple destinations = parallel group
|
|
161
|
+
if (destinations.length > 1) {
|
|
162
|
+
for (const dest of destinations) {
|
|
163
|
+
// Only set if not already in a group (first group wins)
|
|
164
|
+
if (!this.agentParallelGroups.has(dest)) {
|
|
165
|
+
this.agentParallelGroups.set(dest, groupCounter);
|
|
166
|
+
}
|
|
167
|
+
if (!visited.has(dest)) {
|
|
168
|
+
queue.push(dest);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
groupCounter++;
|
|
172
|
+
} else {
|
|
173
|
+
// Single destination - add to queue for traversal
|
|
174
|
+
for (const dest of destinations) {
|
|
175
|
+
if (!visited.has(dest)) {
|
|
176
|
+
queue.push(dest);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Also follow handoff edges for traversal (but they don't create parallel groups)
|
|
183
|
+
for (const edge of this.handoffEdges) {
|
|
184
|
+
const sources = Array.isArray(edge.from) ? edge.from : [edge.from];
|
|
185
|
+
if (!sources.includes(current)) continue;
|
|
186
|
+
|
|
187
|
+
const destinations = Array.isArray(edge.to) ? edge.to : [edge.to];
|
|
188
|
+
for (const dest of destinations) {
|
|
189
|
+
if (!visited.has(dest)) {
|
|
190
|
+
queue.push(dest);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get the parallel group ID for an agent, if any.
|
|
199
|
+
* Returns undefined if the agent is not part of a parallel group.
|
|
200
|
+
* Group IDs are incrementing numbers reflecting execution order.
|
|
201
|
+
*/
|
|
202
|
+
getParallelGroupId(agentId: string): number | undefined {
|
|
203
|
+
return this.agentParallelGroups.get(agentId);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Override to indicate this is a multi-agent graph.
|
|
208
|
+
* Enables agentId to be included in RunStep for frontend agent labeling.
|
|
209
|
+
*/
|
|
210
|
+
protected override isMultiAgentGraph(): boolean {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Override base class method to provide parallel group IDs for run steps.
|
|
216
|
+
*/
|
|
217
|
+
protected override getParallelGroupIdForAgent(
|
|
218
|
+
agentId: string
|
|
219
|
+
): number | undefined {
|
|
220
|
+
return this.agentParallelGroups.get(agentId);
|
|
102
221
|
}
|
|
103
222
|
|
|
104
223
|
/**
|
package/src/messages/format.ts
CHANGED
|
@@ -636,7 +636,7 @@ export const labelContentByAgent = (
|
|
|
636
636
|
*/
|
|
637
637
|
export const formatAgentMessages = (
|
|
638
638
|
payload: TPayload,
|
|
639
|
-
indexTokenCountMap?: Record<number, number>,
|
|
639
|
+
indexTokenCountMap?: Record<number, number | undefined>,
|
|
640
640
|
tools?: Set<string>
|
|
641
641
|
): {
|
|
642
642
|
messages: Array<HumanMessage | AIMessage | SystemMessage | ToolMessage>;
|
|
@@ -648,7 +648,7 @@ export const formatAgentMessages = (
|
|
|
648
648
|
// If indexTokenCountMap is provided, create a new map to track the updated indices
|
|
649
649
|
const updatedIndexTokenCountMap: Record<number, number> = {};
|
|
650
650
|
// Keep track of the mapping from original payload indices to result indices
|
|
651
|
-
const indexMapping: Record<number, number[]> = {};
|
|
651
|
+
const indexMapping: Record<number, number[] | undefined> = {};
|
|
652
652
|
|
|
653
653
|
// Process messages with tool conversion if tools set is provided
|
|
654
654
|
for (let i = 0; i < payload.length; i++) {
|
|
@@ -143,18 +143,49 @@ async function testSequentialAgentChain() {
|
|
|
143
143
|
|
|
144
144
|
// Track agent progression
|
|
145
145
|
let currentAgent = '';
|
|
146
|
+
const startTime = Date.now();
|
|
147
|
+
let messageCount = 0;
|
|
146
148
|
|
|
147
|
-
// Create custom handlers
|
|
149
|
+
// Create custom handlers with extensive metadata logging
|
|
148
150
|
const customHandlers = {
|
|
149
151
|
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
150
|
-
[GraphEvents.CHAT_MODEL_END]:
|
|
152
|
+
[GraphEvents.CHAT_MODEL_END]: {
|
|
153
|
+
handle: (
|
|
154
|
+
_event: string,
|
|
155
|
+
_data: t.StreamEventData,
|
|
156
|
+
metadata?: Record<string, unknown>
|
|
157
|
+
): void => {
|
|
158
|
+
console.log('\n====== CHAT_MODEL_END METADATA ======');
|
|
159
|
+
console.dir(metadata, { depth: null });
|
|
160
|
+
const elapsed = Date.now() - startTime;
|
|
161
|
+
console.log(`⏱️ COMPLETED at ${elapsed}ms`);
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
[GraphEvents.CHAT_MODEL_START]: {
|
|
165
|
+
handle: (
|
|
166
|
+
_event: string,
|
|
167
|
+
_data: t.StreamEventData,
|
|
168
|
+
metadata?: Record<string, unknown>
|
|
169
|
+
): void => {
|
|
170
|
+
console.log('\n====== CHAT_MODEL_START METADATA ======');
|
|
171
|
+
console.dir(metadata, { depth: null });
|
|
172
|
+
const elapsed = Date.now() - startTime;
|
|
173
|
+
console.log(`⏱️ STARTED at ${elapsed}ms`);
|
|
174
|
+
},
|
|
175
|
+
},
|
|
151
176
|
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
152
177
|
[GraphEvents.ON_RUN_STEP]: {
|
|
153
178
|
handle: (
|
|
154
179
|
event: GraphEvents.ON_RUN_STEP,
|
|
155
|
-
data: t.StreamEventData
|
|
180
|
+
data: t.StreamEventData,
|
|
181
|
+
metadata?: Record<string, unknown>
|
|
156
182
|
): void => {
|
|
157
183
|
const runStepData = data as any;
|
|
184
|
+
console.log('\n====== ON_RUN_STEP ======');
|
|
185
|
+
console.log('DATA:');
|
|
186
|
+
console.dir(data, { depth: null });
|
|
187
|
+
console.log('METADATA:');
|
|
188
|
+
console.dir(metadata, { depth: null });
|
|
158
189
|
if (runStepData?.name) {
|
|
159
190
|
currentAgent = runStepData.name;
|
|
160
191
|
console.log(`\n→ ${currentAgent} is processing...`);
|
|
@@ -165,9 +196,15 @@ async function testSequentialAgentChain() {
|
|
|
165
196
|
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
166
197
|
handle: (
|
|
167
198
|
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
168
|
-
data: t.StreamEventData
|
|
199
|
+
data: t.StreamEventData,
|
|
200
|
+
metadata?: Record<string, unknown>
|
|
169
201
|
): void => {
|
|
170
202
|
const runStepData = data as any;
|
|
203
|
+
console.log('\n====== ON_RUN_STEP_COMPLETED ======');
|
|
204
|
+
console.log('DATA:');
|
|
205
|
+
console.dir(data, { depth: null });
|
|
206
|
+
console.log('METADATA:');
|
|
207
|
+
console.dir(metadata, { depth: null });
|
|
171
208
|
if (runStepData?.name) {
|
|
172
209
|
console.log(`✓ ${runStepData.name} completed`);
|
|
173
210
|
}
|
|
@@ -180,16 +217,32 @@ async function testSequentialAgentChain() {
|
|
|
180
217
|
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
181
218
|
handle: (
|
|
182
219
|
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
183
|
-
data: t.StreamEventData
|
|
220
|
+
data: t.StreamEventData,
|
|
221
|
+
metadata?: Record<string, unknown>
|
|
184
222
|
): void => {
|
|
223
|
+
console.log('\n====== ON_RUN_STEP_DELTA ======');
|
|
224
|
+
console.log('DATA:');
|
|
225
|
+
console.dir(data, { depth: null });
|
|
226
|
+
console.log('METADATA:');
|
|
227
|
+
console.dir(metadata, { depth: null });
|
|
185
228
|
aggregateContent({ event, data: data as t.RunStepDeltaEvent });
|
|
186
229
|
},
|
|
187
230
|
},
|
|
188
231
|
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
189
232
|
handle: (
|
|
190
233
|
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
191
|
-
data: t.StreamEventData
|
|
234
|
+
data: t.StreamEventData,
|
|
235
|
+
metadata?: Record<string, unknown>
|
|
192
236
|
): void => {
|
|
237
|
+
messageCount++;
|
|
238
|
+
// Only log first few message deltas to avoid spam
|
|
239
|
+
if (messageCount <= 3) {
|
|
240
|
+
console.log('\n====== ON_MESSAGE_DELTA ======');
|
|
241
|
+
console.log('DATA:');
|
|
242
|
+
console.dir(data, { depth: null });
|
|
243
|
+
console.log('METADATA:');
|
|
244
|
+
console.dir(metadata, { depth: null });
|
|
245
|
+
}
|
|
193
246
|
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
194
247
|
},
|
|
195
248
|
},
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
config();
|
|
3
|
+
|
|
4
|
+
import { HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
5
|
+
import type * as t from '@/types';
|
|
6
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
7
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
8
|
+
import { Providers, GraphEvents } from '@/common';
|
|
9
|
+
import { sleep } from '@/utils/run';
|
|
10
|
+
import { Run } from '@/run';
|
|
11
|
+
|
|
12
|
+
const conversationHistory: BaseMessage[] = [];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Example of parallel multi-agent system that starts with parallel execution immediately
|
|
16
|
+
*
|
|
17
|
+
* Graph structure:
|
|
18
|
+
* START -> [analyst1, analyst2] -> END (parallel from start, both run simultaneously)
|
|
19
|
+
*
|
|
20
|
+
* This demonstrates getting a parallel stream from the very beginning,
|
|
21
|
+
* with two agents running simultaneously. Useful for testing how different
|
|
22
|
+
* models respond to the same input.
|
|
23
|
+
*/
|
|
24
|
+
async function testParallelFromStart() {
|
|
25
|
+
console.log('Testing Parallel From Start Multi-Agent System...\n');
|
|
26
|
+
|
|
27
|
+
// Set up content aggregator
|
|
28
|
+
const { contentParts, aggregateContent, contentMetadataMap } =
|
|
29
|
+
createContentAggregator();
|
|
30
|
+
|
|
31
|
+
// Define two agents - both have NO incoming edges, so they run in parallel from the start
|
|
32
|
+
const agents: t.AgentInputs[] = [
|
|
33
|
+
{
|
|
34
|
+
agentId: 'analyst1',
|
|
35
|
+
provider: Providers.ANTHROPIC,
|
|
36
|
+
clientOptions: {
|
|
37
|
+
modelName: 'claude-haiku-4-5',
|
|
38
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
39
|
+
},
|
|
40
|
+
instructions: `You are a CREATIVE ANALYST. Analyze the user's query from a creative and innovative perspective. Focus on novel ideas, unconventional approaches, and imaginative possibilities. Keep your response concise (100-150 words). Start with "🎨 CREATIVE:"`,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
agentId: 'analyst2',
|
|
44
|
+
provider: Providers.ANTHROPIC,
|
|
45
|
+
clientOptions: {
|
|
46
|
+
modelName: 'claude-haiku-4-5',
|
|
47
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
48
|
+
},
|
|
49
|
+
instructions: `You are a PRACTICAL ANALYST. Analyze the user's query from a logical and practical perspective. Focus on feasibility, metrics, and actionable steps. Keep your response concise (100-150 words). Start with "📊 PRACTICAL:"`,
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
// No edges needed - both agents have no incoming edges, so both are start nodes
|
|
54
|
+
// They will run in parallel and end when both complete
|
|
55
|
+
const edges: t.GraphEdge[] = [];
|
|
56
|
+
|
|
57
|
+
// Track which agents are active and their timing
|
|
58
|
+
const activeAgents = new Set<string>();
|
|
59
|
+
const agentTimings: Record<string, { start?: number; end?: number }> = {};
|
|
60
|
+
const startTime = Date.now();
|
|
61
|
+
|
|
62
|
+
// Create custom handlers with extensive metadata logging
|
|
63
|
+
const customHandlers = {
|
|
64
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
65
|
+
[GraphEvents.CHAT_MODEL_END]: {
|
|
66
|
+
handle: (
|
|
67
|
+
_event: string,
|
|
68
|
+
_data: t.StreamEventData,
|
|
69
|
+
metadata?: Record<string, unknown>
|
|
70
|
+
): void => {
|
|
71
|
+
console.log('\n====== CHAT_MODEL_END METADATA ======');
|
|
72
|
+
console.dir(metadata, { depth: null });
|
|
73
|
+
const nodeName = metadata?.langgraph_node as string;
|
|
74
|
+
if (nodeName) {
|
|
75
|
+
const elapsed = Date.now() - startTime;
|
|
76
|
+
agentTimings[nodeName] = agentTimings[nodeName] || {};
|
|
77
|
+
agentTimings[nodeName].end = elapsed;
|
|
78
|
+
console.log(`⏱️ [${nodeName}] COMPLETED at ${elapsed}ms`);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
[GraphEvents.CHAT_MODEL_START]: {
|
|
83
|
+
handle: (
|
|
84
|
+
_event: string,
|
|
85
|
+
_data: t.StreamEventData,
|
|
86
|
+
metadata?: Record<string, unknown>
|
|
87
|
+
): void => {
|
|
88
|
+
console.log('\n====== CHAT_MODEL_START METADATA ======');
|
|
89
|
+
console.dir(metadata, { depth: null });
|
|
90
|
+
const nodeName = metadata?.langgraph_node as string;
|
|
91
|
+
if (nodeName) {
|
|
92
|
+
const elapsed = Date.now() - startTime;
|
|
93
|
+
agentTimings[nodeName] = agentTimings[nodeName] || {};
|
|
94
|
+
agentTimings[nodeName].start = elapsed;
|
|
95
|
+
activeAgents.add(nodeName);
|
|
96
|
+
console.log(`⏱️ [${nodeName}] STARTED at ${elapsed}ms`);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
101
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
102
|
+
handle: (
|
|
103
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
104
|
+
data: t.StreamEventData,
|
|
105
|
+
metadata?: Record<string, unknown>
|
|
106
|
+
): void => {
|
|
107
|
+
console.log('\n====== ON_RUN_STEP_COMPLETED ======');
|
|
108
|
+
console.log('DATA:');
|
|
109
|
+
console.dir(data, { depth: null });
|
|
110
|
+
console.log('METADATA:');
|
|
111
|
+
console.dir(metadata, { depth: null });
|
|
112
|
+
aggregateContent({
|
|
113
|
+
event,
|
|
114
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
119
|
+
handle: (
|
|
120
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
121
|
+
data: t.StreamEventData,
|
|
122
|
+
metadata?: Record<string, unknown>
|
|
123
|
+
): void => {
|
|
124
|
+
console.log('\n====== ON_RUN_STEP ======');
|
|
125
|
+
console.log('DATA:');
|
|
126
|
+
console.dir(data, { depth: null });
|
|
127
|
+
console.log('METADATA:');
|
|
128
|
+
console.dir(metadata, { depth: null });
|
|
129
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
133
|
+
handle: (
|
|
134
|
+
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
135
|
+
data: t.StreamEventData,
|
|
136
|
+
metadata?: Record<string, unknown>
|
|
137
|
+
): void => {
|
|
138
|
+
console.log('\n====== ON_RUN_STEP_DELTA ======');
|
|
139
|
+
console.log('DATA:');
|
|
140
|
+
console.dir(data, { depth: null });
|
|
141
|
+
console.log('METADATA:');
|
|
142
|
+
console.dir(metadata, { depth: null });
|
|
143
|
+
aggregateContent({ event, data: data as t.RunStepDeltaEvent });
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
147
|
+
handle: (
|
|
148
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
149
|
+
data: t.StreamEventData,
|
|
150
|
+
metadata?: Record<string, unknown>
|
|
151
|
+
): void => {
|
|
152
|
+
// Only log first delta per agent to avoid spam
|
|
153
|
+
console.log('\n====== ON_MESSAGE_DELTA ======');
|
|
154
|
+
console.log('DATA:');
|
|
155
|
+
console.dir(data, { depth: null });
|
|
156
|
+
console.log('METADATA:');
|
|
157
|
+
console.dir(metadata, { depth: null });
|
|
158
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Create multi-agent run configuration
|
|
164
|
+
const runConfig: t.RunConfig = {
|
|
165
|
+
runId: `parallel-start-${Date.now()}`,
|
|
166
|
+
graphConfig: {
|
|
167
|
+
type: 'multi-agent',
|
|
168
|
+
agents,
|
|
169
|
+
edges,
|
|
170
|
+
},
|
|
171
|
+
customHandlers,
|
|
172
|
+
returnContent: true,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
// Create and execute the run
|
|
177
|
+
const run = await Run.create(runConfig);
|
|
178
|
+
|
|
179
|
+
// Debug: Log the graph structure
|
|
180
|
+
console.log('=== DEBUG: Graph Structure ===');
|
|
181
|
+
const graph = (run as any).Graph;
|
|
182
|
+
console.log('Graph exists:', !!graph);
|
|
183
|
+
if (graph) {
|
|
184
|
+
console.log('Graph type:', graph.constructor.name);
|
|
185
|
+
console.log('AgentContexts exists:', !!graph.agentContexts);
|
|
186
|
+
if (graph.agentContexts) {
|
|
187
|
+
console.log('AgentContexts size:', graph.agentContexts.size);
|
|
188
|
+
for (const [agentId, context] of graph.agentContexts) {
|
|
189
|
+
console.log(`\nAgent: ${agentId}`);
|
|
190
|
+
console.log(
|
|
191
|
+
`Tools: ${context.tools?.map((t: any) => t.name || 'unnamed').join(', ') || 'none'}`
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
console.log('=== END DEBUG ===\n');
|
|
197
|
+
|
|
198
|
+
const userMessage = `What are the best approaches to learning a new programming language?`;
|
|
199
|
+
conversationHistory.push(new HumanMessage(userMessage));
|
|
200
|
+
|
|
201
|
+
console.log('Invoking parallel-from-start multi-agent graph...\n');
|
|
202
|
+
console.log('Both analyst1 and analyst2 should start simultaneously!\n');
|
|
203
|
+
|
|
204
|
+
const config = {
|
|
205
|
+
configurable: {
|
|
206
|
+
thread_id: 'parallel-start-conversation-1',
|
|
207
|
+
},
|
|
208
|
+
streamMode: 'values',
|
|
209
|
+
version: 'v2' as const,
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Process with streaming
|
|
213
|
+
const inputs = {
|
|
214
|
+
messages: conversationHistory,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const finalContentParts = await run.processStream(inputs, config);
|
|
218
|
+
const finalMessages = run.getRunMessages();
|
|
219
|
+
|
|
220
|
+
if (finalMessages) {
|
|
221
|
+
conversationHistory.push(...finalMessages);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
console.log('\n\n========== TIMING SUMMARY ==========');
|
|
225
|
+
for (const [agent, timing] of Object.entries(agentTimings)) {
|
|
226
|
+
const duration =
|
|
227
|
+
timing.end && timing.start ? timing.end - timing.start : 'N/A';
|
|
228
|
+
console.log(
|
|
229
|
+
`${agent}: started=${timing.start}ms, ended=${timing.end}ms, duration=${duration}ms`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check if parallel
|
|
234
|
+
const agents = Object.keys(agentTimings);
|
|
235
|
+
if (agents.length >= 2) {
|
|
236
|
+
const [a1, a2] = agents;
|
|
237
|
+
const t1 = agentTimings[a1];
|
|
238
|
+
const t2 = agentTimings[a2];
|
|
239
|
+
if (t1.start && t2.start && t1.end && t2.end) {
|
|
240
|
+
const overlap = Math.min(t1.end, t2.end) - Math.max(t1.start, t2.start);
|
|
241
|
+
if (overlap > 0) {
|
|
242
|
+
console.log(
|
|
243
|
+
`\n✅ PARALLEL EXECUTION CONFIRMED: ${overlap}ms overlap`
|
|
244
|
+
);
|
|
245
|
+
} else {
|
|
246
|
+
console.log(`\n❌ SEQUENTIAL EXECUTION: no overlap`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
console.log('====================================\n');
|
|
251
|
+
|
|
252
|
+
console.log('Final content parts:', contentParts.length, 'parts');
|
|
253
|
+
console.log('\n=== Content Parts (clean, no metadata) ===');
|
|
254
|
+
console.dir(contentParts, { depth: null });
|
|
255
|
+
console.log('\n=== Content Metadata Map (separate from content) ===');
|
|
256
|
+
console.dir(Object.fromEntries(contentMetadataMap), { depth: null });
|
|
257
|
+
|
|
258
|
+
await sleep(3000);
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error('Error in parallel-from-start multi-agent test:', error);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Run the test
|
|
265
|
+
testParallelFromStart();
|
|
@@ -200,18 +200,50 @@ async function testParallelMultiAgent() {
|
|
|
200
200
|
|
|
201
201
|
// Track which agents are active
|
|
202
202
|
const activeAgents = new Set<string>();
|
|
203
|
+
const startTime = Date.now();
|
|
204
|
+
let messageCount = 0;
|
|
203
205
|
|
|
204
|
-
// Create custom handlers
|
|
206
|
+
// Create custom handlers with extensive metadata logging
|
|
205
207
|
const customHandlers = {
|
|
206
208
|
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
207
|
-
[GraphEvents.CHAT_MODEL_END]:
|
|
209
|
+
[GraphEvents.CHAT_MODEL_END]: {
|
|
210
|
+
handle: (
|
|
211
|
+
_event: string,
|
|
212
|
+
_data: t.StreamEventData,
|
|
213
|
+
metadata?: Record<string, unknown>
|
|
214
|
+
): void => {
|
|
215
|
+
console.log('\n====== CHAT_MODEL_END METADATA ======');
|
|
216
|
+
console.dir(metadata, { depth: null });
|
|
217
|
+
const elapsed = Date.now() - startTime;
|
|
218
|
+
const nodeName = metadata?.langgraph_node as string;
|
|
219
|
+
console.log(`⏱️ [${nodeName || 'unknown'}] COMPLETED at ${elapsed}ms`);
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
[GraphEvents.CHAT_MODEL_START]: {
|
|
223
|
+
handle: (
|
|
224
|
+
_event: string,
|
|
225
|
+
_data: t.StreamEventData,
|
|
226
|
+
metadata?: Record<string, unknown>
|
|
227
|
+
): void => {
|
|
228
|
+
console.log('\n====== CHAT_MODEL_START METADATA ======');
|
|
229
|
+
console.dir(metadata, { depth: null });
|
|
230
|
+
const elapsed = Date.now() - startTime;
|
|
231
|
+
const nodeName = metadata?.langgraph_node as string;
|
|
232
|
+
console.log(`⏱️ [${nodeName || 'unknown'}] STARTED at ${elapsed}ms`);
|
|
233
|
+
},
|
|
234
|
+
},
|
|
208
235
|
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
209
236
|
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
210
237
|
handle: (
|
|
211
238
|
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
212
|
-
data: t.StreamEventData
|
|
239
|
+
data: t.StreamEventData,
|
|
240
|
+
metadata?: Record<string, unknown>
|
|
213
241
|
): void => {
|
|
214
|
-
console.log('====== ON_RUN_STEP_COMPLETED ======');
|
|
242
|
+
console.log('\n====== ON_RUN_STEP_COMPLETED ======');
|
|
243
|
+
console.log('DATA:');
|
|
244
|
+
console.dir(data, { depth: null });
|
|
245
|
+
console.log('METADATA:');
|
|
246
|
+
console.dir(metadata, { depth: null });
|
|
215
247
|
const runStepData = data as any;
|
|
216
248
|
if (runStepData?.name) {
|
|
217
249
|
activeAgents.delete(runStepData.name);
|
|
@@ -226,9 +258,14 @@ async function testParallelMultiAgent() {
|
|
|
226
258
|
[GraphEvents.ON_RUN_STEP]: {
|
|
227
259
|
handle: (
|
|
228
260
|
event: GraphEvents.ON_RUN_STEP,
|
|
229
|
-
data: t.StreamEventData
|
|
261
|
+
data: t.StreamEventData,
|
|
262
|
+
metadata?: Record<string, unknown>
|
|
230
263
|
): void => {
|
|
231
|
-
console.log('====== ON_RUN_STEP ======');
|
|
264
|
+
console.log('\n====== ON_RUN_STEP ======');
|
|
265
|
+
console.log('DATA:');
|
|
266
|
+
console.dir(data, { depth: null });
|
|
267
|
+
console.log('METADATA:');
|
|
268
|
+
console.dir(metadata, { depth: null });
|
|
232
269
|
const runStepData = data as any;
|
|
233
270
|
if (runStepData?.name) {
|
|
234
271
|
activeAgents.add(runStepData.name);
|
|
@@ -240,18 +277,32 @@ async function testParallelMultiAgent() {
|
|
|
240
277
|
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
|
241
278
|
handle: (
|
|
242
279
|
event: GraphEvents.ON_RUN_STEP_DELTA,
|
|
243
|
-
data: t.StreamEventData
|
|
280
|
+
data: t.StreamEventData,
|
|
281
|
+
metadata?: Record<string, unknown>
|
|
244
282
|
): void => {
|
|
283
|
+
console.log('\n====== ON_RUN_STEP_DELTA ======');
|
|
284
|
+
console.log('DATA:');
|
|
285
|
+
console.dir(data, { depth: null });
|
|
286
|
+
console.log('METADATA:');
|
|
287
|
+
console.dir(metadata, { depth: null });
|
|
245
288
|
aggregateContent({ event, data: data as t.RunStepDeltaEvent });
|
|
246
289
|
},
|
|
247
290
|
},
|
|
248
291
|
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
249
292
|
handle: (
|
|
250
293
|
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
251
|
-
data: t.StreamEventData
|
|
294
|
+
data: t.StreamEventData,
|
|
295
|
+
metadata?: Record<string, unknown>
|
|
252
296
|
): void => {
|
|
253
|
-
|
|
254
|
-
|
|
297
|
+
messageCount++;
|
|
298
|
+
// Only log first few message deltas per agent to avoid spam
|
|
299
|
+
if (messageCount <= 5) {
|
|
300
|
+
console.log('\n====== ON_MESSAGE_DELTA ======');
|
|
301
|
+
console.log('DATA:');
|
|
302
|
+
console.dir(data, { depth: null });
|
|
303
|
+
console.log('METADATA:');
|
|
304
|
+
console.dir(metadata, { depth: null });
|
|
305
|
+
}
|
|
255
306
|
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
256
307
|
},
|
|
257
308
|
},
|