使用 BedrockLLMAgent 与自定义 Tools 构建数学 Agent:multi-agent-orchestrator 实战指南
【免费下载链接】agent-squadFlexible and powerful framework for managing multiple AI agents and handling complex conversations项目地址: https://gitcode.com/GitHub_Trending/mu/agent-squad
本文以 multi-agent-orchestrator 框架为背景,完整演示如何基于 Amazon Bedrock 的BedrockLLMAgent与自定义工具(Tools)构建一个可执行四则运算、幂运算、三角函数、对数及均值/中位数/方差等统计计算的数学 Agent。你将掌握工具定义(Tool Definition)、工具处理器(Tool Handler)、底层函数实现、Agent 装配以及将其接入MultiAgentOrchestrator进行实时计算的全过程,同时获得仓库源码级的实现佐证。
背景:为什么需要"会计算"的 Agent
基础大语言模型在自然语言理解上表现出色,但涉及精确的数值运算时往往不够可靠。multi-agent-orchestrator 提供了一条标准路径:让 LLM 负责拆解问题、生成"调用计划",把具体的数学计算交给可编程、可验证的 JavaScript 函数去执行,再将计算结果以工具结果的形式回传给模型组织最终答案。本指南中的数学 Agent 正是这一模式的典型落地案例,它只负责数学领域的问题,其余问题则由编排器路由给其他 Agent(如天气、健康、技术类 Agent)。
一、定义数学工具(Tool Definition)
工具定义是 Agent 能力边界的"说明书",它告诉 Bedrock 模型:有哪些工具可用、每个工具的入参结构是什么、哪些参数是必填的。本示例定义了两个工具,完整定义见 examples/chat-demo-app/lambda/multi-agent/math_tool.ts 与 examples/local-demo/tools/math_tool.ts。
A. 工具描述(Tool Descriptions)
export const mathAgentToolDefinition = [ { toolSpec: { name: "perform_math_operation", description: "Perform a mathematical operation. This tool supports basic arithmetic and various mathematical functions.", inputSchema: { json: { type: "object", properties: { operation: { type: "string", description: "The mathematical operation to perform. Supported operations include:\n" + "- Basic arithmetic: 'add', 'subtract', 'multiply', 'divide'\n" + "- Exponentiation: 'power'\n" + "- Trigonometric: 'sin', 'cos', 'tan'\n" + "- Logarithmic and exponential: 'log', 'exp'\n" + "- Rounding: 'round', 'floor', 'ceil'\n" + "- Other: 'sqrt', 'abs'", }, args: { type: "array", items: { type: "number" }, description: "The arguments for the operation.", }, }, required: ["operation", "args"], }, }, }, }, { toolSpec: { name: "perform_statistical_calculation", description: "Perform statistical calculations on a set of numbers.", inputSchema: { json: { type: "object", properties: { operation: { type: "string", description: "The statistical operation to perform. Supported operations include:\n" + "'mean', 'median', 'mode', 'variance', 'stddev'", }, args: { type: "array", items: { type: "number" }, description: "The set of numbers to perform the statistical operation on.", }, }, required: ["operation", "args"], }, }, }, }, ];要点说明:
- 该定义声明了两个工具:
perform_math_operation(数学运算)与perform_statistical_calculation(统计计算); - 每个工具都包含
name、description与inputSchema三要素,其中inputSchema.json遵循 JSON Schema 规范,通过properties描述入参、required声明必填项; - 两个工具统一采用
operation(字符串,指明具体操作)加args(数字数组,操作数)的输入约定,便于模型理解与工具处理器分发。
在仓库的实际实现中,工具定义对入参描述更加细致,例如args会进一步说明:"加法和乘法可接受多个参数;减法、除法和幂运算必须恰好两个参数;其余操作大多接受一个参数"(见 examples/chat-demo-app/lambda/multi-agent/math_tool.ts)。这类"元数据级"的约束描述能显著提升模型生成合法入参的成功率。
B. 工具处理器(Tool Handler)
工具处理器负责解析模型返回的toolUse请求、调用实际执行函数、并把结果包装成符合 Bedrock 协议的消息回传给模型:
import { ConversationMessage, ParticipantRole } from "multi-agent-orchestrator"; export async function mathToolHandler(response, conversation: ConversationMessage[]): Promise<ConversationMessage> { const responseContentBlocks = response.content as any[]; let toolResults: any = []; if (!responseContentBlocks) { throw new Error("No content blocks in response"); } for (const contentBlock of response.content) { if ("toolUse" in contentBlock) { const toolUseBlock = contentBlock.toolUse; const toolUseName = toolUseBlock.name; if (toolUseName === "perform_math_operation") { const result = executeMathOperation(toolUseBlock.input.operation, toolUseBlock.input.args); // Process and add result to toolResults } else if (toolUseName === "perform_statistical_calculation") { const result = calculateStatistics(toolUseBlock.input.operation, toolUseBlock.input.args); // Process and add result to toolResults } } } const message: ConversationMessage = { role: ParticipantRole.USER, content: toolResults }; return messages; }处理流程说明:
- 处理器遍历模型响应中的 content 块,识别包含
toolUse的块; - 依据
toolUse.name分发到executeMathOperation或calculateStatistics; - 将结果格式化为
toolResult结构(需携带toolUseId以关联原始调用),最终以ParticipantRole.USER角色消息返回,从而形成"模型请求工具 → 工具返回结果"的对话闭环。
仓库中的完整实现 examples/chat-demo-app/lambda/multi-agent/math_tool.ts 展示了比上述骨架更完整的细节:
- 对
sin、cos、tan三角运算在调用前自动将角度从度转换为弧度(degToRad = Math.PI / 180); - 成功时以
content: [{ json: { result } }], status: "success"返回结果; - 失败时以
content: [{ text: error }], status: "error"返回错误信息,同时通过Logger记录每次工具调用轨迹(如Tool call 1: perform_math_operation: args=[16] operation=sqrt result=4),便于调试多步计算链。
C. 数学运算与统计计算函数
两个底层执行函数负责真实的数值计算。它们都遵循同一返回契约:成功返回{ result: number },失败返回{ error: string },从而让上层处理器无需处理异常中断。
/** * Executes a mathematical operation using JavaScript's Math library. * @param operation - The mathematical operation to perform. * @param args - Array of numbers representing the arguments for the operation. * @returns An object containing either the result of the operation or an error message. */ function executeMathOperation( operation: string, args: number[] ): { result: number } | { error: string } { const safeEval = (code: string) => { return Function('"use strict";return (' + code + ")")(); }; try { let result: number; switch (operation.toLowerCase()) { case 'add': case 'addition': result = args.reduce((sum, current) => sum + current, 0); break; case 'subtract': case 'subtraction': if (args.length !== 2) { throw new Error('Subtraction requires exactly two arguments'); } result = args[0] - args[1]; break; case 'multiply': case 'multiplication': result = args.reduce((product, current) => product * current, 1); break; case 'divide': case 'division': if (args.length !== 2) { throw new Error('Division requires exactly two arguments'); } if (args[1] === 0) { throw new Error('Division by zero'); } result = args[0] / args[1]; break; case 'power': case 'exponent': if (args.length !== 2) { throw new Error('Power operation requires exactly two arguments'); } result = Math.pow(args[0], args[1]); break; default: // For other operations, use the Math object if the function exists if (typeof Math[operation] === 'function') { result = safeEval(`Math.${operation}(${args.join(",")})`); } else { throw new Error(`Unsupported operation: ${operation}`); } } return { result }; } catch (error) { return { error: `Error executing ${operation}: ${(error as Error).message}`, }; } } function calculateStatistics(operation: string, args: number[]): { result: number } | { error: string } { try { switch (operation.toLowerCase()) { case 'mean': return { result: args.reduce((sum, num) => sum + num, 0) / args.length }; case 'median': { const sorted = args.slice().sort((a, b) => a - b); const mid = Math.floor(sorted.length / 2); return { result: sorted.length % 2 !== 0 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2, }; } case 'mode': { const counts = args.reduce((acc, num) => { acc[num] = (acc[num] || 0) + 1; return acc; }, {} as Record<number, number>); const maxCount = Math.max(...Object.values(counts)); const modes = Object.keys(counts).filter(key => counts[Number(key)] === maxCount); return { result: Number(modes[0]) }; // Return first mode if there are multiple } case 'variance': { const mean = args.reduce((sum, num) => sum + num, 0) / args.length; const squareDiffs = args.map(num => Math.pow(num - mean, 2)); return { result: squareDiffs.reduce((sum, square) => sum + square, 0) / args.length }; } case 'stddev': { const mean = args.reduce((sum, num) => sum + num, 0) / args.length; const squareDiffs = args.map(num => Math.pow(num - mean, 2)); const variance = squareDiffs.reduce((sum, square) => sum + square, 0) / args.length; return { result: Math.sqrt(variance) }; } default: throw new Error(`Unsupported statistical operation: ${operation}`); } } catch (error) { return { error: `Error executing ${operation}: ${(error as Error).message}` }; } }实现要点:
executeMathOperation对add/multiply使用reduce支持多操作数聚合;对subtract/divide/power强制校验恰好两个参数;divide额外做了除零保护;- 对于未显式列举的操作,通过
typeof Math[operation] === 'function'检查后,用safeEval动态调用 JavaScript 内置Math对象方法(如sqrt、abs、log、exp、round、floor、ceil等),使工具具备良好的可扩展性;'use strict'模式限制了动态求值的作用域; calculateStatistics实现了均值(mean)、中位数(median,奇偶长度分别处理)、众数(mode,多众数时返回首个)、方差(variance,总体方差)与标准差(stddev)五种统计指标;- 所有分支均以
try/catch包裹,任何非法操作或参数错误都会转换为{ error }返回,不会导致 Agent 调用中断。
二、创建数学 Agent(BedrockLLMAgent)
将上述工具定义与处理器封装进一个BedrockLLMAgent实例,并通过setSystemPrompt注入领域专用的系统提示词:
import { BedrockLLMAgent } from 'multi-agent-orchestrator'; import { mathAgentToolDefinition, mathToolHandler } from './mathTools'; const MATH_PROMPT = ` You are a mathematical assistant capable of performing various mathematical operations and statistical calculations. Use the provided tools to perform calculations. Always show your work and explain each step and provide the final result of the operation. If a calculation involves multiple steps, use the tools sequentially and explain the process. Only respond to mathematical queries. For non-math questions, politely redirect the conversation to mathematics. `; const mathAgent = new BedrockLLMAgent({ name: "Math Agent", description: "Specialized agent for performing mathematical operations and statistical calculations.", streaming: false, inferenceConfig: { temperature: 0.1, }, toolConfig: { useToolHandler: mathToolHandler, tool: mathAgentToolDefinition, toolMaxRecursions: 5 } }); mathAgent.setSystemPrompt(MATH_PROMPT);配置项逐项解析
对照 typescript/src/agents/bedrockLLMAgent.ts 中的BedrockLLMAgentOptions接口,各配置项的作用如下:
| 配置项 | 类型 | 说明 | 默认值 |
|---|---|---|---|
name/description | string | Agent 名称与能力描述,用于分类器路由与系统提示词生成(继承自AgentOptions,见 typescript/src/agents/agent.ts) | 必填 |
modelId | string | Bedrock 模型 ID,仓库内置的默认模型为anthropic.claude-3-haiku-20240307-v1:0(见 typescript/src/types/index.ts) | Claude 3 Haiku |
region | string | Bedrock 服务的 AWS 区域,用于构造BedrockRuntimeClient | 按 SDK 默认链路解析 |
streaming | boolean | 是否开启流式输出。数学计算场景通常设为false,便于一次拿到完整计算结果 | false |
inferenceConfig.temperature | number | 采样温度,数值越低回答越确定。数学场景建议设为 0~0.1,降低模型"自由发挥"概率 | 未设置 |
toolConfig.tool | AgentTools | Tool[] | 工具定义数组(即上文mathAgentToolDefinition),会原样透传给 Bedrock Converse API 的toolConfig.tools | 无 |
toolConfig.useToolHandler | function | 自定义工具处理器,签名(response, conversation) => any | 无 |
toolConfig.toolMaxRecursions | number | 模型连续调用工具的最大轮次,防止多步计算陷入无限循环 | 20 |
系统提示词的设计
仓库在 examples/chat-demo-app/lambda/multi-agent/prompts.ts 中提供了更完整的MATH_AGENT_PROMPT模板,它额外约束了:
- "必须展示计算过程、解释每一步、给出最终结果;多步计算要按顺序调用工具并说明流程";
- "只回答数学问题,非数学问题礼貌地引导回数学主题";
- 输出格式要求:使用 Markdown 结构(
##/###标题、编号列表、**粗体**强调关键结果、LaTeX 代码块展示公式、表格组织步骤)。
结合 typescript/src/agents/bedrockLLMAgent.ts 可知,setSystemPrompt(template, variables)支持在模板中使用{{variable}}占位符并在运行时替换,适合需要动态注入上下文(如 Agent 列表、知识库检索结果)的场景。
三、将数学 Agent 加入编排器
MultiAgentOrchestrator通过分类器将用户请求路由到最合适的 Agent。添加方式十分简单:
import { MultiAgentOrchestrator } from "multi-agent-orchestrator"; const orchestrator = new MultiAgentOrchestrator(); orchestrator.addAgent(mathAgent);addAgent方法内部会根据name生成唯一 Agent ID(去除特殊字符、空格转连字符、转小写),并注册到路由表中(见 typescript/src/agents/agent.ts)。在真实应用中,编排器通常还会配置存储(如DynamoDbChatStorage)与分类器(如BedrockClassifier),并在一个 Lambda 入口中注册多个 Agent——examples/chat-demo-app/lambda/multi-agent/index.ts 展示了如何把Math Agent、Weather Agent、Health Agent、Tech Agent等一并注册,实现多领域问题的统一路由。
四、使用数学 Agent 执行计算
Agent 注册完成后,即可通过编排器的routeRequest方法发起请求:
const response = await orchestrator.routeRequest( "What is the square root of 16 plus the cosine of 45 degrees?", "user123", "session456" );routeRequest(userInput, userId, sessionId, additionalParams)的调用链见 typescript/src/orchestrator.ts:它先调用分类器确定目标 Agent(本例为 Math Agent),再交由agentProcessRequest执行;若分类器未选中任何 Agent,则返回NO_SELECTED_AGENT_MESSAGE配置的兜底提示。
工作原理:工具调用的递归闭环
一次数学问题的完整处理流程如下:
- 编排器收到数学类查询,经分类器路由到 Math Agent;
- Math Agent 使用
MATH_PROMPT作为系统提示词调用 Bedrock Converse API; - 模型判定需要计算时,在响应中输出
toolUse内容块(如请求perform_math_operation(operation='sqrt', args=[16])); mathToolHandler解析toolUse,调用executeMathOperation/calculateStatistics完成真实计算;- 计算结果的
toolResult以 USER 角色消息追加进对话,并再次发送给模型; - 模型基于工具结果组织最终回答(展示推导过程与结论);若模型再次请求工具,则重复 3~5 步,直到输出
end_turn或达到toolMaxRecursions上限。
对应到源码实现(typescript/src/agents/bedrockLLMAgent.ts),processRequest使用do...while循环执行"发送请求 → 检测toolUse→ 调用工具处理器 → 格式化结果回填对话"的迭代,直到响应中不再包含toolUse或递归次数耗尽。格式化阶段(formatToolResults,见 typescript/src/agents/bedrockLLMAgent.ts)会把工具结果转换为 Bedrock 协议要求的toolResult结构。流式模式(streaming: true)下,handleStreamingResponse 同样实现了工具调用的递归处理,且支持边生成文本边收集工具入参。
因此,"16 的平方根加上 45 度的余弦"这类复合问题会被模型自动拆解为多个工具调用(如先sqrt(16),再将 45° 转为弧度后求cos),由处理器逐步执行并把每一步结果回传,最终由模型汇总为带推导过程的可读答案。
五、把数学 Agent 落地到本地或生产环境
本地交互式运行
examples/local-demo/local-orchestrator.ts 提供了一个基于readline的本地交互入口:构建编排器、注册各 Agent、进入 REPL 循环调用orchestrator.routeRequest,并区分流式/非流式输出。你可以将mathAgent及 examples/local-demo/tools/math_tool.ts 中的工具定义与处理器并入其中,通过终端直接验证"什么是 12 和 8 的平均数与标准差"这类查询。
部署到 AWS Lambda
生产场景可参考 examples/chat-demo-app 的完整架构:BedrockLLMAgent数学 Agent 与DynamoDbChatStorage(会话历史持久化)、BedrockClassifier(智能路由)、流式响应(awslambda.streamifyResponse)配合使用,前端 UI 见 examples/chat-demo-app/ui。其中数学 Agent 的完整注册代码位于 examples/chat-demo-app/lambda/multi-agent/index.ts,工具与处理器位于 examples/chat-demo-app/lambda/multi-agent/math_tool.ts。
运行前提
- 需要具备 Amazon Bedrock 访问权限,并确保所用模型(默认
anthropic.claude-3-haiku-20240307-v1:0,可在modelId中覆盖)在目标区域已开通; - 本地运行需先
npm install安装multi-agent-orchestrator及 AWS SDK 依赖,并配置 AWS 凭证(环境变量或凭证文件); - 工具入参需符合 JSON Schema 约束:
operation为字符串、args为数字数组、二者均必填;模型生成的非法参数会由底层函数转为{ error }返回而非抛出异常。
总结
通过本指南,你已经完成了一个"定义工具 → 编写处理器 → 实现计算函数 → 装配 BedrockLLMAgent → 注册到编排器 → 实时计算"的完整闭环。这套模式的核心价值在于:把 LLM 的"规划能力"与代码的"精确执行能力"解耦——模型负责理解问题、编排步骤、解释过程,JavaScript 函数负责保证计算的正确性与可复现性,toolMaxRecursions与严格参数校验则保证了多步计算链的安全收敛。同样的方法论可以轻松迁移到天气查询、知识库检索、API 调用等任意需要"Agent + 工具"组合的业务场景。
【免费下载链接】agent-squadFlexible and powerful framework for managing multiple AI agents and handling complex conversations项目地址: https://gitcode.com/GitHub_Trending/mu/agent-squad
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考