Agent 工程体系 · 第 7/98 篇。内容以 2026 年 9 月可验证的公开规范和稳定接口为基线;框架版本敏感能力会明确标注,不把实验行为写成通用保证。

Agent 模型选择与路由:能力、延迟、成本、回退和稳定性

在 Agent 系统中,“使用哪个模型”不是一次性的配置问题,而是运行时决策问题。一个 Agent 可能需要先判断意图,再调用工具,再根据工具结果决定下一步,最后生成面向用户的回答。不同步骤对模型的要求不同:分类步骤更重视延迟和成本,复杂规划更重视推理能力,结构化输出更重视协议遵循,最终表达则可能更重视风格和上下文理解。

因此,模型选择应被建模为:

在当前任务、当前状态和当前预算下,为当前 Agent 步骤选择一个能够满足质量约束的模型。

模型路由则是实现这个决策的运行时机制。它不仅决定“请求发给谁”,还要处理超时、限流、服务异常、输出不合格、预算耗尽、上下文超限,以及切换模型后状态如何继续。

Anthropic 将工作流描述为由预定义代码路径编排模型和工具,将 Agent 描述为由模型动态决定过程和工具使用的系统;两者都属于更大的 agentic systems 范畴。这个区分对模型路由很重要:固定工作流可以在代码中明确指定每一步模型,而动态 Agent 则需要把模型选择纳入运行循环。(anthropic.com)

一、先明确“模型选择”到底在选择什么

1. 模型不是单一的能力等级

工程上常把模型简单排列成“小模型、中模型、大模型”,但这种排列不足以指导 Agent 路由。模型能力至少包含以下维度:

  • 理解能力:能否正确识别用户意图、约束和隐含条件。
  • 推理能力:能否进行多步推导、规划和错误修正。
  • 工具使用能力:能否选择正确工具,并生成符合工具协议的参数。
  • 结构化输出能力:能否稳定输出符合 JSON Schema、函数参数或领域协议的数据。
  • 长上下文处理能力:能否在大量历史、检索结果或工具输出中保留关键事实。
  • 多模态能力:能否处理图片、音频、文件或其他输入类型。
  • 领域适配能力:在代码、法律、财务、客服、检索等特定任务上的表现。
  • 安全和策略遵循能力:能否遵守系统约束、权限边界和拒答策略。
  • 可控性:输出是否容易被温度、约束解码、结构化协议和提示词控制。
  • 一致性:同类输入是否产生稳定、可预测的行为。

因此,可以把模型 mm 的能力表示为向量:

C(m)=(cunderstand,creason,ctool,cschema,ccontext,cvision,csafety,cdomain)\mathbf{C}(m) = (c_{\text{understand}}, c_{\text{reason}}, c_{\text{tool}}, c_{\text{schema}}, c_{\text{context}}, c_{\text{vision}}, c_{\text{safety}}, c_{\text{domain}})

任务 xx 也可以表示为需求向量:

R(x)=(runderstand,rreason,rtool,rschema,rcontext,rvision,rsafety,rdomain)\mathbf{R}(x) = (r_{\text{understand}}, r_{\text{reason}}, r_{\text{tool}}, r_{\text{schema}}, r_{\text{context}}, r_{\text{vision}}, r_{\text{safety}}, r_{\text{domain}})

最基本的可行条件是:

ci(m)ri(x),ic_i(m) \ge r_i(x), \quad \forall i

这意味着模型选择首先不是优化成本,而是判断模型是否满足任务的最低能力约束。

例如,一个任务要求:

从用户问题中提取订单号,调用订单查询工具,并输出严格 JSON。

它的主要需求可能是:

理解:中
推理:低
工具调用:中
结构化输出:高
上下文:低
领域知识:低

一个推理能力很强、但结构化输出不稳定的模型,并不一定是合适的候选模型。相反,一个推理能力一般、工具参数和 JSON Schema 遵循稳定的模型,可能更适合这个步骤。

2. 模型选择还包括“模型配置”

同一个基础模型,在不同配置下可能表现为不同的运行单元。选择对象通常不只是模型名称,还包括:

模型标识
提供商或部署区域
推理强度配置
最大输出 Token
温度或采样参数
工具集合
系统提示词
结构化输出约束
超时策略
重试策略
数据处理策略

因此更准确的定义是:

p=(model,provider,prompt,tools,constraints)p = (\text{model}, \text{provider}, \text{prompt}, \text{tools}, \text{constraints})

这里的 pp 是一个模型配置档案,而不是一个裸模型名。路由系统真正选择的是配置档案 pp

