跳至正文
来两杯美式
返回

Agent 评测工程化(五):让 LLM-as-judge 稳定输出结构化结果

By 来两杯美式
发布于

前面几篇讲了 Agent 评测的目标、Rubric 结构和评分体系。走到这里,系统已经有了一套清楚的质量协议:意图清单、硬性要求、红线枚举、1-5 分和规则遵循状态。

接下来要解决一个更工程化的问题:怎么让 LLM-as-judge 稳定输出这些结构化结果?

这一步非常关键。只要评测结果要入库、聚类、统计、出报告,就不能接受一段自由文本。模型说「这条回答整体还可以,但缺少一点风险提示」,对人有帮助;对系统来说,它不知道分数是多少、意图是什么、违反了哪条规则、红线类型是否合法、这一行是不是对应上传数据里的 rowId。

生产评测系统不能靠 prompt 祈祷模型听话。正确分工应该是:模型负责语义判断,系统负责协议边界。

这一篇讲如何把 LLM-as-judge 的输出关进结构里。

一、自由文本评测无法进入生产链路

自由文本评测的最大优点是自然,最大缺点也是自然。

比如模型输出:

这条回答基本能理解用户问题,但没有充分说明风险,也没有给出明确下一步建议。我会给 3 分。

人可以读懂,但系统很难继续处理。

它不知道:

如果再让模型按自然语言生成多条结果,问题会更大:行数可能缺失,rowId 可能重复,字段名可能变化,枚举值可能用别名,数组可能变成字符串。

所以评测结果必须是一份协议,而不是一段作文。

二、JSON 不等于结构化协议

很多人会说,那就让模型输出 JSON。问题是,JSON 只是一种格式,不是协议。

下面这种输出虽然是 JSON,但仍然不可控:

{
  "result": "这条不错,四分吧",
  "problem": "略微不完整"
}

系统真正需要的是字段、类型、枚举和值域都被约束:

{
  "items": [
    {
      "rowId": 12,
      "intentKey": "HEALTH_CONSULTATION",
      "score": 2,
      "redFlagTypes": [],
      "scoreExplanation": "缺少必要追问和风险分流。",
      "unmetRequirementIds": ["HEALTH_FOLLOWUP", "HEALTH_TRIAGE"],
      "questionsCheck": "NOT_PRESENT",
      "questionsIssues": []
    }
  ]
}

这类结构至少要约束几件事:

这就是严格 JSON Schema 的价值。它把协议从 prompt 的自然语言要求,搬到请求参数和服务端校验里。

三、Schema 负责形状,Zod 负责语义

严格 JSON Schema 可以约束模型输出的形状,但它通常还不够。

比如它可以限制 intentKey 来自枚举,却不一定能表达「score 为 1 时必须存在红线」;它可以限制 unmetRequirementIds 是字符串数组,却不一定能判断每个 ID 是否属于当前意图可用规则;它可以限制 score 是 1-5,却不能根据规则命中分重新压低分数。

所以服务端还需要第二道校验。可以用 Zod 或类似工具做语义层检查:

这两层边界分工清楚:JSON Schema 尽量把模型输出约束在合法形状内,服务端校验负责业务语义和数据一致性。

不要把所有规则都塞进 prompt 里求模型自觉。越靠近数据一致性的要求,越应该由应用代码兜底。

四、为什么输出要尽量扁平

模型输出协议还有一个经验:让模型少写,系统多补。

在 Rubric 评测里,最直观的输出结构是完整 referenceBasis,也就是每条规则都给出遵循状态:

{
  "referenceBasis": [
    { "requirementId": "A", "status": "FOLLOWED" },
    { "requirementId": "B", "status": "VIOLATED" },
    { "requirementId": "C", "status": "NOT_APPLICABLE" }
  ]
}

这个结构对报告很好,但对模型输出不友好。规则多起来后,模型要重复输出大量 FOLLOWED,很容易漏项、编造 ID 或把状态写错。

更好的方式是让模型只输出未满足规则:

{
  "unmetRequirementIds": ["B"]
}

服务端再根据当前意图和 Rubric 补齐完整 referenceBasis

这就是扁平输出的好处。模型只做高信息量判断,系统负责展开协议结构。输出越短,模型越稳;协议越集中在服务端,系统越可控。

