
这段时间我们在给一批 Agent/Skills 项目做能力摸底发现一个很尴尬的问题agent 到底行不行很难给出一个可复现的答案。问几个固定问题、看回复像不像、让工程师凭感觉打勾这些方法在 agent 还比较简单的阶段勉强能用。但技能数量一多、工具链一长、场景一复杂人工直接看不过来单测也覆盖不住整个评估过程变成了一个说不清道不明的黑盒。所以我做了一套专门干这件事的东西一个用于 Agent/Skills 专项能力评估的 agent它负责对被测 agent 的不同 skill 进行系统化评估。简单说它会自己生成测试任务、驱动被测 agent 执行、采集过程中的工具调用证据、按事先定义的 rubric 打分最后输出一份可以回归、能解释失败原因的评测报告。这篇文章是这套系统的完整设计记录和实践复盘适合正在做 agent 开发、要给 agent 做验收、或者想把 agent 能力量化成指标的人参考。1. 为什么需要一个“评估 agent”来评测 agent1.1 人肉评估和传统单测为什么失灵传统软件开发里能力验证靠单元测试给定输入断言输出通过就绿。但 agent 不是函数它是一个自主系统。同样的输入它可能走不同的规划路径可能调用不同的工具结果也会因为模型概率波动而不同。单元测试落到 agent 上很容易变成只测 happy path 的假绿屏。我之前就踩过这种坑。某个检索类 agent 的单测全部通过结果一放到真实环境用户往上下文里插了一大段无关内容它把噪声当成了查询条件整个工作流跑偏。这种问题在固定 case 上根本暴露不出来因为单测里每个输入都是精心构造的干净样本没有覆盖真实世界的脏数据。人肉评估同样有瓶颈。把一个 agent 的几十个 skill 逐项摸一遍需要几个工程师连续干好几天而且不同人打分的松紧程度还不一样。更麻烦的是不可复现这次靠印象打了个“还不错”下个月想验证这个技能有没有退化你根本不知道当时到底测了什么、怎么测得。1.2 评估 agent 不是简单的“LLM 当裁判”很多人一听“用 agent 评估 agent”第一反应是拿个大模型当裁判把问题和答案丢给它让它打个分。实际上远不是这么简单。真正的评估 agent 需要承载完整的评估闭环任务生成、场景构造、执行驱动、过程日志采集、证据链拼接、评分裁决、结果汇总。它不能只是个“打分模型”它还得是一个能理解任务意图、主动构造差异场景、并且能读懂被测 agent 运作过程的智能体。一个“请给下面回答打 1 到 5 分”的 prompt 模板没有落地能力。它必须知道被测 agent 的每个 skill 长什么样知道哪些工具调用是合理的知道什么样的过程痕迹代表质量高。1.3 评估对象边界先分清 agent、skill 和 harness做评估之前我花了很多时间先把被测对象搞清楚因为圈子里这几个概念经常混着用agent具有感知、规划、调用工具能力的完整智能体skill它内聚的一项具体能力可以独立触发比如“网页信息提取”“代码片段生成”“多轮对话记忆”harness承载 agent 运行和工具调用的执行框架有点像发动机和车架的关系。如果评估对象是 agent测试的是整体任务完成度如果评估对象是某个 skill测试的是这一项能力的边界、正确性和鲁棒性。两者上报的数据、判断依据完全不同。我的做法是先锁定“按 skill 维度拆”因为技能级的评估结果更细出错时能精准定位到 agent 内部哪个环节出问题等每个 skill 都稳定了再看 agent 整体协作表现。2. 专项能力拆解从使用流程到可测的 skills 清单2.1 从任务链路倒推能力指标拆能力不是凭空想“这个 agent 应该会什么”而是看真实用户使用它的方式。一个带 skills 的 agent 处理任务的链路通常是接收用户指令 → 解析目标 → 规划步骤 → 选择合适的 skill → 调用底层工具 → 校验结果 → 输出最终回答。每一个环节都有可观察的指标指令理解能否正确抽取任务目标、约束条件、格式要求技能选择给定问题时是否调用了正确的 skill而不是选错或反复横跳工具调用传入参数是否完整、类型是否正确、是否处理了工具返回的错误中间校验拿到中间结果后是否做了合理性检查比如发现数据缺失时有没有停止追问最终输出是否遵循用户要求的格式内容是否基于真实证据而非编造。把这几个环节连起来就形成了一张能力候选清单。后面所有测试用例和评分标准都是以这张清单为锚来设计的。2.2 skill 单元的定义可触发、可观察、可判定有了候选清单还不能直接开始写用例。我建议给每个评估项做一张“skill 单元卡片”填清楚五个字段触发条件、输入规范、执行动作、预期行为、判定标准。核心设计原则是三个“可”可触发能构造一个用户指令稳定地把这个 skill 触发出来而不是碰运气可观察该 skill 的执行过程会留下可获取的 trace比如工具调用记录、中间推理、返回值可判定输出结果能用事前写好的规则或明确的 rubric 判断对错而不是看感觉。举一个例子。评估一个“网页信息提取”的 skill单元卡片大致是触发条件是用户要求从指定 URL 提取某些字段输入是 URL 和字段列表执行动作是调用浏览器解析工具并把结果结构化预期行为是返回字段值、对缺失字段标记 null判定标准是字段提取准确率是否达标、缺失处理是否规范。有了这张卡片后面写用例和 rubric 就不会各说各话。2.3 评估维度五层检查同一个 skill 不能只考“答案对不对”。我按五个维度做体检而不是只做一次对错判断评估维度要回答的问题典型判据任务完成度最终结果是否正确答案与 golden reference 匹配或者 checklist 全部满足过程合规性中间动作是否应该发生skill 选择是否正确、工具调用顺序是否合理资源效率是否用最少步骤和成本达成目标调用次数、token 消耗、耗时鲁棒性输入变化时是否依然稳定加噪声、走异常分支、插入干扰项安全边界是否遵守约束、正确拒绝对话不对违规任务照办、不越权调用工具实际执行时我把过程合规性放在任务完成度前面看。因为只看“结果对不对”会漏掉那种“结果是对的但调用了错误技能、绕了远路或者偷看了不该看的信息”的 agent——这类隐蔽问题一旦上线就会出事故。评估 agent 的价值就在于能把这种过程问题从 trace 里挖出来。3. 评估 agent 的整体架构与执行链路3.1 五大核心模块我的评估 agent 不是一个单体脚本而是拆成五个模块各自负责评估链路的一部分测试样本生成器test generator以一组高质量种子用例为底通过变异策略生成海量变体同时维护一个“skill 覆盖矩阵”确保每个待评估 skill 都有足够样本执行器executor / harness负责驱动被测 agent 运行。它不关心被测 agent 内部怎么实现只通过统一接口发任务、收结果、采集过程日志证据采集器collector把执行过程中产生的所有痕迹组织成结构化证据链包括工具调用、输入输出、时间戳、异常信息评分器evaluator / judge基于 rubric 和证据链对每个 skill 打维度分并给出具体理由报告器reporter把分数汇总成可阅读的评估报告同时把原始 traces 存档便于回溯和调 bug。五个模块之间用标准 JSON 格式传数据所以后面想换掉任何一环都不会伤筋动骨。比如不想用大模型 Judge 了可以换成规则打分器想把报告接进飞书只需要改 reporter。3.2 沙箱化执行为什么隔离是硬要求被测 agent 是活的东西它可能会联网、会写文件、会调用外部 API。如果不做隔离就会出现两类事故一是多个测试样本同时运行时互相污染状态比如某个用例把共享目录里的配置改了下一个用例就莫名失败二是有副作用的操作直接打到真实环境比如被测 agent 在测试时真发了一封邮件。我的硬性要求是所有执行都放进沙箱。本地跑就起 Docker 容器限制网络策略和文件系统写权限云端跑就用隔离的 worker。被测 agent 只能访问它明确被授予的工具其余一律默认拒绝。这不仅是安全问题也是评测准确性问题——没有隔离你根本分不清某个失败是 agent 能力问题还是环境残留导致的。3.3 串并行调度与评估成本预算评估是件烧钱的事。我习惯按 skill 维度分组同一组内随机顺序跑不同组之间并行同时控制被测 agent 的并发数防止触发限流。每个样本独立记录 token 消耗和耗时这样最后能算出“一次完整评估到底花了多少钱”。成本估算公式很简单一次完整评估成本 测试用例数 ×被测 agent 平均 token 消耗 Judge 平均 token 消耗× token 单价。举一个实际数字100 个用例被测 agent 平均每次 8000 tokenJudge 平均每次 2000 token单价按通用模型水平算一次全量评估大概是几美元到十几美元的量级。这个数字看着不大但如果每天回归跑十轮一个月就相当可观了。所以我后来做了分层策略日常小回归只跑种子集和少量变异样本大版本发布前才跑全量。4. 评分 rubric 怎么设计让 LLM 打分不飘的关键4.1 从“看结论打分”到“看证据链打分”我最早踩的一个坑是让 Judge 只对着最终输出打分。结果它经常被漂亮的句式带偏结构工整、用词正式的回答容易拿高分而内容正确但表述朴素的反而低分。后来我把输入改成完整证据链——工具调用记录、中间检索结果、每一步的推理摘要都丢给 Judge并要求它“先引用证据再给分数”。这个改动效果非常明显。给证据链之后Judge 至少是在核对事实而不是靠直觉。当然它也还会偷懒这个问题我在后面坑记录里展开。核心原则是分数必须能从 trace 中被解释出来不能是一个凭空出现的数字。4.2 锚点样例与分级标准纯文字 rubric 的毛病是语义模糊。比如“回答质量良好”是什么标准“部分完成”又是什么标准不同 Judge 模型理解完全不一样。我的方案是给每个分数级别配一个锚点样例直接把“什么算 1 分、什么算 4 分”钉死。以“信息提取准确性”这个维度为例我用的评分表是分数标准描述锚点样例0结果完全错误或拒绝执行用户要求提取 3 个字段agent 返回“我不支持该操作”1结果多数错误且未处理缺失3 个字段全错且没有对缺失字段做任何标记2部分正确但存在明显疏漏2 个字段正确1 个字段提取自无关位置3多数正确个别边界情况没处理3 个字段正确但缺失字段被直接省略而非标记 null4全部正确缺失处理和格式都规范3 个字段正确缺失字段标 null输出格式完全符合要求锚点样例不需要很多每个分数档一条即可。它的作用是让 Judge 有一个相对稳定的参照物不至于每次跑出的分数分布都变来变去。4.3 多裁判仲裁降低主观偏差单个 Judge 即使有 rubric 也会有偏差。我在关键评估中用了三个 Judge 并行打分如果分数一致或差距在允许范围内直接取平均如果最大分差超过 1 分触发仲裁流程——让三位 Judge 互相看对方给出的理由再重新打分。多裁仲裁不是让三个模型聊天而是把打分理由公开化。很多时候分歧的根源是某个 Judge 忽略了一处工具调用失败记录或者错误理解了业务约束。一旦公开理由其他 Judge 很容易发现这种低级错误。实测下来仲裁后的分数稳定性明显提高同一份报告的多次运行结果基本可控。4.4 必须把业务约束写进 rubric这是我在实际评估中总结出来的一条铁律rubric 不只要写“好不好”还要把业务方的硬约束写进去。比如对检索报告类 agent必须明确“输出的每条引用必须能在给定材料中找到对应原文”对电商客服类 agent必须明确“不得承诺超出权限范围的赔偿”。如果没有这些业务约束Judge 很容易给出一个“看起来很好但业务上完全是事故”的高分。评估 agent 不只是一个技术工具它还承担着业务验收的职责所以 rubric 的语言必须贴近业务而不是写一堆学术化空话。5. 最小可运行的评估流程从种子样例到回归报告5.1 被测 agent 的接入接口为了让评估 agent 能够驱动任何被测 agent我给被测方定了一个统一接入协议。被测 agent 只需要实现一个包装器把它的原生接口翻译成如下结构{ case_id: case_0001, input_task: 从 https://example.com 提取文章标题和作者输出为 JSON, skills_expected: [web_extract], constraints: [必须包含 missing 字段的 null 标记] }被测 agent 执行完后包装器返回统一的 trace 结构{ final_output: {\title\: \...\, \author\: \...\}, status: completed, trace: [ { step: 1, type: tool_call, tool_name: browser_fetch, arguments: {url: https://example.com}, result_summary: page_title..., author_meta..., duration_ms: 1200 } ], total_tokens: 8342, duration_ms: 3400 }这套协议设计的关键是不管 agent 内部是 React 模式还是 Plan-and-Execute 模式我只需要关心它对外表现。这也让评估 agent 能够同时测不同框架下实现的 agent而不被绑定在某一家框架上。5.2 测试用例构造种子集加变异策略测试数据这一步非常关键。我的做法是先用人工构造一批高质量种子用例覆盖每个 skill 的正常路径和常见边界然后对种子做变异批量生成带干扰的用例。常见变异策略包括改约束条件把“输出为 JSON”改成“输出为表格”验证 agent 是否严格执行格式要求增加干扰信息在任务描述里插入大段无关内容验证 agent 的抗噪声能力反转指令把“提取原文要点”改成“不要提取只判断是否存在”验证 skill 选择是否正确制造信息缺失让页面里缺一个字段观察 agent 是标记 null 还是编造内容调整表述方式从简练指令改成口语化长句验证语义理解稳定性。变异用例的数量可以不设上限但每一批变异都必须在 skill 单元卡片上登记确保变异后的用例仍然是在测同一个技能点而不是莫名其妙换了评估对象。5.3 评估主流程代码示例下面是一个简化但完整的评估主流程用 Python 思路实现from dataclasses import dataclass dataclass class EvalContext: testcases: list # 测试用例列表 executor: Harness # 被测 agent 执行器 judge: Judge # 评分器 reporter: Reporter # 报告器 def run_assessment(ctx: EvalContext) - dict: results [] for tc in ctx.testcases: trace ctx.executor.run(tc.input_task) eval_input { case_id: tc.case_id, task: tc.input_task, golden_checklist: tc.checklist, constraints: tc.constraints, final_output: trace.final_output, trace: trace.trace, } score ctx.judge.score(eval_input) results.append({ case_id: tc.case_id, skills: tc.skills_expected, score: score, evidence: trace, }) report ctx.reporter.build(results) return report实际项目里还需要加入并发控制、重试机制、token 统计和失败重跑。尤其是重试被测 agent 是概率系统偶发错误不一定代表能力退化。我会对失败用例自动重跑两次如果三次都失败才记为真实失败这样能过滤掉大量随机抖动。5.4 回归阈值与结果解读评估报告不是跑完就结束了它必须能驱动决策。我生成的报告包含四块内容总分汇总、各 skill 维度得分率、Top 失败用例清单、完整 traces 存档。同时设一条硬性回归阈值某个目标 skill 的得分率相比基线下降超过 5%就阻塞发布让开发先去查问题。这个方法在团队里推行后效果很好核心原因在于它把“agent 不行”这种模糊感受变成了“检索 skill 鲁棒性得分从 0.92 降到 0.84具体是因为 URL 带重定向时工具调用失败”这种可行动信息。评估的意义不在分数本身而在能定位到具体失败点。6. 实操踩坑记录幻觉打分、数据污染与难度陷阱6.1 Judge 也会抄近道必须强制引用证据这是我最想说的一条经验。即便我把证据链丢给 Judge它也一样会偷懒不看工具调用记录直接从最终输出的格式和字数猜分。解决办法是在评分 prompt 里加硬性要求每个维度打分前必须引用 trace 中的原文片段作为判断依据。我用的要求大致是在最终打分前你必须引用 trace 中至少一条与该项判断直接相关的记录。如果没有引用整体得分按 0 分处理。加了这个强制机制后Judge 的幻觉打分明显减少。因为“引用原文”这个动作本身让模型不得不在证据里寻找支撑点。如果它找不到支撑点就说明它并没有真正核验说明分数不可信。6.2 数据泄漏与动态生成评估另一个容易踩的坑是数据泄漏。如果你用的评估样本是公开 Benchmark 或长期固定不变的数据集被测 agent 很可能在训练阶段已经见过类似题目等于开卷考试。更隐蔽的是如果样本长期不变被测 agent 也不会上感觉而是像背题一样记住答案模式。我的应对是评估样本必须保持动态生成能力种子集固定不变但每次跑评估时都重新做变异组合。同一道业务题一百次评估跑出来的输入表述都不同这才能有效避免被测 agent 通过死记硬背刷分。6.3 “拒绝”行为如何判定合理拒绝、误拒绝、过度拒绝安全边界相关技能的评估最容易被误判。比如给客服 agent 一个退款申请它拒绝退款这是合理拒绝吗不一定是。你需要分三种情况合理拒绝请求确实超出 agent 权限或违反业务规则拒绝是正确行为误拒绝请求本身合法agent 因为指令理解不清而错误拒绝了这是缺陷过度拒绝请求可以部分处理但 agent 选择一刀切全部拒绝这是体验事故。之前我的 rubric 里只有一个笼统的“拒绝正确”档位导致 Judge 把所有拒绝都当成安全行为分数虚高。后来我把这三种情况单独定义并为每一种都配了正反锚点样例评估结果才真正反映业务需求。6.4 不要只看总分难度分层与方差分析有一段时间我们的评估总分稳定在 0.85 左右看起来挺健康但细看才发现里面全是问题简单用例全部通过困难用例几乎全挂。平均分把两极分化掩盖了。这不是 agent 能力进步而是样本难度分布不均造成的错觉。后来我给每个测试样本标了难度等级easy / normal / hard报告里按难度分层统计得分率。这样一眼就能看出 agent 是“普遍能力强”还是“只会做容易题”。如果你只用一个总分来做判断很容易被这种统计假象骗过去。6.5 评估 agent 本身也需要自检最后评估 agent 自己也不是没有 bug。我给系统加了一个 sanity check 流程每次正式评估前先拿一个已知好用的 baseline agent 和一个已知有缺陷的 mock agent 各跑一轮。通过观察评估结果是否与预期一致判断这次的评估配置、Judge 状态、样本生成流程是否正常。如果 baseline 得分比 mock 还低那大概率不是被测 agent 的问题而是评估链路自己出了毛病。这个自检机制帮我排除过不少低级错误比如 prompt 模板被改坏、rubric 字段漏传、变异规则写错导致用例无效。7. 把评估 agent 接入日常回归的落地经验做到这一步之后评估 agent 就不再是一个一次性工具而是一个常驻的评测基础设施。现在我的使用习惯是把它接进平时的开发流程每次被测 agent 有功能变更先跑一轮快速回归只执行种子集和少量变异用例成本控制在几美元内每一个迭代版本结束跑一次全量评估把结果存档到历史记录里。存档这个动作容易被忽略但非常重要。有历史分数做参照才能判断某个技能是在变好还是变差。我还会定期把历史报告拿出来做一次“失败模式分析”看 top 失败用例里是否存在共性——比如最近几轮失败都集中在工具参数传递环节那可能就是某个底层 API 变更引起的不需要等线上事故暴露。如果团队本身有 CI 系统评估 agent 完全可以作为一道独立的流水线阶段接入跑完直接输出报告和阻断判断。但不要一开始就把全量评估放进每次提交里成本和耗时都会拖垮效率。建议先用快速子集跑日常全量评估放到版本节点这类频率设计要根据团队体感来调整。最后分享一个我在实际评估过程中最深的感觉这套系统的价值不在于它能打一个多准的总分而在于每一份评估报告都能指出失败发生在哪个 skill、哪个环节、哪条证据链上。反倒是那些满分报告我通常只看一眼总分就归档了真正有价值的永远是失败 trace 里露出的那些边缘问题。如果你也在做 agent 的能力验收我建议从一张 skill 单元卡片开始把能力拆清楚再往评估循环里填细节不要一上来就追求一个复杂的自动打分平台。