ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent评测体系实战:从零搭建harness、rubric与LLM-judge

Agent评测体系实战:从零搭建harness、rubric与LLM-judge 1. 为什么 Agent 评测不能照搬传统软件测试那套做 Agent 开发的人十个里有八个在第一次上线前都会经历同一个尴尬本地跑 demo 的时候感觉无所不能一旦放到真实场景里用户随便换个问法就开始胡言乱语或者该调工具的时候不调、不该调的时候乱调。更麻烦的是你很难说清楚它到底是变好了还是变差了——因为每次改动 prompt、换模型、加工具输出都是自然语言没有断言可以写没有固定返回值可以比对。这就是 Agent 评测体系要解决的核心问题。传统软件测试的思路是给定输入验证输出是否等于预期这套逻辑在确定性系统里非常有效但放到 LLM 驱动的 Agent 上几乎失效。原因有三层第一Agent 的输出是开放式的自然语言同一个正确意图可以有几十种合理表达第二Agent 的行为是多步轨迹中间任何一步的工具调用、参数选择、上下文管理都可能影响最终结果只看终点等于放弃了过程诊断第三Agent 面对的任务往往没有唯一正确答案比如帮我调研一下竞品这种任务什么叫调研得好本身就是个模糊定义。所以 Agent 评测体系的设计目标不是去判定对或错而是建立一个可复现、可对比、可归因的度量框架。它要能回答三个问题这次改动让 Agent 在哪些维度上变好了变差的地方是哪些 case失败的原因出在规划、工具调用、还是最终生成围绕这三个问题业界逐渐收敛出一套以harness评测执行框架 rubric评分量表 LLM-judge模型裁判为核心的实践范式。下面我会把这套体系从零到一拆开讲包括我实际搭建过程中踩过的坑和那些文档里不会写的细节。2. 评测体系的三层结构数据集、执行器、裁判在动手写代码之前先把整个评测体系的骨架想清楚。我见过太多团队一上来就写评分脚本结果数据集没设计好、执行器不稳定、裁判标准天天变最后评测结果自己都不信。正确的做法是先分层把关注点隔离开。2.1 数据集层case 的设计决定了评测上限数据集是整个评测体系的地基。一个常见的误区是case 越多越好实际上对于 Agent 评测case 的质量和覆盖度远比数量重要。我建议按任务类型分桶每个桶里放 10 到 30 个精心设计的 case而不是堆几千个同质化的样本。分桶的维度可以参考这几个单步工具调用只需要调用一次工具就能完成比如查一下今天北京的天气。这类 case 用来验证工具描述是否清晰、参数抽取是否准确。多步规划需要串联多个工具比如帮我找出上周销售额下降的原因并生成报告。这类 case 考验 Agent 的任务分解能力。歧义与澄清输入本身信息不足正确行为是反问用户而不是瞎猜。很多 Agent 在这里翻车因为它被训练成永远给出答案。边界与拒答超出能力范围或涉及敏感操作的请求正确行为是拒绝或转人工。长上下文需要从大量历史信息里检索关键内容考验记忆和检索策略。每个 case 除了输入还要记录期望行为不是期望输出。期望行为可以是必须调用 search 工具至少一次必须在回答中引用检索到的数据必须反问澄清时间范围。这种描述式的期望比精确匹配的期望输出更适合 Agent。提示数据集一定要版本化。我吃过亏某次评测结果突然全线飘红排查半天发现是有人偷偷改了数据集里的一个 case把期望行为从调用工具改成了直接回答。数据集和代码一样必须进 git每次评测记录对应的 commit hash。2.2 执行器层harness 要解决的是可复现harness 这个词在 Agent 圈子里最近被提得很多本质上它就是评测的执行框架——负责把数据集里的 case 喂给 Agent收集完整的执行轨迹然后交给裁判打分。听起来简单但真正难的是可复现。Agent 的执行天然带有随机性temperature 不为零、工具返回结果可能变化、外部 API 可能超时。如果不控制这些变量你根本没法判断两次评测的差异是来自你的改动还是来自随机噪声。harness 要做的就是把所有能固定的都固定住固定随机种子如果模型 API 支持 seed 参数一定要传。不支持的话至少把 temperature 设成 0。Mock 外部依赖工具调用涉及的外部服务搜索、数据库、第三方 API在评测时应该用固定的 mock 返回否则今天搜到的结果和明天不一样评测就没意义了。记录完整轨迹不只是最终输出中间的每一次 LLM 调用、每一次工具调用、每一次上下文截断都要记录下来。这是后续归因分析的唯一依据。超时与重试策略统一所有 case 用同一套超时和重试逻辑避免个别 case 因为网络抖动被误判为失败。我自己的 harness 是用 Python 写的核心就是一个 runner 循环遍历数据集对每个 case 启动一个隔离的 Agent 实例注入 mock 工具执行并收集轨迹最后序列化成 JSON 落盘。整个执行过程要能一键重跑并且两次重跑的结果差异应该只来自模型本身的随机性。2.3 裁判层LLM-judge 不是万能药裁判层负责把执行轨迹转成分数。这里有两种主流做法规则裁判和LLM-judge。规则裁判适合那些可以精确判定的维度比如是否调用了指定工具工具调用次数是否在合理范围响应时间是否超标。这些用代码就能判又快又稳不要浪费模型调用。LLM-judge 则用于那些需要理解语义的维度比如回答是否准确推理过程是否合理是否真正解决了用户问题。它的优势是灵活能处理开放式任务劣势是不稳定——同一个轨迹换个 prompt、换个模型、甚至同一个模型跑两次分数都可能不一样。所以 LLM-judge 的使用有几个铁律必须给 rubric不能只问这个回答好不好要给出明确的评分维度和每个档位的标准。rubric 越具体judge 越稳定。必须要求给出理由让 judge 先输出分析再输出分数比直接输出分数准确率高很多。这也是 chain-of-thought 在评测场景的典型应用。必须做一致性校验拿一批人工标注过的样本去校准 judge看它的打分和人工打分的相关性。如果相关性低于 0.7说明 rubric 或 prompt 有问题得回去改。能用小模型就不用大模型judge 任务通常比生成任务简单一个中等规模的模型往往就够了成本能降一个数量级。但前提是校准过。3. Rubric 怎么写才不会被 judge 玩坏Rubric 是 LLM-judge 的评分标准也是整个评测体系里最需要反复打磨的部分。写得好judge 稳定可靠写得烂judge 就是个随机数生成器。我前后改过七八版 rubric总结出几条实战经验。3.1 维度要正交档位要可区分最常见的错误是把多个维度混在一个评分项里。比如回答是否准确且完整这就是两个维度揉在一起——一个回答可能准确但不完整也可能完整但有错误judge 遇到这种情况就懵了只能凭感觉给个中间分。正确的做法是拆开维度说明档位示例事实准确性回答中的事实性内容是否正确3全部正确2有轻微错误1有明显错误0完全错误完整性是否覆盖了用户问题的所有要点3全覆盖2覆盖主要点1覆盖部分0未覆盖工具使用合理性工具选择与调用时机是否恰当3完全合理2基本合理1有冗余或遗漏0完全错误表达清晰度语言是否通顺、结构是否清晰3清晰2尚可1混乱0无法理解档位设计的关键是每个档位之间要有明确的、可观察的差异。如果两个档位的区别只是好一点和差一点judge 就会随机选。我通常用是否出现某类具体问题来定义档位比如是否包含事实错误是否遗漏了关键步骤这样 judge 只需要判断有没有比判断程度稳定得多。3.2 给 judge 提供参考锚点光有档位描述还不够最好给每个档位配一个示例。比如在工具使用合理性这个维度下3 分的示例是调用了 search 工具获取实时信息然后基于结果回答1 分的示例是明明需要实时信息却直接凭记忆回答。有了锚点judge 的判断会稳定很多。锚点从哪来从你自己的评测数据里挑。先跑一轮人工标注一批典型样本把每个档位最有代表性的挑出来作为锚点。这个过程叫few-shot 校准虽然费点人工但一次投入长期受益。3.3 避免让 judge 做它不擅长的事LLM-judge 不擅长的事情包括精确的数值计算、需要外部知识验证的事实核查、涉及主观偏好的判断。比如你要评测Agent 计算出的税率是否正确不要让 judge 去算直接写规则裁判用代码算。再比如这个回答的风格是否符合品牌调性这种主观判断最好用人工或者至少用多个 judge 投票。我的一般原则是能用规则判的绝不用 judge能用 judge 判的绝不用人工只有前两者都不行的才上人工。这样能在保证质量的前提下把成本压到最低。4. 从零搭一个 harness我的实际实现路径讲完理论来说说具体怎么落地。我用一个简化的例子走一遍完整流程你可以直接照着改。4.1 目录结构与环境准备先规划目录结构把数据集、执行器、裁判、报告分开agent-eval/ ├── datasets/ │ ├── tool_calling.jsonl │ ├── multi_step.jsonl │ └── ambiguity.jsonl ├── harness/ │ ├── runner.py │ ├── mock_tools.py │ └── trace.py ├── judges/ │ ├── rule_judge.py │ └── llm_judge.py ├── rubrics/ │ └── default_rubric.yaml └── reports/环境上Python 3.10 以上主要依赖就是模型 SDK 和几个数据处理库。如果你的 Agent 本身跑在容器里评测环境最好和线上环境保持一致避免评测通过但上线翻车。4.2 数据集格式设计每个 case 用 JSONL 存一行字段设计如下{ id: tool_001, bucket: tool_calling, input: 帮我查一下明天上海的天气, expected_behavior: [ 必须调用 weather 工具, 工具参数中城市为上海日期为明天 ], mock_tools: { weather: {city: 上海, date: 2025-01-16, result: 晴5-12度} }, max_steps: 5 }这里expected_behavior是自然语言描述方便 judge 理解mock_tools定义了工具在评测时的固定返回max_steps限制最大步数防止 Agent 陷入死循环。4.3 Runner 的核心逻辑Runner 的职责是遍历数据集、执行 Agent、收集轨迹。核心代码大概长这样import json from pathlib import Path def run_case(case, agent_factory): agent agent_factory(mock_toolscase[mock_tools]) trace { case_id: case[id], input: case[input], steps: [], final_output: None, error: None, } try: for step in range(case[max_steps]): action agent.next_action() trace[steps].append(action) if action[type] final: trace[final_output] action[content] break else: result agent.execute_tool(action) trace[steps].append({type: tool_result, content: result}) except Exception as e: trace[error] str(e) return trace def run_dataset(path, agent_factory, output_path): traces [] with open(path) as f: for line in f: case json.loads(line) trace run_case(case, agent_factory) traces.append(trace) Path(output_path).write_text(json.dumps(traces, ensure_asciiFalse, indent2)) return traces关键点是每一步都记录包括工具返回。这样后面归因的时候才能看清楚 Agent 是在哪一步走偏的。4.4 规则裁判先跑一遍在调用 LLM-judge 之前先用规则裁判把能判的都判了。比如是否调用了指定工具这种直接扫 trace 里的 steps 就行def rule_judge(trace, case): scores {} tool_calls [s for s in trace[steps] if s[type] tool_call] called_tools {t[name] for t in tool_calls} required case.get(required_tools, []) scores[tool_coverage] len(called_tools set(required)) / max(len(required), 1) scores[step_efficiency] 1.0 if len(tool_calls) case[max_steps] else 0.0 scores[no_error] 0.0 if trace[error] else 1.0 return scores规则裁判的好处是零成本、零延迟、完全可复现。我一般会把 60% 到 70% 的评测维度交给规则裁判剩下的才用 LLM-judge。4.5 LLM-judge 的调用与解析LLM-judge 的 prompt 结构很关键。我的模板大致是这样你是一个 Agent 评测专家。请根据以下评分标准对 Agent 的执行轨迹进行评分。 【用户输入】 {input} 【Agent 执行轨迹】 {trace} 【评分标准】 { rubric } 请先分析 Agent 的表现然后按以下 JSON 格式输出 { analysis: 你的分析过程, scores: { accuracy: 0-3, completeness: 0-3, tool_usage: 0-3 }, reason: 每个分数的简要理由 }注意几个细节要求先分析再打分利用 CoT 提升准确率、要求输出结构化 JSON方便解析、要求给出理由方便人工复核。解析的时候一定要做容错模型偶尔会输出格式不对的 JSON要有 fallback 逻辑。5. 评测结果怎么读从分数到归因跑完评测拿到一堆分数这只是开始。真正有价值的是从分数定位到具体问题。我一般按这个顺序看5.1 先看整体分布再看单点不要一上来就盯着某个 case 的失败看先看整体。每个维度的分数分布是什么样的是普遍偏低还是个别 case 拉胯如果某个维度整体偏低说明是系统性问题比如工具描述写得不好导致所有 case 的工具调用都不准如果是个别 case 失败那可能是 case 本身设计有问题或者触发了某个边界情况。我习惯用分桶对比的方式看把每个桶的平均分列出来一眼就能看出 Agent 在哪个任务类型上最弱。比如多步规划桶普遍低分那说明任务分解能力需要加强。5.2 失败 case 的归因链路对于失败的 case要沿着轨迹一步步回溯。我通常问三个问题Agent 理解对了吗看第一步的规划它有没有正确理解用户意图。如果理解就错了后面全错问题出在 prompt 或模型能力。工具调用对吗如果理解对了但工具调用错了看是工具选择错、参数错、还是时机错。工具选择错通常是工具描述的问题参数错通常是 schema 定义的问题。最终生成对吗如果前面都对但最终输出不对看是信息整合的问题还是表达的问题。这个归因链路能帮你快速定位到是 prompt、工具、还是模型的问题。我踩过的坑是一开始只看最终输出发现 Agent 答错了就去改 prompt结果改了半天发现真正的问题是工具返回的 mock 数据格式和实际不一致导致 Agent 解析失败。5.3 建立回归基线评测体系最大的价值不是单次评测而是持续对比。你需要一个基线版本每次改动后跑评测和基线对比。如果某个维度下降超过阈值就要警惕。基线怎么定我一般用当前线上版本作为基线新版本必须在这个基线上不下降才能上线。阈值设置上规则裁判的维度可以严格一点比如下降 2% 就报警LLM-judge 的维度要宽松一点比如下降 5% 才报警因为 judge 本身有噪声。注意LLM-judge 的分数波动可能来自 judge 模型本身的更新。如果你的 judge 用的是第三方 API模型悄悄升级了你的历史分数就不可比了。所以要么固定 judge 模型版本要么定期用固定样本重新校准。6. 那些让我返工的坑harness 实践中的真实教训这一节讲讲我实际搭建过程中踩过的坑都是文档里不会写的。6.1 工具 mock 和真实行为不一致最开始我图省事mock 工具直接返回一个简单的字符串。结果评测全绿上线后 Agent 频繁出错。排查发现真实工具返回的是带嵌套结构的 JSONAgent 的解析逻辑在 mock 环境下根本没被触发。后来我改成 mock 返回和真实工具完全一致的结构包括错误码、分页字段、时间戳格式评测才真正有意义。教训是mock 的保真度决定了评测的有效性。宁可多花时间把 mock 做真也不要为了省事用简化版。6.2 judge 的 prompt 里混入了不该有的信息有一次评测结果特别奇怪某个 case 的分数异常高。查了半天发现是 judge 的 prompt 里不小心把 case 的expected_behavior也传进去了judge 直接照着期望行为给分等于作弊。这个 bug 很隐蔽因为大部分 case 的期望行为和实际表现一致只有少数不一致的才暴露出来。所以 judge 的输入要严格审查只给 judge 它需要的信息期望行为、参考答案这些绝对不能传进去。6.3 忽略了执行成本LLM-judge 每次调用都要花钱花时间。我第一版评测跑一次要四十多分钟其中大部分时间花在 judge 上。后来做了几个优化规则裁判前置过滤明显失败的 case 不用 judge 了、judge 结果缓存相同轨迹不重复判、批量并发调用。优化后跑一次降到八分钟。还有一个成本陷阱是评测频率。不要每次改一行代码就跑全量评测日常开发用小子集快速验证只有准备合并或上线时才跑全量。6.4 数据集泄露这个坑最隐蔽。有次我发现 Agent 在某个 case 上表现特别好好到不真实。查了半天发现这个 case 的输入和 Agent 训练数据里的某个样本高度相似Agent 等于背过答案。虽然我们没做微调但用的基础模型可能在预训练时见过类似数据。应对方法是定期更新数据集加入一些新构造的、不太可能出现在公开数据里的 case。另外评测集和开发集要严格分开开发时用来调 prompt 的 case 不能进评测集。7. 让评测体系持续运转的几个工程化建议评测体系搭起来只是第一步让它持续运转、真正融入开发流程才是难点。分享几个我实践下来有效的做法。7.1 把评测接入 CI最理想的状态是每次提交代码自动触发评测。当然全量评测太慢我的做法是分两级PR 级别跑快速子集每个桶抽 3 到 5 个 case五分钟内出结果合并到主分支后跑全量。快速子集只跑规则裁判和少量 judge主要拦截明显的回归。CI 里评测失败要有明确的输出哪个 case 失败了、哪个维度下降了、下降了多少。最好能直接给出失败 case 的轨迹链接方便开发者点进去看。7.2 建立评测报告的可视化纯 JSON 的报告没人看。我做了个简单的 HTML 报告包含整体分数趋势图、分桶对比表、失败 case 列表、每个失败 case 的轨迹展开。这样产品和运营也能看懂不用每次都来问开发。报告要能按时间对比比如本次 vs 上次 vs 基线三列并排一眼看出变化。7.3 人工抽检不能省LLM-judge 再准也有盲区。我坚持每周人工抽检 20 到 30 个 case重点看两类judge 给高分的确认不是误判和 judge 给低分的确认不是冤枉。抽检结果用来校准 rubric也用来发现 judge 的系统性偏差。有一次抽检发现 judge 对简洁的回答有偏好导致一些详细但正确的回答被打了低分。这就是典型的 judge 偏差只能靠人工发现。7.4 评测维度要随产品演进产品早期可能只关心能不能完成任务到了成熟期就要关心完成得好不好用户满不满意。评测维度要跟着产品目标走定期 review 一遍 rubric把不再重要的维度降权把新出现的关注点加进去。我一般每个季度做一次 rubric review结合用户反馈和线上数据调整评分维度和权重。这个过程要有记录否则半年后你都不知道为什么某个维度权重是 0.3。8. 关于 Agent 评测这件事我最后想说的Agent 评测体系不是一个一次性的项目而是一个需要持续投入的基础设施。它的价值不在于某一次评测的分数而在于它让你对 Agent 的行为有了可观测性——你知道它在哪里强、在哪里弱、每次改动带来了什么影响。我见过太多团队在 Agent 开发上凭感觉迭代改完 prompt 觉得好像好了一点但说不出好在哪里、有没有副作用。有了评测体系之后迭代从玄学变成了工程每次改动都有数据支撑每次回归都能快速定位。如果你现在还没开始搭评测体系我的建议是从最小的可用版本开始先选 20 个最有代表性的 case写一个最简单的 runner用规则裁判跑起来。不要一上来就追求大而全先让评测跑起来再逐步完善。评测体系本身也是迭代出来的不是设计出来的。最后分享一个我自己的习惯每次评测跑完我会把失败 case 的轨迹打印出来泡杯茶慢慢看。很多时候看轨迹比看分数收获更大——你会发现 Agent 的很多错误其实是有逻辑的只是它的逻辑和你的预期不一样。理解它的逻辑往往比强行纠正它更有效。
返回列表