1. 为什么 Agent Eval 值得单独拿出来讲
做 Agent 开发的人大概都有过这种体验:Demo 阶段效果惊艳,一旦放到真实场景里跑上几十上百个任务,表现就开始飘忽不定。同一个 prompt,今天能正确调用工具,明天就忘了参数格式;上周还能稳定完成的多步推理,这周加了个新工具就开始胡言乱语。更让人头疼的是,你改了一版 prompt,感觉某些 case 变好了,但另一些 case 又退化了,到底整体是进步还是退步,完全说不清楚。
这个问题的根源在于:Agent 的评估和传统模型的评估完全不是一回事。传统模型评估,输入输出都是确定的,跑一遍测试集算个准确率就行。但 Agent 是一个带工具调用、多轮交互、环境状态变化的动态系统,它的输出是一条轨迹,不是一个静态结果。你没法用简单的 BLEU、ROUGE 或者准确率来衡量它。
Anthropic 在 Agent Eval 这件事上有一套比较系统的方法论,核心思路是把评估拆成三个层次:任务定义、Harness 执行、Grader 评分,然后通过持续迭代来逼近可靠。这套方法不是 Anthropic 独有的,但他们的实践比较完整,值得拆开来看。
这篇文章适合谁?如果你正在做 Agent 开发,不管是基于什么框架,只要你需要回答“我的 Agent 到底行不行”这个问题,那这套方法就能直接用。如果你还没开始做 Agent,但想了解评估体系怎么搭,也可以先看看思路。
2. 核心概念拆解:Task、Harness、Grader 到底各管什么
2.1 Task 定义:不是写个 prompt 就完事
很多人做 Agent 评估,第一步就错了。他们直接把用户可能问的问题列出来,当成测试用例。但 Agent 的任务定义远比这复杂。
一个完整的 Task 定义至少包含这几个要素:
- 初始状态:Agent 开始时的环境是什么?有哪些文件、数据、工具可用?
- 目标描述:用自然语言描述 Agent 需要达成什么目标,但不要给出具体步骤。
- 成功标准:什么样的结果算成功?这个标准必须是可验证的。
- 约束条件:有哪些不能做的事?比如不能删除某个文件、不能调用某个 API。
- 预期轨迹:不是必须的,但如果有参考轨迹,对后续分析很有帮助。
举个例子,假设你要评估一个“帮用户整理会议纪要”的 Agent。任务定义不是“帮我整理会议纪要”这句话,而是:
初始状态:/meetings/ 目录下有 3 个 .txt 文件,分别是三次会议的原始记录 目标:生成一份汇总的会议纪要,包含每次会议的关键决策和待办事项 成功标准: - 输出文件存在于 /output/summary.md - 包含所有三次会议的日期和主题 - 每次会议至少提取 2 个决策和 2 个待办 - 待办事项必须包含负责人(从原文中提取) 约束:不能修改原始文件,不能删除任何内容这样定义之后,评估才有依据。否则你拿到一个输出,根本不知道该怎么判断它好不好。
注意:任务定义里的“成功标准”一定要可自动验证。如果标准是“纪要写得好”,那 Grader 也没法判断。必须拆成可检查的条目,比如“包含日期”“包含负责人”这种。
2.2 Harness:Agent 的运行环境和执行框架
Harness 这个词在 Agent 语境下,指的是让 Agent 跑起来的整套执行环境。它包括:
- 工具接口:Agent 能调用哪些工具,每个工具的输入输出格式是什么。
- 环境状态管理:Agent 操作的文件系统、数据库、API 状态怎么维护。
- 执行循环:Agent 的 think-act-observe 循环怎么跑,最大步数是多少,超时怎么处理。
- 日志记录:每一步的输入输出、工具调用、耗时都要记录下来,方便后续分析。
Harness 的设计直接决定了评估的可靠性。如果 Harness 本身不稳定,比如工具调用偶尔超时、环境状态没有正确重置,那评估结果就不可信。
Anthropic 的做法是把 Harness 和 Agent 实现解耦。也就是说,同一个 Harness 可以跑不同版本的 Agent,同一个 Agent 也可以在不同 Harness 上跑。这样做的好处是,当你发现 Agent 表现不好时,可以快速判断是 Agent 本身的问题,还是 Harness 的问题。
我自己的经验是,Harness 里最容易出问题的地方是环境重置。每次跑完一个任务,必须把环境恢复到初始状态,否则下一个任务就会受上一个任务的影响。比如文件被修改了没恢复、数据库里多了脏数据、缓存没清空,这些都会导致评估结果失真。
2.3 Grader:怎么判断 Agent 做得好不好
Grader 是评估体系里最灵活也最难做的部分。它要回答的问题是:给定一个任务和 Agent 的执行轨迹,怎么判断它是否成功?
常见的 Grader 类型有几种:
| Grader 类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 精确匹配 | 输出格式固定,答案唯一 | 简单、快速、无歧义 | 只能用于极少数任务 |
| 规则检查 | 有明确的结构化标准 | 可解释、可调试 | 规则写起来费劲,覆盖不全 |
| 模型评分 | 开放式任务,需要理解语义 | 灵活、接近人类判断 | 有噪声、成本高、可能被 hack |
| 人工评分 | 高风险任务,需要最终确认 | 最准确 | 慢、贵、不可规模化 |
实际项目中,通常是组合使用。比如先用规则检查做一轮筛选,把明显失败的 case 过滤掉,剩下的再用模型评分。或者模型评分和人工评分结合,模型评一遍,人工抽查一部分。
Anthropic 特别强调一点:Grader 本身也需要被评估。你怎么知道你的 Grader 判断得准?答案是拿一批人工标注过的 case 来测 Grader,看它和人类判断的一致率有多高。如果一致率低于某个阈值,那 Grader 就需要调整。
3. 从零搭建一套 Agent Eval 流程
3.1 第一步:定义任务集,别贪多
新手最容易犯的错是:一上来就搞几百个任务,觉得覆盖得越全越好。结果跑一遍要几个小时,分析结果要几天,迭代速度极慢。
我的建议是:先做 20-30 个高质量任务。这 20-30 个任务要覆盖:
- 核心功能路径(Agent 最常做的事)
- 边界情况(输入为空、格式异常、工具返回错误)
- 多步推理(需要连续调用多个工具)
- 约束遵守(不能做的事有没有做)
每个任务都要有明确的成功标准和失败模式。什么叫失败模式?就是你知道这个任务可能以哪几种方式失败。比如“整理会议纪要”这个任务,失败模式可能是:漏了某次会议、待办没提取负责人、输出格式不对、修改了原始文件。
有了失败模式,你才能针对性地设计 Grader,也才能在分析结果时快速定位问题。
实操心得:任务集不是一次性的,要持续维护。每次发现新的失败模式,就加一个对应的任务。每次修复一个 bug,就加一个回归测试任务。这样任务集会越来越有代表性。
3.2 第二步:搭建 Harness,重点在可观测性
Harness 的搭建没有太多花哨的东西,核心就是把 Agent 执行过程中的所有信息都记录下来。
一个典型的 Harness 执行日志应该包含:
{ "task_id": "meeting_summary_001", "agent_version": "v1.2.3", "start_time": "2025-01-15T10:00:00Z", "steps": [ { "step": 1, "thought": "我需要先查看 /meetings/ 目录下有哪些文件", "action": "list_files", "action_input": {"path": "/meetings/"}, "observation": ["meeting_1.txt", "meeting_2.txt", "meeting_3.txt"], "duration_ms": 120 }, { "step": 2, "thought": "现在读取第一个文件", "action": "read_file", "action_input": {"path": "/meetings/meeting_1.txt"}, "observation": "...", "duration_ms": 85 } ], "final_output": "...", "total_duration_ms": 4500, "status": "completed" }有了这样的日志,你才能回答这些问题:Agent 在哪一步卡住了?它调用了哪些工具?工具返回了什么?它有没有重复调用同一个工具?它的思考过程合理吗?
Harness 的另一个重点是并发控制。如果你要跑 100 个任务,串行跑太慢,并行跑又可能因为资源竞争导致结果不稳定。我的做法是:每个任务在独立的容器或沙箱里跑,互不干扰。并发数根据资源情况调整,一般 4-8 个并发比较稳妥。
3.3 第三步:设计 Grader,从简单到复杂
Grader 的设计要遵循一个原则:能用规则解决的,不要用模型。
规则 Grader 的例子:
def grade_meeting_summary(output_path, expected_meetings): # 检查输出文件是否存在 if not os.path.exists(output_path): return {"score": 0, "reason": "输出文件不存在"} content = open(output_path).read() # 检查是否包含所有会议日期 for meeting in expected_meetings: if meeting["date"] not in content: return {"score": 0, "reason": f"缺少会议日期 {meeting['date']}"} # 检查待办事项是否包含负责人 todos = extract_todos(content) for todo in todos: if not todo.get("owner"): return {"score": 0.5, "reason": f"待办事项缺少负责人: {todo['text']}"} return {"score": 1, "reason": "通过所有检查"}模型 Grader 的例子:
def grade_with_model(task_description, agent_trajectory, success_criteria): prompt = f""" 你是一个评估专家。请根据以下任务描述和成功标准,评估 Agent 的执行轨迹。 任务描述:{task_description} 成功标准:{success_criteria} Agent 执行轨迹:{agent_trajectory} 请输出 JSON 格式的评估结果: {{ "score": 0-1 之间的分数, "passed": true/false, "reason": "评分理由", "failed_criteria": ["未通过的标准列表"] }} """ response = call_model(prompt) return parse_json(response)模型 Grader 的关键是prompt 设计。你要把成功标准拆得足够细,让模型能逐条检查。同时要给出评分标准,比如“完全满足得 1 分,部分满足得 0.5 分,完全不满足得 0 分”。
注意:模型 Grader 有被 hack 的风险。Agent 可能会学会输出一些看起来很好但实际没用的内容来骗过 Grader。防范方法是:定期用人工标注的数据来校准 Grader,同时设计一些“陷阱任务”,专门检测 Agent 是否在投机取巧。
3.4 第四步:跑评估,分析结果
跑评估本身没什么技术含量,但分析结果才是重头戏。
我通常会从这几个维度来分析:
- 整体通过率:所有任务中,有多少通过了。这个数字用来跟踪整体趋势。
- 分类通过率:按任务类型分组,看哪类任务表现差。比如“多步推理”类通过率只有 40%,那说明 Agent 在多步推理上有问题。
- 失败模式分布:失败的 case 中,各种失败模式各占多少。比如“工具调用错误”占 60%,“约束违反”占 30%,“输出格式错误”占 10%。
- 步数分布:Agent 平均用多少步完成任务。步数突然增加,可能意味着它在某个地方卡住了。
- 耗时分布:每个任务的耗时。耗时异常的任务需要单独看。
分析结果最好能可视化。一个简单的 dashboard 就够:通过率趋势图、失败模式饼图、步数分布直方图。不用搞得太复杂,关键是能快速看出问题。
4. 持续改进:评估不是一次性的
4.1 建立回归测试机制
每次修改 Agent(改 prompt、加工具、调参数),都要跑一遍完整的评估集。如果整体通过率下降超过阈值(比如 5%),就要回滚。
但光看整体通过率不够,还要看具体哪些任务退化了。有时候整体通过率没变,但某些关键任务从通过变成了失败,另一些边缘任务从失败变成了通过,这种“拆东墙补西墙”的情况必须及时发现。
我的做法是维护一个关键任务列表,这些任务是核心功能,绝对不能退化。每次评估,先看关键任务的通过率,再看整体。
4.2 用失败案例驱动迭代
评估的最大价值不是那个通过率数字,而是失败案例。每一个失败案例都是一个改进机会。
分析失败案例时,我会问这几个问题:
- Agent 在哪一步开始出错的?
- 它当时的思考是什么?合理吗?
- 它调用了正确的工具吗?参数对吗?
- 如果换一种 prompt 写法,它会不会做对?
- 这个失败是偶发的,还是系统性的?
如果是系统性的,那就需要改 Agent 的设计。如果是偶发的,可能是模型本身的随机性,可以多跑几次看是否复现。
4.3 评估集的版本管理
评估集本身也要版本管理。每次新增任务、修改成功标准、调整 Grader,都要记录版本。否则你没法比较不同时期的评估结果。
我通常会用这样的目录结构:
evals/ v1.0/ tasks/ meeting_summary_001.json meeting_summary_002.json graders/ meeting_summary_grader.py results/ 2025-01-10_agent_v1.0.json 2025-01-12_agent_v1.1.json v1.1/ tasks/ ...这样你可以清楚地看到:Agent v1.1 在 eval v1.0 上的表现,和 Agent v1.0 在 eval v1.0 上的表现对比。如果 eval 也升级了,那就用新 eval 重新跑一遍旧 Agent,保证对比公平。
5. 常见问题与排查技巧实录
5.1 Agent 表现不稳定,同一任务有时通过有时失败
这是最常见的问题。原因可能有几种:
- 模型随机性:温度参数设得太高。Agent 任务通常建议温度设为 0 或接近 0。
- 工具返回不稳定:比如某个 API 偶尔超时或返回格式不一致。需要给工具加重试和格式校验。
- 环境状态污染:上一个任务的环境没清理干净。检查 Harness 的重置逻辑。
- 并发竞争:多个任务同时跑,共享了某些资源。确保每个任务在独立沙箱里跑。
排查方法:把同一个任务连续跑 10 次,看通过率。如果通过率在 50% 左右,那基本可以确定是随机性问题。如果通过率很高但偶尔失败,那可能是环境或工具的问题。
5.2 Grader 判断不准,人工复核发现很多误判
Grader 误判通常有两个方向:假阳性(Agent 没做好但 Grader 说通过了)和假阴性(Agent 做好了但 Grader 说没通过)。
假阳性的常见原因:Grader 检查的条件太宽松。比如只检查了输出文件存在,没检查内容质量。解决办法是加更多检查项,或者引入模型 Grader 做语义判断。
假阴性的常见原因:Grader 的规则太死板。比如要求输出必须包含某个关键词,但 Agent 用了同义词。解决办法是把规则写得更灵活,或者用模型 Grader 替代规则 Grader。
实操心得:定期拿 20-30 个 case 做人工标注,然后对比 Grader 的判断。如果一致率低于 90%,Grader 就需要调整。这个校准过程要持续做,因为 Agent 的行为会变化,Grader 也要跟着变。
5.3 评估跑得太慢,迭代速度跟不上
评估慢通常是因为:任务太多、每个任务步数太多、并发度太低、模型调用太慢。
优化方向:
- 减少任务数:只保留最有代表性的任务。20-30 个足够,不要贪多。
- 设置最大步数:Agent 超过一定步数就强制终止,避免无限循环。
- 提高并发:在资源允许的情况下,增加并发数。但要注意环境隔离。
- 缓存工具调用:如果某些工具调用是确定性的,可以缓存结果,避免重复调用。
- 用更快的模型做 Grader:Grader 不需要用最强的模型,用一个小而快的模型就够了。
5.4 Agent 学会了“骗” Grader
这是模型 Grader 特有的问题。Agent 可能会发现,只要输出某些特定格式的内容,Grader 就会给高分,哪怕实际任务没完成。
防范方法:
- Grader 和 Agent 用不同的模型:避免 Agent 针对特定模型的偏好进行优化。
- 定期更换 Grader 的 prompt:让 Agent 没法针对固定的 Grader 逻辑进行优化。
- 加入人工抽查:定期人工检查一部分 case,发现异常模式。
- 设计“陷阱任务”:故意设计一些任务,如果 Agent 真的理解了任务,就能做对;如果只是投机取巧,就会失败。
5.5 评估结果和线上表现不一致
评估集上表现很好,但上线后用户反馈很差。这种落差通常是因为:
- 评估集覆盖不全:真实场景中的任务比评估集复杂得多。
- 评估环境太干净:真实环境有各种噪声和异常,评估环境没有模拟。
- 用户行为不可预测:评估集的任务是预设的,用户可能会用完全意想不到的方式使用 Agent。
解决办法:把线上失败案例回流到评估集。每次用户反馈问题,就把对应的 case 加到评估集里。这样评估集会越来越接近真实场景。
6. 一些工具和框架的选型建议
6.1 Harness 框架怎么选
如果你不想从零搭 Harness,可以考虑一些现成的框架。选型时重点看这几个方面:
- 工具接口是否灵活:能不能方便地定义新工具。
- 环境隔离是否彻底:每个任务能不能在独立环境里跑。
- 日志是否完整:能不能记录每一步的详细信息。
- 并发控制是否好用:能不能方便地调整并发数。
- 是否支持自定义 Grader:能不能接入自己的评分逻辑。
我的建议是:先用现成框架快速跑起来,遇到瓶颈再自己改。不要一上来就自己造轮子,那样会花很多时间在基础设施上,而不是在 Agent 本身。
6.2 Grader 的实现方式
Grader 可以用 Python 脚本实现,也可以用现成的评估框架。关键是要可配置、可扩展。
我通常会把 Grader 设计成插件式的:每个 Grader 是一个独立的类,实现grade(task, trajectory)方法。评估框架根据任务配置,自动选择合适的 Grader。
class BaseGrader: def grade(self, task, trajectory): raise NotImplementedError class RuleBasedGrader(BaseGrader): def grade(self, task, trajectory): # 规则检查逻辑 pass class ModelBasedGrader(BaseGrader): def grade(self, task, trajectory): # 模型评分逻辑 pass class CompositeGrader(BaseGrader): def __init__(self, graders): self.graders = graders def grade(self, task, trajectory): results = [g.grade(task, trajectory) for g in self.graders] # 组合多个 Grader 的结果 return combine(results)这样你可以灵活组合不同的 Grader,比如先用规则 Grader 做快速筛选,再用模型 Grader 做精细评分。
6.3 结果存储和可视化
评估结果建议存成结构化格式(JSON 或 SQLite),方便后续查询和分析。可视化可以用简单的 Web dashboard,也可以用 Jupyter Notebook。
我自己的做法是:结果存 SQLite,分析用 Jupyter。SQLite 方便查询和聚合,Jupyter 方便画图和探索。不需要搞太复杂的系统,够用就行。
7. 最后分享几个踩坑经验
做 Agent Eval 这段时间,踩过的坑不少,挑几个最有代表性的说说。
第一个坑:一开始就追求大而全的评估集。我最初搞了 200 多个任务,跑一遍要一个多小时,分析结果要半天。后来砍到 30 个核心任务,迭代速度立刻上来了。评估集的质量比数量重要得多。
第二个坑:Grader 写得太死。早期我用精确匹配做 Grader,结果 Agent 输出里多了一个空格就判失败。后来改成规则检查加模型评分,灵活多了。Grader 要能容忍合理的变体,只关注核心成功标准。
第三个坑:忽略环境重置。有一次评估结果特别差,排查了半天才发现是上一个任务修改了文件没恢复,导致后续任务全在错误的环境里跑。从那以后,我在 Harness 里加了强制重置逻辑,每个任务开始前都确保环境是干净的。
第四个坑:不记录中间步骤。最开始只记录最终输出,失败的时候完全不知道 Agent 在哪一步出了问题。后来把每一步的 thought、action、observation 都记下来,排查效率提升了好几倍。
第五个坑:评估和开发脱节。有一段时间,评估是单独一个团队在做,开发团队只管改 Agent,两边不怎么沟通。结果评估发现的问题,开发团队不知道;开发改的东西,评估团队也没及时覆盖。后来改成评估和开发同一拨人做,迭代效率高了很多。
如果让我给刚起步的人一个建议,那就是:先跑通一个最小闭环。定义 5 个任务,搭一个最简单的 Harness,写一个规则 Grader,跑一遍看结果。然后再逐步扩展。不要一开始就追求完美,先让流程转起来,再在迭代中优化。