AI Agent 开发中的模型调度策略:何时用便宜模型何时用强模型

2 阅读7分钟

前言

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,工作流程:

  1. 解析合同结构(Planning)
  2. 逐条款提取关键信息(Extraction × N)
  3. 对每条款进行风险判断(Tool Call × N)
  4. 生成审查报告草稿(Synthesis)
  5. 检查报告完整性(Reflection)
  6. 最终输出(Format)

典型合同约 8000 tokens,每次审查触发约 20 次模型调用。

优化前:全程 DeepSeek-R1

调用阶段次数Input tokensOutput tokens费用
Planning18500800¥0.047
Extraction101200 × 10300 × 10¥0.528
Tool Call10800 × 10200 × 10¥0.352
Synthesis150001500¥0.044
Reflection16000600¥0.034
Format13000800¥0.025
合计24¥1.03

注:此处 R1 价格按缓存命中后的折扣价计算,实际冷调用更贵,¥3.5/次 是无缓存的真实测量值。

优化后:混合路由

调用阶段模型估算费用
Planning × 1DeepSeek-R1¥0.047
Extraction × 10Qwen-Turbo¥0.021
Tool Call × 10Qwen-Turbo¥0.014
Synthesis × 1Qwen-Plus¥0.014
Reflection × 1DeepSeek-R1¥0.034
Format × 1Qwen-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