@librechat/agents 3.0.53 → 3.0.54

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.
@@ -58,7 +58,7 @@ async function testParallelFromStart() {
58
58
  const agentTimings: Record<string, { start?: number; end?: number }> = {};
59
59
  const startTime = Date.now();
60
60
 
61
- // Create custom handlers
61
+ // Create custom handlers with extensive metadata logging
62
62
  const customHandlers = {
63
63
  [GraphEvents.TOOL_END]: new ToolEndHandler(),
64
64
  [GraphEvents.CHAT_MODEL_END]: {
@@ -67,6 +67,8 @@ async function testParallelFromStart() {
67
67
  _data: t.StreamEventData,
68
68
  metadata?: Record<string, unknown>
69
69
  ): void => {
70
+ console.log('\n====== CHAT_MODEL_END METADATA ======');
71
+ console.dir(metadata, { depth: null });
70
72
  const nodeName = metadata?.langgraph_node as string;
71
73
  if (nodeName) {
72
74
  const elapsed = Date.now() - startTime;
@@ -82,6 +84,8 @@ async function testParallelFromStart() {
82
84
  _data: t.StreamEventData,
83
85
  metadata?: Record<string, unknown>
84
86
  ): void => {
87
+ console.log('\n====== CHAT_MODEL_START METADATA ======');
88
+ console.dir(metadata, { depth: null });
85
89
  const nodeName = metadata?.langgraph_node as string;
86
90
  if (nodeName) {
87
91
  const elapsed = Date.now() - startTime;
@@ -96,8 +100,14 @@ async function testParallelFromStart() {
96
100
  [GraphEvents.ON_RUN_STEP_COMPLETED]: {
97
101
  handle: (
98
102
  event: GraphEvents.ON_RUN_STEP_COMPLETED,
99
- data: t.StreamEventData
103
+ data: t.StreamEventData,
104
+ metadata?: Record<string, unknown>
100
105
  ): void => {
106
+ console.log('\n====== ON_RUN_STEP_COMPLETED ======');
107
+ console.log('DATA:');
108
+ console.dir(data, { depth: null });
109
+ console.log('METADATA:');
110
+ console.dir(metadata, { depth: null });
101
111
  aggregateContent({
102
112
  event,
103
113
  data: data as unknown as { result: t.ToolEndEvent },
@@ -107,25 +117,43 @@ async function testParallelFromStart() {
107
117
  [GraphEvents.ON_RUN_STEP]: {
108
118
  handle: (
109
119
  event: GraphEvents.ON_RUN_STEP,
110
- data: t.StreamEventData
120
+ data: t.StreamEventData,
121
+ metadata?: Record<string, unknown>
111
122
  ): void => {
123
+ console.log('\n====== ON_RUN_STEP ======');
124
+ console.log('DATA:');
125
+ console.dir(data, { depth: null });
126
+ console.log('METADATA:');
127
+ console.dir(metadata, { depth: null });
112
128
  aggregateContent({ event, data: data as t.RunStep });
113
129
  },
114
130
  },
115
131
  [GraphEvents.ON_RUN_STEP_DELTA]: {
116
132
  handle: (
117
133
  event: GraphEvents.ON_RUN_STEP_DELTA,
118
- data: t.StreamEventData
134
+ data: t.StreamEventData,
135
+ metadata?: Record<string, unknown>
119
136
  ): void => {
137
+ console.log('\n====== ON_RUN_STEP_DELTA ======');
138
+ console.log('DATA:');
139
+ console.dir(data, { depth: null });
140
+ console.log('METADATA:');
141
+ console.dir(metadata, { depth: null });
120
142
  aggregateContent({ event, data: data as t.RunStepDeltaEvent });
121
143
  },
122
144
  },
123
145
  [GraphEvents.ON_MESSAGE_DELTA]: {
124
146
  handle: (
125
147
  event: GraphEvents.ON_MESSAGE_DELTA,
126
- data: t.StreamEventData
148
+ data: t.StreamEventData,
149
+ metadata?: Record<string, unknown>
127
150
  ): void => {
151
+ // Only log first delta per agent to avoid spam
152
+ console.log('\n====== ON_MESSAGE_DELTA ======');
153
+ console.log('DATA:');
128
154
  console.dir(data, { depth: null });
155
+ console.log('METADATA:');
156
+ console.dir(metadata, { depth: null });
129
157
  aggregateContent({ event, data: data as t.MessageDeltaEvent });
130
158
  },
131
159
  },
@@ -222,6 +250,9 @@ async function testParallelFromStart() {
222
250
 
223
251
  console.log('Final content parts:', contentParts.length, 'parts');
224
252
  console.dir(contentParts, { depth: null });
253
+
254
+ // groupId on each content part allows frontend to derive boundaries if needed
255
+
225
256
  await sleep(3000);
226
257
  } catch (error) {
227
258
  console.error('Error in parallel-from-start multi-agent test:', error);
@@ -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]: new ModelEndHandler(),
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
- console.log('====== ON_MESSAGE_DELTA ======');
254
- console.dir(data, { depth: null });
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
  },
@@ -0,0 +1,274 @@
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 } from '@/events';
8
+ import { Providers, GraphEvents } from '@/common';
9
+ import { sleep } from '@/utils/run';
10
+ import { Run } from '@/run';
11
+ import { Calculator } from '@/tools/Calculator';
12
+
13
+ const conversationHistory: BaseMessage[] = [];
14
+
15
+ /**
16
+ * Test ASYMMETRIC parallel execution:
17
+ * - agent1: NO tools (will finish quickly in step 1)
18
+ * - agent2: HAS tools (will go step 1 → step 2 → step 3)
19
+ *
20
+ * This tests whether langgraph_step can still detect parallel execution
21
+ * when agents have different tool-calling patterns.
22
+ */
23
+ async function testAsymmetricParallelTools() {
24
+ console.log(
25
+ 'Testing ASYMMETRIC Parallel Agents (one with tools, one without)...\n'
26
+ );
27
+
28
+ const { contentParts, aggregateContent } = createContentAggregator();
29
+
30
+ // Track metadata for analysis
31
+ const metadataLog: Array<{
32
+ event: string;
33
+ langgraph_step: number;
34
+ langgraph_node: string;
35
+ timestamp: number;
36
+ }> = [];
37
+ const startTime = Date.now();
38
+
39
+ // Define two agents - one WITH tools, one WITHOUT
40
+ const agents: t.AgentInputs[] = [
41
+ {
42
+ agentId: 'simple_agent',
43
+ provider: Providers.ANTHROPIC,
44
+ clientOptions: {
45
+ modelName: 'claude-haiku-4-5',
46
+ apiKey: process.env.ANTHROPIC_API_KEY,
47
+ },
48
+ // NO TOOLS - will complete in single step
49
+ instructions: `You are a simple assistant. Just answer the question directly in 1-2 sentences. Start with "🗣️ SIMPLE:". Do NOT try to use any tools.`,
50
+ },
51
+ {
52
+ agentId: 'math_agent',
53
+ provider: Providers.ANTHROPIC,
54
+ clientOptions: {
55
+ modelName: 'claude-haiku-4-5',
56
+ apiKey: process.env.ANTHROPIC_API_KEY,
57
+ },
58
+ tools: [new Calculator()],
59
+ instructions: `You are a MATH SPECIALIST. ALWAYS use the calculator tool to perform calculations, even simple ones. Start your response with "🧮 MATH:". Keep your response concise.`,
60
+ },
61
+ ];
62
+
63
+ // No edges - both run in parallel from start
64
+ const edges: t.GraphEdge[] = [];
65
+
66
+ const agentTimings: Record<string, { start?: number; end?: number }> = {};
67
+
68
+ // Helper to log metadata
69
+ const logMetadata = (
70
+ eventName: string,
71
+ metadata?: Record<string, unknown>
72
+ ) => {
73
+ if (metadata) {
74
+ const entry = {
75
+ event: eventName,
76
+ langgraph_step: metadata.langgraph_step as number,
77
+ langgraph_node: metadata.langgraph_node as string,
78
+ timestamp: Date.now() - startTime,
79
+ };
80
+ metadataLog.push(entry);
81
+ console.log(
82
+ `📊 [${entry.timestamp}ms] ${eventName}: step=${entry.langgraph_step}, node=${entry.langgraph_node}`
83
+ );
84
+ }
85
+ };
86
+
87
+ const customHandlers = {
88
+ [GraphEvents.TOOL_END]: {
89
+ handle: (
90
+ _event: string,
91
+ data: t.StreamEventData,
92
+ metadata?: Record<string, unknown>
93
+ ): void => {
94
+ console.log('\n====== TOOL_END ======');
95
+ logMetadata('TOOL_END', metadata);
96
+ },
97
+ },
98
+ [GraphEvents.TOOL_START]: {
99
+ handle: (
100
+ _event: string,
101
+ _data: t.StreamEventData,
102
+ metadata?: Record<string, unknown>
103
+ ): void => {
104
+ console.log('\n====== TOOL_START ======');
105
+ logMetadata('TOOL_START', metadata);
106
+ },
107
+ },
108
+ [GraphEvents.CHAT_MODEL_END]: {
109
+ handle: (
110
+ _event: string,
111
+ _data: t.StreamEventData,
112
+ metadata?: Record<string, unknown>
113
+ ): void => {
114
+ console.log('\n====== CHAT_MODEL_END ======');
115
+ logMetadata('CHAT_MODEL_END', metadata);
116
+ const nodeName = metadata?.langgraph_node as string;
117
+ if (nodeName) {
118
+ const elapsed = Date.now() - startTime;
119
+ agentTimings[nodeName] = agentTimings[nodeName] || {};
120
+ agentTimings[nodeName].end = elapsed;
121
+ }
122
+ },
123
+ },
124
+ [GraphEvents.CHAT_MODEL_START]: {
125
+ handle: (
126
+ _event: string,
127
+ _data: t.StreamEventData,
128
+ metadata?: Record<string, unknown>
129
+ ): void => {
130
+ console.log('\n====== CHAT_MODEL_START ======');
131
+ logMetadata('CHAT_MODEL_START', metadata);
132
+ const nodeName = metadata?.langgraph_node as string;
133
+ if (nodeName) {
134
+ const elapsed = Date.now() - startTime;
135
+ agentTimings[nodeName] = agentTimings[nodeName] || {};
136
+ if (!agentTimings[nodeName].start) {
137
+ agentTimings[nodeName].start = elapsed;
138
+ }
139
+ }
140
+ },
141
+ },
142
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
143
+ [GraphEvents.ON_RUN_STEP]: {
144
+ handle: (
145
+ event: GraphEvents.ON_RUN_STEP,
146
+ data: t.StreamEventData,
147
+ metadata?: Record<string, unknown>
148
+ ): void => {
149
+ console.log('\n====== ON_RUN_STEP ======');
150
+ logMetadata('ON_RUN_STEP', metadata);
151
+ aggregateContent({ event, data: data as t.RunStep });
152
+ },
153
+ },
154
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
155
+ handle: (
156
+ event: GraphEvents.ON_RUN_STEP_DELTA,
157
+ data: t.StreamEventData,
158
+ metadata?: Record<string, unknown>
159
+ ): void => {
160
+ // Don't log these to reduce noise
161
+ aggregateContent({ event, data: data as t.RunStepDeltaEvent });
162
+ },
163
+ },
164
+ [GraphEvents.ON_MESSAGE_DELTA]: {
165
+ handle: (
166
+ event: GraphEvents.ON_MESSAGE_DELTA,
167
+ data: t.StreamEventData,
168
+ metadata?: Record<string, unknown>
169
+ ): void => {
170
+ // Don't log these to reduce noise
171
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
172
+ },
173
+ },
174
+ };
175
+
176
+ const runConfig: t.RunConfig = {
177
+ runId: `asymmetric-parallel-${Date.now()}`,
178
+ graphConfig: {
179
+ type: 'multi-agent',
180
+ agents,
181
+ edges,
182
+ },
183
+ customHandlers,
184
+ returnContent: true,
185
+ };
186
+
187
+ try {
188
+ const run = await Run.create(runConfig);
189
+
190
+ // Ask a question that will trigger only the math agent to use tools
191
+ const userMessage = `What is 42 * 17?`;
192
+
193
+ conversationHistory.push(new HumanMessage(userMessage));
194
+
195
+ console.log('User message:', userMessage);
196
+ console.log('\nExpected behavior:');
197
+ console.log(' - simple_agent: Step 1 only (no tools)');
198
+ console.log(' - math_agent: Step 1 → Step 2 (tool) → Step 3 (response)');
199
+ console.log('\n');
200
+
201
+ const config = {
202
+ configurable: {
203
+ thread_id: 'asymmetric-test-1',
204
+ },
205
+ streamMode: 'values',
206
+ version: 'v2' as const,
207
+ };
208
+
209
+ const inputs = {
210
+ messages: conversationHistory,
211
+ };
212
+
213
+ await run.processStream(inputs, config);
214
+
215
+ // Analysis
216
+ console.log('\n\n========== METADATA ANALYSIS ==========');
217
+ console.log('\nAll events by step and node:');
218
+ console.table(metadataLog);
219
+
220
+ // Group by step
221
+ const stepGroups = new Map<number, Map<string, string[]>>();
222
+ for (const entry of metadataLog) {
223
+ if (!stepGroups.has(entry.langgraph_step)) {
224
+ stepGroups.set(entry.langgraph_step, new Map());
225
+ }
226
+ const nodeMap = stepGroups.get(entry.langgraph_step)!;
227
+ if (!nodeMap.has(entry.langgraph_node)) {
228
+ nodeMap.set(entry.langgraph_node, []);
229
+ }
230
+ nodeMap.get(entry.langgraph_node)!.push(entry.event);
231
+ }
232
+
233
+ console.log('\n\n========== STEP BREAKDOWN ==========');
234
+ for (const [step, nodeMap] of stepGroups) {
235
+ console.log(`\nStep ${step}:`);
236
+ for (const [node, events] of nodeMap) {
237
+ console.log(` ${node}: ${events.join(', ')}`);
238
+ }
239
+ console.log(` → ${nodeMap.size} unique node(s) at this step`);
240
+ }
241
+
242
+ console.log('\n\n========== PARALLEL DETECTION CHALLENGE ==========');
243
+ console.log('\nAt which steps can we detect parallel execution?');
244
+ for (const [step, nodeMap] of stepGroups) {
245
+ if (nodeMap.size > 1) {
246
+ console.log(
247
+ ` ✅ Step ${step}: ${nodeMap.size} agents detected - PARALLEL`
248
+ );
249
+ } else {
250
+ const [nodeName] = nodeMap.keys();
251
+ console.log(
252
+ ` ⚠️ Step ${step}: Only 1 agent (${nodeName}) - looks sequential!`
253
+ );
254
+ }
255
+ }
256
+
257
+ console.log('\n\n========== KEY INSIGHT ==========');
258
+ console.log(
259
+ 'If we only look at step 2 or 3, we miss the parallel context!'
260
+ );
261
+ console.log(
262
+ 'We need to detect parallelism EARLY (at step 1) and carry that forward.'
263
+ );
264
+
265
+ console.log('\n\nFinal content parts:');
266
+ console.dir(contentParts, { depth: null });
267
+
268
+ await sleep(2000);
269
+ } catch (error) {
270
+ console.error('Error:', error);
271
+ }
272
+ }
273
+
274
+ testAsymmetricParallelTools();