@librechat/agents 3.0.0-rc1 → 3.0.0-rc10
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/common/enum.cjs +1 -0
- package/dist/cjs/common/enum.cjs.map +1 -1
- package/dist/cjs/graphs/Graph.cjs +0 -1
- package/dist/cjs/graphs/Graph.cjs.map +1 -1
- package/dist/cjs/graphs/MultiAgentGraph.cjs +229 -44
- package/dist/cjs/graphs/MultiAgentGraph.cjs.map +1 -1
- package/dist/cjs/llm/openai/index.cjs +33 -0
- package/dist/cjs/llm/openai/index.cjs.map +1 -1
- package/dist/cjs/run.cjs +28 -15
- package/dist/cjs/run.cjs.map +1 -1
- package/dist/cjs/stream.cjs +1 -1
- package/dist/cjs/stream.cjs.map +1 -1
- package/dist/esm/common/enum.mjs +1 -0
- package/dist/esm/common/enum.mjs.map +1 -1
- package/dist/esm/graphs/Graph.mjs +0 -1
- package/dist/esm/graphs/Graph.mjs.map +1 -1
- package/dist/esm/graphs/MultiAgentGraph.mjs +230 -45
- package/dist/esm/graphs/MultiAgentGraph.mjs.map +1 -1
- package/dist/esm/llm/openai/index.mjs +33 -0
- package/dist/esm/llm/openai/index.mjs.map +1 -1
- package/dist/esm/run.mjs +28 -15
- package/dist/esm/run.mjs.map +1 -1
- package/dist/esm/stream.mjs +1 -1
- package/dist/esm/stream.mjs.map +1 -1
- package/dist/types/common/enum.d.ts +2 -1
- package/dist/types/graphs/MultiAgentGraph.d.ts +12 -2
- package/dist/types/llm/openai/index.d.ts +10 -0
- package/dist/types/run.d.ts +1 -1
- package/dist/types/types/graph.d.ts +38 -4
- package/dist/types/types/llm.d.ts +1 -0
- package/dist/types/types/run.d.ts +5 -1
- package/package.json +10 -2
- package/src/common/enum.ts +1 -0
- package/src/graphs/Graph.ts +0 -1
- package/src/graphs/MultiAgentGraph.ts +267 -50
- package/src/llm/openai/index.ts +41 -0
- package/src/run.ts +38 -27
- package/src/scripts/multi-agent-chain.ts +278 -0
- package/src/scripts/multi-agent-document-review-chain.ts +197 -0
- package/src/scripts/multi-agent-hybrid-flow.ts +310 -0
- package/src/scripts/multi-agent-parallel.ts +27 -23
- package/src/scripts/multi-agent-supervisor.ts +362 -0
- package/src/scripts/test-custom-prompt-key.ts +145 -0
- package/src/scripts/test-handoff-input.ts +170 -0
- package/src/scripts/test-multi-agent-list-handoff.ts +261 -0
- package/src/scripts/test-tools-before-handoff.ts +233 -0
- package/src/stream.ts +4 -1
- package/src/types/graph.ts +51 -5
- package/src/types/llm.ts +1 -0
- package/src/types/run.ts +6 -1
- package/dist/types/scripts/abort.d.ts +0 -1
- package/dist/types/scripts/ant_web_search.d.ts +0 -1
- package/dist/types/scripts/args.d.ts +0 -7
- package/dist/types/scripts/caching.d.ts +0 -1
- package/dist/types/scripts/cli.d.ts +0 -1
- package/dist/types/scripts/cli2.d.ts +0 -1
- package/dist/types/scripts/cli3.d.ts +0 -1
- package/dist/types/scripts/cli4.d.ts +0 -1
- package/dist/types/scripts/cli5.d.ts +0 -1
- package/dist/types/scripts/code_exec.d.ts +0 -1
- package/dist/types/scripts/code_exec_files.d.ts +0 -1
- package/dist/types/scripts/code_exec_simple.d.ts +0 -1
- package/dist/types/scripts/content.d.ts +0 -1
- package/dist/types/scripts/empty_input.d.ts +0 -1
- package/dist/types/scripts/handoff-test.d.ts +0 -1
- package/dist/types/scripts/image.d.ts +0 -1
- package/dist/types/scripts/memory.d.ts +0 -1
- package/dist/types/scripts/multi-agent-conditional.d.ts +0 -1
- package/dist/types/scripts/multi-agent-parallel.d.ts +0 -1
- package/dist/types/scripts/multi-agent-sequence.d.ts +0 -1
- package/dist/types/scripts/multi-agent-test.d.ts +0 -1
- package/dist/types/scripts/search.d.ts +0 -1
- package/dist/types/scripts/simple.d.ts +0 -1
- package/dist/types/scripts/stream.d.ts +0 -1
- package/dist/types/scripts/thinking.d.ts +0 -1
- package/dist/types/scripts/tools.d.ts +0 -1
- package/dist/types/specs/spec.utils.d.ts +0 -1
- package/src/scripts/multi-agent-example-output.md +0 -110
|
@@ -0,0 +1,261 @@
|
|
|
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, createContentAggregator } from '@/stream';
|
|
9
|
+
import { Providers, GraphEvents, Constants } from '@/common';
|
|
10
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
11
|
+
import type * as t from '@/types';
|
|
12
|
+
|
|
13
|
+
const conversationHistory: BaseMessage[] = [];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Test supervisor-based multi-agent system using a single edge with multiple destinations
|
|
17
|
+
*
|
|
18
|
+
* Instead of creating 5 separate edges, we use one edge with an array of destinations
|
|
19
|
+
* This should create handoff tools for all 5 specialists from a single edge definition
|
|
20
|
+
*/
|
|
21
|
+
async function testSupervisorListHandoff() {
|
|
22
|
+
console.log('Testing Supervisor with List-Based Handoff Edge...\n');
|
|
23
|
+
|
|
24
|
+
// Set up content aggregator
|
|
25
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
26
|
+
|
|
27
|
+
// Track which specialist role was selected
|
|
28
|
+
let selectedRole = '';
|
|
29
|
+
|
|
30
|
+
// Create custom handlers
|
|
31
|
+
const customHandlers = {
|
|
32
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
|
33
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
34
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
35
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
36
|
+
handle: (
|
|
37
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
38
|
+
data: t.StreamEventData
|
|
39
|
+
): void => {
|
|
40
|
+
const runStepData = data as any;
|
|
41
|
+
if (runStepData?.name) {
|
|
42
|
+
console.log(`\n[${runStepData.name}] Processing...`);
|
|
43
|
+
}
|
|
44
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
48
|
+
handle: (
|
|
49
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
50
|
+
data: t.StreamEventData
|
|
51
|
+
): void => {
|
|
52
|
+
aggregateContent({
|
|
53
|
+
event,
|
|
54
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
59
|
+
handle: (
|
|
60
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
61
|
+
data: t.StreamEventData
|
|
62
|
+
): void => {
|
|
63
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
[GraphEvents.TOOL_START]: {
|
|
67
|
+
handle: (
|
|
68
|
+
_event: string,
|
|
69
|
+
data: t.StreamEventData,
|
|
70
|
+
metadata?: Record<string, unknown>
|
|
71
|
+
): void => {
|
|
72
|
+
const toolData = data as any;
|
|
73
|
+
if (toolData?.name?.startsWith(Constants.LC_TRANSFER_TO_)) {
|
|
74
|
+
const specialist = toolData.name.replace(
|
|
75
|
+
Constants.LC_TRANSFER_TO_,
|
|
76
|
+
''
|
|
77
|
+
);
|
|
78
|
+
console.log(`\n🔀 Transferring to ${specialist}...`);
|
|
79
|
+
selectedRole = specialist;
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Function to create the graph with a single edge to multiple specialists
|
|
86
|
+
function createSupervisorGraphWithListEdge(): t.RunConfig {
|
|
87
|
+
console.log(`\nCreating graph with supervisor and 5 specialist agents.`);
|
|
88
|
+
console.log(
|
|
89
|
+
'Using a SINGLE edge with multiple destinations (list-based handoff).\n'
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Define the adaptive specialist configuration that will be reused
|
|
93
|
+
const specialistConfig = {
|
|
94
|
+
provider: Providers.ANTHROPIC,
|
|
95
|
+
clientOptions: {
|
|
96
|
+
modelName: 'claude-3-5-sonnet-latest',
|
|
97
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
98
|
+
},
|
|
99
|
+
instructions: `You are an Adaptive Specialist. Your agent ID indicates your role:
|
|
100
|
+
|
|
101
|
+
- data_analyst: Focus on statistical analysis, metrics, ML evaluation, A/B testing
|
|
102
|
+
- security_expert: Focus on cybersecurity, vulnerability assessment, compliance
|
|
103
|
+
- product_designer: Focus on UX/UI design, user research, accessibility
|
|
104
|
+
- devops_engineer: Focus on CI/CD, infrastructure, cloud platforms, monitoring
|
|
105
|
+
- legal_advisor: Focus on licensing, privacy laws, contracts, regulatory compliance
|
|
106
|
+
|
|
107
|
+
The supervisor will provide specific instructions. Follow them while maintaining your expert perspective.`,
|
|
108
|
+
maxContextTokens: 8000,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Create the graph with supervisor and all 5 specialists
|
|
112
|
+
const agents: t.AgentInputs[] = [
|
|
113
|
+
{
|
|
114
|
+
agentId: 'supervisor',
|
|
115
|
+
provider: Providers.ANTHROPIC,
|
|
116
|
+
clientOptions: {
|
|
117
|
+
modelName: 'claude-3-5-sonnet-latest',
|
|
118
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
119
|
+
},
|
|
120
|
+
instructions: `You are a Task Supervisor with access to 5 specialist agents:
|
|
121
|
+
1. transfer_to_data_analyst - For statistical analysis and metrics
|
|
122
|
+
2. transfer_to_security_expert - For cybersecurity and vulnerability assessment
|
|
123
|
+
3. transfer_to_product_designer - For UX/UI design
|
|
124
|
+
4. transfer_to_devops_engineer - For infrastructure and deployment
|
|
125
|
+
5. transfer_to_legal_advisor - For compliance and licensing
|
|
126
|
+
|
|
127
|
+
Your role is to:
|
|
128
|
+
1. Analyze the incoming request
|
|
129
|
+
2. Decide which specialist is best suited
|
|
130
|
+
3. Use the appropriate transfer tool (e.g., transfer_to_data_analyst)
|
|
131
|
+
4. Provide specific instructions to guide their work
|
|
132
|
+
|
|
133
|
+
Be specific about what you need from the specialist.`,
|
|
134
|
+
maxContextTokens: 8000,
|
|
135
|
+
},
|
|
136
|
+
// Include all 5 specialists with the same adaptive configuration
|
|
137
|
+
{
|
|
138
|
+
agentId: 'data_analyst',
|
|
139
|
+
...specialistConfig,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
agentId: 'security_expert',
|
|
143
|
+
...specialistConfig,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
agentId: 'product_designer',
|
|
147
|
+
...specialistConfig,
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
agentId: 'devops_engineer',
|
|
151
|
+
...specialistConfig,
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
agentId: 'legal_advisor',
|
|
155
|
+
...specialistConfig,
|
|
156
|
+
},
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
// Create a SINGLE edge from supervisor to ALL 5 specialists using a list
|
|
160
|
+
const edges: t.GraphEdge[] = [
|
|
161
|
+
{
|
|
162
|
+
from: 'supervisor',
|
|
163
|
+
to: [
|
|
164
|
+
'data_analyst',
|
|
165
|
+
'security_expert',
|
|
166
|
+
'product_designer',
|
|
167
|
+
'devops_engineer',
|
|
168
|
+
'legal_advisor',
|
|
169
|
+
],
|
|
170
|
+
description:
|
|
171
|
+
'Transfer to appropriate specialist based on task requirements',
|
|
172
|
+
edgeType: 'handoff',
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
runId: `supervisor-list-handoff-${Date.now()}`,
|
|
178
|
+
graphConfig: {
|
|
179
|
+
type: 'multi-agent',
|
|
180
|
+
agents,
|
|
181
|
+
edges,
|
|
182
|
+
},
|
|
183
|
+
customHandlers,
|
|
184
|
+
returnContent: true,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
// Test with different queries
|
|
190
|
+
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
|
+
'What are the legal implications of using GPL-licensed code in our product?',
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
const config = {
|
|
199
|
+
configurable: {
|
|
200
|
+
thread_id: 'supervisor-list-handoff-1',
|
|
201
|
+
},
|
|
202
|
+
streamMode: 'values',
|
|
203
|
+
version: 'v2' as const,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
for (const query of testQueries) {
|
|
207
|
+
console.log(`\n${'='.repeat(60)}`);
|
|
208
|
+
console.log(`USER QUERY: "${query}"`);
|
|
209
|
+
console.log('='.repeat(60));
|
|
210
|
+
|
|
211
|
+
// Reset conversation
|
|
212
|
+
conversationHistory.length = 0;
|
|
213
|
+
conversationHistory.push(new HumanMessage(query));
|
|
214
|
+
|
|
215
|
+
// Create graph with supervisor having a single edge to multiple specialists
|
|
216
|
+
const runConfig = createSupervisorGraphWithListEdge();
|
|
217
|
+
const run = await Run.create(runConfig);
|
|
218
|
+
|
|
219
|
+
console.log('Processing request...');
|
|
220
|
+
|
|
221
|
+
// Process with streaming
|
|
222
|
+
const inputs = {
|
|
223
|
+
messages: conversationHistory,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const finalContentParts = await run.processStream(inputs, config);
|
|
227
|
+
const finalMessages = run.getRunMessages();
|
|
228
|
+
|
|
229
|
+
if (finalMessages) {
|
|
230
|
+
conversationHistory.push(...finalMessages);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Show summary
|
|
234
|
+
console.log(`\n${'─'.repeat(60)}`);
|
|
235
|
+
console.log(`Graph structure:`);
|
|
236
|
+
console.log(`- Agents: 6 total (supervisor + 5 specialists)`);
|
|
237
|
+
console.log(`- Edges: 1 edge with multiple destinations`);
|
|
238
|
+
console.log(
|
|
239
|
+
`- Edge type: handoff (creates individual tools for each destination)`
|
|
240
|
+
);
|
|
241
|
+
console.log(
|
|
242
|
+
`- Result: Supervisor has 5 handoff tools from a single edge`
|
|
243
|
+
);
|
|
244
|
+
console.log('─'.repeat(60));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Final summary
|
|
248
|
+
console.log(`\n${'='.repeat(60)}`);
|
|
249
|
+
console.log('TEST COMPLETE');
|
|
250
|
+
console.log('='.repeat(60));
|
|
251
|
+
console.log('\nThis test demonstrates that a single edge with multiple');
|
|
252
|
+
console.log('destinations in the "to" field creates individual handoff');
|
|
253
|
+
console.log('tools for each destination agent, achieving the same result');
|
|
254
|
+
console.log('as creating separate edges for each specialist.');
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error('Error in supervisor list handoff test:', error);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Run the test
|
|
261
|
+
testSupervisorListHandoff();
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
config();
|
|
3
|
+
|
|
4
|
+
import { TavilySearch } from '@langchain/tavily';
|
|
5
|
+
import { HumanMessage, BaseMessage } from '@langchain/core/messages';
|
|
6
|
+
import { Run } from '@/run';
|
|
7
|
+
import { Providers, GraphEvents } from '@/common';
|
|
8
|
+
import { ChatModelStreamHandler, createContentAggregator } from '@/stream';
|
|
9
|
+
import { ToolEndHandler, ModelEndHandler } from '@/events';
|
|
10
|
+
import type * as t from '@/types';
|
|
11
|
+
|
|
12
|
+
const conversationHistory: BaseMessage[] = [];
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Test edge case: Agent performs 2 web searches before handing off
|
|
16
|
+
*
|
|
17
|
+
* This tests how the system behaves when an agent with handoff capabilities
|
|
18
|
+
* uses tools before transferring control to another agent.
|
|
19
|
+
*/
|
|
20
|
+
async function testToolsBeforeHandoff() {
|
|
21
|
+
console.log('Testing Tools Before Handoff Edge Case...\n');
|
|
22
|
+
|
|
23
|
+
// Set up content aggregator
|
|
24
|
+
const { contentParts, aggregateContent } = createContentAggregator();
|
|
25
|
+
|
|
26
|
+
// Track tool calls and handoffs
|
|
27
|
+
let toolCallCount = 0;
|
|
28
|
+
let handoffOccurred = false;
|
|
29
|
+
|
|
30
|
+
// Create custom handlers
|
|
31
|
+
const customHandlers = {
|
|
32
|
+
[GraphEvents.TOOL_END]: new ToolEndHandler(undefined, (name?: string) => {
|
|
33
|
+
console.log(`\n✅ Tool completed: ${name}`);
|
|
34
|
+
return true;
|
|
35
|
+
}),
|
|
36
|
+
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
|
37
|
+
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
|
38
|
+
[GraphEvents.ON_RUN_STEP]: {
|
|
39
|
+
handle: (
|
|
40
|
+
event: GraphEvents.ON_RUN_STEP,
|
|
41
|
+
data: t.StreamEventData
|
|
42
|
+
): void => {
|
|
43
|
+
const runStepData = data as any;
|
|
44
|
+
if (runStepData?.name) {
|
|
45
|
+
console.log(`\n[${runStepData.name}] Processing...`);
|
|
46
|
+
}
|
|
47
|
+
aggregateContent({ event, data: data as t.RunStep });
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
|
51
|
+
handle: (
|
|
52
|
+
event: GraphEvents.ON_RUN_STEP_COMPLETED,
|
|
53
|
+
data: t.StreamEventData
|
|
54
|
+
): void => {
|
|
55
|
+
aggregateContent({
|
|
56
|
+
event,
|
|
57
|
+
data: data as unknown as { result: t.ToolEndEvent },
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
[GraphEvents.ON_MESSAGE_DELTA]: {
|
|
62
|
+
handle: (
|
|
63
|
+
event: GraphEvents.ON_MESSAGE_DELTA,
|
|
64
|
+
data: t.StreamEventData
|
|
65
|
+
): void => {
|
|
66
|
+
// console.log('====== ON_MESSAGE_DELTA ======');
|
|
67
|
+
console.dir(data, { depth: null });
|
|
68
|
+
aggregateContent({ event, data: data as t.MessageDeltaEvent });
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
[GraphEvents.TOOL_START]: {
|
|
72
|
+
handle: (
|
|
73
|
+
_event: string,
|
|
74
|
+
data: t.StreamEventData,
|
|
75
|
+
metadata?: Record<string, unknown>
|
|
76
|
+
): void => {
|
|
77
|
+
const toolData = data as any;
|
|
78
|
+
console.log(`\n🔧 Tool started:`);
|
|
79
|
+
console.dir({ toolData, metadata }, { depth: null });
|
|
80
|
+
|
|
81
|
+
if (toolData?.output?.name === 'tavily_search_results_json') {
|
|
82
|
+
toolCallCount++;
|
|
83
|
+
console.log(`📊 Search #${toolCallCount} initiated`);
|
|
84
|
+
} else if (toolData?.output?.name?.includes('transfer_to_')) {
|
|
85
|
+
handoffOccurred = true;
|
|
86
|
+
const specialist = toolData.name.replace('transfer_to_', '');
|
|
87
|
+
console.log(`\n🔀 Handoff initiated to: ${specialist}`);
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Create the graph with research agent and report writer
|
|
94
|
+
function createGraphWithToolsAndHandoff(): t.RunConfig {
|
|
95
|
+
const agents: t.AgentInputs[] = [
|
|
96
|
+
{
|
|
97
|
+
agentId: 'research_coordinator',
|
|
98
|
+
provider: Providers.OPENAI,
|
|
99
|
+
clientOptions: {
|
|
100
|
+
modelName: 'gpt-4.1-mini',
|
|
101
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
102
|
+
},
|
|
103
|
+
tools: [new TavilySearch({ maxResults: 3 })],
|
|
104
|
+
instructions: `You are a Research Coordinator with access to web search and a report writer specialist.
|
|
105
|
+
|
|
106
|
+
Your workflow MUST follow these steps IN ORDER:
|
|
107
|
+
1. FIRST: Write an initial response acknowledging the request and outlining your research plan
|
|
108
|
+
- Explain what aspects you'll investigate
|
|
109
|
+
- Describe your search strategy
|
|
110
|
+
2. SECOND: Conduct exactly 2 web searches to gather comprehensive information
|
|
111
|
+
- Search 1: Get general information about the topic
|
|
112
|
+
- Search 2: Get specific details, recent updates, or complementary data
|
|
113
|
+
- Note: Even if your searches are unsuccessful, you MUST still proceed to handoff after EXACTLY 2 searches
|
|
114
|
+
3. FINALLY: After completing both searches, transfer to the report writer
|
|
115
|
+
- Provide the report writer with a summary of your findings
|
|
116
|
+
|
|
117
|
+
CRITICAL: You MUST write your initial response before ANY tool use. Then complete both searches before handoff.`,
|
|
118
|
+
maxContextTokens: 8000,
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
agentId: 'report_writer',
|
|
122
|
+
provider: Providers.OPENAI,
|
|
123
|
+
clientOptions: {
|
|
124
|
+
modelName: 'gpt-5-mini',
|
|
125
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
126
|
+
},
|
|
127
|
+
instructions: `You are a Report Writer specialist. Your role is to:
|
|
128
|
+
1. Receive research findings from the Research Coordinator
|
|
129
|
+
2. Create a well-structured, comprehensive report
|
|
130
|
+
3. Include all key findings from the research
|
|
131
|
+
4. Format the report with clear sections and bullet points
|
|
132
|
+
5. Add a brief executive summary at the beginning
|
|
133
|
+
|
|
134
|
+
Focus on clarity, completeness, and professional presentation.`,
|
|
135
|
+
maxContextTokens: 8000,
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
// Create edge from research coordinator to report writer
|
|
140
|
+
const edges: t.GraphEdge[] = [
|
|
141
|
+
{
|
|
142
|
+
from: 'research_coordinator',
|
|
143
|
+
to: 'report_writer',
|
|
144
|
+
description: 'Transfer to report writer after completing research',
|
|
145
|
+
edgeType: 'handoff',
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
runId: `tools-before-handoff-${Date.now()}`,
|
|
151
|
+
graphConfig: {
|
|
152
|
+
type: 'multi-agent',
|
|
153
|
+
agents,
|
|
154
|
+
edges,
|
|
155
|
+
},
|
|
156
|
+
customHandlers,
|
|
157
|
+
returnContent: true,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
// Single test query that requires research before report writing
|
|
163
|
+
const query = `Research the latest developments in quantum computing from 2025,
|
|
164
|
+
including major breakthroughs and commercial applications.
|
|
165
|
+
I need a comprehensive report with recent findings.`;
|
|
166
|
+
|
|
167
|
+
console.log('='.repeat(60));
|
|
168
|
+
console.log(`USER QUERY: "${query}"`);
|
|
169
|
+
console.log('='.repeat(60));
|
|
170
|
+
|
|
171
|
+
// Create the graph
|
|
172
|
+
const runConfig = createGraphWithToolsAndHandoff();
|
|
173
|
+
const run = await Run.create(runConfig);
|
|
174
|
+
|
|
175
|
+
console.log('\nExpected behavior:');
|
|
176
|
+
console.log('1. Research Coordinator writes initial response/plan');
|
|
177
|
+
console.log('2. Research Coordinator performs 2 web searches');
|
|
178
|
+
console.log('3. Research Coordinator hands off to Report Writer');
|
|
179
|
+
console.log('4. Report Writer creates final report\n');
|
|
180
|
+
|
|
181
|
+
// Process with streaming
|
|
182
|
+
conversationHistory.push(new HumanMessage(query));
|
|
183
|
+
const inputs = {
|
|
184
|
+
messages: conversationHistory,
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const config = {
|
|
188
|
+
configurable: {
|
|
189
|
+
thread_id: 'tools-handoff-test-1',
|
|
190
|
+
},
|
|
191
|
+
streamMode: 'values',
|
|
192
|
+
version: 'v2' as const,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const finalContentParts = await run.processStream(inputs, config);
|
|
196
|
+
const finalMessages = run.getRunMessages();
|
|
197
|
+
|
|
198
|
+
if (finalMessages) {
|
|
199
|
+
conversationHistory.push(...finalMessages);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Show results summary
|
|
203
|
+
console.log(`\n${'─'.repeat(60)}`);
|
|
204
|
+
console.log('EDGE CASE TEST RESULTS:');
|
|
205
|
+
console.log('─'.repeat(60));
|
|
206
|
+
console.log(`Tool calls before handoff: ${toolCallCount}`);
|
|
207
|
+
console.log(`Expected tool calls: 2`);
|
|
208
|
+
console.log(`Handoff occurred: ${handoffOccurred ? 'Yes ✅' : 'No ❌'}`);
|
|
209
|
+
console.log(
|
|
210
|
+
`Test status: ${toolCallCount === 2 && handoffOccurred ? 'PASSED ✅' : 'FAILED ❌'}`
|
|
211
|
+
);
|
|
212
|
+
console.log('─'.repeat(60));
|
|
213
|
+
|
|
214
|
+
// Display conversation history
|
|
215
|
+
console.log('\nConversation History:');
|
|
216
|
+
console.log('─'.repeat(60));
|
|
217
|
+
conversationHistory.forEach((msg, idx) => {
|
|
218
|
+
const role = msg.constructor.name.replace('Message', '');
|
|
219
|
+
console.log(`\n[${idx}] ${role}:`);
|
|
220
|
+
if (typeof msg.content === 'string') {
|
|
221
|
+
console.log(
|
|
222
|
+
msg.content.substring(0, 200) +
|
|
223
|
+
(msg.content.length > 200 ? '...' : '')
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error('Error in tools-before-handoff test:', error);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Run the test
|
|
233
|
+
testToolsBeforeHandoff();
|
package/src/stream.ts
CHANGED
|
@@ -153,7 +153,10 @@ export class ChatModelStreamHandler implements t.EventHandler {
|
|
|
153
153
|
if (
|
|
154
154
|
chunk.tool_calls &&
|
|
155
155
|
chunk.tool_calls.length > 0 &&
|
|
156
|
-
chunk.tool_calls.every(
|
|
156
|
+
chunk.tool_calls.every(
|
|
157
|
+
(tc) =>
|
|
158
|
+
tc.id != null && tc.id !== '' && tc.name != null && tc.name !== ''
|
|
159
|
+
)
|
|
157
160
|
) {
|
|
158
161
|
hasToolCalls = true;
|
|
159
162
|
await handleToolCalls(chunk.tool_calls, metadata, graph);
|
package/src/types/graph.ts
CHANGED
|
@@ -66,6 +66,10 @@ export type BaseGraphState = {
|
|
|
66
66
|
messages: BaseMessage[];
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
export type MultiAgentGraphState = BaseGraphState & {
|
|
70
|
+
agentMessages?: BaseMessage[];
|
|
71
|
+
};
|
|
72
|
+
|
|
69
73
|
export type IState = BaseGraphState;
|
|
70
74
|
|
|
71
75
|
export interface EventHandler {
|
|
@@ -116,6 +120,27 @@ export type CompiledStateWorkflow = CompiledStateGraph<
|
|
|
116
120
|
StateDefinition
|
|
117
121
|
>;
|
|
118
122
|
|
|
123
|
+
export type CompiledMultiAgentWorkflow = CompiledStateGraph<
|
|
124
|
+
StateType<{
|
|
125
|
+
messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
|
|
126
|
+
agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
|
|
127
|
+
}>,
|
|
128
|
+
UpdateType<{
|
|
129
|
+
messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
|
|
130
|
+
agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
|
|
131
|
+
}>,
|
|
132
|
+
string,
|
|
133
|
+
{
|
|
134
|
+
messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
|
|
135
|
+
agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
messages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
|
|
139
|
+
agentMessages: BinaryOperatorAggregate<BaseMessage[], BaseMessage[]>;
|
|
140
|
+
},
|
|
141
|
+
StateDefinition
|
|
142
|
+
>;
|
|
143
|
+
|
|
119
144
|
export type CompiledAgentWorfklow = CompiledStateGraph<
|
|
120
145
|
{
|
|
121
146
|
messages: BaseMessage[];
|
|
@@ -290,19 +315,40 @@ export type StandardGraphInput = {
|
|
|
290
315
|
};
|
|
291
316
|
|
|
292
317
|
export type GraphEdge = {
|
|
293
|
-
/**
|
|
318
|
+
/** Agent ID, use a list for multiple sources */
|
|
294
319
|
from: string | string[];
|
|
295
|
-
/**
|
|
320
|
+
/** Agent ID, use a list for multiple destinations */
|
|
296
321
|
to: string | string[];
|
|
297
322
|
description?: string;
|
|
298
323
|
/** Can return boolean or specific destination(s) */
|
|
299
324
|
condition?: (state: BaseGraphState) => boolean | string | string[];
|
|
300
325
|
/** 'handoff' creates tools for dynamic routing, 'direct' creates direct edges, which also allow parallel execution */
|
|
301
326
|
edgeType?: 'handoff' | 'direct';
|
|
302
|
-
/**
|
|
303
|
-
|
|
327
|
+
/**
|
|
328
|
+
* For direct edges: Optional prompt to add when transitioning through this edge.
|
|
329
|
+
* String prompts can include variables like {results} which will be replaced with
|
|
330
|
+
* messages from startIndex onwards. When {results} is used, excludeResults defaults to true.
|
|
331
|
+
*
|
|
332
|
+
* For handoff edges: Description for the input parameter that the handoff tool accepts,
|
|
333
|
+
* allowing the supervisor to pass specific instructions/context to the transferred agent.
|
|
334
|
+
*/
|
|
335
|
+
prompt?:
|
|
304
336
|
| string
|
|
305
|
-
| ((
|
|
337
|
+
| ((
|
|
338
|
+
messages: BaseMessage[],
|
|
339
|
+
runStartIndex: number
|
|
340
|
+
) => string | Promise<string> | undefined);
|
|
341
|
+
/**
|
|
342
|
+
* When true, excludes messages from startIndex when adding prompt.
|
|
343
|
+
* Automatically set to true when {results} variable is used in prompt.
|
|
344
|
+
*/
|
|
345
|
+
excludeResults?: boolean;
|
|
346
|
+
/**
|
|
347
|
+
* For handoff edges: Customizes the parameter name for the handoff input.
|
|
348
|
+
* Defaults to "instructions" if not specified.
|
|
349
|
+
* Only applies when prompt is provided for handoff edges.
|
|
350
|
+
*/
|
|
351
|
+
promptKey?: string;
|
|
306
352
|
};
|
|
307
353
|
|
|
308
354
|
export type MultiAgentGraphInput = StandardGraphInput & {
|
package/src/types/llm.ts
CHANGED
package/src/types/run.ts
CHANGED
|
@@ -103,9 +103,14 @@ export type MultiAgentGraphConfig = {
|
|
|
103
103
|
edges: g.GraphEdge[];
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
+
export type StandardGraphConfig = Omit<
|
|
107
|
+
MultiAgentGraphConfig,
|
|
108
|
+
'edges' | 'type'
|
|
109
|
+
> & { type?: 'standard'; signal?: AbortSignal };
|
|
110
|
+
|
|
106
111
|
export type RunConfig = {
|
|
107
112
|
runId: string;
|
|
108
|
-
graphConfig: LegacyGraphConfig | MultiAgentGraphConfig;
|
|
113
|
+
graphConfig: LegacyGraphConfig | StandardGraphConfig | MultiAgentGraphConfig;
|
|
109
114
|
customHandlers?: Record<string, g.EventHandler>;
|
|
110
115
|
returnContent?: boolean;
|
|
111
116
|
tokenCounter?: TokenCounter;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|