/** * 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[], ): 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": , "USD/GBP": , ... }`, ) 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}`)