forked from JackChen-me/open-multi-agent
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path04-multi-model-team.ts
More file actions
261 lines (223 loc) · 9.43 KB
/
04-multi-model-team.ts
File metadata and controls
261 lines (223 loc) · 9.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
/**
* Example 04 — Multi-Model Team with Custom Tools
*
* Demonstrates:
* - Mixing Anthropic and OpenAI models in the same team
* - Defining custom tools with defineTool() and Zod schemas
* - Building agents with a custom ToolRegistry so they can use custom tools
* - Running a team goal that uses the custom tools
*
* Run:
* npx tsx examples/04-multi-model-team.ts
*
* Prerequisites:
* ANTHROPIC_API_KEY and OPENAI_API_KEY env vars must be set.
* (If you only have one key, set useOpenAI = false below.)
*/
import { z } from 'zod'
import { OpenMultiAgent, defineTool } from '../src/index.js'
import type { AgentConfig, OrchestratorEvent } from '../src/types.js'
// ---------------------------------------------------------------------------
// Custom tools — defined with defineTool() + Zod schemas
// ---------------------------------------------------------------------------
/**
* A custom tool that fetches live exchange rates from a public API.
*/
const exchangeRateTool = defineTool({
name: 'get_exchange_rate',
description:
'Get the current exchange rate between two currencies. ' +
'Returns the rate as a decimal: 1 unit of `from` = N units of `to`.',
inputSchema: z.object({
from: z.string().describe('ISO 4217 currency code, e.g. "USD"'),
to: z.string().describe('ISO 4217 currency code, e.g. "EUR"'),
}),
execute: async ({ from, to }) => {
try {
const url = `https://api.exchangerate.host/convert?from=${from}&to=${to}&amount=1`
const resp = await fetch(url, { signal: AbortSignal.timeout(5000) })
if (!resp.ok) throw new Error(`HTTP ${resp.status}`)
interface ExchangeRateResponse {
result?: number
info?: { rate?: number }
}
const json = (await resp.json()) as ExchangeRateResponse
const rate: number | undefined = json?.result ?? json?.info?.rate
if (typeof rate !== 'number') throw new Error('Unexpected API response shape')
return {
data: JSON.stringify({ from, to, rate, timestamp: new Date().toISOString() }),
isError: false,
}
} catch (err) {
// Graceful degradation — return a stubbed rate so the team can still proceed
const stub = parseFloat((Math.random() * 0.5 + 0.8).toFixed(4))
return {
data: JSON.stringify({
from,
to,
rate: stub,
note: `Live fetch failed (${err instanceof Error ? err.message : String(err)}). Using stub rate.`,
}),
isError: false,
}
}
},
})
/**
* A custom tool that formats a number as a localised currency string.
*/
const formatCurrencyTool = defineTool({
name: 'format_currency',
description: 'Format a number as a localised currency string.',
inputSchema: z.object({
amount: z.number().describe('The numeric amount to format.'),
currency: z.string().describe('ISO 4217 currency code, e.g. "USD".'),
locale: z
.string()
.optional()
.describe('BCP 47 locale string, e.g. "en-US". Defaults to "en-US".'),
}),
execute: async ({ amount, currency, locale = 'en-US' }) => {
try {
const formatted = new Intl.NumberFormat(locale, {
style: 'currency',
currency,
}).format(amount)
return { data: formatted, isError: false }
} catch {
return { data: `${amount} ${currency}`, isError: true }
}
},
})
// ---------------------------------------------------------------------------
// Helper: build an AgentConfig whose tools list includes custom tool names.
//
// Agents reference tools by name in their AgentConfig.tools array.
// The ToolRegistry is injected via the Agent constructor. When using OpenMultiAgent
// convenience methods (runTeam, runTasks, runAgent), the orchestrator builds
// agents internally using buildAgent(), which registers only the five built-in
// tools. For custom tools, use AgentPool + Agent directly (see the note in the
// README) or provide the custom tool names in the tools array and rely on a
// registry you inject yourself.
//
// In this example we demonstrate the custom-tool pattern by running the agents
// directly through AgentPool rather than through the OpenMultiAgent high-level API.
// ---------------------------------------------------------------------------
import { Agent, AgentPool, ToolRegistry, ToolExecutor, registerBuiltInTools } from '../src/index.js'
/**
* Build an Agent with both built-in and custom tools registered.
*/
function buildCustomAgent(
config: AgentConfig,
extraTools: ReturnType<typeof defineTool>[],
): Agent {
const registry = new ToolRegistry()
registerBuiltInTools(registry)
for (const tool of extraTools) {
registry.register(tool)
}
const executor = new ToolExecutor(registry)
return new Agent(config, registry, executor)
}
// ---------------------------------------------------------------------------
// Agent definitions — mixed providers
// ---------------------------------------------------------------------------
const useOpenAI = Boolean(process.env.OPENAI_API_KEY)
const researcherConfig: AgentConfig = {
name: 'researcher',
model: 'claude-sonnet-4-6',
provider: 'anthropic',
systemPrompt: `You are a financial data researcher.
Use the get_exchange_rate tool to fetch current rates between the currency pairs you are given.
Return the raw rates as a JSON object keyed by pair, e.g. { "USD/EUR": 0.91, "USD/GBP": 0.79 }.`,
tools: ['get_exchange_rate'],
maxTurns: 6,
temperature: 0,
}
const analystConfig: AgentConfig = {
name: 'analyst',
model: useOpenAI ? 'gpt-5.4' : 'claude-sonnet-4-6',
provider: useOpenAI ? 'openai' : 'anthropic',
systemPrompt: `You are a foreign exchange analyst.
You receive exchange rate data and produce a short briefing.
Use format_currency to show example conversions.
Keep the briefing under 200 words.`,
tools: ['format_currency'],
maxTurns: 4,
temperature: 0.3,
}
// ---------------------------------------------------------------------------
// Build agents with custom tools
// ---------------------------------------------------------------------------
const researcher = buildCustomAgent(researcherConfig, [exchangeRateTool])
const analyst = buildCustomAgent(analystConfig, [formatCurrencyTool])
// ---------------------------------------------------------------------------
// Run with AgentPool for concurrency control
// ---------------------------------------------------------------------------
console.log('Multi-model team with custom tools')
console.log(`Providers: researcher=anthropic, analyst=${useOpenAI ? 'openai (gpt-5.4)' : 'anthropic (fallback)'}`)
console.log('Custom tools:', [exchangeRateTool.name, formatCurrencyTool.name].join(', '))
console.log()
const pool = new AgentPool(1) // sequential for readability
pool.add(researcher)
pool.add(analyst)
// Step 1: researcher fetches the rates
console.log('[1/2] Researcher fetching FX rates...')
const researchResult = await pool.run(
'researcher',
`Fetch exchange rates for these pairs using the get_exchange_rate tool:
- USD to EUR
- USD to GBP
- USD to JPY
- EUR to GBP
Return the results as a JSON object: { "USD/EUR": <rate>, "USD/GBP": <rate>, ... }`,
)
if (!researchResult.success) {
console.error('Researcher failed:', researchResult.output)
process.exit(1)
}
console.log('Researcher done. Tool calls made:', researchResult.toolCalls.map(c => c.toolName).join(', '))
// Step 2: analyst writes the briefing, receiving the researcher output as context
console.log('\n[2/2] Analyst writing FX briefing...')
const analystResult = await pool.run(
'analyst',
`Here are the current FX rates gathered by the research team:
${researchResult.output}
Using format_currency, show what $1,000 USD and €1,000 EUR convert to in each of the other currencies.
Then write a short FX market briefing (under 200 words) covering:
- Each rate with a brief observation
- The strongest and weakest currency in the set
- One-sentence market comment`,
)
if (!analystResult.success) {
console.error('Analyst failed:', analystResult.output)
process.exit(1)
}
console.log('Analyst done. Tool calls made:', analystResult.toolCalls.map(c => c.toolName).join(', '))
// ---------------------------------------------------------------------------
// Results
// ---------------------------------------------------------------------------
console.log('\n' + '='.repeat(60))
console.log('\nResearcher output:')
console.log(researchResult.output.slice(0, 400))
console.log('\nAnalyst briefing:')
console.log('─'.repeat(60))
console.log(analystResult.output)
console.log('─'.repeat(60))
const totalInput = researchResult.tokenUsage.input_tokens + analystResult.tokenUsage.input_tokens
const totalOutput = researchResult.tokenUsage.output_tokens + analystResult.tokenUsage.output_tokens
console.log(`\nTotal tokens — input: ${totalInput}, output: ${totalOutput}`)
// ---------------------------------------------------------------------------
// Bonus: show how defineTool() works in isolation (no LLM needed)
// ---------------------------------------------------------------------------
console.log('\n--- Bonus: testing custom tools in isolation ---\n')
const fmtResult = await formatCurrencyTool.execute(
{ amount: 1234.56, currency: 'EUR', locale: 'de-DE' },
{ agent: { name: 'test', role: 'test', model: 'test' } },
)
console.log(`format_currency(1234.56, EUR, de-DE) = ${fmtResult.data}`)
const rateResult = await exchangeRateTool.execute(
{ from: 'USD', to: 'EUR' },
{ agent: { name: 'test', role: 'test', model: 'test' } },
)
console.log(`get_exchange_rate(USD→EUR) = ${rateResult.data}`)