用置信度路由降低大模型调用成本
作者分享在工单分类和发票提取中使用模型级联:小模型先处理,置信度不足时升级到大模型,并记录难例用于后续评估。其报告 API 支出下降超过 60%、准确率基本不变,重点讨论置信度估计与阈值选择。
AI 深度解读
让便宜的小模型先处理请求,拿不准再交给大模型,能省多少 API 费用?作者把模型级联(model cascading)用在客服工单分类和发票提取上,报告账单下降超过 60%,准确率几乎不变。关键不只是接入两个模型,而是建立一套可验证的升级规则。
这套方法适合输出短、结构明确、能够判定对错的任务。对程序员最有用的部分,是如何读取置信信号、用真实样本选择阈值,以及识别那些看起来很自信、实际可能答错的结果。
省钱的关键,是让多数请求停在小模型
作者的判断是:实际业务里,不少请求并不难。明确的工单分类、格式固定的发票字段提取、常见 FAQ,可能用本地 7B–8B 模型就能完成。
处理流程很直接:先调用小模型,检查输出和置信信号;达到门槛就返回,否则升级到 frontier API,并记录难例,留作评估或微调数据。
平均成本可以写成:
cost = cost_small + escalation_rate × cost_large
假设小模型便宜 20 倍,只有 15% 的请求升级,平均调用成本就是直接使用大模型的约 20%。但这是按调用成本计算的简化账目,本地 GPU、运维和重试成本还需要另算。
开头提到的 Hacker News 帖子超过 500 分、成本约降至四分之一,是作者转述的另一项案例。作者自己报告的是费用下降超过 60%,两组数字不能混用。
短输出怎么判断可信,先看概率再看差距
对于分类标签、yes/no、单个数字,作者使用 token 的对数概率(logprobs)作为信号。原文列出的服务包括 vLLM 0.6+、llama.cpp 的 llama-server 和新版 Ollama,但没有明确 Ollama 的最低版本。
作者用 Q4_K_M 量化的 Qwen2.5-7B-Instruct,启动命令是 ./llama-server -m models/qwen2.5-7b-instruct-q4_k_m.gguf --port 8080 -c 8192 -ngl 99。测试通过 /v1/chat/completions 请求,设置 max_tokens: 3、temperature: 0、logprobs: true、top_logprobs: 3,再用 jq '.choices[0].logprobs.content[0].top_logprobs' 查看候选。
logprob 为 -0.02,换算概率约为 98%;如果 billing 为 0.48、account 为 0.41,两个候选接近,就适合升级。
下面是作者精简后的生产分类路由,它把合法标签、概率和候选差距一起作为放行条件:
import math
from openai import OpenAI # openai>=1.40
small = OpenAI(base_url='http://localhost:8080/v1', api_key='none')
large = OpenAI() # 通过 API 调用 frontier model,密钥从环境变量读取
LABELS = ['billing', 'bug', 'account', 'other']
def classify(ticket: str, threshold: float = 0.85) -> dict:
prompt = f'Phân loại ticket sau. Chỉ trả lời 1 từ trong {LABELS}.\n\n{ticket}'
resp = small.chat.completions.create(
model='local',
messages=[{'role': 'user', 'content': prompt}],
max_tokens=3, temperature=0, logprobs=True, top_logprobs=5,
)
choice = resp.choices[0]
label = choice.message.content.strip().lower()
first = choice.logprobs.content[0]
prob = math.exp(first.logprob)
# top-1 与 top-2 的概率差距,比单看概率多一个判断依据
tops = sorted((math.exp(t.logprob) for t in first.top_logprobs), reverse=True)
margin = tops[0] - (tops[1] if len(tops) > 1 else 0)
if label in LABELS and prob >= threshold and margin >= 0.5:
return {'label': label, 'source': 'small', 'conf': round(prob, 3)}
big = large.chat.completions.create(
model='YOUR_LARGE_MODEL',
messages=[{'role': 'user', 'content': prompt}],
max_tokens=5, temperature=0,
)
return {'label': big.choices[0].message.content.strip().lower(),
'source': 'large', 'conf': None}
重点看 if label in LABELS ...:输出必须属于 billing、bug、account、other,概率至少为 0.85,margin 至少为 0.5。大模型名称仍是 YOUR_LARGE_MODEL 占位符,代码没有交代具体选型。
token 概率是路由信号,不是答案正确率。
尤其要检查标签如何分词。代码只读取第一个 token;如果多个标签共享前缀,或一个标签拆成多个 token,这个信号就需要重新验证。作者认为 margin 比单看概率稳定,但没有提供两者的对照数据。
长 JSON 要看一致性,阈值要靠样本选
发票 JSON 或 SQL 更长,作者不建议依赖平均 logprob,而是采用自一致性(self-consistency):让小模型多答几次,比较结果是否一致。
发票示例使用 n=3、temperature=0.7、max_tokens=300,并设置 response_format={'type': 'json_object'}。程序解析 vendor、total、date,统一供应商大小写、去掉金额里的逗号,再比较三个字段组成的结果。
三份答案中至少两份一致,就接受多数结果;否则把原文和三份草稿交给大模型。作者认为草稿能帮助大模型定位分歧,但没有给出速度或准确率的对照测试。小模型采样也有成本,原文建议 GPU 较弱时降到 2 次,并要求 100% 一致。
真正决定效果的是校准。作者建议取 300–500 条有正确标签的真实样本,记录小模型的 prob、margin 和对错,再把 threshold 从 0.5 扫到 0.99,比较升级率与整体准确率,寻找收益开始变小的拐点。
作者的工单实验中,threshold 为 0.85、margin 为 0.5,升级率约 18%,准确率比全量调用大模型低 1.2%。阈值升到 0.95,准确率再增加 0.4%,升级率却升到 41%。原文没有说明这些准确率差异是否指百分点,不能替它补充口径。
线上还要处理两件事:Q4 换成 Q8 后重新校准;监控升级率是否因新产品、新工单而突增。升级请求要串行等待两个模型,作者建议给小模型设置例如 800ms 的超时,超时后转大模型。
我的判断
我赞成把预算花在难例上,尤其适合已有标注和明确验收标准的分类、提取任务。相比所有请求直发大模型,级联增加了维护工作,但提供了可调的成本与质量边界。
不过,作者扫描阈值时假设升级给大模型的案例全部正确。这个前提会高估整体准确率。正式评估应实际运行完整链路,并用人工确认的答案核验结果。
一致性同样不等于正确。三次采样可能重复同一个错误;JSON 能解析,也不代表字段齐全、金额正确。示例对大模型返回的标签和字段也缺少完整校验,落地时需要补上。
因此,超过 60% 的降费和上述阈值,应当视为作者特定工作负载下的经验结果。原文没有完整数据集、成本明细和延迟分布,不能据此保证一般团队也能得到相同比例的收益。
能拿来做什么
- 在 Node 服务里先选一个标签固定、能人工核验的工单分类接口试点,收集至少 300 条真实标注样本,再决定是否接入级联。
- 把输出校验放在返回业务结果之前。分类检查标签白名单,发票提取检查字段、类型和业务约束;小模型与大模型都要经过检查。
- 用真实调用结果比较阈值下的成本、准确率和延迟,同时记录升级率与超时情况;更换模型或量化版本后重新校准。
- 保存升级案例及其最终确认结果,建立持续评估集,再判断是否值得微调小模型,避免只追求降低升级率。