端到端Agent评测体系搭建实录:从评测集到CI落地

发布时间:2026/10/5 9:33:30
端到端Agent评测体系搭建实录:从评测集到CI落地 做 Agent 开发半年多我踩过最大的坑不是 Agent 不输出而是没人能说清楚“这次改动到底变好了没有”。功能演示看着很惊艳换成真实业务场景就频繁翻车修好一个任务另一个任务又悄悄回退。这种靠肉眼点验和零散截图过日子的状态持续了两周我决定搭一套真正能用的 Agent Evaluation 体系把端到端的用户任务自动化地跑起来用可复现的评测集和量化指标回答三个问题Agent 能不能完成真实任务、在哪个环节失败、每个版本改动到底是进步还是回退。这篇文章就是我搭建这套端到端 Agent Evaluation 的最小闭环实录从评测集构建、环境隔离、执行编排到指标分析都会讲到也会把踩过的坑一并列出来。适合已经被 Agent 效果问题折磨过或者正准备把 Agent 落到真实业务里的开发者参考。1. 为什么需要一套端到端的 Agent 评测体系1.1 先想清楚测模型智商还是测 Agent 干活的能力很多团队会把 Agent 评测等同于“跑几百条 prompt看 LLM 答得好不好”这其实还是传统模型评测的思路。端到端 Agent 评测完全不同Agent 要在一个真实或模拟的环境中自己理解任务、调用工具、观察结果、修正计划最后交付结果。普通模型评测像笔试Agent 评测更像试用期考察不仅要看能不能回答还要看能不能把事办成。举个例子任务“查一下订单 Ord-123 的物流状态并把结果同步给客户”。模型完全可以正确回答“应该调用订单查询 API”但在真实链路里Agent 需要先找到工具传对订单号参数解析返回值判断状态再调用发送邮件工具邮件内容要包含订单号和状态。任何一环错了最终都算失败。这种链路级验证只有端到端评测才能覆盖这也是它比单轮问答评测更能代表产品体验的原因。我实测下来聊天评测分数高的 Agent端到端任务成功率可能不到 60%。两者根本不是一回事评测方式必须跟着产品形态走Agent 产品就该用 Agent Evaluation 的方式去验收。1.2 端到端评测解决的三个现实问题第一是回归防退化。Agent 系统由 prompt、工具定义、上下文构建和模型选择多部分组成改一个 prompt 可能让 A 任务变得更好同时让 B 任务直接崩掉。没有自动化评测这种回退往往要过很久才在线上暴露到时候连改了什么导致回退都很难排查。第二是版本准入。每次发布前能不能上线不该凭感觉而是看评测集上的关键指标有没有达到阈值。以前我发版本基本靠“我测了几个用例没问题”后来改成评测通过才允许合并版本质量明显稳了。第三是过程归因。端到端失败经常不是模型不聪明而是某个中间步骤错了。评测过程中保留完整轨迹能定位是规划出错、工具参数错误还是环境理解错误。这三点是我把评测体系定义为“端到端”的原因不是看单个模型输出而是看完整任务上下文中的综合表现。1.3 谁来搭搭到什么程度独立开发者可以先从一套脚本加几十个任务用例开始不需要一开始就做平台小团队可以直接用 pytest 把评测纳入 CI平台型产品则需要考虑大规模任务调度、沙盒资源池、结果聚合和监控。不管规模多大核心逻辑都是同一套任务定义、环境准备、Agent 执行、轨迹记录、断言打分、报告对比。先搭出最小闭环再逐步加复杂度是我最推荐的方式。不要一上来就追求全自动化和精细指标先把离散的任务用例跑起来后面所有优化都有数据支撑。2. 评测集构建从任务采集到用例管理2.1 任务素材从哪来线上日志、用户反馈和公开基准评测集的质量决定了评测体系的可信度。如果任务都是自己拍的“理想任务”跑出来的结果再漂亮也无法反映真实用户。最稳妥的来源是线上日志把真实用户和 Agent 的交互 prompt 脱敏、聚类选高频和高价值的场景转成评测任务。客服工单和用户反馈是第二来源它们能暴露边界和异常。比如用户反复问“订单为什么没发货”背后往往涉及多轮澄清和状态判断这种真实需求比凭空设计更有价值。公开基准如 GAIA、AgentBench、tau-bench、WebArena 可以给你任务设计灵感但不要直接照搬因为业务工具和环境差异很大直接背题会导致评测失真。我自己的习惯是真实场景任务占 70%边界和异常场景占 20%对抗性任务比如诱导模型做危险操作或重复循环占 10%。这个比例不是固定的但可以保证评测集不只覆盖 happy path也能暴露真实问题。2.2 一个评测任务最少包含哪些字段一个 task 文件最少要有任务 ID、给 Agent 看的任务描述、初始状态、可用的工具或动作、期望结果与验收规则、难度和标签。初始状态非常关键很多评测不稳定就是因为任务依赖的外部状态没固定下来。比如订单查询任务必须在启动前把“订单存在且状态为已发货”注入 mock 服务。下面是一个 YAML 示例task_id: order_query_notify_001 description: 用户想查询订单 Ord-123 的物流状态并把最新状态通知客户。 initial_state: orders: - id: Ord-123 status: shipped carrier: SF tracking_no: SF123456 available_tools: - query_order - send_email expected: check_tool_called: - send_email check_state: outbox: - to: customerexample.com subject_contains: 已发货 check_final_answer: contains: [运输中, 已发货] max_steps: 8 difficulty: easy tags: [order, email, notification]available_tools 是给 Agent 的工具清单也是 harness 允许调用的白名单。expected 里的 check_tool_called、check_state、check_final_answer 分别对应过程、副作用和最终回答三级验收。max_steps 是防死循环用的没有预算的评测很容易被带偏。2.3 验收规则优先硬断言慎用 LLM 当裁判端到端任务的“最终结果”通常有三种形态系统状态变化、外部副作用、最终自然语言回复。所以验收规则也要分三类状态断言、副作用断言、语义断言。能写硬断言的就不要用软判断。比如邮件是否发送、数据库中状态是否变更这些用代码判断非常稳定最终回复是否包含关键信息也可以用正则或关键词覆盖大部分场景。只有当前两种不好判断时才考虑引入 LLM-as-judge比如评价“回复是否礼貌且完整”。LLM judge 会带来额外的不确定性和成本使用时需要固定评分标准、选择与 Agent 不同模型体系的裁判、多次采样取平均。我在实践中把超过两成的任务都改成硬断言后评测稳定性提升非常明显回归对比也终于有了参考价值。2.4 评测集也要版本管理防止偷偷“改答案”评测集是代码资产不是随手维护的 txt。任务描述或期望结果一旦变更会影响所有历史对比。我的做法是把评测集放在独立目录进入 Git 管理每次修改任务用例都要记录变更原因并把任务的 version 字段递增。同时严格禁止评测集进入训练集或者 Few-shot 示例否则模型会把“背答案”当成“会干活”线上真实任务立刻现原形。这里也容易踩坑很多同学在调试时发现 Agent 过不了就直接在评测集里放宽断言最后整个评测集失去意义。放宽断言不是不行但一定要留存记录并且让放宽的理由可追溯。评测集保护得越好评测结果的可信度越高。3. 评测执行环境把 Agent 放进一个可控的考场3.1 Evaluation Harness 和 Agent 的职责边界先分清两个角色Agent 是被测对象Evaluation Harness 是考场、考官和计时器。Harness 负责读任务、准备环境、向 Agent 暴露工具、记录交互、执行断言、生成报告Agent 只负责根据任务描述和观察结果做决策。两者必须解耦。很多新手会把评测逻辑写进 Agent 代码里比如 Agent 内部自己判断“是不是在评测如果是就抄近路”。这是典型的作弊式通过跑分好看但上线就废。正确定位是Agent 不知道检查点的存在它只能感知到任务、工具和外界的反馈。Harness 在外围观察一切包括合法工具调用、最终结果和关键中间状态。理解 harness 和 agent 的区别是搭评测体系的第一步。3.2 环境隔离沙盒、mock 服务与依赖快照端到端评测要跑真实动作但绝不能碰生产环境。最轻量的方案是给每个任务准备一个临时“世界”文件系统用临时目录数据库和第三方 API 用 mock 服务代替外部实时数据则提前抓快照并离线回放。比如“查询今天天气再总结给用户”的任务如果在真实天气 API 上跑结果每天都不一样根本没法回归对比正确做法是固定一个历史天气快照让任务输入完全确定。如果要同时跑很多任务建议引入容器或虚拟环境做沙盒隔离限制文件读写和网络权限。注意沙盒的权限边界应当模仿生产限制不能给 Agent 比真实环境更多权限否则评测通过不代表可以上线。这是一个很容易被忽略的“评测环境一致性问题”。3.3 端到端执行的最小循环评测执行本质是一个事件循环harness 把任务描述交给 AgentAgent 返回动作harness 执行动作并返回观察结果如此交替直到 Agent 输出 final_answer 或达到步数上限。伪代码可以写成这样def run_evaluation(agent, task, env): env.reset(task.initial_state) transcript TrajectoryRecorder() for step in range(task.max_steps): observation env.observe() action agent.step(observation) transcript.append(action) if action.type final_answer: break env.apply(action) report EvaluationReport(transcripttranscript, env_snapshotenv.snapshot()) return report这段代码的要点是每次循环都让 Agent 基于最新 observation 决策env.apply 是执行动作的唯一入口所有动作都会被记录final_answer 是正常出口max_steps 是强制出口。我在真实系统里还会加一个 wall-clock 超时防止某个工具调用挂死。执行框架越简单越容易排错。3.4 每一步都记录轨迹评测报告的数据源评测的价值一半在最终指标另一半在轨迹。没有轨迹的失败用例是没办法调试的。我会以事件流的方式保存每一步模型输入输出、工具名称、参数、返回值、耗时、token 消耗、当前状态快照。一个典型事件长这样{ event: tool_call, step: 3, agent_version: v0.12.1, task_id: order_query_notify_001, tool: query_order, arguments: { order_id: Ord-123 }, result: { status: shipped, carrier: SF }, duration_ms: 240, tokens: { prompt: 120, completion: 80 } }记录这些数据后失败用例可以完整回放Agent 看到了什么、做了什么、环境如何反馈全部都在。这比只看“失败”两个字高效得多。我的经验是轨迹记录格式最好在项目早期就定好后面要改会牵动所有报告和分析逻辑成本相当高。4. 指标设计与结果分析不要只问“过没过”4.1 核心指标分四类不要只统计成功率端到端评测最自然的指标是任务成功率但只看它会掩盖大量过程问题。我建议每个任务至少记录四类指标完成类、过程类、效率类和稳定性类。完成类是最终是否通过过程类是关键中间步骤是否达成比如“是否在发邮件前先查询了订单”效率类是步数、耗时和 token 消耗稳定性类是同一任务多次运行的结果离散程度。指标类型示例指标用途完成类任务通过率、最终答案命中率判断整体效果过程类关键里程碑达成率、工具调用正确率、危险动作次数定位失败环节效率类平均步数、平均耗时、平均 token 消耗防止低效绕圈稳定性类多次运行通过率、标准差判断结果可信度成功率提高但 token 消耗翻倍说明 Agent 可能变保守了这对线上成本影响很大不能不看。4.2 过程指标能精确定位失败环节端到端失败通常不是“一句话错误”而是链路里的某个节点出错。我在任务定义里加入了“里程碑检查点”。例如订单通知任务中设定必须依次经过 query_order 和 send_email会在报告中标记哪个里程碑没达成。如果 Agent 调用 query_order 但传错了订单号那么查询这个里程碑虽然触发但算未达标。透过这类过程指标就能区分是规划错误、参数生成错误还是环境理解错误。另外要关注“危险动作”。例如调用删除接口、向非目标地址发邮件、执行高权限命令即使最终结果通过也必须标记为风险。评测不只是看任务结果也要看行为是否安全可控。高危动作的一票否决在 Agent 落地面向上非常关键。4.3 报告与回归对比让每次改动都有记录评测报告不要只打一个 pass/fail建议生成 JSON 或 HTML 报告包含每个用例的通过状态、失败原因、轨迹链接和指标明细。每次 Agent 改动后跑同一评测集然后对比两个版本的结果一定要生成三类差异新增通过的用例、新增失败的用例、指标明显变化的用例。新增失败才是需要优先处理的回归新增通过则能帮你确认改动有效。阈值门禁方面我的建议是对冒烟集要求 100% 通过对回归集设置一个目标值比如关键任务通过率不低于 85%一旦低于阈值就阻塞合并。单纯看平均值很容易被简单任务拉高我会同时统计“困难任务通过率”这样评测压力更大也更真实。4.4 用评测结果反向驱动开发评测的最终目的是指导迭代。我每次跑完评测第一件事不是改 prompt而是把失败用例按根因分类任务理解错误、规划错误、工具调用参数错误、环境观察错误、模型幻觉、超时。分类之后决定本轮迭代只修一个根因改完再跑同一组用例看是否改善。一次改太多变量会导致根本不知道哪个改动有效。另一个建议是把线上真实用户的失败反馈定期转成新的评测任务。这样评测集其实是活的会随着产品增长持续补强。评测集不能一成不变但每次变更都要可控和可追溯。5. 实操实录从零搭一套最小闭环并接入 CI5.1 目录结构先搭起来我建议按下面的目录组织评测工程eval/ tasks/ order_query_notify_001.yaml calendar_booking_002.yaml src/ harness.py mock_service.py assertions.py reports/ conftest.pytasks 放所有任务定义src 放执行逻辑reports 放每次运行的报告。把评测当作一个独立模块不要散落在 agent 项目里。这样 Agent 主流程改动时评测工程不会被动牵连。5.2 用 pytest 当评测执行器成本低好集成独立研发阶段没必要自己写一套 runner直接用 pytest 足够。它能做参数化、生成 JUnit XML 报告、轻松接入 CI。核心测试函数可以这样写import pytest from eval.src.harness import run_evaluation, load_task from myagent import build_agent def all_task_ids(level): return [t.id for t in load_tasks(level)] pytest.mark.parametrize(task_id, all_task_ids(smoke)) def test_agent_smoke(task_id): task load_task(task_id) agent build_agent(configcurrent) result run_evaluation(agent, task) assert result.passed, result.failure_detail把任务放到参数化里每个失败用例都能独立定位。借助 pytest 的标记机制把任务分为 smoke、regression、full 三层日常跑冒烟集夜间跑全量回归。JUnit XML 报告可以直接在 GitLab CI 或 GitHub Actions 里展示。5.3 Mock 服务要能做到“每次运行前重置”端到端评测最怕用例之间相互污染。比如上一个任务的邮件还在 outbox 里下一个任务断言就误判。我实现的 mock 服务都带 reset 方法每次 run_evaluation 前清空所有状态再根据 task.initial_state 注入初始数据。以一个订单服务为例class MockOrderService: def __init__(self): self.orders {} self.outbox [] def reset(self, snapshot): self.orders snapshot.get(orders, {}) self.outbox.clear() def query_order(self, order_id): return self.orders.get(order_id, {error: not found}) def send_email(self, to, subject, body): self.outbox.append({to: to, subject: subject, body: body}) return {ok: True}这个类只做两件事根据快照注入状态暴露 Agent 可调用的工具函数。不要让它有太多业务逻辑否则 mock 和真实服务偏差又会被放大。5.4 把评测接入 CI形成“先评测后合并”的流程本地跑评测很容易偷懒所以必须让评测在 CI 里强制运行。我的 GitHub Actions 示例只跑冒烟集因为要快速反馈全量回归放到定时任务或发布前的 workflow。YAML 大致长这样name: agent-eval on: pull_request: paths: - agent/** - eval/** jobs: eval: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-pythonv5 with: python-version: 3.11 - run: pip install -r requirements.txt - run: pytest -m smoke -x --junitxmlreports/smoke.xml - uses: actions/upload-artifactv4 with: name: eval-reports path: reports/引入 CI 之后评测才真正成为开发流程的一部分。任何人改 agent 逻辑都必须通过评测才能合并而不是靠口头承诺。6. 常见问题与排查技巧实录6.1 同一个任务时好时坏评测结果不稳定这是我会最先遇到的坑。LLM 输出本身有随机性温度设置过高会让同样输入产生不同动作。其次如果任务依赖外部实时数据那两次运行的环境本身就不一样。我的解法是多数任务温度固定为 0 或 0.2涉及外部数据的任务全部使用快照回放关键任务连续跑三次取两次及以上通过作为最终结果。但完全确定性很难达到所以指标对比时看趋势而不是抠单次数值。6.2 Mock 太假评测过了但一上线就挂mock 服务如果不是从真实流量抽象出来的就容易出现“评测环境太干净”的问题。真实系统有延迟、有异常、有参数校验如果 mock 全都返回成功Agent 就没机会锻炼真实场景的处理能力。建议从线上真实请求中采样响应保存成快照并在评测集中加入故障注入用例接口超时、返回 500、参数非法。这样评测覆盖的不只是理想路径。6.3 Agent 卡在循环里资源被白白浪费没有步数限制的评测会让一个失败的 Agent 反复调用同一个工具。我在 harness 中同时加了 max_steps 和 wall-clock 超时并在轨迹里检测“同一工具、同一参数连续调用超过三次”的模式直接标记为循环问题。评测预算是成本的一部分无限制执行的代价很高。6.4 评测集膨胀全量执行时间越来越长任务数量从 30 涨到 300 后全量评测可能要数小时。把所有任务都放在 PR 里跑会让团队等太久。我做了三层拆分冒烟集控制在 10 到 20 个任务每次 PR 必跑回归集覆盖核心链路日级别跑全量集含边界和对抗样本发布前跑。另外使用 pytest-xdist 并行跑任务每个任务一个独立进程或容器可以显著缩短执行时间。6.5 通过率上升很爽但别忽略“评测集被污染”如果发现通过率异常高先怀疑评测集是否泄露进开发过程。比如调试时把任务样例写进 Few-shot 示例或者手动调整断言让 Agent 通过都会让分数虚高。保护评测集的方法是评测集和训练样本严格隔离任何用例变更都要走版本管理并且每周随机抽 5% 的失败用例人工复核防止体系自身失真。7. 落地体会与运营建议7.1 别等到系统复杂了再搭评测我的体会是评测体系越早搭越省钱。最开始哪怕只有 30 个任务也能把每次改动的收益和损失具象化。不要等 Agent 已经接入一堆工具、prompt 也堆到几十个版本后再补评测那时候历史包袱已经很大想定位问题很难。从第一个能稳定跑通的任务开始就把任务样本、断言和环境准备好后面只是持续加用例的问题。7.2 把评测当作长期基础设施来运营评测体系本身也要持续运营。任务要随真实用户反馈补强断言要随系统能力演进调整报告要归档才能做长周期趋势对比。我个人还会在每次发布前留存一份评测基线这样几个月后回头看能清楚看到 Agent 能力的真实变化轨迹。评测不是一次性的工程而是和 Agent 一起迭代的长期基础设施越早开始收益越大。