动手做一个会调用工具的 AI Agent:多模型路由与工具编排实战

发布时间:2026/7/15 2:53:38
动手做一个会调用工具的 AI Agent:多模型路由与工具编排实战 2026 年被称为「智能体落地元年」但真正能稳定干活的 Agent难点往往不在模型本身而在「怎么把模型、工具、上下文编排到一起」。本文用一个可运行的例子讲清楚 Agent 的工程骨架以及为什么在真实项目里我们通常不直接裸连单个模型而是加一层模型路由。一、为什么 Agent 需要「多模型路由」很多初学 Agent 的同学会默认一个 Agent 一个模型 一堆 prompt。这在 demo 阶段没问题一上生产就暴露问题长任务推理用旗舰模型更稳但成本高海量短问答 / 分类 / 抽取用轻量模型就够了硬上旗舰是浪费钱中文长文档摘要某些模型表现更好代码生成又是另一批模型领先单一供应商有可用率风险需要故障转移。所以真实项目里Agent 内部通常不是「一个模型打到死」而是有一层模型路由model routing根据任务类型、输入长度、预算把请求派给最合适的模型。# agent/router.py # 根据任务特征选择后端模型统一通过接入层发出请求 # 接入层负责密钥管理、限流、故障转移业务代码不关心具体供应商 # 注册一个统一接入层示例https://www.moyu.info/register?affCRB8 from enum import Enum class TaskKind(Enum): REASONING reasoning # 复杂推理用旗舰模型 CODEGEN codegen # 代码生成用代码强的模型 EXTRACT extract # 短文本抽取用轻量模型 SUMMARY_CN summary_cn # 中文长文摘要 ROUTING_TABLE { TaskKind.REASONING: gpt-5, TaskKind.CODEGEN: claude-4-sonnet, TaskKind.EXTRACT: gpt-5-mini, TaskKind.SUMMARY_CN: deepseek-v4, } def select_model(task: TaskKind) - str: return ROUTING_TABLE[task]路由表本身很简单麻烦的是后端怎么接。下面展开。二、Agent 的核心骨架循环 工具一个最小可用的 Agent本质是「模型调用 ↔ 工具执行」的循环。这里用 OpenAI 兼容的接口格式主流接入层都支持这套协议换供应商不用改业务代码# agent/run.py import json from agent.router import select_model, TaskKind def call_chat(model: str, messages: list, tools: list None) - dict: # 统一走接入层base_url 指向你的接入网关 # 业务侧只传 model 名 messages密钥在接入层统一管理 return gateway_chat(modelmodel, messagesmessages, toolstools) def run_agent(user_input: str, tools: list): messages [{role: user, content: user_input}] for _ in range(8): # 最多 8 轮工具调用 task classify(user_input) # 简单场景可直接指定 model select_model(task) resp call_chat(model, messages, tools) if resp.get(tool_calls): for call in resp[tool_calls]: result execute_tool(call, tools) messages.append({role: tool, content: result}) else: return resp[content] return 超过最大轮次关键点model字段是动态的。同一个 Agent推理用gpt-5抽字段用gpt-5-mini代码用claude-4-sonnet中文摘要用deepseek-v4。如果每次都去各家官网申请密钥、拼不同 SDK代码会很快腐烂。这就是为什么工程上会引入一层统一接入业务代码只认model字符串密钥、配额、限流、重试、故障转移都在接入层做换模型、加模型、切供应商业务零改动。三、工具怎么定义才不踩坑工具function calling是 Agent 的「手」。定义工具时三个常见坑1. 参数 schema 太松{ name: query_orders, parameters: { type: object, properties: { status: {type: string, description: 订单状态} } } }status不写枚举模型可能传「已付款」「已支付」「paid」三种东西下游解析崩溃。一定要写enum。2. 工具太多导致模型选错一次性塞 30 个工具弱模型会选错。按场景做工具分组或先让模型「选工具组」再「选具体工具」。3. 工具执行没有超时与兜底模型可能生成无限循环的工具调用。上面run_agent里的range(8)就是兜底生产环境还要加单次执行超时。四、上下文怎么管Agent 跑几轮后messages会爆。常用策略滑动窗口只保留最近 N 条压缩记忆把早期对话 summarize 成一条外部记忆把事实写进向量库 / KV用时再取。简单实现def compress_history(messages: list, threshold: int 12) - list: if len(messages) threshold: return messages early, recent messages[:-6], messages[-6:] summary call_chat(gpt-5-mini, [{role: user, content: f用三句话总结对话: {early}}]) return [{role: system, content: f历史摘要: {summary}}] recent注意这里压缩用的也是轻量模型进一步省钱。五、接入层选型直连 vs 聚合真实项目里「模型接入」有几种做法各有取舍方案优点缺点适合场景各家官网直连最直接密钥散落、无统一限流、供应商绑定只用一个模型自建轻量代理可控、可加审计要自己维护可用性有基础设施团队聚合接入层一套协议接多模型、统一配额多一层依赖多模型 / 成本敏感聚合接入层把多家供应商的接口收敛成统一协议业务侧切换模型只改一个字符串还能集中做限流、计费和故障转移。市面上这类方案不少从开源自建到托管服务都有选的时候看清楚是否支持你需要的模型、是否支持自定义模型服务地址、计费是否透明。六、小结一个能稳定干活的 Agent技术含量有一半在「编排」而不是「模型」用模型路由按任务分流别让旗舰模型干抽字段的活业务代码只认model字符串密钥与故障转移交给接入层工具 schema 写死枚举、控制数量、加超时上下文用滑动窗口 摘要压缩别让messages无限涨。把这几件事做对Agent 从 demo 到生产的距离会短很多。