这也是为什么同一个模型在“自由文本回答”和“工具参数生成”上的质量不能直接类比。提示词、工具描述、上下文长度和输出约束都会改变任务难度。

二、能力约束先于成本优化

1. 用“可行集合”筛选候选模型

假设候选模型集合为:

M={m1,m2,,mn}M = \{m_1, m_2, \dots, m_n\}

对于输入 xx,先根据能力约束筛选可行集合:

F(x)={mMC(m)R(x)}F(x) = \{m \in M \mid \mathbf{C}(m) \succeq \mathbf{R}(x)\}

只有属于 F(x)F(x) 的模型,才进入延迟和成本优化阶段。

错误的做法是直接计算:

argminmCost(m)\arg\min_m \text{Cost}(m)

这会把最便宜的模型用于所有请求。它可能在简单问答中有效,但在工具调用、复杂规划或高风险任务中,会因为错误的工具选择或错误的中间结论导致更多步骤,最终成本和延迟反而更高。

正确的决策顺序是:

  1. 识别任务类型和风险。
  2. 得到最低能力要求。
  3. 过滤不满足要求的模型。
  4. 在剩余模型中优化质量、延迟、成本和稳定性。
  5. 为失败情况准备回退路径。

2. 能力不是静态标签,而是任务条件下的测量值

“支持函数调用”只说明模型具备某种接口能力,不等于它在你的工具定义、参数约束和业务数据上可靠。

例如,模型可能能生成如下参数:

{
  "order_id": "A10086",
  "include_items": true
}

但在真实输入中,它可能出现:

{
  "orderId": "A10086"
}

或者:

{
  "order_id": 10086
}

如果工具协议要求字符串类型,这两种输出都可能导致调用失败。此时应测量的不是“模型是否支持工具调用”,而是:

P(合法工具参数x,tool schema,m)P(\text{合法工具参数} \mid x, \text{tool schema}, m)

结构化输出质量也应使用通过率衡量:

Qschema(m)=Schema 校验通过的响应数总响应数Q_{\text{schema}}(m) = \frac{\text{Schema 校验通过的响应数}} {\text{总响应数}}

对于 Agent,工具参数合法率通常比普通文本的主观可读性更接近生产质量。

三、延迟:模型延迟只是 Agent 延迟的一部分

1. TTFT 与完整响应时间

TTFT(Time To First Token) 是从请求发出到收到第一个输出 Token 的时间。它影响用户何时看到系统开始响应,尤其适用于流式交互。

完整响应时间是从请求发出到模型完成输出的时间。可以写成:

Tmodel=Tqueue+Tnetwork+Tprefill+TdecodeT_{\text{model}} = T_{\text{queue}} + T_{\text{network}} + T_{\text{prefill}} + T_{\text{decode}}

其中:

  • TqueueT_{\text{queue}}:服务端排队时间;
  • TnetworkT_{\text{network}}:网络往返时间;
  • TprefillT_{\text{prefill}}:处理输入上下文的时间;
  • TdecodeT_{\text{decode}}:生成输出 Token 的时间。

近似地:

TTFTTqueue+Tnetwork+TprefillTTFT \approx T_{\text{queue}} + T_{\text{network}} + T_{\text{prefill}}

而完整时间还要加上输出生成时间:

TtotalTTFT+NoutvdecodeT_{\text{total}} \approx TTFT + \frac{N_{\text{out}}}{v_{\text{decode}}}

其中:

  • NoutN_{\text{out}} 是输出 Token 数;
  • vdecodev_{\text{decode}} 是模型生成速度。

一个模型 TTFT 较低,不代表它一定更快完成整个 Agent 任务。如果它经常需要更多工具步骤,或者需要额外修复错误 JSON,最终端到端延迟可能更高。

2. Agent 延迟是串行关键路径的总和

设一次 Agent 运行包含 KK 个串行步骤:

Tagent=k=1K(Tmodel,k+Ttool,k+Torchestrator,k)T_{\text{agent}} = \sum_{k=1}^{K} \left( T_{\text{model},k} + T_{\text{tool},k} + T_{\text{orchestrator},k} \right)

如果存在并行步骤,则并行分支的耗时不是求和,而是取最大值:

Tparallel=max(T1,T2,,Tn)T_{\text{parallel}} = \max(T_1, T_2, \dots, T_n)

例如:

模型规划:300 ms
工具 A:800 ms
模型判断:250 ms
工具 B:600 ms
模型总结:500 ms

若全部串行:

T=300+800+250+600+500=2450 msT = 300 + 800 + 250 + 600 + 500 = 2450\text{ ms}