五、批量评测里的行级归并

生产评测通常不会一条一条调用模型,而是批量提交多个样本。批量能省请求开销,但也引入一个问题:模型可能返回不完整的批次。

常见坏情况包括:

这里不能简单地「整批失败」。如果一个批次 10 条里 9 条是好结果,只有 1 条坏结果,直接丢掉整批会浪费调用成本,也会拉低覆盖率。

更稳的做法是行级归并:

  1. 解析响应;
  2. rowId 和输入行对齐;
  3. 每一项独立做 schema 和语义校验;
  4. 校验通过的立刻保存;
  5. 缺失、重复、非法的行进入修复候选;
  6. 修复失败只影响对应行,不回滚同批好结果。

这一步让 LLM 像一个不稳定的外部服务,而不是一个可信的本地函数。调用外部服务时,行级隔离比整批事务更符合生产现实。

六、一次修复,不要无限重试

坏行要不要修?要。要不要无限修?不要。

一次定向修复通常就够了。修复 prompt 不需要重新提交整批,只提交那一行输入、原始坏结果、校验错误和要求的 rowId。目标很明确:请只返回这一行的合法结构。

伪流程如下:

初始批量请求
  ├─ 好行 → 保存
  └─ 坏行 → 单行修复
          ├─ 修复成功 → 保存
          └─ 修复失败 → 标记失败

为什么只修一次?

因为无限重试会制造三个问题。

第一,成本不可控。评测任务可能有几千行,如果坏行反复重试,成本会被长尾拖垮。

第二,延迟不可控。报告迟迟不结束,用户不知道任务到底卡在哪。

第三,语义收益递减。如果同一行在明确错误提示下仍然修不好,继续重试往往只是在碰运气。

失败行应该被记录下来,进入 FAILED 状态,并计入覆盖率。报告可以是部分完成,但不能假装全量成功。

七、覆盖率是可信度,不是质量分

当评测任务存在失败行时,系统需要一个指标表达报告可信度:覆盖率。

coverageRate = diagnosedInstances / totalInstances

覆盖率回答的是:这份报告基于多少比例的可评测样本。它不回答 Agent 好不好。

这个边界很重要。覆盖率低,说明报告结论需要谨慎;但覆盖率低不等于 Agent 质量差。反过来,覆盖率 100% 也不等于 Agent 表现好,只说明所有样本都完成了诊断。

任务终态可以分成几类:

状态含义
COMPLETED所有可评测样本完成诊断
PARTIAL诊断覆盖率达到可报告阈值,但有失败行
INSUFFICIENT_DATA覆盖率太低,不输出普通优化报告
FAILED流水线阶段性失败,无法生成报告

报告页应该把覆盖率放在显眼位置,并在部分完成时提示读者:当前结论只基于已完成诊断样本。

八、不要静默归一化语义字段

有些模型输出错误可以安全归一化,比如把单个字符串包成数组、去掉多余空格、补齐服务端可推导字段。还有些错误不能静默修。

比如 intentKey 输出了一个不存在的别名,或者 redFlagTypes 写了一个不在枚举里的值。系统不应该猜它是什么意思。猜错一次,后面的聚类和报告都会被污染。

可以把归一化分成两类:

类型是否可自动处理示例
形式归一化可以去重、trim、把缺失问题卡片转成 NOT_PRESENT
语义归一化谨慎或拒绝未知意图、未知红线、未知规则 ID

生产评测系统宁愿让一行失败,也不要把语义猜错后保存成正式结论。失败行会影响覆盖率,但错误结论会影响信任。

九、Prompt 的职责要收窄

严格协议并不意味着 prompt 不重要。它仍然负责讲清楚评测语义。

但 prompt 的职责要收窄:

不要让 prompt 承担这些职责:

后面这些都应该由结构化输出和服务端代码完成。prompt 越像协议文档,系统越脆;prompt 越专注语义判断,工程边界越清楚。

十、一个简化的处理流程

把上面的设计串起来,一次批量评测大概是这样:

构建输入批次
  → 注入 Rubric prompt
  → 请求严格 JSON Schema 输出
  → 解析顶层 items
  → 按 rowId 对齐输入
  → 单项 Zod 校验
  → hydrate:根据 unmetRequirementIds 补齐 referenceBasis
  → apply policy:按红线和命中分修正 score
  → 保存通过项
  → 修复坏行一次
  → 保存修复项或标记失败
  → 更新覆盖率

