技术思考问题44:Jev级联省钱会误判?

把 Agent 的方向盘交给小模型,谁来踩刹车?

级联省钱的前提不是小模型够聪明,是刹车踩得住。

[读者] 正在给 Agent 降本的后端 / 平台工程师
[痛点] 全量走大模型账单太高,换小模型又怕任务崩
[现在读] 因为你正准备上「小模型先答、拿不准再回退」这一版
[读完] 能拿到置信度校准 + 回退闸门的可执行落地方案
[旧方案] 一次任务里所有决策点,一律调大模型
    |
    v
[新需求] 成本和延迟都要压下来,任务成功率不能明显掉
    |
    v
[冲突] 小模型单题答得对,串成整条任务就崩
    |
    v
[后果] 省下的 Token,被返工和重跑吃回去

我是老李,在腾讯云做 Agent 平台相关的事。最近被问得最多的一句话是:级联到底能不能上?

我的一般回答是:能上,但你要先回答一个更难的问题——小模型答错的时候,你怎么知道它错了?

大部分团队卡在这里,不是卡在模型选型,而是卡在「没有刹车」。所以这篇不谈哪个小模型更强,只谈一件事:把方向盘交出去之后,刹车装在哪。

小模型在孤立题目上答对,是不是就等于整条任务能跑通?

决策点是按「题目类型」拆着交给小模型,还是按「置信度」统一交给一个全局阈值?

如果这个阈值是拍脑袋定的,任务成功率掉下来的时候,你靠什么把车停下来?

01、故事

我们平台上有一个 Agent 负责「查订单 → 判断异常类型 → 选工具 → 填参数 → 生成回复」这条链路。任务是:在十几秒内完成一次售后咨询的自动处理。

V1 很朴素——链路里每一个决策点,全部走同一个大模型。选工具问一次,填参数问一次,判意图问一次,决定要不要转人工再问一次。它能跑通,也能上线,但账很难看:一次任务要串行调好几次大模型,延迟叠起来,成本也叠起来。

真正让这件事变成问题的,是需求变了:业务方要求把单次任务成本压下来,P95 延迟压进可接受区间,同时任务成功率不能明显掉。于是「级联」被提上桌:简单决策先问小模型,拿不准再回退大模型。

听起来很合理。可我们第一次跑的时候,端到端任务成功率是往下走的。而那篇讨论 Agent 决策的论文里恰好有一句很扎心的话:孤立题目答得好,不等于整任务成功;未调整阈值的工具委托,曾让任务成功率下降 19 个百分点(见 arXiv:2610.21412,具体数值属该论文实验条件,不可直接外推)。

我们踩的,大概就是同一个坑。

单题答得对
整链未必通
省一笔小钱
赔一轮返工

02、问题

旧方案失效的点,不是小模型不够强,而是「单点正确率」这个指标本身失效了。

业务影响是清楚的:一次错误的工具选择会触发一次错误的工具调用,前端拿到错误结果,用户重问,整条任务重跑。省下来的是几次小模型调用,赔进去的是一次完整重跑加上用户的信任。

技术表现有三条,都很典型。第一,单点准确率看着还行,但一条链路上有七八个决策点,误差是乘起来的,不是加起来的。第二,错误会传播:参数填错,后面每一步都错,可日志里每一步都显示「成功」。第三,你无法区分「小模型很自信地答错」和「小模型明显拿不准」——因为这两者在日志里长得一模一样。

所以这一版的完成标准,我写成四条可验证的:在固定评测集上,端到端任务成功率不低于全大模型基线(容差事先声明);单位任务成本下降;P95 延迟不劣化;每一次回退都能在轨迹里被定位,看得见 kind、置信度、阈值、是否回退。

做不到第四条,前面三条都是运气。

指标看单点
失败看整链
差在传播处
补在观测点

03、原理

芒格讲的系统一与系统二,用在这里特别贴:系统一是熟悉且低风险的选择,快速完成;系统二把复杂情境交给审慎判断。级联就是让系统一先上,系统二兜底。

但真正决定成败的,是机会成本怎么算。机会成本不只是省掉的 Token,还包括错误路径导致的返工——重跑一次任务、重新调一次大模型、重发一次消息、多一次人工介入。写成一句话:级联的净收益 = 省下的调用成本 − 回退成本 − 错误路径返工成本。前半项很好算,后面两项才是账本的主体。