如果工具 A 和工具 B 互不依赖,可以并行:

T=300+max(800,600)+500=1600 msT = 300 + \max(800, 600) + 500 = 1600\text{ ms}

但并行化只适用于依赖关系允许的情况。如果工具 B 必须读取工具 A 的结果,强行并行会产生错误状态,而不是性能优化。

Anthropic 将并行化分为“分区”和“投票”:前者并行处理独立子任务,后者并行执行同一任务以获得多个尝试或视角。两者都需要在代码中明确聚合或决策规则。(anthropic.com)

3. 延迟路由不能只看平均值

生产系统应关注:

TTFT p50 / p95 / p99
完整响应 p50 / p95 / p99
工具耗时 p50 / p95 / p99
单次运行步骤数
超时率
回退率

如果模型 A 平均 400 ms,但 p99 为 8 s;模型 B 平均 600 ms,但 p99 为 1.5 s,那么对交互式系统而言,模型 B 可能更适合。

平均值掩盖了尾延迟。Agent 的串行步骤会放大尾延迟:若一次运行包含多个步骤,任意一个步骤发生慢请求,都可能拖慢最终响应。

四、成本:不只计算模型 Token 价格

1. 单次模型调用成本

设一次调用的成本为:

Ccall=NinPin+NoutPout+Ctool+Cstorage+CnetworkC_{\text{call}} = N_{\text{in}} \cdot P_{\text{in}} + N_{\text{out}} \cdot P_{\text{out}} + C_{\text{tool}} + C_{\text{storage}} + C_{\text{network}}

其中:

  • NinN_{\text{in}}:输入 Token 数;
  • NoutN_{\text{out}}:输出 Token 数;
  • PinP_{\text{in}}:输入 Token 单价;
  • PoutP_{\text{out}}:输出 Token 单价;
  • CtoolC_{\text{tool}}:工具调用成本;
  • CstorageC_{\text{storage}}:状态、日志或向量存储成本;
  • CnetworkC_{\text{network}}:跨服务传输成本。

一次 Agent 运行可能调用多个模型:

Crun=k=1KCcall,k+j=1JCtool,jC_{\text{run}} = \sum_{k=1}^{K} C_{\text{call},k} + \sum_{j=1}^{J} C_{\text{tool},j}

因此,“单次调用更便宜”不等于“单个任务更便宜”。

2. 用完整算例比较两种路由策略

假设有两个模型:

模型 单次调用成本 平均延迟 一次完成率
快速模型 mfm_f 0.001 300 ms 82%
高能力模型 msm_s 0.010 900 ms 96%

若快速模型失败后必须再调用高能力模型,则快速模型策略的期望成本为:

E[Cf]=0.001+(10.82)×0.010E[C_f] = 0.001 + (1 - 0.82)\times 0.010

E[Cf]=0.001+0.0018=0.0028E[C_f] = 0.001 + 0.0018 = 0.0028

期望延迟近似为:

E[Tf]=300+(10.82)×900=462 msE[T_f] = 300 + (1 - 0.82)\times 900 = 462\text{ ms}

这里假设失败能够被检测,并且回退不会重复执行不可逆工具。

如果直接使用高能力模型:

E[Cs]=0.010E[C_s] = 0.010

E[Ts]=900 msE[T_s] = 900\text{ ms}

从成本和平均延迟看,快速模型加回退更优;但这只有在以下条件成立时才成立:

  1. 快速模型的失败可被可靠检测;
  2. 失败不会导致错误的副作用;
  3. 回退模型能从完整状态继续;
  4. 回退本身的超时和限流可控;
  5. 82% 的成功率不是由低风险样本偏差造成的。

如果快速模型错误地调用了“退款”工具,而不是返回失败,那么回退机制无法撤销已经发生的副作用。这是“先快后强”策略最危险的边界。

五、路由:把请求送到合适的模型或工作流

1. 路由的输入不是只有用户文本

一个合格的路由器至少需要读取:

用户输入
会话状态
当前 Agent 步骤
工具可用性
任务风险
历史失败记录
剩余 Token 预算
剩余时间预算
模型健康状态
租户或用户等级
数据合规约束

可以把路由函数写成:

r=f(x,s,b,h,p)r = f(x, s, b, h, p)

其中:

  • xx:当前输入;
  • ss:Agent 状态;
  • bb:预算状态;
  • hh:模型健康状态;
  • pp:策略和权限;
  • rr:目标模型及其配置。

如果只根据用户文本路由,就无法处理“同一个问题在不同运行阶段需要不同模型”的情况。例如:

