@librechat/agents 3.0.17 → 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.
- package/dist/cjs/graphs/Graph.cjs +80 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/main.cjs +2 -0
- package/dist/cjs/main.cjs.map +1 -1
- package/dist/cjs/messages/format.cjs +242 -6
- package/dist/cjs/messages/format.cjs.map +1 -1
- package/dist/cjs/stream.cjs +3 -2
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/cjs/tools/handlers.cjs +5 -5
- package/dist/cjs/tools/handlers.cjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +80 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/main.mjs +1 -1
- package/dist/esm/messages/format.mjs +242 -8
- package/dist/esm/messages/format.mjs.map +1 -1
- package/dist/esm/stream.mjs +3 -2
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/esm/tools/handlers.mjs +5 -5
- package/dist/esm/tools/handlers.mjs.map +1 -1
- package/dist/types/graphs/Graph.d.ts +19 -2
- package/dist/types/messages/format.d.ts +25 -1
- package/dist/types/tools/handlers.d.ts +2 -1
- package/dist/types/types/stream.d.ts +1 -0
- package/package.json +9 -8
- package/src/graphs/Graph.ts +99 -2
- package/src/messages/ensureThinkingBlock.test.ts +393 -0
- package/src/messages/format.ts +312 -6
- package/src/messages/labelContentByAgent.test.ts +887 -0
- package/src/scripts/test-multi-agent-list-handoff.ts +169 -13
- package/src/scripts/test-parallel-agent-labeling.ts +325 -0
- package/src/scripts/test-thinking-handoff-bedrock.ts +153 -0
- package/src/scripts/test-thinking-handoff.ts +147 -0
- package/src/specs/thinking-handoff.test.ts +620 -0
- package/src/stream.ts +19 -10
- package/src/tools/handlers.ts +36 -18
- package/src/types/stream.ts +1 -0
|
@@ -4,11 +4,12 @@ import { config } from 'dotenv';
|
|
|
4
4
|
config();
|
|
5
5
|
|
|
6
6
|
import { HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
7
|
-
import
|
|
7
|
+
import type * as t from '@/types';
|
|
8
|
+
import { labelContentByAgent, formatAgentMessages } from '@/messages/format';
|
|
8
9
|
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
9
|
-
import { Providers, GraphEvents, Constants } from '@/common';
|
|
10
|
+
import { Providers, GraphEvents, Constants, StepTypes } from '@/common';
|
|
10
11
|
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
11
|
-
import
|
|
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(
|
|
208
|
-
console.log(`USER QUERY: "${query}"`);
|
|
209
|
-
console.log('='.repeat(
|
|
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 = {
|
|
@@ -230,9 +227,59 @@ async function testSupervisorListHandoff() {
|
|
|
230
227
|
conversationHistory.push(...finalMessages);
|
|
231
228
|
}
|
|
232
229
|
|
|
233
|
-
//
|
|
230
|
+
// Demo: Map contentParts to agentIds
|
|
231
|
+
console.log(`\n${'─'.repeat(60)}`);
|
|
232
|
+
console.log('CONTENT PARTS TO AGENT MAPPING:');
|
|
233
|
+
console.log('─'.repeat(60));
|
|
234
|
+
|
|
235
|
+
if (run.Graph) {
|
|
236
|
+
// Get the mapping of contentPart index to agentId
|
|
237
|
+
const contentPartAgentMap = run.Graph.getContentPartAgentMap();
|
|
238
|
+
|
|
239
|
+
console.log(`\nTotal content parts: ${contentParts.length}`);
|
|
240
|
+
console.log(`\nContent Part → Agent Mapping:`);
|
|
241
|
+
|
|
242
|
+
contentPartAgentMap.forEach((agentId, index) => {
|
|
243
|
+
const contentPart = contentParts[index];
|
|
244
|
+
const contentType = contentPart?.type || 'unknown';
|
|
245
|
+
const preview =
|
|
246
|
+
contentType === 'text'
|
|
247
|
+
? (contentPart as any).text?.slice(0, 50) || ''
|
|
248
|
+
: contentType === 'tool_call'
|
|
249
|
+
? `Tool: ${(contentPart as any).tool_call?.name || 'unknown'}`
|
|
250
|
+
: contentType;
|
|
251
|
+
|
|
252
|
+
console.log(
|
|
253
|
+
` [${index}] ${agentId} → ${contentType}: ${preview}${preview.length >= 50 ? '...' : ''}`
|
|
254
|
+
);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Show agent participation summary
|
|
258
|
+
console.log(`\n${'─'.repeat(60)}`);
|
|
259
|
+
console.log('AGENT PARTICIPATION SUMMARY:');
|
|
260
|
+
console.log('─'.repeat(60));
|
|
261
|
+
|
|
262
|
+
const activeAgents = run.Graph.getActiveAgentIds();
|
|
263
|
+
console.log(`\nActive agents (${activeAgents.length}):`, activeAgents);
|
|
264
|
+
|
|
265
|
+
const stepsByAgent = run.Graph.getRunStepsByAgent();
|
|
266
|
+
stepsByAgent.forEach((steps, agentId) => {
|
|
267
|
+
const toolCallSteps = steps.filter(
|
|
268
|
+
(s) => s.type === StepTypes.TOOL_CALLS
|
|
269
|
+
).length;
|
|
270
|
+
const messageSteps = steps.filter(
|
|
271
|
+
(s) => s.type === StepTypes.MESSAGE_CREATION
|
|
272
|
+
).length;
|
|
273
|
+
console.log(`\n ${agentId}:`);
|
|
274
|
+
console.log(` - Total steps: ${steps.length}`);
|
|
275
|
+
console.log(` - Message steps: ${messageSteps}`);
|
|
276
|
+
console.log(` - Tool call steps: ${toolCallSteps}`);
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Show graph structure summary
|
|
234
281
|
console.log(`\n${'─'.repeat(60)}`);
|
|
235
|
-
console.log(`
|
|
282
|
+
console.log(`GRAPH STRUCTURE:`);
|
|
236
283
|
console.log(`- Agents: 6 total (supervisor + 5 specialists)`);
|
|
237
284
|
console.log(`- Edges: 1 edge with multiple destinations`);
|
|
238
285
|
console.log(
|
|
@@ -242,6 +289,115 @@ async function testSupervisorListHandoff() {
|
|
|
242
289
|
`- Result: Supervisor has 5 handoff tools from a single edge`
|
|
243
290
|
);
|
|
244
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.');
|
|
245
401
|
}
|
|
246
402
|
|
|
247
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();
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { config } from 'dotenv';
|
|
4
|
+
config();
|
|
5
|
+
|
|
6
|
+
import { HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
7
|
+
import { Run } from '@/run';
|
|
8
|
+
import { ChatModelStreamHandler } from '@/stream';
|
|
9
|
+
import { Providers, GraphEvents } from '@/common';
|
|
10
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
11
|
+
import type * as t from '@/types';
|
|
12
|
+
|
|
13
|
+
const conversationHistory: BaseMessage[] = [];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Test edge case: switching from OpenAI supervisor (no thinking) to Bedrock specialist (with thinking enabled)
|
|
17
|
+
* This should not throw an error about missing thinking blocks
|
|
18
|
+
*/
|
|
19
|
+
async function testBedrockThinkingHandoff() {
|
|
20
|
+
console.log('Testing OpenAI → Bedrock (with thinking) handoff...\n');
|
|
21
|
+
|
|
22
|
+
// Create custom handlers
|
|
23
|
+
const customHandlers = {
|
|
24
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
25
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
26
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
27
|
+
[GraphEvents.TOOL_START]: {
|
|
28
|
+
handle: (_event: string, data: t.StreamEventData): void => {
|
|
29
|
+
const toolData = data as any;
|
|
30
|
+
if (toolData?.name) {
|
|
31
|
+
console.log(`\n🔧 Tool called: ${toolData.name}`);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Create the graph configuration
|
|
38
|
+
function createGraphConfig(): t.RunConfig {
|
|
39
|
+
console.log(
|
|
40
|
+
'Creating graph with OpenAI supervisor and Bedrock specialist with thinking enabled.\n'
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const agents: t.AgentInputs[] = [
|
|
44
|
+
{
|
|
45
|
+
agentId: 'supervisor',
|
|
46
|
+
provider: Providers.OPENAI,
|
|
47
|
+
clientOptions: {
|
|
48
|
+
modelName: 'gpt-4o-mini',
|
|
49
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
50
|
+
},
|
|
51
|
+
instructions: `You are a task supervisor. When the user asks about code review, use transfer_to_code_reviewer to hand off to the specialist.`,
|
|
52
|
+
maxContextTokens: 8000,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
agentId: 'code_reviewer',
|
|
56
|
+
provider: Providers.BEDROCK,
|
|
57
|
+
clientOptions: {
|
|
58
|
+
region: process.env.BEDROCK_AWS_REGION || 'us-east-1',
|
|
59
|
+
model: 'us.anthropic.claude-3-7-sonnet-20250219-v1:0',
|
|
60
|
+
credentials: {
|
|
61
|
+
accessKeyId: process.env.BEDROCK_AWS_ACCESS_KEY_ID!,
|
|
62
|
+
secretAccessKey: process.env.BEDROCK_AWS_SECRET_ACCESS_KEY!,
|
|
63
|
+
},
|
|
64
|
+
additionalModelRequestFields: {
|
|
65
|
+
thinking: {
|
|
66
|
+
type: 'enabled',
|
|
67
|
+
budget_tokens: 2000,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
instructions: `You are a code review specialist using Bedrock with extended thinking. Think carefully about the code quality, best practices, and potential issues. Provide thoughtful feedback.`,
|
|
72
|
+
maxContextTokens: 8000,
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
const edges: t.GraphEdge[] = [
|
|
77
|
+
{
|
|
78
|
+
from: 'supervisor',
|
|
79
|
+
to: ['code_reviewer'],
|
|
80
|
+
description: 'Transfer to code review specialist',
|
|
81
|
+
edgeType: 'handoff',
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
runId: `bedrock-thinking-handoff-test-${Date.now()}`,
|
|
87
|
+
graphConfig: {
|
|
88
|
+
type: 'multi-agent',
|
|
89
|
+
agents,
|
|
90
|
+
edges,
|
|
91
|
+
},
|
|
92
|
+
customHandlers,
|
|
93
|
+
returnContent: true,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
// Test query that should trigger a handoff
|
|
99
|
+
const query =
|
|
100
|
+
'Can you review this function and tell me if there are any issues?\n\nfunction add(a, b) { return a + b; }';
|
|
101
|
+
|
|
102
|
+
console.log(`${'='.repeat(60)}`);
|
|
103
|
+
console.log(`USER QUERY: "${query}"`);
|
|
104
|
+
console.log('='.repeat(60));
|
|
105
|
+
|
|
106
|
+
// Initialize conversation
|
|
107
|
+
conversationHistory.push(new HumanMessage(query));
|
|
108
|
+
|
|
109
|
+
// Create and run the graph
|
|
110
|
+
const runConfig = createGraphConfig();
|
|
111
|
+
const run = await Run.create(runConfig);
|
|
112
|
+
|
|
113
|
+
const config = {
|
|
114
|
+
configurable: {
|
|
115
|
+
thread_id: 'bedrock-thinking-handoff-test-1',
|
|
116
|
+
},
|
|
117
|
+
streamMode: 'values',
|
|
118
|
+
version: 'v2' as const,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
console.log('\nProcessing request...\n');
|
|
122
|
+
|
|
123
|
+
// Process with streaming
|
|
124
|
+
const inputs = {
|
|
125
|
+
messages: conversationHistory,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
await run.processStream(inputs, config);
|
|
129
|
+
const finalMessages = run.getRunMessages();
|
|
130
|
+
|
|
131
|
+
if (finalMessages) {
|
|
132
|
+
conversationHistory.push(...finalMessages);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Success!
|
|
136
|
+
console.log(`\n${'='.repeat(60)}`);
|
|
137
|
+
console.log('✅ TEST PASSED');
|
|
138
|
+
console.log('='.repeat(60));
|
|
139
|
+
console.log('\nSuccessfully handed off from OpenAI (no thinking) to');
|
|
140
|
+
console.log('Bedrock with thinking enabled without errors!');
|
|
141
|
+
console.log('\nThe ensureThinkingBlockInMessages() function correctly');
|
|
142
|
+
console.log('handled the transition by converting tool sequences to');
|
|
143
|
+
console.log('HumanMessages before calling the Bedrock API.');
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error('\n❌ TEST FAILED');
|
|
146
|
+
console.error('='.repeat(60));
|
|
147
|
+
console.error('Error:', error);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Run the test
|
|
153
|
+
testBedrockThinkingHandoff();
|