前一阵我们团队在做电商场景的 AI 购物助手,就是把大模型接进商品导购、参数对比、优惠计算这类真实买卖链路里。模型上线后第一个问题不是“效果好不好”,而是“怎么量化它到底好不好”。传统评估指标在生成式任务里几乎失效——你没法用“是否包含关键词”或“embedding 余弦相似度”去衡量一段推荐理由是否真的帮到了用户。我们最后把方案收敛到了 LLM-as-a-Judge,也就是让一个大模型去当裁判,给助手回答打分、写评语,再从评语里反推质量瓶颈出在哪一环。这篇内容就把这套“在线评测 + 质量归因”的工程实践拆开讲一遍,覆盖评测维度设计、Judge 模板编写、偏差治理、线上采集与归因分析,适合正在做大模型应用评测、AI 导购类产品或者还在为“不知道该怎么评估 LLM 输出好坏”发愁的团队参考。
1. 评测目标与场景拆解
1.1 购物助手到底在替用户体验什么
AI 购物助手表面上是在“回答问题”,实际在电商语境里,它至少叠加了三层能力。第一层是商品匹配,用户说“三百左右适合通勤的蓝牙耳机”,助手得准确理解“三百左右”是预算上限而不是参考价,“通勤”意味着降噪和佩戴稳固优先级更高。第二层是信息综合,从商品库里筛出候选后,要把参数、卖点、价格、优惠规则汇总成一段有主次、有依据的表述。第三层是决策引导,比如提醒用户“这款耳机支持无线充电但通勤场景更看重续航”,或者追问“您更在意降噪还是佩戴舒适度”。
这三层里任何一层出错,用户体感都是“这助手根本没用”。更麻烦的是,它们的错误形态完全不一样。商品匹配出错表现为“推荐的耳机价格 500 元”,信息综合出错表现为“参数正确但推荐理由空洞”,决策引导出错表现为“没接住用户的隐性需求”。如果只用单一分数,比如“回答是否包含商品名称”或“是否包含价格”,根本分不清到底应该优化检索、优化 Prompt 还是换模型。这也是我们一开始就确定评测体系必须多维度的原因。
1.2 传统指标为什么撑不起生成式评测
做搜索和推荐时我们习惯用 DCG、召回率、CTR 这类指标,因为它们有明确的“正负”参照。可大模型输出是开放文本,同一个问题可以有无数种正确答法。用规则匹配去查“回答里有没有出现某个商品 ID”,只能抓到非常表面的错误,抓不到“推荐理由和用户需求不相关”这种语义级问题。用人工标注又慢又贵,尤其是线上每天几十万次对话,人工抽检比例一低,异常问题根本浮不上来。
那阵子我们团队争论过要不要上“答案对拍”——把助手回答和一个标准答案做向量相似度。实验做下来发现,购物场景的答案天然是多样化的,同一款商品可以从音质、价格、佩戴三个完全不同的角度去推荐,向量相似度低并不代表答得差。反过来,一个“答非所问但文本风格很像”的回答,相似度反而很高。最后大家达成共识:用一个大模型当裁判,用另一个大模型做选手,让裁判按电商场景的“用户视角标准”来评判选手的回答。这件事要靠谱,第一步得先把“评判标准”写清楚。
1.3 LLM-as-a-Judge 的适用前提
在把 LLM-as-a-Judge 当成主力方案之前,得先确认场景是否满足三个前提。第一个是“评判标准能否被显式表达”,购物助手的回答是否包含了正确商品、是否考虑了预算、是否提供了决策依据,这些都是可以写进评分卡里的显式规则。第二个是“裁判能力是否覆盖任务难度”,如果选手模型的输出涉及复杂逻辑推理,比如跨店铺满减计算,那裁判模型本身也得有足够强的推理能力,否则它给出的低分理由可能完全是错的。第三个是“是否有人工校验闭环”,Judge 不是一次性工具,它的打分质量需要定期和金标样本对比,不然模型一升级,裁判标准也跟着漂移。
我们当时做的第一个版本非常保守:只让 Judge 处理“单轮问答评测”和“检索质量判分”,不碰多轮复杂对话,也不让它直接决定线上策略。先跑通小范围,确认法官和人工标注的一致率到了 85% 以上,才开始铺开在线评测管道。
2. Judge 模板设计与偏差治理
2.1 评分卡到底该写几个维度
Judge 的 Prompt 是整个评测体系的灵魂。我们第一版写的评分卡只有两个维度,“有用性”和“事实正确性”,跑了一轮就发现问题:助手回答在“有用性”上得分很高,因为它推荐理由写得很流畅,但根本没有回应用户“通勤”这个场景需求。原因在于“有用性”太笼统,裁判的注意力被文本流畅度带跑了。
后来我们把评分卡拆成六个维度,并且每个维度给了非常具体的判定锚点:
- 需求覆盖度:是否识别并回应了用户显式提出的预算、场景、偏好等约束。
- 商品匹配正确性:推荐商品是否满足核心约束,价格、参数是否对应。
- 信息事实性:商品参数、优惠信息是否与商品库数据一致,有无明显的幻觉编造。
- 推荐理由充分性:是否给出了符合用户场景的决策依据,而不是泛泛而谈。
- 上下文连续性:多轮对话中是否记住了此前提到的约束条件。
- 引导友好度:追问是否自然、表述是否清晰、有没有错误承诺或误导。
每个维度独立打分,1 到 5 分,同时要求 Judge 输出一段理由,说明扣分点是什么。理由是后面做归因的关键——没有理由的分数,充其量是一个“质量体温计”,根本定位不到病灶。
2.2 Judge Prompt 的写法细节
这里贴一个我们后来沉淀下来的、相对稳定的 Judge Prompt 模板。注意它不只是列维度,还把“错误类型”显式给出来了,这能让裁判从“模糊评价”转向“分类评价”。
你是一位资深的电商导购质检专家。下面是一次购物助手与用户的对话,请根据预设标准对助手的最后一条回复进行评价。 【对话记录】 用户:{conversation_history} 助手回复:{model_response} 【评测维度】 请分别对以下维度打出 1-5 分(5 为最高): 1. 需求覆盖度:是否捕捉并满足用户明确或隐含的需求约束。 2. 商品匹配:推荐商品是否与用户约束条件一致。 3. 事实准确性:商品信息、价格、优惠等是否与已知知识库一致,有无编造。 4. 推荐理由充分性:是否提供对用户决策有帮助的依据。 5. 上下文一致性:是否沿用对话中已确认的信息。 6. 安全与规范:是否出现绝对化承诺、虚构商品、恶意贬低等。 【输出格式】 严格按 JSON 输出: { "scores": {"需求覆盖度": 0, "商品匹配": 0, "事实准确性": 0, "推荐理由充分性": 0, "上下文一致性": 0, "安全与规范": 0}, "reason": "用简洁中文说明每个扣分维度的依据,引用助手原句", "failure_modes": ["从以下候选里选,可多选:未识别预算约束/推荐价格超限/编造参数/理由空洞/遗忘上下文/其他"] } 【注意】 - 只评价助手的回复,不要评价用户提问是否合理。 - 如果助手主动澄清用户需求,不应扣分。 - 宁可漏报,不可误报。没有充分证据时,给满分。有几个细节值得单独说。第一个是“宁可漏报,不可误报”,这一条很关键。Judge 天然有“挑刺”倾向,如果 prompt 里强调“严格审查”,它会把一些可给可不给的扣分当成必扣项,导致整体分数偏低,最后你看任何回答都是“有瑕疵”,反而不利于区分质量高低。第二个是“引用助手原句”。我们要求 judge 在 reason 里引用原句,这样人工复核时能快速判断它是不是在瞎编理由。实践下来,“有引用原句”比“写得看起来很有道理”可信得多。
2.3 Judge 自身偏差的三种形态与治理
用大模型当裁判,光是 prompt 写得好还不够,它自己有一堆根深蒂固的偏差。我们实际踩到的主要有三种。
第一种是位置偏差。给 Judge 展示多个候选回答时,它倾向于给排在前面的答案更高分。购物场景里我们经常要对比“同一个问题的两个不同版本回答”,位置偏差会把 A/B 对比实验直接带偏。治理办法是交换顺序各测一次,取平均值,或者在 prompt 里明确写“两个回答质量可能相同,不要因为顺序影响判断”。
第二种是冗长偏差。Judge 普遍会认为“写得更长 = 信息更丰富”,于是给长答案高分。但电商导购里,一句话简要回答可能比洋洋洒洒三段话更精准。后来我们在评分卡里加了一条“信息密度”,明确告诉 Judge:如果回答虽然长但信息重复,要扣分。
第三种是自我偏好偏差。当选手模型和 Judge 模型是同源模型时,Judge 更容易给“风格像自己”的回答打高分。这个不太好完全消除,我们做法是混合使用了多个评测模型,并且定期跑金标样本统计 Judge 与人工标注的“一致率”,如果某段时间一致率下降了,就审查是不是模型升级导致风格偏好加重了。
注意:Judge 不是一次配好就能一直用的。模型会升级,商品库会变化,用户话术也会变化,评测标准必须当作一个持续维护的工程物,而不是一次性脚本。
3. 在线评测:从离线回归到全量抽样
3.1 评测管道整体架构先说清楚
我们的评测体系分成三层,离线回归层、在线抽样层、人工复核层。离线回归层负责每次模型版本更新前跑一个固定问题集,大约一千二百条覆盖典型场景的种子问题,快速返回质量分数变化。在线抽样层从线上真实流量里按比例抽取对话,灌入评测队列,由 Judge 打分并产出归因标签。人工复核层只处理“Judge 打了极端低分”或“归因标签置信度低”的样本,每天只需要看几百条,成本可控。
这三层缺一不可。没有离线回归,模型升级前你心里没底;没有在线抽样,你发现不了那些 offline 测试集覆盖不到的刁钻用户表达;没有人工复核,Judge 的错误判断会逐渐累积成“系统性偏见”。我们把三层的关系比作“体检 + 日常监控 + 专家门诊”——离线是定期体检,在线是动态心电监测,人工是看到异常指标后的确诊环节。
3.2 在线抽样最容易栽的坑:谁去定义“有效对话”
在线抽样不是把线上所有对话都拿去打分,那样成本太高。但“抽哪些”这件事很有讲究。我们早期只随机抽流量的千分之几,结果抽回来一大堆无效对话,比如用户只说了“你好”,助手回了“您好,请问需要什么”,这种对话对质量评估毫无价值。后来我们给抽样器加了三道过滤:
- 助手真实返回了商品推荐或参数信息,而不是纯寒暄。
- 用户消息长度不小于 4 个字符,排除“嗯”“好”这类无信息量输入。
- 对话未被用户主动中断,比如没有连续 2 条以上用户未回复的情况。
加了过滤之后,有效样本率从不到 20% 提到了接近 70%。另外,抽样时要带上完整的对话上下文,不能只截取最后一条回复。购物助手经常在第二轮才确认用户预算,到第三轮给出推荐,如果只给 Judge 看第三轮,它根本不知道预算已经被确认过,会误判“推荐价格超限”。
3.3 在线评测的延迟和成本怎么控制
在线评测对延迟不敏感,但不能完全不敏感。我们走的是“异步流式处理”:线上对话产生的日志先进 Kafka,消费端做一些字段清洗后落到存储,评测任务按批次投喂给 Judge 模型接口,整个链路延迟可以接受,但不能超过 10 分钟。一旦延迟太长,出了问题没法及时止损。
成本控制上有两个技巧。第一个是“两阶段判分”——先用一个轻量级小模型做粗筛,只有粗筛得分低于某个阈值的回答才送进完整 Judge 管道跑六维评分。购物场景里大部分回答是合格的,粗筛能挡掉 80% 左右的请求。第二个是“缓存复用”——向 Judge 提问时,把“用户需求标签 + 商品 ID 序列 + 回答类型”拼成缓存键,同一类问题短时间内只评测一次,避免同一款商品的不同用户问题被反复重跑。
3.4 人工复核闭环怎么做
人工复核的角色不是“重新打分”,而是“校准裁判”。每天系统会挑出三类样本给标注同学看:Judge 打 1 分或 2 分的低分样本、Judge 给满分但用户点了“不推荐”的样本、以及 Judge 归因标签和理由互相矛盾的样本。标注同学只需要判断一件事:Judge 的判断对不对,如果不对,错在哪里。
这些复核结果会回流成金标样本库,用来定期计算 Judge 一致率。我们统计的指标有两个——“判分方向一致率”,即 Judge 给分是否与人工同向(好或坏);“归因标签正确率”,即 Judge 给的 failure_modes 是否在人工看来戳中了要害。这两个指标是 Judge 的体检表,任何一个低于阈值,就要触发 Judge Prompt 或模型版本调整。
4. 质量归因:把一个低分翻译成一组操作项
4.1 从评语到失败模式的抽取
Judge 打完分,如果只是告诉你“这个回答只有 2 分”,价值很小。真正有价值的是它输出的那一段 reason 和 failure_modes。我们把这一步叫归因——把低分拆解成“具体的失败模式”,再把失败模式映射到“系统组件”。
Judge 的 reason 是自由文本,不能直接结构化使用,所以需要再做一层解析。我们有两条路并行。一条是规则抽取,用正则和关键词匹配 reason 里出现的模式,比如“预算”“超价”“未提及”这些词,命中就映射到“未识别预算约束”或者“推荐价格超限”。另一条是让一个轻量级模型对 reason 做摘要分类,归纳成标准失败模式。规则抽取准但覆盖面窄,模型分类覆盖广但偶尔不准,两者取交集并打上置信度,低置信度的进人工复核队列。
这里分享一个关键的归因统计教训:刚开始我们只统计“每种失败模式出现了多少次”,发现最多的永远是“理由空洞”和“未识别需求”,于是团队一股脑去调 Prompt 和 RAG 策略。后来把“产品目标”和“失败模式”做交叉之后才发现,很多“未识别需求”的根因不是模型不行,而是意图识别模块把“三百左右”归类成了“价格区间”却没把“预算上限”这个约束传下去。可见只盯着语义层面的失败模式远远不够,要结合产品流程链路做交叉分析。
4.2 失败模式到系统组件的映射
在购物助手的系统架构里,回答质量主要受四个组件影响:意图理解、检索召回、知识库数据、生成模型本身。我们把 Judge 给的 failure_modes 做了如下映射:
| 失败模式 | 最常见根因组件 | 排查方向 |
|---|---|---|
| 未识别价格约束 | 意图理解 / 排序策略 | 检查约束抽取模型是否漏掉了“左右”“以内” |
| 推荐商品价格超限 | 检索召回 | 检索时是否应用了预算过滤条件 |
| 编造参数或优惠 | 生成模型 / 知识库 | 检查生成时是否接入了商品卡事实约束 |
| 理由空洞,没有决策依据 | 提示词模板 / 生成模型 | 检查 Prompt 是否要求输出对比维度 |
| 召回商品不相关 | 检索排序 | 检查 embedding 模型对场景化描述的语义匹配 |
| 忘掉上文已确认的预算 | 对话状态管理 | 检查上下文压缩策略或记忆模块 |
这张表的作用是给团队一个“待办优先级的翻译器”。当某个失败模式频次飙升,不用开会讨论“这是不是大模型幻觉问题”,直接看对应组件最近的改动记录。我们遇到过很多次:模型本身没有变,但商品库某个类目的属性填充率下降,导致“编造参数”类失败率暴涨。如果只看失败模式不看组件,很容易把锅扣在生成模型上,白调一个月 Prompt。
4.3 归因结果怎么驱动优化
归因不是终点,终点是优化动作。我们每周会把归因标签汇总成一张“质量卡片”,主要包含三个区块:Top 5 失败模式及占比变化、每个模式对应的系统组件归属、“本次周期建议优化项”。
有一次我们发现“推荐理由充分性”的分数下降了 15%,归因标签集中在“未给出对比信息”。排查发现最近一轮 Prompt 改版中,为了让回答更简洁,删除了“请从价格、性能、场景适配三个维度给出推荐依据”的要求。这就是很典型的“归因直接指出了 Prompt 缺失项”。
另外,归因结果还能用来做“评测集迭代”。归因里出现的高频失败模式,会被实时吸收进离线回归的种子问题集。比如连续两周出现“用户说不要太贵,助手推荐了高价款”,团队就会把这类问题加进 offline 测试集,确保下次模型升级时不会在这类问题上回退。这也意味着评测体系和真实用户体验之间形成了一个持续进化的双向闭环。
4.4 一个具体案例:低频长尾问题怎么归因
举一个我们实际处理过的例子。用户问“能给老人用的手机,不要太大,能放大字体”,助手推荐了一款 6.7 英寸的手机并强调“屏幕大适合观看”,显然没有理解“不要太大”这个约束。Judge 给“需求覆盖度”打了 1 分,failure_modes 是“未识别尺寸偏好”。
如果只看这个失败模式,大家可能觉得是意图理解模块的问题,但当我们检索同类型样本时发现:凡涉及“老人用”“字体大”的问题,助手推荐的全是大屏手机,因为知识库里根本没有“大字模式 + 适中尺寸”这类组合筛选条件。根因不在意图理解,而在商品属性体系里缺少“是否有大字体模式”这个维度。后来运营团队推动商品库补齐了这个字段,同时调整了检索排序策略,这类问题的覆盖率才明显改善。
这个案例让我们确定了一件事:Judge 的 failure_mode 是“表面症状”,归因到组件只是第二层,真正的根因往往要结合业务数据链路继续下钻。这也是为什么我们强调“归因看板”必须放在业务团队看得见的地方,而不能只停留在算法团队内部。
5. 常见问题与排查技巧实录
5.1 Judge 打分和用户体验对不上,怎么办
我们遇到过最困惑的情况:Judge 打 4 分、5 分的回答,用户标注里却出现了大量点踩。后来分析发现,Judge 的评分卡只关注“内容质量”,完全没考虑“情感倾向”。很多用户点踩不是因为回答不正确,而是因为助手“太官方”“像机器人”。比如用户问“这款手机拍照真的好吗”,助手回答“该机型配置了 5000 万像素主摄,支持夜景模式”,内容完全正确,但用户想要的是某种主观判断或经验分享。
这个问题的解决方式是给 Judge 增加“语气与共情”维度,同时在在线评测系统中加入用户反馈信号融合。不能只看 Judge 分数,要把“用户是否追问”“是否点踩”“是否结束对话”这类行为信号加权进来。两套信号矛盾时,优先相信用户行为——毕竟做购物助手,最终服务对象是人,不是 Judge 模型。
5.2 同一个 Prompt 的 Judge 分数忽高忽低,怎么排查
有一次我们做回归测试,同一个问题集上午跑和下午跑,平均分差了 0.4 分左右。刚开始怀疑是线上服务抖动,排查后发现是 Judge 模型的 temperature 没固定。评测管道的稳定性对随机性极为敏感,temperature 必须固定成 0,top_p 固定成 1,所有跟评测相关的模型调用都要关闭随机采样。
还有一种情况是 Judge 模型本身有版本更新,服务侧悄悄切换了新版本。这种情况最阴间,因为分数变化毫无征兆。我们现在会在评测管道的日志里记录每次调用 Judge 的“模型版本号”,一旦发现某天分数分布整体偏移,先比对版本号变化。不要让“模型版本”成为隐式变量。
5.3 两级模型互相增强导致评分虚高,如何避免
在“选手模型”和“Judge 模型”升级之后,我们都观察到评分上涨。一开始以为效果真的变好了,后来人工复核发现是虚高——两个模型的升级方向使得它们在措辞风格上更加趋同,Judge 对“像自己”的回答天然给高分。这就是前面说的自我偏好偏差的进阶形态。
应对策略有三个。第一,加入多个异构模型交叉评测,至少保证其中一个与选手模型不是同源。第二,定期用“坏答案注入”做自检:往评测集里混入一些明显有缺陷的人工构造回答,如果 Judge 给它们的分数也变高了,说明校准失效。第三,保持人工复核金标样本的独立更新,不要用 Judge 自己打的标签去训练校准数据,否则偏差会自我强化。
5.4 快速排查速查表
为了方便大家上手,把经验汇总成一张速查表。
| 现象 | 可能原因 | 优先排查动作 |
|---|---|---|
| 在线分数整体骤降 0.3 以上 | 线上请求带上了不完整上下文 | 检查抽样器的上下文截断策略 |
| Judge 给分普遍偏高 | 评分卡缺少扣分锚点 | 补充“引用原句”与“信息密度”约束 |
| 分数正常但用户点踩率上升 | 评测维度没覆盖语气与共情 | 增加主观体验维度,融合行为信号 |
| 归因标签集中在“理由空洞” | 生成 Prompt 缺少维度引导 | 检查 Prompt 是否要求对比决策依据 |
| 同一问题多次评测结果不一致 | 模型采样参数未固定 | 强制 temperature=0,记录模型版本号 |
| Judge 判定“编造参数”但实际正确 | 商品库字段与事实不一致 | 校准知识库快照,排查延迟更新 |
5.5 关于成本的一个朴素建议
最后再提一句成本。很多人一听到 LLM-as-a-Judge 就觉得“烧钱”,其实如果控制得当,这个方案的成本远低于全人工标注。我们的经验是:最花钱的不是 Judge 本身,而是“无差别地让 Judge 评测所有回答”。加上粗筛、缓存和分层策略后,单次在线评测的实际成本可以压到极低的水平,换来的是每天稳定输出的质量监控和归因数据。相比一个质量问题在线上放一周带来的体验损失,这点成本很划算。
这套体系从搭建到稳定运行,我们用了接近两个月。如果让我回头总结,做在评测里的时间其实只占三分之一,剩下三分之二都在处理“裁判自己的可靠性”和“归因结果的可落地性”。别小看这两个问题——它们决定了一套评测体系是变成你手里的手术刀,还是变成一个只会报数的花瓶。