第一步:判断是否需要查订单
第二步:生成工具参数
第三步:解释订单状态
第四步:处理退款申请

这四步可以有四种不同的模型策略,因为它们的能力和风险要求不同。

2. 路由方式一:基于规则

规则路由适用于边界清晰、可解释性要求高的场景:

def route_by_rule(task):
    if task.requires_vision:
        return "vision-capable"

    if task.risk_level == "high":
        return "high-capability"

    if task.step == "tool_arguments":
        return "schema-reliable"

    if task.input_tokens > 100_000:
        return "long-context"

    return "fast"

规则路由的优点是:

  • 决策可解释;
  • 延迟低;
  • 不额外消耗模型调用;
  • 适合安全和合规硬约束。

缺点是规则容易变多,并且难以覆盖模糊任务。比如“帮我看看这份合同有没有问题”同时具有长文档、领域分析和高风险特征,不能依赖单一关键词判断。

3. 路由方式二:分类器路由

可以先使用一个轻量分类器预测任务类别:

y^=g(x)\hat{y} = g(x)

再建立类别到模型配置的映射:

m=π(y^)m = \pi(\hat{y})

例如:

faq              -> fast
tool_call         -> schema-reliable
multi-step-plan   -> high-capability
long-document     -> long-context
high-risk         -> high-capability + human review

分类器不一定必须是大模型,也可以是传统机器学习模型、规则系统或嵌入相似度匹配。Anthropic 对 Routing 模式的描述也是:先对输入分类,再把请求送入专业化的后续任务;当任务类别明显且分类准确时,这种方式可以减少不同输入之间的提示词冲突。(anthropic.com)

分类错误会造成两类问题:

  • 欠路由:复杂请求被送到能力不足的模型;
  • 过路由:简单请求被送到昂贵模型。

可以使用置信度阈值:

maxyP(yx)<τ升级到更强模型或人工确认\max_y P(y\mid x) < \tau \Rightarrow \text{升级到更强模型或人工确认}

不要把低置信度请求强行送入某个普通路径。路由器本身不确定时,保守升级通常比错误降级安全。

4. 路由方式三:能力探测或逐级升级

逐级升级不预先精确判断任务,而是先使用较便宜的模型,再根据结果决定是否升级:

快速模型
  ├─ 输出合法且满足质量检查 -> 接受
  └─ 失败或不确定 -> 高能力模型

质量检查可以包括:

Schema 校验
必填字段检查
工具名称白名单检查
事实引用完整性检查
业务规则校验
答案是否包含拒答原因
置信度或自评信号

需要注意,模型的自评结果不能单独作为质量判定。模型说“我很确定”不是可靠的正确性证明。应优先使用确定性的程序检查、工具返回值和独立评估器。

5. 路由方式四:按模型健康状态路由

即使模型在能力上最合适,也可能暂时不可用。健康状态可以表示为:

available
degraded
rate_limited
timeout-prone
disabled

路由器需要同时满足两个条件:

mF(x)m \in F(x)

以及:

Health(m)=available\text{Health}(m) = \text{available}

因此实际候选集合是:

F(x)={mF(x)Health(m)=available}F'(x) = \{m \in F(x) \mid \text{Health}(m)=\text{available}\}

健康状态不应只由人工配置,还可以由以下信号更新:

最近窗口超时率
最近窗口 5xx 比例
限流比例
p95 / p99 延迟
结构化输出失败率
工具调用失败率
人工反馈或离线评估分数

但是,健康检查本身也要避免抖动。若每一次偶发超时都立即切换模型,系统会在模型之间来回震荡。常见做法是使用滑动窗口、连续失败阈值、冷却时间和半开探测。

六、一个可执行的路由器骨架

下面的 Python 示例不依赖具体厂商 SDK,展示的是路由器的核心状态和回退语义。真实系统只需替换 call_modelvalidate

from dataclasses import dataclass
from typing import Any


@dataclass
class Request:
    text: str
    step: str
    risk_level: str
    requires_vision: bool = False


@dataclass
class Budget:
    remaining_ms: int
    remaining_cost: float


@dataclass
class ModelResult:
    ok: bool
    output: Any = None
    error: str | None = None
    cost: float = 0.0
    latency_ms: int = 0