反直觉判断:小模型单点准确率的提升,几乎不改变级联的净收益;真正改变净收益的,是你能否在它答错的那一刻识别出它错了。

第二个反直觉判断:级联省下的不是「模型调用」,而是「重复决策」。同一种决策类型在一条链路里、在成千上万条任务里被反复问,开销才显著;一次性、高风险、不可回滚的决策交给小模型,是纯亏。

生活里的类比很好懂:老司机开车,路口下意识打方向,那是系统一;下暴雨、上结冰路面,就必须切回系统二。级联的置信度阈值,就是「下暴雨」的判据——它不该是一个全国统一的气温值,而该是按路况分开的判据。

快脑管熟路
慢脑管险途
机会成本里
返工最昂贵

04、架构

[输入] 一次任务中的单个决策请求(kind + prompt + 有限候选项)
    |
    v
[模块] 路由器 Router:小模型推理 + 阈值表 + 回退闸门
    |
    v
[数据/状态] 置信度、按 kind 校准的阈值、完整轨迹
    |
    v
[处理] 置信度 ≥ 阈值 → 采用小模型;否则回退大模型
    |
    v
[输出] 决策结果 + 来源标记 + 可回归的轨迹

边界在哪里:只有能列出有限候选项、且结果可被结构校验的决策点,才适合放进这条链路。开放式生成、需要长上下文推理、不可回滚的写操作,直接走大模型,不要放进级联。

收益是什么:把重复决策的开销从大模型挪到小模型,同时保留一条能兜底的路径。代价是什么:决策路径变复杂,可观测要求变高,你还得维护一张阈值表,并且这张表会随模型和业务漂移。

适用条件是三条同时成立:决策点可枚举、错误可被发现、错误可回退。少一条,级联就是在赌。

一档分快慢
一闸定放行
一轨留证据
一线可回退

05、实战一次

环境:Python 3.10+,不依赖特定模型 SDK(模型版本与参数以当前官方文档核验为准)。目标是把最小闭环跑通:先只做 shadow,不真正执行小模型结果。

from dataclasses import dataclass, field
from typing import Callable

@dataclass
class Decision:
    kind: str                 # 决策类型,如 tool_choice / param_fill / intent
    prompt: str
    options: list[str]        # 有限候选项

@dataclass
class Result:
    answer: str
    source: str               # shadow / small / large
    confidence: float
    traced: dict = field(default_factory=dict)

# 阈值先给保守初值,后续用真实轨迹重新拟合
THRESHOLDS = {'tool_choice': 0.90, 'param_fill': 0.95, 'intent': 0.85}

def decide(d: Decision,
           small: Callable[[Decision], tuple[str, float]],
           large: Callable[[Decision], str],
           shadow: bool = True) -> Result:
    ans, conf = small(d)
    thr = THRESHOLDS.get(d.kind, 1.0)
    traced = {'kind': d.kind, 'threshold': thr, 'confidence': conf}

    if shadow:
        # 只记录不执行:小模型答案与大模型答案并存,供事后拟合阈值
        return Result(ans, 'shadow', conf, traced)

    if conf >= thr:
        return Result(ans, 'small', conf, traced)
    return Result(large(d), 'large', 1.0, traced)

为了能直接跑,配两个占位实现(真实场景替换为模型调用):

def fake_small(d: Decision) -> tuple[str, float]:
    return d.options[0], 0.71

def fake_large(d: Decision) -> str:
    return d.options[0]

if __name__ == '__main__':
    d = Decision('tool_choice', '订单还没到,能退款吗',
                 ['refund', 'query_logistics', 'transfer_human'])
    r = decide(d, small=fake_small, large=fake_large, shadow=True)
    print(r.source, r.traced)

运行:

python cascade.py

示例输出(占位实现,仅验证链路可通):

shadow {'kind': 'tool_choice', 'threshold': 0.9, 'confidence': 0.71}

未在当前环境实测,以上为预期结果。V1 到此为止,故意留下两个问题:shadow 不执行,等于没有收益;阈值是拍脑袋的,等于没有校准。这两个问题正好是下一章要查的。

先跑通最小
再记录影子
不急着执行
先攒够证据

06、排查

