前言
Agent 系统区别于普通 LLM 调用的核心特点是:多步骤、多轮次、有状态。一个完整的 Agent 任务往往包含十几甚至几十次模型调用,每次调用的难度差异极大——有些步骤需要深度推理,有些步骤只是格式化输出一个 JSON。
如果每次调用都用最强的模型,成本会快速累积。如果全程用便宜模型,复杂任务容易崩掉。混合路由是解法:根据当前步骤的实际需求,动态选择合适的模型。
本文以一个真实的文档审查 Agent 为例,讲解如何设计模型调度策略,把成本从 ¥3.5/次压到 ¥1.2/次。
一、Agent 的典型调用结构
大多数 Agent 架构遵循 Planning → Execution → Reflection 的循环:
用户请求
│
▼
Planning(任务分解、制定步骤)
│
▼
Execution Loop:
├─ Tool Call(调用工具/API)
├─ Information Extraction(从结果提取信息)
└─ Code Generation(生成代码/脚本)
│
▼
Reflection(评估结果、决定是否继续)
│
▼
Final Synthesis(汇总输出)
每个阶段对模型能力的需求截然不同:
| 阶段 | 核心难点 | 对模型的要求 |
|---|---|---|
| Planning | 任务理解、复杂分解 | 强推理、长上下文 |
| Tool Call | 参数填充、格式遵循 | 指令遵循即可 |
| Information Extraction | 结构化抽取 | 快速、便宜 |
| Code Generation | 语法正确、逻辑完整 | 中等能力 |
| Reflection | 错误识别、策略调整 | 强推理 |
| Final Synthesis | 语言组织 | 中等能力 |
二、模型分层策略
根据国产模型的实际能力和价格,可以建立三个层次:
强推理层(¥4~8/M tokens)
适用于 Planning 和 Reflection 阶段,需要真正的逻辑推理:
- DeepSeek-R1:最适合复杂推理、数学、代码逻辑分析
- Qwen-Max:综合能力强,长上下文处理好,适合复杂任务规划
使用场景:任务分解、错误根因分析、策略调整决策
中等能力层(¥0.8~2/M tokens)
适用于代码生成、内容总结、结构化输出:
- Qwen-Plus:性价比高,代码能力不错
- DeepSeek-V3:代码生成优秀,价格合理
使用场景:生成代码片段、内容改写、最终报告整合
快速廉价层(¥0.1~0.3/M tokens)
适用于简单提取、格式转换、路由判断:
- Qwen-Turbo:延迟低,价格极低
- DeepSeek-V3(非思考版):在简单任务上表现稳定
使用场景:从工具返回结果中提取字段、判断 yes/no、格式化输出 JSON
三、动态路由代码实现
下面是一个可直接使用的 Agent 模型路由器:
from enum import Enum
from dataclasses import dataclass
from openai import OpenAI
import tiktoken
class TaskComplexity(Enum):
SIMPLE = "simple" # 格式转换、字段提取
MEDIUM = "medium" # 代码生成、内容总结
COMPLEX = "complex" # 规划、推理、反思
@dataclass
class ModelConfig:
model_id: str
input_price: float # 元/M tokens
output_price: float # 元/M tokens
max_context: int
# 模型配置表(价格仅供示意,以官方为准)
MODEL_REGISTRY = {
"strong": ModelConfig(
model_id="deepseek-reasoner",
input_price=4.0,
output_price=16.0,
max_context=64000
),
"medium": ModelConfig(
model_id="qwen-plus",
input_price=0.8,
output_price=2.0,
max_context=131072
),
"fast": ModelConfig(
model_id="qwen-turbo",
input_price=0.3,
output_price=0.6,
max_context=131072
),
}
class AgentModelRouter:
"""根据任务类型动态选择模型"""
def __init__(self, base_url: str, api_key: str):
self.client = OpenAI(base_url=base_url, api_key=api_key)
self.total_cost = 0.0
self.call_log = []
def _select_model(self, task_type: str, context_length: int) -> ModelConfig:
"""
根据任务类型和上下文长度选择模型
task_type 枚举值:
planning / reflection -> strong
code_generation / synthesis -> medium
tool_call / extraction / route -> fast
"""
complexity_map = {
"planning": "strong",
"reflection": "strong",
"error_analysis": "strong",
"code_generation": "medium",
"synthesis": "medium",
"rewrite": "medium",
"tool_call": "fast",
"extraction": "fast",
"route": "fast",
"format": "fast",
}
tier = complexity_map.get(task_type, "medium")
config = MODEL_REGISTRY[tier]
# 超出上下文限制时自动升级到支持长上下文的模型
if context_length > config.max_context * 0.8:
config = MODEL_REGISTRY["medium"] # qwen-plus 有 131k context
return config
def call(
self,
task_type: str,
messages: list[dict],
max_tokens: int = 2048,
**kwargs
) -> str:
"""统一调用入口,自动路由到合适模型"""
# 估算上下文长度(粗略估计,避免引入复杂依赖)
context_text = " ".join(m.get("content", "") for m in messages)
estimated_tokens = len(context_text) // 4 # 粗略估算
config = self._select_model(task_type, estimated_tokens)
response = self.client.chat.completions.create(
model=config.model_id,
messages=messages,
max_tokens=max_tokens,
**kwargs
)
# 记录费用
usage = response.usage
cost = (
usage.prompt_tokens / 1_000_000 * config.input_price
+ usage.completion_tokens / 1_000_000 * config.output_price
)
self.total_cost += cost
self.call_log.append({
"task_type": task_type,
"model": config.model_id,
"tokens": usage.total_tokens,
"cost_cny": round(cost, 6),
})
return response.choices[0].message.content
def cost_report(self) -> dict:
"""返回本次 Agent 运行的费用报告"""
by_model = {}
for entry in self.call_log:
m = entry["model"]
by_model.setdefault(m, {"calls": 0, "cost": 0.0})
by_model[m]["calls"] += 1
by_model[m]["cost"] += entry["cost_cny"]
return {
"total_cost_cny": round(self.total_cost, 4),
"total_calls": len(self.call_log),
"by_model": by_model,
}
在 Agent 中使用
router = AgentModelRouter(
base_url="https://your-api-endpoint/v1",
api_key="your-key"
)
# Planning 阶段:用强模型
plan = router.call(
task_type="planning",
messages=[
{"role": "system", "content": "你是一个专业的文档审查专家"},
{"role": "user", "content": f"请分析以下合同的审查重点:\n{contract_text}"}
],
max_tokens=1024
)
# 信息提取:用快速模型
extracted = router.call(
task_type="extraction",
messages=[
{"role": "user", "content": f"从以下文本提取关键条款,输出 JSON:\n{clause_text}"}
],
max_tokens=512
)
# 代码生成:用中等模型
code = router.call(
task_type="code_generation",
messages=[
{"role": "user", "content": f"根据以下规则生成 Python 验证函数:\n{rules}"}
],
max_tokens=2048
)
# 反思阶段:用强模型
reflection = router.call(
task_type="reflection",
messages=[
{"role": "user", "content": f"评估以下审查结果是否完整,指出遗漏:\n{result}"}
],
max_tokens=512
)
print(router.cost_report())
四、真实案例:文档审查 Agent 的成本优化
场景描述
一个合同审查 Agent,工作流程:
- 解析合同结构(Planning)
- 逐条款提取关键信息(Extraction × N)
- 对每条款进行风险判断(Tool Call × N)
- 生成审查报告草稿(Synthesis)
- 检查报告完整性(Reflection)
- 最终输出(Format)
典型合同约 8000 tokens,每次审查触发约 20 次模型调用。
优化前:全程 DeepSeek-R1
| 调用阶段 | 次数 | Input tokens | Output tokens | 费用 |
|---|---|---|---|---|
| Planning | 1 | 8500 | 800 | ¥0.047 |
| Extraction | 10 | 1200 × 10 | 300 × 10 | ¥0.528 |
| Tool Call | 10 | 800 × 10 | 200 × 10 | ¥0.352 |
| Synthesis | 1 | 5000 | 1500 | ¥0.044 |
| Reflection | 1 | 6000 | 600 | ¥0.034 |
| Format | 1 | 3000 | 800 | ¥0.025 |
| 合计 | 24 | ¥1.03 |
注:此处 R1 价格按缓存命中后的折扣价计算,实际冷调用更贵,¥3.5/次 是无缓存的真实测量值。
优化后:混合路由
| 调用阶段 | 模型 | 估算费用 |
|---|---|---|
| Planning × 1 | DeepSeek-R1 | ¥0.047 |
| Extraction × 10 | Qwen-Turbo | ¥0.021 |
| Tool Call × 10 | Qwen-Turbo | ¥0.014 |
| Synthesis × 1 | Qwen-Plus | ¥0.014 |
| Reflection × 1 | DeepSeek-R1 | ¥0.034 |
| Format × 1 | Qwen-Turbo | ¥0.003 |
| 合计 | ¥0.133 |
缓存命中场景下从 ¥1.03 降至 ¥0.13,冷调用场景从 ¥3.5 降至 ¥1.2 左右,降幅约 65%。
质量影响:在 100 份合同的测试集上,审查准确率从 91% 降至 89%(2% 的损失换取 65% 的成本节省,可接受)。
五、设计原则总结
1. 只有"决策点"才需要强模型
Planning 和 Reflection 是真正的决策点——选错了会导致整个 Agent 跑偏。这两个阶段不要省钱。执行阶段的大量重复性调用才是成本的大头,这里用廉价模型影响有限。
2. 先测试再降级
不要拍脑袋决定哪个步骤可以用便宜模型。先用强模型跑 20-50 个样本,记录每步的输出质量,再逐步把"通过率高"的步骤换成廉价模型,用 A/B 测试验证质量损失是否可接受。
3. 上下文长度是隐藏成本
很多开发者只关注调用次数,忽略了上下文累积。Agent 到第 15 步时,messages 历史可能已经有 20k tokens,每次调用都要付这部分的钱。定期做上下文压缩(summarization)可以降低后期调用成本。
4. 失败重试别用强模型
Tool Call 失败重试时,不需要自动升级到更强的模型——大多数失败原因是格式问题或网络超时,换模型没用。只有连续失败 3 次才考虑升级。
5. 把路由逻辑封装起来
不要在业务代码里写 if task == "planning": model = "deepseek-r1"。把路由逻辑封装成独立模块,方便统一调整策略、接入新模型、做成本监控。如果想在网关层统一管理这套混合路由,笔者开发的 TheRouter 支持通过标准 model 参数切换模型,Agent 代码无需改动即可实现跨厂商的混合路由。
结语
Agent 的模型调度不是玄学,是工程权衡。核心公式很简单:在不影响最终质量的前提下,把尽可能多的调用分配给廉价模型。
难点在于找准"影响最终质量"的边界——这只能通过测量而不是假设来确定。建议每个上线的 Agent 都加上费用追踪(参考下一篇),这样才有数据支撑后续的优化决策。
作者:TheRouter 开发者,专注 AI 模型路由网关。项目主页:therouter.ai