动手做一个会调用工具的 AI Agent:多模型路由与工具编排实战
2026 年被称为「智能体落地元年」,但真正能稳定干活的 Agent,难点往往不在模型本身,而在「怎么把模型、工具、上下文编排到一起」。本文用一个可运行的例子,讲清楚 Agent 的工程骨架,以及为什么在真实项目里我们通常不直接裸连单个模型,而是加一层模型路由。
一、为什么 Agent 需要「多模型路由」
很多初学 Agent 的同学会默认:一个 Agent = 一个模型 + 一堆 prompt。这在 demo 阶段没问题,一上生产就暴露问题:
- 长任务推理用旗舰模型更稳,但成本高;
- 海量短问答 / 分类 / 抽取用轻量模型就够了,硬上旗舰是浪费钱;
- 中文长文档摘要某些模型表现更好,代码生成又是另一批模型领先;
- 单一供应商有可用率风险,需要故障转移。
所以真实项目里,Agent 内部通常不是「一个模型打到死」,而是有一层 模型路由(model routing):根据任务类型、输入长度、预算,把请求派给最合适的模型。
# agent/router.py
# 根据任务特征选择后端模型,统一通过接入层发出请求
# 接入层负责密钥管理、限流、故障转移,业务代码不关心具体供应商
# 注册一个统一接入层示例:https://www.moyu.info/register?aff=CRB8
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(model=model, messages=messages, tools=tools)
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 到生产的距离会短很多。
更多推荐



所有评论(0)