这里的关键词是 hydrateapply policy

hydrate 负责把模型的扁平判断扩展成系统完整记录。比如模型只说违反了哪些规则,服务端补齐所有规则的遵循状态。

apply policy 负责让模型判断服从业务规则。比如命中红线就补红线,命中规则上限就压分。

最终入库的不是模型原始输出,而是服务端规范化后的诊断记录。后续聚类、报告和导出都只使用这份记录。

十一、可观测性也要跟上

LLM-as-judge 是长任务里的外部依赖,必须有运行日志。

至少要记录:

这些日志不是为了好看,而是为了排障。当任务覆盖率异常低时,你需要知道是模型输出坏了、接口超时了、prompt 太长了,还是某批数据触发了规则校验问题。

没有可观测性,评测系统会变成另一个黑盒。我们已经在评测黑盒 Agent 了,最好不要把评测平台本身也做成黑盒。

十二、总结

LLM-as-judge 可以做语义判断,但不能被当作可信协议端。

生产评测系统要做的是把模型输出关进一套稳定边界里:严格 JSON Schema 约束形状,服务端校验约束语义,扁平输出降低模型负担,规则补齐保证报告口径,一次修复控制成本,失败隔离保住覆盖率和可信度。

这套分工可以压缩成一句话:模型负责判断语义,系统负责守住协议边界。

只有这样,评测结果才能稳定入库、聚类、统计和导出。否则我们得到的只是一次看起来合理的模型回答,而不是一套可以长期运行的评测系统。


分享这篇文章:
通过邮件分享这篇文章✓ 链接已复制
查看系列全部文章
  1. 01.把存量系统交给 Agent,先想清楚 API、Skill、MCP 各是什么
  2. 02.存量系统 Agent 化,物理上到底怎么「接」
  3. 03.单点跑通之后,怎么把 Agent 能力沉淀成全公司可复用
  4. 04.DeepSeek Harness:把 Agent 宿主变成可组合的基础设施,企业能拿它做什么
  5. 05.一个系统,两种访客:给浏览器和 Agent 设计同一套身份体系
  6. 06.定时是 Agent 平台的另一半:Trigger/Action Registry 与调度原语分层
  7. 07.Agent 刚才干了什么:审计、调用日志与敏感数据豁免
  8. 08.提示词安全攻防全解析:越狱、注入与信息泄露
  9. 09.给生产 Agent 做体检(一):只用对话文本的黑盒诊断工作流
  10. 10.给生产 Agent 做体检(二):诊断之前,先定义人口——数据质检与风险筛选
  11. 11.给生产 Agent 做体检(三):让 LLM 稳定输出结构化诊断——严格 JSON Schema 实践
  12. 12.给生产 Agent 做体检(四):LLM 输出不完美怎么办——归一化、一次修复与失败隔离
  13. 13.给生产 Agent 做体检(五):不要消息队列——基于数据库的任务编排与可靠性
  14. 14.给生产 Agent 做体检(六):从诊断明细到 PM 能用的报告——评估结果的产品化
  15. 15.AI Agent:从工具到同事,中间隔着一层「自主性」
  16. 16.Agent 评测工程化(一):为什么不能只看平均分
  17. 17.Agent 评测工程化(二):Rubric 不是 Prompt,而是可执行的质量协议
  18. 18.Agent 评测工程化(三):从低分样本到问题簇
  19. 19.Agent 评测工程化(四):红线、一票否决与 1-5 分
  20. 20.Agent 评测工程化(五):让 LLM-as-judge 稳定输出结构化结果
  21. 21.Agent 评测工程化(六):评测前,先把 Agent 输出拆开
  22. 22.Agent 评测工程化(七):从评分报表到 PM 决策工作台
  23. 23.Agent 评测工程化(八):不用消息队列,也能跑长任务
  24. 24.Agent 评测工程化(九):把一次性评测变成持续优化体系

上一篇
Agent 评测工程化(六):评测前,先把 Agent 输出拆开
下一篇
Agent 评测工程化(四):红线、一票否决与 1-5 分