诊断链一。 现象:shadow 日志里置信度几乎全落在 0.7–0.9 之间,按初版阈值几乎全部要回退,等于白干。怀疑:阈值定得太高。检查:拉出各 kind 的置信度直方图,再对每段置信度统计该段在真实轨迹里的正确率。证据:某个 kind 在 0.7 分段的实际正确率并不低。根因:置信度未校准——模型输出的分数与实际正确率不单调对应。修复:按 kind 分箱,画出可靠性曲线,再定阈值。

错误尝试: 直接把全局阈值从 0.90 统一降到 0.70。为什么错:不同 kind 的「置信度—正确率」曲线形状完全不同,统一降阈值等于把高风险决策一起放出去,成功率掉得比成本省得多。这是我们第一次真正吃到「一刀切」的苦。

诊断链二。 现象:上线后任务成功率下降,可每一步的日志都显示成功。怀疑:小模型选错了工具。检查:按 trace_id 把轨迹还原,逐个决策点与全大模型基线比对,找第一个分歧点。证据:分歧集中在 param_fill,且几乎都出现在低置信区间被放行的情况下。根因:错误传播——单步无异常,整链失败,日志层根本反映不出来。修复:param_fill 提高阈值,并加 schema 校验,校验不过直接回退。

两条链的共性:能不能查到,取决于你有没有留轨迹。

一处先定位
一证定根因
一刀切阈值
一改全盘错

07、优化

根因明确之后,V2 只做四件事,每一件都对应上一条证据。

第一,按 kind 拟合阈值。用 shadow 阶段积累的(置信度,是否正确)样本,从高置信往低放,只要累计精度还满足目标准确率就继续放。

def fit_threshold(rows: list[tuple[float, bool]], target_precision: float) -> float:
    rows = sorted(rows, key=lambda r: -r[0])   # 按置信度降序
    threshold, kept, ok = 1.0, 0, 0
    for conf, correct in rows:
        kept += 1
        ok += int(correct)
        if ok / kept >= target_precision:
            threshold = conf               # 仍达标,可以再往下放
        else:
            break
    return threshold

第二,高风险 kind 进白名单,直接不走小模型。第三,回退链路带 trace_id,任何一次回退都能回溯到具体任务和具体决策点。第四,加结构校验:工具名必须在候选项里,参数必须过 schema,校验失败即回退——把「自信地答错」挡在执行之前。

原因说清楚:前两条解决校准,后两条解决发现。新行为是同样的输入下,低置信请求自动落回大模型,且每一步都有来源标记。验证方式不写虚的:把端到端成功率、单位任务成本、P95 延迟、风险事件数四项放在同一张 A/B 报表里看,任何一项劣化都要能解释。

关于论文里 81.1–89.0% 的降本和至多 2.5 倍提速,必须说清楚:那些数字来自其特定实验条件,不能视为所有 Agent 的通用收益。我们的报表只能由我们自己的轨迹产出。相关工程侧的展开,可参考 JevAgentHarness 深度解析。

按类分箱看
按需拟合闸
校验挡错参
回退兜风险

08、演进

[同一输入] 一次任务里的同一个决策请求
    |
    +--[V1] 全局阈值 + shadow 记录
    |       代价:阈值未校准,回退率极高,收益接近零
    |
    +--[V2] 按 kind 校准阈值 + 结构校验 + 高风险白名单
    |       代价:需要离线拟合流程,需要维护阈值表并持续重拟合
    |
[Trade-off]
得到:在成功率可控前提下压成本;失去:路径更复杂、可观测要求更高;
边界:只有可枚举、可校验、可回退的决策点才适合进级联

比较一下。正确性上,V2 把「发现错误」提前到了执行之前,V1 只能事后统计。稳定性上,V2 依赖阈值表,表漂移就会退化,这是新的运维负担。复杂度上,V1 一个人半天能写完,V2 需要一条拟合流水线。成本上,V2 首次收益取决于回退率是否真的降下来,而不是模型有多便宜。适用范围上,两者都只覆盖有限候选的决策点。遗留问题仍然存在:置信度分数本身的可靠性,取决于小模型自身的校准水平,而这需要按当前官方文档核验并按版本重跑。

同输入两路
各付各的价
得在省与稳
失在复杂度

09、洞见

9.1 级联的收益来自「重复」,不来自「便宜」

反直觉判断:把最贵的那个决策点交给小模型,往往省不了多少;把最高频的那个决策点交给小模型,收益才显现。定位级联收益的正确姿势,是统计决策类型的分频次,而不是分单价。