def choose_candidates(req: Request, budget: Budget) -> list[str]:
    # 硬约束优先:不满足能力或安全要求的模型不进入候选集。
    if req.requires_vision:
        candidates = ["vision-strong"]

    elif req.risk_level == "high":
        candidates = ["strong", "strong-backup"]

    elif req.step == "tool_arguments":
        candidates = ["schema-reliable", "strong"]

    elif req.step == "planning":
        candidates = ["strong", "fast"]

    else:
        candidates = ["fast", "strong"]

    # 时间预算不足时,优先保留低延迟候选。
    if budget.remaining_ms < 800:
        candidates.sort(key=lambda x: {
            "fast": 0,
            "schema-reliable": 1,
            "strong": 2,
            "strong-backup": 3,
            "vision-strong": 2,
        }.get(x, 99))

    return candidates


def validate(req: Request, output: Any) -> tuple[bool, str | None]:
    if req.step == "tool_arguments":
        if not isinstance(output, dict):
            return False, "output_not_object"
        if "order_id" not in output:
            return False, "missing_order_id"
        if not isinstance(output["order_id"], str):
            return False, "order_id_must_be_string"

    if req.risk_level == "high":
        # 示例:高风险结果不能仅凭自由文本通过。
        if not isinstance(output, dict) or output.get("requires_review") is not True:
            return False, "review_flag_missing"

    return True, None


def call_model(model: str, req: Request, budget: Budget) -> ModelResult:
    """
    实际实现中,这里应:
    1. 设置连接和读取超时;
    2. 记录 request_id、model、步骤和预算;
    3. 调用对应模型;
    4. 统一转换供应商错误;
    5. 返回 Token、成本和延迟。
    """
    raise NotImplementedError


def route(req: Request, budget: Budget) -> ModelResult:
    last_error = None

    for model in choose_candidates(req, budget):
        result = call_model(model, req, budget)

        if not result.ok:
            last_error = result.error
            continue

        valid, reason = validate(req, result.output)
        if valid:
            return result

        # 输出不合格不是网络重试;它应触发模型升级或修复路径。
        last_error = reason

    return ModelResult(
        ok=False,
        error=f"all_candidates_failed:{last_error}",
    )

这个骨架中有三个关键点。

第一,choose_candidates 先处理能力和风险,再处理延迟预算。不能因为剩余时间少,就把不支持视觉输入的模型作为视觉任务的回退模型。

第二,网络错误和业务输出错误被区分开。网络错误可以考虑重试同一模型;Schema 错误、工具参数错误或事实校验失败,则更可能需要升级模型、缩短上下文或修正提示词。

第三,路由器返回的是统一的 ModelResult,而不是把每个供应商的异常直接暴露给 Agent 主循环。统一结果格式使得回退、指标和诊断能够跨模型工作。

七、回退:重试、切换和降级不是同一件事

1. 重试

重试是对同一个逻辑请求再次发送,目标通常是处理瞬时故障:

网络断开
连接超时
服务端临时错误
限流后等待

重试适合幂等的模型调用,但也必须设置:

最大重试次数
指数退避
随机抖动
总时间预算
请求去重标识

指数退避可以写为:

dk=min(dmax,d02k)+U(0,j)d_k = \min(d_{\max}, d_0 \cdot 2^k) + U(0,j)

其中:

  • dkd_k:第 kk 次重试前等待时间;
  • d0d_0:初始等待时间;
  • dmaxd_{\max}:最大等待时间;
  • U(0,j)U(0,j):随机抖动。

如果每个步骤都允许重试三次,十步 Agent 的最坏调用次数可能迅速膨胀。因此,重试次数必须受整个运行预算约束,而不能由每个组件独立决定。

2. 切换模型

切换模型是把同一个逻辑任务交给另一个模型,目标通常是处理:

主模型不可用
主模型延迟过高
主模型不满足输出协议
主模型能力不足
主模型成本超预算

切换模型时必须保留足够的上下文:

原始用户输入
系统约束
当前步骤
已完成工具调用
工具返回结果
已消耗 Token
已消耗时间
失败原因

但不能无条件把所有中间思考文本复制给回退模型。应优先保存结构化状态,而不是依赖不可解析的自然语言过程。

3. 降级

降级是主动降低服务目标,例如:

从完整研究报告降级为摘要
从实时检索降级为缓存结果
从复杂多步规划降级为单步回答
从自动执行降级为生成操作建议

降级不等于换一个更小模型。模型切换解决的是执行者变化,功能降级解决的是任务目标变化。

例如,支付退款服务不可用时,不能让一个普通模型“猜测退款是否成功”。合理降级是:

明确告知暂时无法执行退款
返回已验证的订单状态
提供人工处理入口或重试选项

4. 熔断

当某个模型持续失败时,路由器应暂时停止向它发送流量:

Closed      -> 正常请求
Open        -> 直接跳过主模型,使用回退
Half-Open   -> 允许少量探测请求

状态转移可以表示为:

