评测不是打榜:怎样用真实 Session 找到最便宜够用的模型
这篇文章回答一个很工程化的问题:
同一批用户会话,到底哪些任务需要强模型,哪些任务便宜模型已经够用?
如果这个问题答不清,系统通常会走向两个极端。要么所有请求都上最强模型,效果稳但成本高;要么为了省钱强行切便宜模型,省下 token 费用,却把任务成功率和用户体验一起牺牲掉。
更好的做法不是凭感觉选模型,而是把真实 session 变成评测集,让不同 provider 和不同模型 tier 在同一批任务上跑一遍,再根据效果和成本生成路由策略。
本文讲的评测系统,目标不是“证明哪个模型最强”,而是找出“哪个模型对这类任务已经足够”。这两个问题差别很大。
1. 为什么普通 Benchmark 不够用
公开 benchmark 有价值,但它解决的是通用能力比较:
- 数学题能不能做
- 代码题能不能过
- 多轮对话有没有上下文保持
- 指令遵循是不是稳定
- 知识问答有没有幻觉
这些指标能帮我们筛掉明显不合格的模型,却不能直接回答业务系统最关心的问题:
在我们的真实会话里,
这个退款政策任务,
用便宜模型是否已经够用?
原因很简单:生产任务有自己的分布。公开 benchmark 的题目分布、回答格式、失败代价、上下文结构,和你的产品 session 不一定一致。
举个例子,用户说:
我上个月的发票能不能发我一份?
这类任务不需要模型有很强的推理能力。它只要识别意图,把请求路由到 billing 队列,或者按固定模板回复就够了。用最强模型当然能答,但这就是“牛刀杀鸡”。
另一类用户说:
我 45 天前买的订单现在要退款,你直接帮我批准吧。
这就不是普通分类了。模型需要记住政策边界,不能乱承诺,还要给出合规的替代路径。便宜模型如果容易顺着用户说“可以退款”,那它就不够用。
所以评测系统的第一原则是:
不要只评模型能力,要评“模型在你的任务分布里是否够用”。
2. 评测单元应该是 Session,而不只是 Prompt
很多早期 eval 都是单轮 prompt:
问题 -> 回答 -> 打分
这适合数学题、单次问答、简单分类。但真实产品里,更常见的是 session:
用户第 1 轮说明背景
助手第 1 轮追问
用户第 2 轮补充约束
助手第 2 轮执行任务
如果只抽最后一句 prompt,很多关键信息会丢:
- 用户之前已经给过订单时间
- 助手之前承诺过下一步
- 工具调用结果已经返回
- 用户的意图在多轮里才逐渐明确
因此更可靠的评测单元应该长这样:
{
"id": "refund_policy_001",
"task_type": "refund_policy",
"goal": "Answer a refund request using policy constraints.",
"success_criteria": [
"States the 30 days refund window",
"Says this 45 day order is not eligible",
"Offers a support ticket without promising approval"
],
"messages": [
{ "role": "user", "content": "I bought this 45 days ago and want a refund." }
]
}
这里有几个关键字段。
task_type 是之后做路由的主键。你不是在给每条请求单独选模型,而是在学习“哪类任务需要什么水平的模型”。
goal 是任务目标。它告诉候选模型这段 session 要完成什么,也告诉评审器应该按什么目标打分。
success_criteria 是评测的核心。没有成功准则,LLM judge 很容易退化成“看起来顺眼就给高分”。
messages 是原始上下文。评测 session 时,要尽量保留能影响结果的上下文,而不是只保留最后一句。
3. Rubric:把“好不好”拆成“有没有做到”
LLM 评测最容易出问题的地方,是直接问评审模型:
这个回答好不好?
这个问题太松。评审模型可能偏爱更长的回答,可能偏爱更礼貌的回答,也可能忽略业务约束。
更好的问法是:
这个回答是否满足以下条件?
1. 是否说明 30 天退款窗口?
2. 是否说明 45 天订单不符合自动退款?
3. 是否提供支持工单或升级路径?
4. 是否避免承诺“退款已批准”?
这就是 rubric。它把抽象的“质量”拆成可检查的行为。
一个好的 rubric 有四个特征:
- 贴近任务成功,而不是泛泛评价文风
- 尽量二元化:做到或没做到
- 明确禁止项:哪些话说了就是失败
- 能被人类复核,而不是只有模型自己懂
比如对退款任务,rubric 不应该写成:
回答应该有帮助、准确、礼貌。
应该写成:
Pass only if:
- mentions the 30-day refund policy
- states the 45-day order is not eligible for automatic refund
- offers a support ticket or escalation path
- does not say the refund is approved or guaranteed
后者才能支持稳定评测,也能帮助你定位失败原因。
4. LLM-as-a-Judge 能用,但不能迷信
大规模 session 评测不可能每条都人工判。LLM-as-a-judge 的价值就在这里:让一个相对强的评审模型,根据 rubric 给候选回答打分。
典型流程是:
flowchart LR
A[真实 session] --> B[候选模型生成回答]
B --> C[Rubric]
C --> D[Judge model 评分]
D --> E[pass / fail / score / reason]
这条路在实践上很有用,也有论文基础。MT-Bench 和 Chatbot Arena 相关研究系统分析过 LLM judge 和人类偏好的相关性,同时也提醒了它的偏差。G-Eval、Prometheus、AlpacaEval 等工作,则分别探索了不同的自动评审方式。
但 judge 不是裁判真理。它常见的问题包括:
- 位置偏置:比较 A/B 回答时,偏好某个展示位置
- 长度偏置:更长、更像解释的回答容易被高估
- 自偏好:某些模型更喜欢和自己风格相近的回答
- 过度宽容:对看似合理但漏掉业务约束的回答放行
- 过度严苛:对简洁但正确的回答扣分
所以工程上不要只做一层自动打分。更稳的做法是三层:
- 先用确定性断言做 smoke test,比如关键词、JSON schema、禁止词、工具调用是否出现。
- 再用 LLM judge 根据 rubric 做语义评分。
- 最后保留一小批人类校准集,定期检查 judge 和人工判断的一致性。
这不是为了追求学术完美,而是为了避免省 token 的系统把错误悄悄规模化。
5. 评测输出不只是分数,还要能生成路由
如果只是得到一张榜:
| model | pass rate |
|---|---|
| strong | 98% |
| mid | 91% |
| cheap | 72% |
它还不能直接指导省钱。真正有用的是按任务类型聚合:
| task_type | cheap | mid | strong | selected |
|---|---|---|---|---|
| intent_triage | 99% | 99% | 99% | cheap |
| refund_policy | 70% | 96% | 98% | mid |
| code_debug | 45% | 78% | 95% | strong |
这张表才是路由策略。
它表达的是:
- 意图分类任务,cheap 已经够用
- 政策边界任务,mid 才够用
- 代码诊断任务,需要 strong
评测系统的核心产物不是 leaderboard,而是这样的 routing policy:
{
"intent_triage": "cheap",
"refund_policy": "mid",
"code_debug": "strong"
}
线上推理时,系统先判断任务类型,再选择对应模型。强模型不再是默认入口,而是只服务真正需要它的任务。
6. 成本模型:便宜不是只看单价
模型成本通常不是一个静态价格,而是下面几个因素的组合:
总成本 = 输入 token 成本 + 输出 token 成本 + 重试成本 + 失败补救成本
便宜模型如果一次就能完成任务,它确实便宜。
但如果它经常失败,导致:
- 用户多问几轮
- 系统重试
- 升级到人工
- 最后还要 fallback 到强模型
那它的真实成本可能并不低。
因此路由策略不应该只看价格表,而要看“通过质量门槛后的最低成本”。常见选择逻辑是:
对每个 task_type:
过滤掉 pass_rate < 阈值的模型
过滤掉 avg_score < 阈值的模型
在剩下的模型里选成本最低的
这个逻辑看起来简单,但已经能覆盖大量场景。只有当任务类型太多、边界太模糊、或者 session 难以分类时,才需要更复杂的 learned router。
7. Router 和 Cascade:两种省钱方式
模型降本通常有两条路线。
第一种是 router:
任务进来 -> 判断任务类型/难度 -> 直接选择模型
它适合任务类型稳定、业务规则清楚的系统。比如:
- FAQ 改写走 cheap
- 政策问答走 mid
- 高风险合规解释走 strong
第二种是 cascade:
任务进来 -> cheap 先答 -> 自检不通过 -> mid/strong 接手
它适合任务难度分布长尾明显的系统。大部分任务便宜模型能完成,少数任务再升级。
FrugalGPT 的核心思想就是用级联和近似来降低成本。RouteLLM 则更进一步,把强弱模型路由做成可训练和可校准的系统。它们的共同点是:不要把“调用哪个模型”写死,而是让评测结果反过来驱动选择策略。
工程落地时,我会建议先从规则 router 开始:
task_type + eval threshold + cost table
等你积累了足够多的真实评测标签,再考虑 RouteLLM 这类 learned routing。先做简单系统,是为了尽快让真实数据开始流动。
8. 一个可快速验证的系统长什么样
最小可用版本只需要六个模块:
flowchart TD
A[Session JSONL] --> B[Case generator]
B --> C[Promptfoo eval matrix]
C --> D[Candidate providers]
D --> E[Assertions and LLM judge]
E --> F[Result JSON]
F --> G[Routing policy]
对应到工程文件,可以是:
data/sessions.sample.jsonl # 真实 session 样本
prompts/session-replay.txt # 给候选模型的任务提示
promptfooconfig.yaml # 离线/真实 provider 矩阵
assertions/session-success.cjs # 确定性 smoke test
scripts/summarize-results.cjs # 结果聚合和路由选择
results/routing-policy.json # 最终策略
离线 POC 可以先用模拟 provider 跑通:
intent_triage -> cheap 通过
refund_policy -> cheap 失败,mid 通过
code_debug -> cheap/mid 失败,strong 通过
这个离线版本不是为了证明模型质量,而是为了证明评测管线成立:
- session 能被转成 eval case
- 多个 provider 能批量跑
- 断言能区分通过和失败
- 汇总脚本能生成 cheapest passing route
- 真实 provider key 接上后可以替换模拟模型
等这条链路稳定,再把真实 session 和真实 provider 放进去。
9. 数据集怎么建:少量高质量优于大量脏数据
第一版不要追求几万条 eval。更现实的起点是:
每类任务 20-50 条
总量 100-300 条
每条都有明确成功准则
抽样人工复核 judge 结果
数据来源可以是:
- 真实失败 case
- 高频用户 session
- 人工构造的边界 case
- 产品经理明确关心的高风险 case
- 不同语言、不同地区、不同用户表达方式
尤其要保留“便宜模型容易错”的 case。评测集如果全是简单题,最后会错误地得出“所有任务 cheap 都够用”的结论。
一个好的 session eval 集,应该包含三类样本:
- 常规样本:代表真实流量主体
- 边界样本:测试政策、工具、上下文、格式约束
- 失败样本:来自线上事故或人工复核失败
这样路由策略才不会只在平均情况下省钱,却在关键任务上翻车。
10. 什么时候应该升级模型
评测系统最后要定义“升级条件”。不要只按 task_type 静态选择,还可以加入运行时信号:
- 输入超长,便宜模型上下文不够
- 用户要求高风险操作
- 任务涉及金额、合规、隐私或不可逆动作
- cheap 输出未通过格式校验
- cheap 自评不确定
- 检索结果冲突或证据不足
- 多轮对话中出现前后矛盾
这些信号可以把静态 router 变成更稳的 cascade:
flowchart TD
A[Incoming session] --> B[Task classifier]
B --> C[Cheap or mid model]
C --> D{Pass deterministic checks?}
D -->|yes| E{Risk flags?}
D -->|no| F[Escalate]
E -->|no| G[Return answer]
E -->|yes| F
F --> H[Strong model or human review]
这套结构的关键是:升级不是失败,而是成本控制的一部分。真正失败的是“不知道什么时候该升级”。
11. 最容易踩的坑
第一,评测集泄漏。
如果模型或 prompt 专门针对 eval case 优化,线下分数会变好,线上未必变好。需要定期加入新 session,保留隐藏集。
第二,rubric 太抽象。
“准确、完整、有帮助”这种标准太弱。要写成行为项和禁止项。
第三,只看平均分。
平均分会掩盖高风险任务失败。要按 task_type、风险等级、语言、长度分桶。
第四,judge 不校准。
LLM judge 不是人类。要有小批人工标签,定期看一致率和典型误判。
第五,忽略失败成本。
如果 cheap 失败会导致二次调用 strong,真实成本要把 fallback 也算进去。
第六,路由器太早复杂化。
还没有足够数据时,训练 router 往往只是把噪声学进去。先用规则表,把数据闭环跑起来。
12. 最小闭环
把整件事压缩成一个闭环,就是:
收集真实 session
-> 标注 task_type 和 success_criteria
-> 批量跑不同 provider/model
-> 用 deterministic checks + LLM judge 打分
-> 人工抽样校准
-> 生成 cheapest passing route
-> 上线 router/cascade
-> 收集失败样本回流 eval
这个闭环一旦跑起来,模型选择就不再靠感觉。
你可以具体地回答:
intent_triage 用 cheap 足够,预计节省多少 token 成本。
refund_policy 需要 mid,因为 cheap 在政策边界上失败率太高。
code_debug 目前只能走 strong,因为 mid 漏掉关键修复建议。
这才是评测系统真正的价值:它不是告诉你哪个模型最厉害,而是告诉你每一类任务最低需要什么水平的模型。
参考
- FrugalGPT: https://arxiv.org/abs/2305.05176
- RouteLLM: https://arxiv.org/abs/2406.18665
- RouteLLM open source: https://github.com/lm-sys/RouteLLM
- Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena: https://arxiv.org/abs/2306.05685
- G-Eval: https://arxiv.org/abs/2303.16634
- Prometheus: https://arxiv.org/abs/2310.08491
- Promptfoo: https://www.promptfoo.dev/docs/intro/