9.2 置信度不是数字,是一张按类型分开的表

一个全局阈值之所以危险,是因为它把不同风险等级的判断压成了同一个刻度。分开拟合,本质上是在给每一种决策类型单独定义「什么叫拿不准」。

9.3 回退不是兜底,是可观测性的一部分

反直觉判断:回退机制的价值,一半在正确性,一半在让你看见小模型在哪里不行。没有轨迹的回退,只是把问题藏起来——成功率掉了,你连第一个分歧点都找不到。

9.4 论文的数字是实验条件,不是你的账

那 19 个百分点的下降、81.1–89.0% 的降本、至多 2.5 倍提速,都绑定在特定实验设定上。可以借鉴的是方法和结论方向——阈值要校准、要保留回退——不能直接搬的是数字。

便宜不是源
重复才是源
阈值不是数
是分类型的表

10、系统落地

原来有什么:一条全大模型的决策链,能跑通,但成本高、延迟长,且没有任何分级判断的能力。

本篇新增什么:一个路由器、一张按 kind 分开的阈值表、一条 shadow 通道、一个结构校验闸门、一份带 trace_id 的轨迹。

现在能做什么:在不明显牺牲任务完成率的前提下,把重复决策的开销降下来,并且任何一次异常都能回溯到具体决策点。

还缺什么:阈值表的自动重拟合与漂移告警;小模型版本升级后的回归基线;以及跨决策类型的联合评估——现在还是一个点一个点地看,缺少整链视角的自动归因。

下一步怎么演进:把 shadow 从「记录」升级为「常态对照」,让每一版小模型上线前都先跑一段影子流量;把阈值拟合嵌进 CI,模型换代即重算;再把四条指标做成固定看板,作为放量前的准入条件。

先有观测轨
再有阈值表
后有回退闸
才谈得上省

11、小结

Q1 → 单题答对不等于整链成功,验收要看端到端成功率而不是单点准确率
Q2 → 按决策类型分别拟合阈值,高风险类型走白名单强制回退
Q3 → 阈值必须由真实轨迹校准,并保留可回溯的回退闸门
状态 → 一套能跑 shadow、能拟合阈值、能结构校验、能回退的最小级联闭环

单点不作数
整链才算数
阈值要校准
回退要留门

12、作业

12.1 理解题:为什么「小模型单点准确率 90%」不足以支撑级联上线?

参考答案:因为链路是多决策点串联,误差乘起来会放大;更重要的是单点准确率不告诉你错在哪、能不能发现、能不能回退。机会成本里返工占大头,而这部分在单点指标里完全不可见。

12.2 实战题:把本文的 shadow 模式改造为按 kind 分别拟合阈值,并给出验证方式。

参考答案:先用 shadow 收集(置信度,是否正确)样本,按 kind 分组;对每组调用 fit_threshold 得到候选阈值;写入阈值表前先在一段影子流量上回放,比较端到端成功率、成本、P95 延迟与风险事件数四项,全部不劣化才放量。

12.3 排障题:上线后任务成功率下降,但每一步日志都显示成功,你从哪里查起?

参考答案:先从 trace_id 还原整条轨迹,与全大模型基线逐决策点比对,找第一个分歧点;再统计该点所在 kind 的置信度分布与阈值,判断是校准问题还是阈值问题;最后加结构校验把这类错误挡在执行之前。

12.4 架构判断题:所有决策点都接级联,是否合理?

参考答案:不合理。级联只适合可枚举、可校验、可回退的决策点。开放式生成、长上下文推理、不可回滚的写操作应当直接走大模型,否则回退成本会吞掉全部收益。

题在纸上练
证在轨上找
判在取舍间
行在闸门后

13、思考

把方向盘交给小模型这件事,本身没有对错。有对错的是:你有没有在交出去的同时,装上一只能被验证的刹车。

芒格的双系统告诉我们快慢要分工,机会成本告诉我们账要算全。合起来就是一句工程判断:省钱要以不明显牺牲任务完成率为约束,而约束的执行点,是那张按决策类型分开的阈值表和那条随时能回退的路。

下一次有人问你「级联能不能上」,别急着报模型名字,先问一句:小模型答错的时候,你怎么知道?

一快辨熟路
一慢审险局
一闸定取舍
一轨见真章

posted @ 2026-10-26 20:45  李福春  阅读(6)  评论(0)    收藏  举报