stateDiagram-v2
    [*] --> Closed
    Closed --> Open: 失败率超过阈值
    Closed --> Closed: 请求成功
    Open --> HalfOpen: 冷却时间结束
    HalfOpen --> Closed: 探测成功
    HalfOpen --> Open: 探测失败

熔断保护的是整个 Agent 系统,而不是单个请求。没有熔断时,所有请求都先等待一个已经不可用的模型超时,再进入回退,导致回退模型也被流量冲垮。

八、稳定性:不仅是“接口可用”

1. 稳定性的四个层次

模型路由中的稳定性至少包含四层:

  1. 传输稳定性:请求能否建立、返回和在超时时间内完成。
  2. 协议稳定性:输出能否通过 Schema、工具参数和格式校验。
  3. 语义稳定性:同类任务是否得到质量相近的结果。
  4. 运行稳定性:Agent 是否能在预算、步骤和状态约束内终止。

一个模型可能传输稳定,但语义不稳定;也可能语义质量高,却经常输出非法工具参数。生产路由不能只监控 HTTP 200。

2. 用概率理解回退链

假设主模型成功概率为 p1p_1,第一个回退模型成功概率为 p2p_2,第二个回退模型成功概率为 p3p_3,并且失败事件近似独立,则完整回退链成功概率为:

Psuccess=1(1p1)(1p2)(1p3)P_{\text{success}} = 1-(1-p_1)(1-p_2)(1-p_3)

例如:

主模型成功率:0.90
回退一成功率:0.80
回退二成功率:0.70

则:

Psuccess=1(0.10)(0.20)(0.30)=0.994P_{\text{success}} = 1-(0.10)(0.20)(0.30) = 0.994

看起来成功率达到 99.4%,但这个计算存在重要前提:三个模型的失败必须大致独立。

现实中,失败往往具有相关性:

  • 三个模型都无法理解同一个含糊需求;
  • 三个模型都拿不到同一个失效工具;
  • 三个模型都受到错误系统提示词影响;
  • 回退模型复用了同一份错误缓存;
  • 所有模型都共享同一个上游数据库。

如果失败高度相关,增加模型数量并不能带来同等幅度的稳定性提升。真正有价值的回退往往要改变故障域,例如切换区域、工具代理、数据源、提示词版本或人工处理路径。

3. 不要把“错误答案”当作可回退错误

以下错误通常容易检测:

连接超时
HTTP 429
HTTP 5xx
JSON 解析失败
Schema 校验失败
工具不存在

以下错误更难检测:

工具选错但参数合法
查询结果解释错误
漏掉用户约束
把未知信息说成确定事实
把建议误写成已执行

后者不能仅通过异常捕获发现,需要增加业务校验、事实核对、结果评估或人工审批。

对于有副作用的工具,推荐采用:

规划 -> 预览 -> 校验 -> 审批 -> 执行 -> 确认

而不是:

模型直接生成参数 -> 立即执行

模型选择和回退必须遵守副作用边界:在“规划”阶段可以更积极地切换模型;在“执行”阶段必须使用幂等键、权限校验和明确的执行确认。

九、模型路由与 Agent 运行循环的关系

Agent 的运行循环可以抽象为:

观察 Observation
    ↓
决策 Decision
    ↓
行动 Action
    ↓
反馈 Feedback
    ↓
状态 State
    ↓
终止或继续

模型路由插入在“决策”阶段,但路由结果会改变后续所有状态:

flowchart LR
    O[观察: 用户输入/工具结果] --> D[决策: 识别当前步骤]
    D --> R[路由器: 能力/预算/健康状态]
    R --> M[模型调用]
    M --> V[结果校验]
    V -->|通过| A[行动: 工具调用或最终回答]
    V -->|失败| F[错误分类]
    F -->|瞬时故障| T[重试]
    F -->|能力或协议失败| B[切换模型]
    F -->|不可恢复或高风险| H[人工处理/安全降级]
    A --> S[更新状态]
    T --> M
    B --> M
    S --> D

一次完整运行的状态可以表示为:

St=(history,artifacts,tool_results,step,budget,attempts,model_trace)S_t = ( \text{history}, \text{artifacts}, \text{tool\_results}, \text{step}, \text{budget}, \text{attempts}, \text{model\_trace} )

其中 model_trace 至少应记录:

本步骤选择的模型
路由原因
候选模型
失败类型
输入和输出 Token
TTFT
完整延迟
工具调用次数
是否发生回退
回退后的结果

如果只在最终日志中记录“请求成功”,就无法回答以下生产问题:

