前面几篇默认了一件事:我们拿到一条对话,然后让 LLM-as-judge 按 Rubric 评测。但在真实系统里,「一条对话」并不总是一段干净的文本。
用户的问题可能依赖前文。Agent 的输出可能混着思考痕迹、正式答案、结构化卡片、推荐问题,甚至还有空回答、截断回答和异常标记。如果评测前不先把这些东西拆清楚,后面的分数就会建立在误读之上。
比如用户第二轮只问「那还能吃吗」,单看这一句毫无意义;放回上文,可能是在问某个商品、药品、辅食或服务方案。再比如 Agent 输出里有 <think> 或内部推理片段,如果直接参与评分,模型可能会把不该给用户看的内容也算进正式回答。再比如结尾的推荐问题卡片,在某些意图下是允许的,在另一些意图下就是违规。
所以黑盒评测的前置步骤不是调用模型,而是还原语境、拆解输出、定义哪些内容参与评分。
这一篇讲评测前的数据还原。
一、单轮文本经常不够
黑盒评测最小输入是 query 和 answer,但这不代表每条记录都应该孤立评测。
真实用户很少每轮都把上下文说完整。常见问题像这样:
第 1 轮:我家宝宝 8 个月,最近开始加辅食了。
第 2 轮:那一天吃几次?
第 3 轮:如果不爱吃怎么办?
如果只看第 2 轮,「那一天吃几次」缺少对象,模型可能不知道用户问的是奶量、辅食、药物还是训练安排。只有把同一 session_id 下的历史轮次拼出来,评测才有机会判断 Agent 有没有承接上下文。
因此评测输入应该包含两层内容:
当前轮次:本行 query / answer
会话上下文:同 session 下的前序问答
会话上下文也不能无限拼。生产数据里一个 session 可能有几十轮,如果全部塞进模型,会带来 token 成本和注意力干扰。更稳的做法是配置一个最大历史轮数,只保留当前轮之前最近的若干轮。
上下文的作用不是让模型重新评测整段会话,而是帮助它理解当前回答是否承接了用户意图。
二、先定义会话顺序
有 session_id 还不够,还要知道轮次顺序。
优先使用 request_time 排序。如果 request_time 是时间戳或可解析日期,就按时间排列;如果时间无效或缺失,则退回原始行号。没有 session_id 的记录,按单轮样本处理。
这一步看起来像数据清洗,但它会直接影响评测结论。
比如下面两条记录:
row 21:用户:我想给 6 个月宝宝换奶粉,有推荐吗?
row 22:用户:那这个适合过敏体质吗?
如果 row 22 没有上下文,Agent 回答「这款要结合过敏史判断」可能显得含糊;如果知道上一轮在谈某款奶粉,这个回答就有了明确指代。反过来,如果 Agent 在 row 22 完全忘了上一轮推荐过什么,就可能是上下文承接问题。
会话顺序不是附属信息,它决定了评测对象到底是什么。
三、Agent 输出不是纯文本
很多 Agent 的最终输出不是单一回答,而是一个混合结构:
<think>
用户在问高风险症状,需要先安抚再分流……
</think>
建议你先观察宝宝精神状态,如果持续高热或明显不适,请及时就医。
```json
{"card":"product","items":[...]}
{“card”:“questions”,“items”:[“还需要看什么症状?”]}
从用户视角看,里面至少有四类东西:
| 部分 | 是否参与正式回答评分 | 说明 |
| --- | --- | --- |
| 思考痕迹 | 否 | 不应作为用户可见内容评分,除非它真的泄露给用户 |
| 正式答案 | 是 | 主要评分对象 |
| 结构化卡片 | 视规则而定 | 有些场景需要,有些场景禁止 |
| 推荐问题 / 激发问题 | 单独检查 | 通常不影响正式答案分数,但可触发独立问题 |
如果不拆开,评测会非常混乱。模型可能把思考过程当成正式回答,也可能把卡片里的内容当成文本解释,还可能忽略推荐问题中的违规表达。
所以评测前应该先做输出解析,把原始 `answer` 拆成结构化字段。
## 四、思考痕迹要剥离,但泄露要记录
思考痕迹是一个微妙问题。
从评分角度看,思考过程不应该参与正式答案质量判断。用户真正消费的是正式答案,不能因为内部草稿里提到了某个正确事实,就给最终回答加分。
但如果思考痕迹真的出现在用户可见输出里,它又可能构成另一个问题:后台暴露或内部推理泄露。
因此解析策略要分两步:
1. 从正式答案中剥离 `<think>`、特殊标记包裹的思考片段;
2. 如果这些片段原本出现在可见 `answer` 中,保留一个结构化标记,供后台暴露规则判断。
换句话说,思考痕迹不用于证明回答正确,但可以用于判断是否泄露了不该给用户看的内容。
这是一个典型的评测边界:同一段文本,对不同规则有不同意义。
## 五、正式答案才是主评分对象
正式答案是 Rubric 评测的主体。
评测模型最终应该判断的是:用户看到的主回复是否满足当前意图下的硬性要求。
例如:
- 有没有回答用户核心问题;
- 有没有遵循必要流程;
- 有没有给出安全边界;
- 有没有编造事实;
- 有没有违反品牌或合规表达;
- 话术是否符合基本风格要求。
如果 Agent 同时输出了卡片,正式答案和卡片要综合看,但不能混为一谈。卡片可能补充了信息,也可能制造了违规。比如正式答案没有推荐商品,但卡片里推荐了不该推荐的商品,这仍然是输出层面的规则问题。
因此给 LLM-as-judge 的输入里,最好同时提供:
```json
{
"row": {
"query": "用户原始问题",
"answer": "原始回答",
"referenceOutput": "可选参考答案"
},
"parsedOutput": {
"formalAnswer": "剥离思考片段后的正式答案",
"cards": [],
"questions": []
},
"context": {
"previousTurns": []
}
}
模型可以看到原始回答,但评分锚点应该落在解析后的正式答案和可见结构上。
六、卡片要按业务规则评,不要一概而论
结构化卡片是很多 Agent 的常见输出:商品卡、文章卡、活动卡、工具入口、推荐问题等。
卡片评测最容易出两个问题。
第一,把卡片当纯文本忽略。这样会漏掉结构化输出里的违规内容,比如不该推荐时推荐了商品,或在错误场景给了购买入口。
第二,把卡片来源当成可见事实。比如业务规则要求卡片必须来自某个知识源,但黑盒数据里没有来源字段,评测模型不能凭空判断它是否来自正确来源。
所以卡片规则要拆成两类:
| 规则类型 | 黑盒中是否可评 | 示例 |
|---|---|---|
| 可见结构规则 | 可评 | 某意图禁止输出商品卡、一次只能有一种卡片类型、字段不能互相拼装 |
| 内部来源规则 | 不可评 | 卡片必须来自指定知识源、必须由某个工具生成 |
可见结构规则可以进入评分。内部来源规则应该标记为 NOT_APPLICABLE,除非上传数据额外提供了工具或知识源日志。
这能防止模型把「看起来像」当成事实判断。
七、推荐问题要单独检查
很多 Agent 会在回答末尾附几个推荐问题,用来引导用户继续聊。这类内容常常不影响正式答案质量,但可能带来独立风险。
比如推荐问题里出现:
- 竞品名称;
- 不合适的业务引导;
- 从「全智能体视角」说话,而不是从当前 Agent 视角说话;
- 承诺不存在的功能。
如果把推荐问题混进正式答案评分,容易出现两种误判:一是正式答案很好,却因为推荐问题小问题被整体降分;二是正式答案很差,推荐问题反而干扰模型注意力。
更好的方式是单独输出 questionsCheck:
{
"questionsCheck": "ISSUE",
"questionsIssues": ["FUNCTIONAL_MISLEAD"]
}
它可以进入报告的专项统计,但默认不改变正式答案分数。只有当业务明确规定某类推荐问题属于红线时,才通过 Rubric 规则把它纳入分数策略。
这样既不漏问题,也不让边缘结构污染主评分。
八、空回答要有专门规则
空回答、纯空白、只有思考过程没有正式答案,是生产数据里一定会遇到的情况。
这类样本不需要再交给 LLM 判断。规则层可以直接生成诊断:回答无效、低分、违反响应有效性要求。
这样做有两个好处。
第一,省成本。空回答没有语义判断空间,不需要花一次模型调用。
第二,口径稳定。空回答到底是 1 分还是 2 分,应该由业务规则决定,而不是每次让模型临场判断。
空回答还要单独进入问题簇,比如:
COMMON::EMPTY_ANSWER
它可能指向完全不同的根因:模型服务异常、流式输出中断、前端渲染失败、网关超时、内容安全拦截等。虽然黑盒评测不能确定真实根因,但至少能把这类输出层问题稳定收集起来。
九、截断是必要的,但要保留语义中心
生产对话里经常有超长回答和超长上下文。评测请求如果不做截断,会遇到两个问题:token 成本不可控,模型注意力被无关内容稀释。
截断不能简单粗暴地从头取固定长度。更好的策略是按字段分别处理:
- 当前
query尽量完整保留; - 当前正式答案优先保留前后关键段落;
- 历史轮次限制最大条数;
- 参考答案只保留可评估部分;
- 卡片字段只保留结构和关键文本,去掉大块无关数据。
这里的原则是:保留判断当前回答质量所需的信息,删掉不影响评测的冗余。
如果截断策略不清楚,评测会变得不稳定。模型有时看到完整上下文,有时只看到片段,分数就会漂。
十、把解析结果显式传给评测模型
评测模型既可以收到原始 answer,也可以收到解析后的结构。不要指望它每次自己从原始文本里稳定拆解。
一个合理的输入结构是:
{
"row": {
"rowId": 21,
"query": "那一天吃几次?",
"answer": "原始 Agent 输出",
"referenceOutput": null
},
"context": {
"previousTurns": [
{
"query": "我家宝宝 8 个月,最近开始加辅食了。",
"answer": "可以从高铁米粉开始,少量尝试。"
}
]
},
"parsedOutput": {
"formalAnswer": "建议每天 1-2 次,先少量观察接受度。",
"cards": [],
"questions": []
}
}
这样 prompt 可以明确要求:评分以 parsedOutput.formalAnswer 为主,结合 context.previousTurns 判断上下文承接;卡片和推荐问题按 Rubric 规则单独检查。
把解析责任前置到应用代码里,是为了降低模型负担。模型越少做机械拆解,越能专注语义判断。
十一、一个虚构例子
假设原始输出是这样:
<think>
用户在问宝宝高烧,要先判断风险,再考虑是否推荐内容。
</think>
宝宝持续高烧并伴随哭闹时,建议尽快联系医生或线下就医。
你也可以继续观察精神状态、饮水和尿量。
{"card":"questions","items":["还想了解哪款产品更适合发烧宝宝吗?"]}
解析后可以得到:
{
"thinking": "用户在问宝宝高烧,要先判断风险,再考虑是否推荐内容。",
"formalAnswer": "宝宝持续高烧并伴随哭闹时,建议尽快联系医生或线下就医。你也可以继续观察精神状态、饮水和尿量。",
"cards": [],
"questions": ["还想了解哪款产品更适合发烧宝宝吗?"]
}
正式答案可能满足高风险分流要求,因此不应该因为 <think> 里有内部分析就加分,也不应该把 <think> 当成正式回答的一部分。
但推荐问题里出现了不合适的产品引导,应该进入 questionsCheck:
{
"questionsCheck": "ISSUE",
"questionsIssues": ["FUNCTIONAL_MISLEAD"]
}
如果业务规则规定这类推荐问题不影响正式答案分数,它就只进入报告专项统计;如果规则规定高风险健康场景禁止产品引导,它就会通过 Rubric 规则影响分数。
这就是输出解析带来的好处:不同内容被放回不同评价通道,而不是混成一团。
十二、总结

黑盒评测不是拿到 answer 就立刻打分。真实 Agent 输出通常混有多轮上下文、思考痕迹、正式答案、结构化卡片和推荐问题。评测前如果不先拆清楚,后面的分数和规则判断都会变得不可靠。
一套稳一点的前置处理应该做到:
- 按
session_id和时间恢复会话上下文; - 控制历史轮次,避免上下文过长;
- 剥离思考痕迹,不让它参与正式答案加分;
- 保留泄露信号,用于后台暴露等规则判断;
- 把正式答案作为主评分对象;
- 将卡片和推荐问题拆成独立结构;
- 对空回答和异常回答走确定性规则;
- 把解析结果显式传给评测模型。
评测前先看懂答案,否则分数只是误读的结果。到了这里,逐条评测的输入、规则和输出协议都已经成型,下一步就是把评测结果展示给人:如何组织评分分布、规则遵循率、红线报告、问题证据和优化建议,让它成为 PM 真正能用的决策工作台。