@librechat/agents 3.0.52 → 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.
@@ -0,0 +1,240 @@
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 { Providers, GraphEvents } from '@/common';
8
+ import { sleep } from '@/utils/run';
9
+ import { Run } from '@/run';
10
+ import { Calculator } from '@/tools/Calculator';
11
+
12
+ const conversationHistory: BaseMessage[] = [];
13
+
14
+ /**
15
+ * Dump ALL metadata fields to understand what LangSmith uses
16
+ * to detect parallel execution
17
+ */
18
+ async function testFullMetadata() {
19
+ console.log('Dumping FULL metadata to find parallel detection fields...\n');
20
+
21
+ const { contentParts, aggregateContent } = createContentAggregator();
22
+
23
+ // Collect ALL metadata from all events
24
+ const allMetadata: Array<{
25
+ event: string;
26
+ timestamp: number;
27
+ metadata: Record<string, unknown>;
28
+ }> = [];
29
+ const startTime = Date.now();
30
+
31
+ const agents: t.AgentInputs[] = [
32
+ {
33
+ agentId: 'agent_a',
34
+ provider: Providers.ANTHROPIC,
35
+ clientOptions: {
36
+ modelName: 'claude-haiku-4-5',
37
+ apiKey: process.env.ANTHROPIC_API_KEY,
38
+ },
39
+ instructions: `You are Agent A. Just say "Hello from A" in one sentence.`,
40
+ },
41
+ {
42
+ agentId: 'agent_b',
43
+ provider: Providers.ANTHROPIC,
44
+ clientOptions: {
45
+ modelName: 'claude-haiku-4-5',
46
+ apiKey: process.env.ANTHROPIC_API_KEY,
47
+ },
48
+ tools: [new Calculator()],
49
+ instructions: `You are Agent B. Calculate 2+2 using the calculator tool.`,
50
+ },
51
+ ];
52
+
53
+ const edges: t.GraphEdge[] = [];
54
+
55
+ const captureMetadata = (
56
+ eventName: string,
57
+ metadata?: Record<string, unknown>
58
+ ) => {
59
+ if (metadata) {
60
+ allMetadata.push({
61
+ event: eventName,
62
+ timestamp: Date.now() - startTime,
63
+ metadata: { ...metadata },
64
+ });
65
+ }
66
+ };
67
+
68
+ const customHandlers = {
69
+ [GraphEvents.TOOL_END]: {
70
+ handle: (
71
+ _event: string,
72
+ _data: t.StreamEventData,
73
+ metadata?: Record<string, unknown>
74
+ ): void => {
75
+ captureMetadata('TOOL_END', metadata);
76
+ },
77
+ },
78
+ [GraphEvents.TOOL_START]: {
79
+ handle: (
80
+ _event: string,
81
+ _data: t.StreamEventData,
82
+ metadata?: Record<string, unknown>
83
+ ): void => {
84
+ captureMetadata('TOOL_START', metadata);
85
+ },
86
+ },
87
+ [GraphEvents.CHAT_MODEL_END]: {
88
+ handle: (
89
+ _event: string,
90
+ _data: t.StreamEventData,
91
+ metadata?: Record<string, unknown>
92
+ ): void => {
93
+ captureMetadata('CHAT_MODEL_END', metadata);
94
+ },
95
+ },
96
+ [GraphEvents.CHAT_MODEL_START]: {
97
+ handle: (
98
+ _event: string,
99
+ _data: t.StreamEventData,
100
+ metadata?: Record<string, unknown>
101
+ ): void => {
102
+ captureMetadata('CHAT_MODEL_START', metadata);
103
+ },
104
+ },
105
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
106
+ [GraphEvents.ON_RUN_STEP]: {
107
+ handle: (
108
+ event: GraphEvents.ON_RUN_STEP,
109
+ data: t.StreamEventData,
110
+ metadata?: Record<string, unknown>
111
+ ): void => {
112
+ captureMetadata('ON_RUN_STEP', metadata);
113
+ const runStep = data as t.RunStep;
114
+ console.log(
115
+ `\n🔍 ON_RUN_STEP: agentId=${runStep.agentId}, groupId=${runStep.groupId}`
116
+ );
117
+ aggregateContent({ event, data: runStep });
118
+ },
119
+ },
120
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
121
+ handle: (
122
+ event: GraphEvents.ON_RUN_STEP_DELTA,
123
+ data: t.StreamEventData,
124
+ metadata?: Record<string, unknown>
125
+ ): void => {
126
+ captureMetadata('ON_RUN_STEP_DELTA', metadata);
127
+ aggregateContent({ event, data: data as t.RunStepDeltaEvent });
128
+ },
129
+ },
130
+ [GraphEvents.ON_MESSAGE_DELTA]: {
131
+ handle: (
132
+ event: GraphEvents.ON_MESSAGE_DELTA,
133
+ data: t.StreamEventData,
134
+ metadata?: Record<string, unknown>
135
+ ): void => {
136
+ captureMetadata('ON_MESSAGE_DELTA', metadata);
137
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
138
+ },
139
+ },
140
+ };
141
+
142
+ const runConfig: t.RunConfig = {
143
+ runId: `full-metadata-${Date.now()}`,
144
+ graphConfig: {
145
+ type: 'multi-agent',
146
+ agents,
147
+ edges,
148
+ },
149
+ customHandlers,
150
+ returnContent: true,
151
+ };
152
+
153
+ try {
154
+ const run = await Run.create(runConfig);
155
+
156
+ const userMessage = `Hi, and calculate 2+2`;
157
+ conversationHistory.push(new HumanMessage(userMessage));
158
+
159
+ const config = {
160
+ configurable: {
161
+ thread_id: 'full-metadata-test-1',
162
+ },
163
+ streamMode: 'values',
164
+ version: 'v2' as const,
165
+ };
166
+
167
+ await run.processStream({ messages: conversationHistory }, config);
168
+
169
+ // Analysis - find ALL unique metadata keys
170
+ console.log('\n\n========== ALL UNIQUE METADATA KEYS ==========\n');
171
+ const allKeys = new Set<string>();
172
+ for (const entry of allMetadata) {
173
+ for (const key of Object.keys(entry.metadata)) {
174
+ allKeys.add(key);
175
+ }
176
+ }
177
+ console.log('Keys found:', [...allKeys].sort());
178
+
179
+ // Print first CHAT_MODEL_START for each agent with FULL metadata
180
+ console.log(
181
+ '\n\n========== FULL METADATA FROM CHAT_MODEL_START ==========\n'
182
+ );
183
+ const seenAgents = new Set<string>();
184
+ for (const entry of allMetadata) {
185
+ if (entry.event === 'CHAT_MODEL_START') {
186
+ const node = entry.metadata.langgraph_node as string;
187
+ if (!seenAgents.has(node)) {
188
+ seenAgents.add(node);
189
+ console.log(`\n--- ${node} ---`);
190
+ console.dir(entry.metadata, { depth: null });
191
+ }
192
+ }
193
+ }
194
+
195
+ // Look specifically at checkpoint_ns and __pregel_task_id
196
+ console.log('\n\n========== POTENTIAL PARALLEL INDICATORS ==========\n');
197
+ console.log(
198
+ 'Comparing checkpoint_ns and __pregel_task_id across agents:\n'
199
+ );
200
+
201
+ const agentMetadataMap = new Map<string, Record<string, unknown>>();
202
+ for (const entry of allMetadata) {
203
+ if (entry.event === 'CHAT_MODEL_START') {
204
+ const node = entry.metadata.langgraph_node as string;
205
+ if (!agentMetadataMap.has(node)) {
206
+ agentMetadataMap.set(node, entry.metadata);
207
+ }
208
+ }
209
+ }
210
+
211
+ for (const [node, meta] of agentMetadataMap) {
212
+ console.log(`${node}:`);
213
+ console.log(` langgraph_step: ${meta.langgraph_step}`);
214
+ console.log(
215
+ ` langgraph_triggers: ${JSON.stringify(meta.langgraph_triggers)}`
216
+ );
217
+ console.log(` checkpoint_ns: ${meta.checkpoint_ns}`);
218
+ console.log(` __pregel_task_id: ${meta.__pregel_task_id}`);
219
+ console.log(` langgraph_path: ${JSON.stringify(meta.langgraph_path)}`);
220
+ console.log(` langgraph_checkpoint_ns: ${meta.langgraph_checkpoint_ns}`);
221
+ console.log();
222
+ }
223
+
224
+ // Check langgraph_triggers specifically
225
+ console.log('\n========== LANGGRAPH_TRIGGERS ANALYSIS ==========\n');
226
+ for (const [node, meta] of agentMetadataMap) {
227
+ const triggers = meta.langgraph_triggers as string[];
228
+ console.log(`${node}: triggers = ${JSON.stringify(triggers)}`);
229
+ }
230
+
231
+ console.log('\n\nFinal content parts:');
232
+ console.dir(contentParts, { depth: null });
233
+
234
+ await sleep(1000);
235
+ } catch (error) {
236
+ console.error('Error:', error);
237
+ }
238
+ }
239
+
240
+ testFullMetadata();
@@ -0,0 +1,340 @@
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
+ import { Tool } from '@langchain/core/tools';
13
+
14
+ const conversationHistory: BaseMessage[] = [];
15
+
16
+ // Create a simple "WordCount" tool for the second agent
17
+ class WordCounter extends Tool {
18
+ static lc_name(): string {
19
+ return 'WordCounter';
20
+ }
21
+
22
+ name = 'word_counter';
23
+
24
+ description =
25
+ 'Useful for counting the number of words, characters, and sentences in a given text. Input should be the text to analyze.';
26
+
27
+ async _call(input: string): Promise<string> {
28
+ const words = input.trim().split(/\s+/).filter(Boolean).length;
29
+ const characters = input.length;
30
+ const sentences = input.split(/[.!?]+/).filter(Boolean).length;
31
+ return JSON.stringify({ words, characters, sentences });
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Example of parallel multi-agent system with tools
37
+ *
38
+ * Graph structure:
39
+ * START -> [math_agent, text_agent] -> END (parallel from start, both run simultaneously)
40
+ *
41
+ * Both agents have tools they can use. This tests how langgraph_step behaves
42
+ * when parallel agents call tools.
43
+ */
44
+ async function testParallelWithTools() {
45
+ console.log('Testing Parallel From Start Multi-Agent System WITH TOOLS...\n');
46
+
47
+ // Set up content aggregator
48
+ const { contentParts, aggregateContent } = createContentAggregator();
49
+
50
+ // Track metadata for analysis
51
+ const metadataLog: Array<{
52
+ event: string;
53
+ langgraph_step: number;
54
+ langgraph_node: string;
55
+ timestamp: number;
56
+ }> = [];
57
+ const startTime = Date.now();
58
+
59
+ // Define two agents with different tools
60
+ const agents: t.AgentInputs[] = [
61
+ {
62
+ agentId: 'math_agent',
63
+ provider: Providers.ANTHROPIC,
64
+ clientOptions: {
65
+ modelName: 'claude-haiku-4-5',
66
+ apiKey: process.env.ANTHROPIC_API_KEY,
67
+ },
68
+ tools: [new Calculator()],
69
+ instructions: `You are a MATH SPECIALIST. When asked about numbers or calculations, ALWAYS use the calculator tool to perform the calculation. Start your response with "🧮 MATH:". Keep your response concise.`,
70
+ },
71
+ {
72
+ agentId: 'text_agent',
73
+ provider: Providers.ANTHROPIC,
74
+ clientOptions: {
75
+ modelName: 'claude-haiku-4-5',
76
+ apiKey: process.env.ANTHROPIC_API_KEY,
77
+ },
78
+ tools: [new WordCounter()],
79
+ instructions: `You are a TEXT ANALYST. When asked about text or content, ALWAYS use the word_counter tool to analyze the text. Start your response with "📝 TEXT:". Keep your response concise.`,
80
+ },
81
+ ];
82
+
83
+ // No edges - both agents run in parallel from start
84
+ const edges: t.GraphEdge[] = [];
85
+
86
+ // Track active agents and timing
87
+ const activeAgents = new Set<string>();
88
+ const agentTimings: Record<string, { start?: number; end?: number }> = {};
89
+
90
+ // Helper to log metadata
91
+ const logMetadata = (
92
+ eventName: string,
93
+ metadata?: Record<string, unknown>
94
+ ) => {
95
+ if (metadata) {
96
+ const entry = {
97
+ event: eventName,
98
+ langgraph_step: metadata.langgraph_step as number,
99
+ langgraph_node: metadata.langgraph_node as string,
100
+ timestamp: Date.now() - startTime,
101
+ };
102
+ metadataLog.push(entry);
103
+ console.log(
104
+ `📊 [${entry.timestamp}ms] ${eventName}: step=${entry.langgraph_step}, node=${entry.langgraph_node}`
105
+ );
106
+ }
107
+ };
108
+
109
+ // Create custom handlers with metadata logging
110
+ const customHandlers = {
111
+ [GraphEvents.TOOL_END]: {
112
+ handle: (
113
+ _event: string,
114
+ data: t.StreamEventData,
115
+ metadata?: Record<string, unknown>
116
+ ): void => {
117
+ console.log('\n====== TOOL_END ======');
118
+ logMetadata('TOOL_END', metadata);
119
+ console.dir(data, { depth: null });
120
+ },
121
+ },
122
+ [GraphEvents.TOOL_START]: {
123
+ handle: (
124
+ _event: string,
125
+ data: t.StreamEventData,
126
+ metadata?: Record<string, unknown>
127
+ ): void => {
128
+ console.log('\n====== TOOL_START ======');
129
+ logMetadata('TOOL_START', metadata);
130
+ },
131
+ },
132
+ [GraphEvents.CHAT_MODEL_END]: {
133
+ handle: (
134
+ _event: string,
135
+ _data: t.StreamEventData,
136
+ metadata?: Record<string, unknown>
137
+ ): void => {
138
+ console.log('\n====== CHAT_MODEL_END ======');
139
+ logMetadata('CHAT_MODEL_END', metadata);
140
+ const nodeName = metadata?.langgraph_node as string;
141
+ if (nodeName) {
142
+ const elapsed = Date.now() - startTime;
143
+ agentTimings[nodeName] = agentTimings[nodeName] || {};
144
+ agentTimings[nodeName].end = elapsed;
145
+ }
146
+ },
147
+ },
148
+ [GraphEvents.CHAT_MODEL_START]: {
149
+ handle: (
150
+ _event: string,
151
+ _data: t.StreamEventData,
152
+ metadata?: Record<string, unknown>
153
+ ): void => {
154
+ console.log('\n====== CHAT_MODEL_START ======');
155
+ logMetadata('CHAT_MODEL_START', metadata);
156
+ const nodeName = metadata?.langgraph_node as string;
157
+ if (nodeName) {
158
+ const elapsed = Date.now() - startTime;
159
+ agentTimings[nodeName] = agentTimings[nodeName] || {};
160
+ if (!agentTimings[nodeName].start) {
161
+ agentTimings[nodeName].start = elapsed;
162
+ }
163
+ activeAgents.add(nodeName);
164
+ }
165
+ },
166
+ },
167
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
168
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
169
+ handle: (
170
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
171
+ data: t.StreamEventData,
172
+ metadata?: Record<string, unknown>
173
+ ): void => {
174
+ console.log('\n====== ON_RUN_STEP_COMPLETED ======');
175
+ logMetadata('ON_RUN_STEP_COMPLETED', metadata);
176
+ aggregateContent({
177
+ event,
178
+ data: data as unknown as { result: t.ToolEndEvent },
179
+ });
180
+ },
181
+ },
182
+ [GraphEvents.ON_RUN_STEP]: {
183
+ handle: (
184
+ event: GraphEvents.ON_RUN_STEP,
185
+ data: t.StreamEventData,
186
+ metadata?: Record<string, unknown>
187
+ ): void => {
188
+ console.log('\n====== ON_RUN_STEP ======');
189
+ logMetadata('ON_RUN_STEP', metadata);
190
+ aggregateContent({ event, data: data as t.RunStep });
191
+ },
192
+ },
193
+ [GraphEvents.ON_RUN_STEP_DELTA]: {
194
+ handle: (
195
+ event: GraphEvents.ON_RUN_STEP_DELTA,
196
+ data: t.StreamEventData,
197
+ metadata?: Record<string, unknown>
198
+ ): void => {
199
+ logMetadata('ON_RUN_STEP_DELTA', metadata);
200
+ aggregateContent({ event, data: data as t.RunStepDeltaEvent });
201
+ },
202
+ },
203
+ [GraphEvents.ON_MESSAGE_DELTA]: {
204
+ handle: (
205
+ event: GraphEvents.ON_MESSAGE_DELTA,
206
+ data: t.StreamEventData,
207
+ metadata?: Record<string, unknown>
208
+ ): void => {
209
+ logMetadata('ON_MESSAGE_DELTA', metadata);
210
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
211
+ },
212
+ },
213
+ };
214
+
215
+ // Create multi-agent run configuration
216
+ const runConfig: t.RunConfig = {
217
+ runId: `parallel-tools-${Date.now()}`,
218
+ graphConfig: {
219
+ type: 'multi-agent',
220
+ agents,
221
+ edges,
222
+ },
223
+ customHandlers,
224
+ returnContent: true,
225
+ };
226
+
227
+ try {
228
+ // Create and execute the run
229
+ const run = await Run.create(runConfig);
230
+
231
+ // User message that should trigger both agents to use their tools
232
+ const userMessage = `I have two tasks:
233
+ 1. Calculate 1234 + 5678 * 2
234
+ 2. Analyze this text: "The quick brown fox jumps over the lazy dog"
235
+
236
+ Please help with both!`;
237
+
238
+ conversationHistory.push(new HumanMessage(userMessage));
239
+
240
+ console.log(
241
+ 'Invoking parallel-from-start multi-agent graph WITH TOOLS...\n'
242
+ );
243
+ console.log(
244
+ 'Both math_agent and text_agent should start simultaneously and use tools!\n'
245
+ );
246
+
247
+ const config = {
248
+ configurable: {
249
+ thread_id: 'parallel-tools-test-1',
250
+ },
251
+ streamMode: 'values',
252
+ version: 'v2' as const,
253
+ };
254
+
255
+ // Process with streaming
256
+ const inputs = {
257
+ messages: conversationHistory,
258
+ };
259
+
260
+ const finalContentParts = await run.processStream(inputs, config);
261
+ const finalMessages = run.getRunMessages();
262
+
263
+ if (finalMessages) {
264
+ conversationHistory.push(...finalMessages);
265
+ }
266
+
267
+ // Analysis output
268
+ console.log('\n\n========== METADATA ANALYSIS ==========');
269
+ console.log('\nAll metadata entries by timestamp:');
270
+ console.table(metadataLog);
271
+
272
+ // Group by langgraph_step
273
+ const stepGroups = new Map<number, typeof metadataLog>();
274
+ for (const entry of metadataLog) {
275
+ if (!stepGroups.has(entry.langgraph_step)) {
276
+ stepGroups.set(entry.langgraph_step, []);
277
+ }
278
+ stepGroups.get(entry.langgraph_step)!.push(entry);
279
+ }
280
+
281
+ console.log('\n\nGrouped by langgraph_step:');
282
+ for (const [step, entries] of stepGroups) {
283
+ const nodes = [...new Set(entries.map((e) => e.langgraph_node))];
284
+ console.log(
285
+ `\n Step ${step}: ${nodes.length} unique nodes: ${nodes.join(', ')}`
286
+ );
287
+ console.log(` Events: ${entries.map((e) => e.event).join(', ')}`);
288
+ }
289
+
290
+ // Identify parallel groups (same step, different nodes)
291
+ console.log('\n\n========== PARALLEL DETECTION ANALYSIS ==========');
292
+ for (const [step, entries] of stepGroups) {
293
+ const uniqueNodes = [...new Set(entries.map((e) => e.langgraph_node))];
294
+ if (uniqueNodes.length > 1) {
295
+ console.log(
296
+ `✅ Step ${step} has MULTIPLE agents: ${uniqueNodes.join(', ')} - PARALLEL!`
297
+ );
298
+ } else {
299
+ console.log(` Step ${step} has single agent: ${uniqueNodes[0]}`);
300
+ }
301
+ }
302
+
303
+ console.log('\n\n========== TIMING SUMMARY ==========');
304
+ for (const [agent, timing] of Object.entries(agentTimings)) {
305
+ const duration =
306
+ timing.end && timing.start ? timing.end - timing.start : 'N/A';
307
+ console.log(
308
+ `${agent}: started=${timing.start}ms, ended=${timing.end}ms, duration=${duration}ms`
309
+ );
310
+ }
311
+
312
+ // Check overlap for parallel confirmation
313
+ const agentList = Object.keys(agentTimings);
314
+ if (agentList.length >= 2) {
315
+ const [a1, a2] = agentList;
316
+ const t1 = agentTimings[a1];
317
+ const t2 = agentTimings[a2];
318
+ if (t1.start && t2.start && t1.end && t2.end) {
319
+ const overlap = Math.min(t1.end, t2.end) - Math.max(t1.start, t2.start);
320
+ if (overlap > 0) {
321
+ console.log(
322
+ `\n✅ PARALLEL EXECUTION CONFIRMED: ${overlap}ms overlap`
323
+ );
324
+ } else {
325
+ console.log(`\n❌ SEQUENTIAL EXECUTION: no overlap`);
326
+ }
327
+ }
328
+ }
329
+ console.log('====================================\n');
330
+
331
+ console.log('Final content parts:', contentParts.length, 'parts');
332
+ console.dir(contentParts, { depth: null });
333
+ await sleep(2000);
334
+ } catch (error) {
335
+ console.error('Error in parallel-with-tools test:', error);
336
+ }
337
+ }
338
+
339
+ // Run the test
340
+ testParallelWithTools();