为什么这类请求突然变慢?
为什么成本上涨?
为什么回退率增加?
是主模型失败,还是校验器变严格?
是模型能力不足,还是工具变慢?

OpenAI 的 Agents SDK 将 Agent 描述为能够规划、调用工具、协作并保留足够状态来完成多步工作的应用;其运行器可以管理工具循环、Agent 切换,并在运行完成或需要审批时停止。对于需要自定义循环、分支和模型路由的系统,也可以直接使用更底层的 Responses API,由应用自己管理模型交互、工具结果和分支逻辑。(developers.openai.com)

这形成了一个工程取舍:

  • 如果路由逻辑简单、循环固定,使用 SDK 的运行循环可以减少样板代码。
  • 如果需要复杂的候选模型评分、供应商熔断、预算分配或自定义回退,应用通常需要保留更直接的循环控制。
  • 无论使用哪种抽象,都必须能观察每次模型调用、工具调用、状态变化和回退原因。

十、按步骤分配模型,而不是给整个 Agent 固定一个模型

一个多步骤 Agent 可以这样分配:

Agent 步骤 主要目标 适合的模型特征
意图分类 快速判断任务类型 低延迟、分类稳定
参数抽取 生成字段和约束 结构化输出可靠
规划 拆解任务、选择工具 推理和工具选择能力强
工具结果解释 结合状态得出结论 上下文理解稳定
最终表达 清晰、符合产品语气 指令遵循和表达稳定
高风险执行 控制副作用 强约束、审批、可审计

这种拆分来自一个基本事实:Agent 的每一步都有自己的错误代价

假设:

分类错误:只需重新分类,代价低
工具参数错误:可能导致一次失败调用
错误退款参数:可能造成业务副作用
最终表达不佳:主要影响用户体验

越接近副作用边界,越不应只依据成本选择模型。

十一、预算路由:时间预算和成本预算要同时扣减

可以为每次运行建立预算:

B=(Btime,Bcost,Bsteps,Btokens)B = (B_{\text{time}}, B_{\text{cost}}, B_{\text{steps}}, B_{\text{tokens}})

每次模型调用或工具调用后更新:

Bt+1=BtΔBtB_{t+1} = B_t - \Delta B_t

例如:

@dataclass
class RuntimeBudget:
    remaining_ms: int
    remaining_cost: float
    remaining_steps: int
    remaining_tokens: int

    def can_continue(self, estimated_ms: int, estimated_cost: float) -> bool:
        return (
            self.remaining_ms >= estimated_ms
            and self.remaining_cost >= estimated_cost
            and self.remaining_steps > 0
            and self.remaining_tokens > 0
        )

当预算不足时,系统应有明确的终止策略:

时间不足 -> 返回已有可靠结果,不再启动新工具
成本不足 -> 切换低成本总结模型,或结束运行
步骤耗尽 -> 返回部分结果并说明未完成原因
Token 不足 -> 压缩上下文或生成摘要后继续

预算不应只限制模型调用。工具耗时和工具费用也必须计入,因为在很多 Agent 中,工具调用才是主要的端到端延迟来源。

十二、常见反例:看似优化,实际变差

反例一:所有请求都先走最小模型

问题在于小模型的错误可能无法被发现,尤其是合法但错误的工具参数。结果可能变成:

小模型错误规划
-> 调用错误工具
-> 获得无关结果
-> 大模型只能基于错误结果总结

此时最后使用大模型也不能修复已经污染的状态。

反例二:每次异常都重试同一个模型

如果错误来自模型能力或提示词,重试只会增加成本:

非法 JSON
-> 原样重试
-> 再次非法 JSON
-> 再次重试

正确路径应是:

识别为协议错误
-> 尝试修复或切换结构化输出更可靠的模型

反例三:只按模型价格路由

便宜模型若多生成两步工具调用,或者导致一次额外人工审核,总成本可能高于昂贵模型。

应比较:

E[Ctask]E[C_{\text{task}}]

而不是:

Cone callC_{\text{one call}}

反例四:回退时重新开始整个 Agent

如果主模型已经完成:

用户身份验证
订单查询
库存查询

但在最终总结阶段超时,回退模型不应重新执行前三步。正确做法是从结构化状态继续:

{
  "step": "final_response",
  "verified_user": true,
  "order": {
    "id": "A10086",
    "status": "shipped"
  },
  "inventory_checked": true
}

重新执行已完成工具不仅浪费时间,还可能产生重复写入或重复计费。

反例五:用竞速请求掩盖所有延迟问题

**请求竞速(hedged requests)**是同时向多个模型发送相同请求,取第一个合格结果。它可以降低尾延迟,但会增加调用成本:

ChedgedC1+C2C_{\text{hedged}} \approx C_1 + C_2

而不是:

min(C1,C2)\min(C_1, C_2)

更重要的是,如果请求可能触发工具调用,不能直接竞速执行两个副作用操作。竞速适合:

只读回答
只读检索
可取消的规划
具有幂等键的操作

不适合未经隔离的支付、退款、写库或发送消息。

十三、如何建立模型选择的评估闭环

模型路由不能依靠一次基准测试永久确定。应建立按任务类别分层的评估集:

简单问答
复杂推理
工具参数生成
多工具串联
长上下文
边界和拒答
高风险操作
模型回退场景
超时和限流场景

对每个模型配置测量:

Q(m,c)Q(m, c)

其中 cc 是具体任务类别,而不是一个全局平均分。

同时记录:

任务成功率
工具选择准确率
工具参数通过率
Schema 通过率
事实错误率
人工接管率
TTFT
完整延迟
Token 消耗
单任务成本
回退率
终止率

路由策略的评价也要单独测量:

Qrouter=路由后满足质量约束的任务数总任务数Q_{\text{router}} = \frac{\text{路由后满足质量约束的任务数}} {\text{总任务数}}

路由器的“分类准确率”不是最终目标。如果路由分类看起来准确,但把大量高价值请求送入成本过高的模型,或者漏掉了高风险任务,系统仍然是不合格的。

可以使用离线回放:

固定一批历史请求
固定工具返回结果
分别运行不同路由策略
比较质量、延迟、成本和回退

这样可以避免因为真实工具状态变化而误把外部因素归因于模型路由。

十四、推荐的决策顺序

一个可审计的模型选择流程如下:

第一步:确定当前 Agent 步骤

先判断当前是在:

分类
抽取
规划
工具调用
工具结果解释
最终回答
高风险执行

不能只依据用户原始问题,因为同一请求在不同步骤上的模型需求不同。

第二步:确定硬约束

检查:

是否需要视觉或文件输入
是否需要严格 Schema
是否允许工具调用
是否涉及高风险领域
是否需要长上下文
是否必须在指定区域处理数据

任何不满足硬约束的模型都直接排除。

第三步:估算剩余预算

读取:

剩余时间
剩余成本
剩余步骤
剩余 Token

预算不足时,不能继续使用默认模型配置。

第四步:读取模型健康状态

过滤:

已熔断模型
近期严重超时模型
当前不可用区域
达到租户限额的提供商

第五步:在可行候选中优化

可以定义一个经验评分:

S(m)=wqQ(m)wtT^(m)wcC^(m)+wrR(m)S(m) = w_q Q(m) - w_t \hat{T}(m) - w_c \hat{C}(m) + w_r R(m)

其中:

  • Q(m)Q(m):任务质量估计;
  • T^(m)\hat{T}(m):归一化延迟;
  • C^(m)\hat{C}(m):归一化成本;
  • R(m)R(m):稳定性或可用性评分;
  • wq,wt,wc,wrw_q,w_t,w_c,w_r:业务权重。

这个评分只能用于满足硬约束后的候选模型。高风险任务不能因为某个低能力模型便宜、快速,就通过调大 wtw_twcw_c 把它选出来。

第六步:为每个结果定义后续动作

每次调用都必须能落入明确状态:

成功并通过校验 -> 继续运行
传输失败 -> 重试或切换
协议失败 -> 修复或切换
能力不足 -> 升级
预算不足 -> 降级或终止
高风险不确定 -> 审批或人工处理

没有明确后续动作的错误,会在 Agent 运行循环中变成无限重试、静默错误或错误执行。

结语

Agent 的模型选择,本质上是在运行时为一个具体步骤寻找满足能力约束的执行者;模型路由,则负责把这个决策连接到预算、延迟、健康状态、回退和终止机制。

完整的选择逻辑不是:

选最强模型

也不是:

选最便宜模型

而是:

先判断任务需要什么能力,
再排除不满足硬约束的模型,
然后在剩余候选中优化质量、TTFT、步骤数、Token 和成本,
最后为超时、限流、协议失败、能力不足和预算耗尽定义可恢复路径。

稳定的 Agent 不依赖某一个永远可用、永远正确的模型。它依赖的是一套能够识别失败、保留状态、控制副作用、限制预算,并在不同故障域之间安全切换的运行机制。


系列导航与关联阅读

官方资料

本文依据 Agent、模型、协议与框架官方资料重新梳理;正文、示例与生产清单由 WR BLOG 编写。