@librechat/agents 3.0.18 → 3.0.19

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.
@@ -4,11 +4,12 @@ import { config } from 'dotenv';
4
4
  config();
5
5
 
6
6
  import { HumanMessage, BaseMessage } from '@langchain/core/messages';
7
- import { Run } from '@/run';
7
+ import type * as t from '@/types';
8
+ import { labelContentByAgent, formatAgentMessages } from '@/messages/format';
8
9
  import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
9
10
  import { Providers, GraphEvents, Constants, StepTypes } from '@/common';
10
11
  import { ToolEndHandler, ModelEndHandler } from '@/events';
11
- import type * as t from '@/types';
12
+ import { Run } from '@/run';
12
13
 
13
14
  const conversationHistory: BaseMessage[] = [];
14
15
 
@@ -188,10 +189,6 @@ async function testSupervisorListHandoff() {
188
189
  try {
189
190
  // Test with different queries
190
191
  const testQueries = [
191
- // 'How can we analyze user engagement metrics to improve our product?',
192
- // 'What security measures should we implement for our new API?',
193
- // 'Can you help design a better onboarding flow for our mobile app?',
194
- // 'We need to set up a CI/CD pipeline for our microservices.',
195
192
  'What are the legal implications of using GPL-licensed code in our product?',
196
193
  ];
197
194
 
@@ -204,9 +201,9 @@ async function testSupervisorListHandoff() {
204
201
  };
205
202
 
206
203
  for (const query of testQueries) {
207
- console.log(`\n${'='.repeat(60)}`);
208
- console.log(`USER QUERY: "${query}"`);
209
- console.log('='.repeat(60));
204
+ console.log(`\n${'='.repeat(80)}`);
205
+ console.log(`FIRST RUN - USER QUERY: "${query}"`);
206
+ console.log('='.repeat(80));
210
207
 
211
208
  // Reset conversation
212
209
  conversationHistory.length = 0;
@@ -216,7 +213,7 @@ async function testSupervisorListHandoff() {
216
213
  const runConfig = createSupervisorGraphWithListEdge();
217
214
  const run = await Run.create(runConfig);
218
215
 
219
- console.log('Processing request...');
216
+ console.log('Processing first request...');
220
217
 
221
218
  // Process with streaming
222
219
  const inputs = {
@@ -292,6 +289,115 @@ async function testSupervisorListHandoff() {
292
289
  `- Result: Supervisor has 5 handoff tools from a single edge`
293
290
  );
294
291
  console.log('─'.repeat(60));
292
+
293
+ // =============================================================
294
+ // SECOND RUN: Demonstrate agent-labeled history
295
+ // =============================================================
296
+ console.log(`\n${'='.repeat(80)}`);
297
+ console.log(`SECOND RUN - Simulating DB Load with Agent-Labeled History`);
298
+ console.log('='.repeat(80));
299
+
300
+ // Simulate what happens in the main app:
301
+ // 1. Store contentParts + agentIdMap to "DB" (in-memory here)
302
+ const dbStoredContentParts = [...contentParts];
303
+ const dbStoredAgentIdMap = Object.fromEntries(
304
+ run.Graph!.getContentPartAgentMap()
305
+ );
306
+
307
+ console.log('\nšŸ“¦ Simulating DB storage:');
308
+ console.log(` - Stored ${dbStoredContentParts.length} content parts`);
309
+ console.log(
310
+ ` - Stored agent mappings for ${Object.keys(dbStoredAgentIdMap).length} parts`
311
+ );
312
+
313
+ // 2. On next run, load from "DB" and label by agent
314
+ console.log('\nšŸ“„ Loading from DB and labeling by agent...');
315
+
316
+ const agentNames = {
317
+ supervisor: 'Supervisor',
318
+ legal_advisor: 'Legal Advisor',
319
+ data_analyst: 'Data Analyst',
320
+ security_expert: 'Security Expert',
321
+ product_designer: 'Product Designer',
322
+ devops_engineer: 'DevOps Engineer',
323
+ };
324
+
325
+ const labeledContentParts = labelContentByAgent(
326
+ dbStoredContentParts.filter(
327
+ (p): p is t.MessageContentComplex => p != null
328
+ ),
329
+ dbStoredAgentIdMap,
330
+ agentNames
331
+ );
332
+
333
+ console.log(
334
+ ` - Labeled ${labeledContentParts.length} content parts by agent`
335
+ );
336
+
337
+ // 3. Convert labeled content parts to payload format
338
+ const payload: t.TPayload = [
339
+ {
340
+ role: 'user',
341
+ content: query,
342
+ },
343
+ {
344
+ role: 'assistant',
345
+ content: labeledContentParts,
346
+ },
347
+ ];
348
+
349
+ // 4. Format using formatAgentMessages (simulates what main app does)
350
+ console.log('\nšŸ”§ Calling formatAgentMessages...');
351
+ const { messages: formattedMessages } = formatAgentMessages(payload);
352
+
353
+ console.log(
354
+ ` - Formatted into ${formattedMessages.length} BaseMessages`
355
+ );
356
+
357
+ // Show a preview of what the supervisor will see
358
+ console.log('\nšŸ‘ļø Preview of formatted history for supervisor:');
359
+ console.log('─'.repeat(80));
360
+ for (let i = 0; i < formattedMessages.length; i++) {
361
+ const msg = formattedMessages[i];
362
+ const role = msg._getType();
363
+ const preview =
364
+ typeof msg.content === 'string'
365
+ ? msg.content.slice(0, 200)
366
+ : JSON.stringify(msg.content).slice(0, 200);
367
+ console.log(
368
+ `[${i}] ${role}: ${preview}${preview.length >= 200 ? '...' : ''}`
369
+ );
370
+ }
371
+ console.log('─'.repeat(80));
372
+
373
+ // 5. Create a new run with the formatted history + a followup question
374
+ console.log(
375
+ '\nšŸš€ Starting second run with agent-labeled history + followup question...'
376
+ );
377
+ const followupQuery =
378
+ 'Can you summarize the key legal points from your previous response?';
379
+ console.log(` Followup: "${followupQuery}"`);
380
+
381
+ // Reset for second run
382
+ const secondRunHistory: BaseMessage[] = [
383
+ ...formattedMessages,
384
+ new HumanMessage(followupQuery),
385
+ ];
386
+
387
+ const runConfig2 = createSupervisorGraphWithListEdge();
388
+ const run2 = await Run.create(runConfig2);
389
+
390
+ const inputs2 = {
391
+ messages: secondRunHistory,
392
+ };
393
+
394
+ await run2.processStream(inputs2, config);
395
+
396
+ console.log('\nāœ… Second run completed successfully!');
397
+ console.log(
398
+ ' The supervisor correctly understood that the legal_advisor handled'
399
+ );
400
+ console.log(' the previous query, avoiding identity confusion.');
295
401
  }
296
402
 
297
403
  // Final summary
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { config } from 'dotenv';
4
+ config();
5
+
6
+ import { HumanMessage, BaseMessage } from '@langchain/core/messages';
7
+ import type * as t from '@/types';
8
+ import { labelContentByAgent, formatAgentMessages } from '@/messages/format';
9
+ import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
10
+ import { Providers, GraphEvents, StepTypes } from '@/common';
11
+ import { ToolEndHandler, ModelEndHandler } from '@/events';
12
+ import { Run } from '@/run';
13
+
14
+ const conversationHistory: BaseMessage[] = [];
15
+
16
+ /**
17
+ * Test parallel multi-agent system with agent labeling on subsequent runs
18
+ *
19
+ * Graph structure:
20
+ * START -> researcher
21
+ * researcher -> [analyst1, analyst2, analyst3] (fan-out)
22
+ * [analyst1, analyst2, analyst3] -> summarizer (fan-in)
23
+ * summarizer -> END
24
+ */
25
+ async function testParallelWithAgentLabeling() {
26
+ console.log('Testing Parallel Multi-Agent with Agent Labeling...\n');
27
+
28
+ // Set up content aggregator
29
+ const { contentParts, aggregateContent } = createContentAggregator();
30
+
31
+ // Define specialized agents
32
+ const agents: t.AgentInputs[] = [
33
+ {
34
+ agentId: 'researcher',
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 research coordinator. Analyze the request and provide 2-3 sentence coordination brief.`,
41
+ },
42
+ {
43
+ agentId: 'analyst1',
44
+ provider: Providers.ANTHROPIC,
45
+ clientOptions: {
46
+ modelName: 'claude-haiku-4-5',
47
+ apiKey: process.env.ANTHROPIC_API_KEY,
48
+ },
49
+ instructions: `You are FINANCIAL ANALYST. Provide 2-3 sentence financial analysis. Start with "FINANCIAL ANALYSIS:"`,
50
+ },
51
+ {
52
+ agentId: 'analyst2',
53
+ provider: Providers.ANTHROPIC,
54
+ clientOptions: {
55
+ modelName: 'claude-haiku-4-5',
56
+ apiKey: process.env.ANTHROPIC_API_KEY,
57
+ },
58
+ instructions: `You are TECHNICAL ANALYST. Provide 2-3 sentence technical analysis. Start with "TECHNICAL ANALYSIS:"`,
59
+ },
60
+ {
61
+ agentId: 'analyst3',
62
+ provider: Providers.ANTHROPIC,
63
+ clientOptions: {
64
+ modelName: 'claude-haiku-4-5',
65
+ apiKey: process.env.ANTHROPIC_API_KEY,
66
+ },
67
+ instructions: `You are MARKET ANALYST. Provide 2-3 sentence market analysis. Start with "MARKET ANALYSIS:"`,
68
+ },
69
+ {
70
+ agentId: 'summarizer',
71
+ provider: Providers.ANTHROPIC,
72
+ clientOptions: {
73
+ modelName: 'claude-haiku-4-5',
74
+ apiKey: process.env.ANTHROPIC_API_KEY,
75
+ },
76
+ instructions: `You are SYNTHESIS EXPERT. Review all analyses and provide 2-3 sentence integrated summary.`,
77
+ },
78
+ ];
79
+
80
+ // Define direct edges (fan-out and fan-in)
81
+ const edges: t.GraphEdge[] = [
82
+ {
83
+ from: 'researcher',
84
+ to: ['analyst1', 'analyst2', 'analyst3'],
85
+ description: 'Distribute research to specialist analysts',
86
+ edgeType: 'direct',
87
+ },
88
+ {
89
+ from: ['analyst1', 'analyst2', 'analyst3'],
90
+ to: 'summarizer',
91
+ description: 'Aggregate analysis results',
92
+ edgeType: 'direct',
93
+ prompt:
94
+ 'Based on the analyses below, provide an integrated summary:\n\n{results}',
95
+ },
96
+ ];
97
+
98
+ // Create custom handlers
99
+ const customHandlers = {
100
+ [GraphEvents.TOOL_END]: new ToolEndHandler(),
101
+ [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
102
+ [GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
103
+ [GraphEvents.ON_RUN_STEP_COMPLETED]: {
104
+ handle: (
105
+ event: GraphEvents.ON_RUN_STEP_COMPLETED,
106
+ data: t.StreamEventData
107
+ ): void => {
108
+ aggregateContent({
109
+ event,
110
+ data: data as unknown as { result: t.ToolEndEvent },
111
+ });
112
+ },
113
+ },
114
+ [GraphEvents.ON_RUN_STEP]: {
115
+ handle: (
116
+ event: GraphEvents.ON_RUN_STEP,
117
+ data: t.StreamEventData
118
+ ): void => {
119
+ aggregateContent({ event, data: data as t.RunStep });
120
+ },
121
+ },
122
+ [GraphEvents.ON_MESSAGE_DELTA]: {
123
+ handle: (
124
+ event: GraphEvents.ON_MESSAGE_DELTA,
125
+ data: t.StreamEventData
126
+ ): void => {
127
+ aggregateContent({ event, data: data as t.MessageDeltaEvent });
128
+ },
129
+ },
130
+ };
131
+
132
+ try {
133
+ const query = 'What are the implications of widespread AI adoption?';
134
+
135
+ console.log(`${'='.repeat(80)}`);
136
+ console.log(`FIRST RUN - USER QUERY: "${query}"`);
137
+ console.log('='.repeat(80));
138
+
139
+ // Reset conversation
140
+ conversationHistory.length = 0;
141
+ conversationHistory.push(new HumanMessage(query));
142
+
143
+ // Create graph
144
+ const runConfig: t.RunConfig = {
145
+ runId: `parallel-test-${Date.now()}`,
146
+ graphConfig: {
147
+ type: 'multi-agent',
148
+ agents,
149
+ edges,
150
+ },
151
+ customHandlers,
152
+ returnContent: true,
153
+ };
154
+
155
+ const run = await Run.create(runConfig);
156
+
157
+ console.log('\nProcessing first run with parallel agents...\n');
158
+
159
+ const config = {
160
+ configurable: {
161
+ thread_id: 'parallel-agent-labeling-1',
162
+ },
163
+ streamMode: 'values',
164
+ version: 'v2' as const,
165
+ };
166
+
167
+ const inputs = {
168
+ messages: conversationHistory,
169
+ };
170
+
171
+ await run.processStream(inputs, config);
172
+ const finalMessages = run.getRunMessages();
173
+
174
+ if (finalMessages) {
175
+ conversationHistory.push(...finalMessages);
176
+ }
177
+
178
+ // Show agent participation
179
+ console.log(`\n${'─'.repeat(80)}`);
180
+ console.log('FIRST RUN - AGENT PARTICIPATION:');
181
+ console.log('─'.repeat(80));
182
+
183
+ if (run.Graph) {
184
+ const activeAgents = run.Graph.getActiveAgentIds();
185
+ console.log(`\nActive agents (${activeAgents.length}):`, activeAgents);
186
+
187
+ const stepsByAgent = run.Graph.getRunStepsByAgent();
188
+ stepsByAgent.forEach((steps, agentId) => {
189
+ console.log(` ${agentId}: ${steps.length} steps`);
190
+ });
191
+
192
+ console.log(`\nTotal content parts: ${contentParts.length}`);
193
+ }
194
+
195
+ // =============================================================
196
+ // SECOND RUN: Test with agent-labeled history
197
+ // =============================================================
198
+ console.log(`\n${'='.repeat(80)}`);
199
+ console.log(`SECOND RUN - Simulating DB Load with Parallel Agent Labeling`);
200
+ console.log('='.repeat(80));
201
+
202
+ // Simulate DB storage
203
+ const dbStoredContentParts = [...contentParts];
204
+ const dbStoredAgentIdMap = Object.fromEntries(
205
+ run.Graph!.getContentPartAgentMap()
206
+ );
207
+
208
+ console.log('\nšŸ“¦ Simulating DB storage:');
209
+ console.log(` - Stored ${dbStoredContentParts.length} content parts`);
210
+ console.log(
211
+ ` - Stored agent mappings for ${Object.keys(dbStoredAgentIdMap).length} parts`
212
+ );
213
+
214
+ // Load and label by agent with labelNonTransferContent option
215
+ console.log('\nšŸ“„ Loading from DB and labeling ALL agent content...');
216
+
217
+ const agentNames = {
218
+ researcher: 'Researcher',
219
+ analyst1: 'Financial Analyst',
220
+ analyst2: 'Technical Analyst',
221
+ analyst3: 'Market Analyst',
222
+ summarizer: 'Synthesizer',
223
+ };
224
+
225
+ const labeledContentParts = labelContentByAgent(
226
+ dbStoredContentParts.filter(
227
+ (p): p is t.MessageContentComplex => p != null
228
+ ),
229
+ dbStoredAgentIdMap,
230
+ agentNames,
231
+ { labelNonTransferContent: true } // NEW: Label all content
232
+ );
233
+
234
+ console.log(
235
+ ` - Labeled ${labeledContentParts.length} content groups by agent`
236
+ );
237
+
238
+ // Convert to payload
239
+ const payload: t.TPayload = [
240
+ {
241
+ role: 'user',
242
+ content: query,
243
+ },
244
+ {
245
+ role: 'assistant',
246
+ content: labeledContentParts,
247
+ },
248
+ ];
249
+
250
+ // Format using formatAgentMessages
251
+ console.log('\nšŸ”§ Calling formatAgentMessages...');
252
+ const { messages: formattedMessages } = formatAgentMessages(payload);
253
+
254
+ console.log(` - Formatted into ${formattedMessages.length} BaseMessages`);
255
+
256
+ // Show preview
257
+ console.log('\nšŸ‘ļø Preview of formatted history:');
258
+ console.log('─'.repeat(80));
259
+ for (let i = 0; i < formattedMessages.length; i++) {
260
+ const msg = formattedMessages[i];
261
+ const role = msg._getType();
262
+ const preview =
263
+ typeof msg.content === 'string'
264
+ ? msg.content.slice(0, 300)
265
+ : JSON.stringify(msg.content).slice(0, 300);
266
+ console.log(
267
+ `[${i}] ${role}: ${preview}${preview.length >= 300 ? '...' : ''}`
268
+ );
269
+ console.log('');
270
+ }
271
+ console.log('─'.repeat(80));
272
+
273
+ // Create a second run with labeled history
274
+ console.log(
275
+ '\nšŸš€ Starting second run with agent-labeled parallel history...'
276
+ );
277
+ const followupQuery = 'Which analyst identified the most significant risk?';
278
+ console.log(` Followup: "${followupQuery}"`);
279
+
280
+ const secondRunHistory: BaseMessage[] = [
281
+ ...formattedMessages,
282
+ new HumanMessage(followupQuery),
283
+ ];
284
+
285
+ const runConfig2: t.RunConfig = {
286
+ runId: `parallel-test-2-${Date.now()}`,
287
+ graphConfig: {
288
+ type: 'multi-agent',
289
+ agents,
290
+ edges,
291
+ },
292
+ customHandlers,
293
+ returnContent: true,
294
+ };
295
+
296
+ const run2 = await Run.create(runConfig2);
297
+
298
+ const inputs2 = {
299
+ messages: secondRunHistory,
300
+ };
301
+
302
+ await run2.processStream(inputs2, config);
303
+
304
+ console.log('\nāœ… Second run completed successfully!');
305
+ console.log(
306
+ ' The researcher correctly understood that parallel analysts handled'
307
+ );
308
+ console.log(' the previous analysis, with clear attribution per agent.');
309
+
310
+ console.log(`\n${'='.repeat(80)}`);
311
+ console.log('TEST COMPLETE');
312
+ console.log('='.repeat(80));
313
+ console.log(
314
+ '\nThis demonstrates that parallel multi-agent patterns work correctly'
315
+ );
316
+ console.log(
317
+ 'with agent labeling, preventing confusion about who said what.'
318
+ );
319
+ } catch (error) {
320
+ console.error('Error in parallel agent labeling test:', error);
321
+ }
322
+ }
323
+
324
+ // Run the test
325
+ testParallelWithAgentLabeling();