用LLM-as-Judge为电商Agent构建评估体系,每晚运行40个真实对话按5个指标打分,解决"Agent好不好"无法量化的问题。
上周,我给一位同事演示了这个 Agent,用于他决策是否要在它上面赌一个功能。第七部分设计的审批门禁运行得完全符合预期——Agent 搜索了商品、加入了购物车、询问了地址,然后在确认链接处停了下来。同事点点头,问了一个问题:"好,但这它实际好用吗?"
我没有答案。我有第六部分的 31 个通过测试,它们证明了 Agent 是无 Bug 的。我有状态机,它证明了 Agent 不可能绕过人类下单。但这些都不能证明 Agent 对客户的回答是优质的。一个无 Bug 的 Agent 仍然可能告诉客户"两天内发货"而实际上物流合作方需要五天。没有任何单元测试能捕获这个问题,因为没有任何单元测试会去读回答的内容。
这就是本部分要填补的空白。第七部分结束时有一个承诺:下一部分将构建一个评估工具,让"它好用吗"不再是一种感觉,而是一个分数。这就是本部分要做的事。我为第一到第七部分中那个相同的电商 Agent 构建了一个 LLM-as-a-Judge 评估工具:相同的九个工具、相同的 Supervisor、相同的记忆。它每晚针对五个指标运行 40 个真实对话并打印每个指标的分数。第一次运行结果令人不安,而这正是它存在的意义。
我是孟加拉国达卡 BS23 的高级软件工程师二岗,从事 Spring Boot 和 Spring AI 生产级 AI Agent 开发已超过一年。以下就是我实际运行的评估工具的全部内容。
第六部分在没有 LLM 的情况下测试了 Agent:31 个测试、零次模型调用、断言工具调用、服务和订单状态机。那套测试回答的是"Agent 是否在正确的时机、用正确的参数调用了正确的工具?"它无法回答"回答是否正确?"因为你没法对 LLM 的措辞写断言。
评估是另一层。你无法断言回答的内容,但你可以评判它,而且你可以用 LLM 来做这个评判。Spring AI 文档在其 LLM-as-a-Judge 指南中记录了这个模式,我就是从这个指南里偷的这个思路:评估从根本上比生成更容易。批判比创作容易。评判者只需要评估现有文本的属性,这比生成文本同时平衡约束要简单得多。
该指南中的两个数字让我彻底信服。复杂的评判模型与人类判断的一致性高达 85%,高于人类之间 81% 的一致率。评判者不是完美的神谕。它只是你能负担得起的、最一致的审查者,可以在每次变更后运行。
分数的质量取决于指标的质量,一个指标需要三样东西:名称、通过的定义、以及评判者类型。我在写任何代码之前先写下了电商 Agent 的五个指标,而且我保持只做五个,因为每增加一个指标都会成倍增加评判调用次数和噪声。
回答正确性。 给定完整对话,回复是否实际回答了客户的问题?通过意味着客户得到了他们想要的东西,而不是一场说教。评判者:LLM。
上下文事实性。 回复是否与给定的产品数据、价格或政策相矛盾?通过意味着答案中的每个声明都被检索到的上下文所支持。评判者:LLM,可能的话用廉价的专用模型。
工具纪律性。 Agent 是否在需要时调用了正确的工具,而且只在需要时调用?通过意味着预期工具以预期参数被调用,中间没有浪费的调用。评判者:无。这个指标是确定性的,直接对工具调用日志做断言即可。
格式合规性。 回复是否符合前端期望的格式?聊天前端渲染纯文本,而如果 Agent 丢出一个 Markdown 表格就会破坏 UI。通过意味着回复是纯文本且结构符合预期。评判者:LLM,因为这里的"格式"指的是对话格式,不是 JSON。
无害拒绝。 Agent 是否拒绝了应该拒绝的内容?Agent 拒绝空购物车结账、超出作用域的请求、以及修改凭证的尝试——因为第七部分之后,最后一项根本不在它的工具集里。通过意味着拒绝是正确的且简短。评判者:LLM。
让这个列表可用的规则是:一条指标、一个通过定义、一个评判者。如果一个指标需要一段话来解释什么时候通过,就拆开它或者删掉它。
评估工具的质量取决于数据集的质量,而数据集应该来自现实,而不是在 Agent 还清晰地印在你脑子里时自己想出来的问题。我从三个来源构建了第一批数据:
来自日志的真实对话。 我提取了 40 个对话,做了匿名化处理,只保留了有明确结果的那些:完成的购买、退款、取消的订单、或者放弃的客户。
手写的边界用例。 日志里还没有的那些:客户要求对尚未发货的订单退款、匹配不到任何产品的价格过滤器、用混合孟加拉语和英语提问而 Agent 需要优雅地处理。
从现在起的每个生产投诉。 这是保持数据集诚实的规则。当客户或测试人员报告问题时,那个案例就进入数据集——当周就进。这个 instinct 与第六部分相同,只不过应用在行为而不是代码上。
每个案例是一条包含四个字段的记录:
public record EvalCase(
String id, // "case-041"
String userText, // the first user message, or the full transcript
String expectedTool, // "searchProducts" or null
String groundTruth, // the correct answer, written by a human
List<String> contextHints // product IDs the answer should mention
) {}
ground truth(标准答案)是昂贵的部分,没有捷径。必须由人类来写正确答案是什么。数据集有意做得很小:40 个案例,不是 4000 个。评估工具每晚运行,所以数据集每周增长一两个案例,每个案例都是凭实力留下来的。
Spring AI 为你提供了开箱即用的评估者接口。评估测试参考文档:
@FunctionalInterface
public interface Evaluator {
EvaluationResponse evaluate(EvaluationRequest evaluationRequest);
}
请求携带的正是评判者所需的全部信息:用户文本、Agent 看到的上下文数据、以及 Agent 的回复。
public class EvaluationRequest {
private final String userText; // the raw user input
private final List<Content> dataList; // contextual data, e.g. RAG results
private final String responseContent; // the agent's response
}
运行器是对数据集的循环。对每个案例,运行真实的 Agent、捕获回复和工具调用日志、构建 EvaluationRequest,然后让每个评估者返回通过或失败:
for (EvalCase evalCase : evalCases) {
// Run the real agent, same ChatClient with tools and memory as production
String response = agent.run(evalCase.userText());
// Deterministic metric: did it call the expected tool?
ToolCallLog log = agent.lastToolCalls();
boolean toolDiscipline = evalCase.expectedTool() == null
|| log.contains(evalCase.expectedTool());
// LLM metrics: build the request with the context the agent actually saw
EvaluationRequest request = new EvaluationRequest(
evalCase.userText(),
agent.lastContext(), // the dataList the agent retrieved
response);
boolean correct = answerEvaluator.evaluate(request).isPass();
boolean factual = factEvaluator.evaluate(request).isPass();
results.record(evalCase.id(), toolDiscipline, correct, factual);
}
然后按指标聚合,不是按案例聚合。一个案例失败是一个故事;回答正确性 72.5% 是一个趋势。每个夜间运行输出的不是每个案例的结果,而是五个数字,挨着前一夜的五个数字打印,这样回归就会显示为 diff:
metric today yesterday
answer_correctness 0.725 0.775
factuality 0.875 0.900
tool_discipline 1.000 1.000
format_compliance 0.925 0.950
harmless_refusal 0.950 0.950
评判者选择是成本与质量的权衡,Spring AI 的文档对这个方向有明确说明:"选择用于评估的最佳 AI 模型,这可能与用于生成回复的模型不是同一个模型。"我在工具中运行三种类型的评判者。
尽可能使用确定性检查。 工具纪律性根本不需要模型。它是对工具调用日志做一个 contains 操作,毫秒级完成,永远不会漂移。LLM 评判者只用于那些真正主观的指标,而且只用于那些。
两个内置评估者。 RelevancyEvaluator 检查回复是否与用户查询和提供的上下文一致,这对应回答正确性。FactCheckingEvaluator 检查回复中的每个声明是否被文档支持,这对应事实性。两者都返回通过或失败,而且如果你需要更严格的标准,都可以交换提示模板。
用于事实检查的廉价评判者。 文档推荐专门为此任务构建的小型模型:"有更小、更高效的专用 AI 模型可用,例如 Bespoke 的 Minicheck,它有助于降低与旗舰模型相比执行这些检查的成本。"Minicheck 运行在 Ollama 上,所以事实性指标每次运行几乎不花钱,而正确性评判者留在强模型上。
三条规则保持分数可信:
用 temperature 0.0 做评判。 文档中的 FactCheckingEvaluator 示例用 temperature(0.0d) 构建其模型,我的正确性评判者也是如此。有温度的评判者就是在掷骰子。
用独立的客户端做评判,绝不用 Agent 自己的模型。 LLM-as-a-Judge 指南的示例代码在一个注释中说得清清楚楚:"使用独立的 ChatClient 进行评估以避免自恋偏差。"模型给自己的输出打分有喜欢自己的动机。评判者应该是不同的模型,或者至少是不同的客户端配合不同的提示。
关注排行榜。 Judge Arena 追踪哪些模型实际擅长做评判,这与通用聊天机器人排名是分开的。最好的评判者不总是最流行的模型。当一个评判者模型的分数开始看起来太稳定时我会重新检查——通常是评判者变软了,而不是 Agent 变好了。
评估工具以两种方式运行。夜间全量运行:40 个案例、全部五个指标、用昂贵的正确性评判者。CI 中的冒烟运行:10 个案例子集,只有确定性指标加事实性,这样一个破坏了工具纪律性的合并会在几分钟内而不是明天早上失败。
第一次全量运行立即发挥了它的价值。三个失败特别突出:
发货窗口。 Agent 告诉客户"两天内发货",因为工具描述说的是快速发货,而实际履约 SLA 对大多数地区是五天。事实性指标在第一个晚上就标记了它。人工测试员两周都没发现。
过时库存。 Agent 推荐了一款一小时前已售罄的产品,因为搜索工具缓存了结果而回复引用了缓存的价格。工具纪律性通过了,因为正确的工具确实被调用了。事实性捕获了回答与实时产品数据之间的矛盾。
Markdown 表格。 在价格比较问题上,Agent 用 Markdown 表格回答。在终端里看起来很美,在聊天前端渲染出来是破碎的。格式合规性捕获了它;31 个测试套件永远不可能捕获,因为前端从未进入过循环。
两周后,回答正确性从 72.5% 升到了 89%,失败从"客户会碰到的问题"变成了"需要数据集条目的边界情况"。这正是评估工具按设计运转的样子:分数不是成绩单,而是回归检测器。
这个模式要花真金白银和真实的注意力,假装不是这样就是它腐坏的开始。
评判调用是一笔新开支。 40 个案例乘以三个 LLM 指标是每晚 120 次评判调用,加上 CI 冒烟运行。用 Minicheck 做事实性指标而正确性评判者只用强模型,每晚成本是几分钱。用旗舰模型做正确性评判者每月是几美元,仍然比它取代的人工测试员便宜,但它不是免费的,应该被纳入预算。
数据集会腐坏。 产品会变、发货政策会变、价格会变。一个在六月标准答案正确的案例可能在八月失败,因为业务变了而不是 Agent 变了。每次指标下降时,第一个问题是"Agent 变差了,还是世界变了?"第二个问题才是重要的。
评判者有偏见,分数是信号。 85% 的一致率意味着七分之一的判决可能是错的。单晚的下降是噪声。三晚的趋势是信号。第一周之后我停止对单晚变化做出反应了,因为评估工具太擅长制造 drama。
评判者回归是真实存在的。 评判者模型更新可以改变所有分数而 Agent 纹丝不动。当一个指标在一夜之间在整个数据集上移动时,先查评判者的 changelog 而不是动 Agent。
如果本部分你只记住一件事,就记住这个清单:
评估工具回答了"Agent 好不好?"但它还没有回答"哪个版本更好?"对提示或工具描述的每次变更目前都是靠判断发货的。LLM-as-a-Judge 指南记录了正好回答这个问题的第二个模式:成对比较,评判者在两个回复中选更好的。这就是第九部分:在提示和工具描述变更到达生产环境之前,用黄金数据集跑一遍,让"我认为这个提示更好"变成一个有衡量的事实。
你怎么知道你的 Agent 现在好不好?你有一个分数,还是只是一种感觉?你黄金数据集中的是什么,是生产投诉还是自己编的问题?我读每一条回复。
我每周写关于 Java、Spring Boot 和 AI Agent 的文章。订阅,免费。
收藏这一篇。你会在 Agent 自信且错误地回答的那个星期重读这份清单。