2026/10/29 17:46:15

Agent真实任务评测框架:从跑分到归因的工程实践

Agent真实任务评测框架:从跑分到归因的工程实践 1. 模型宣传页上的跑分为什么一放到真实任务里就崩了这两年做 Agent 相关开发的人应该都有同一种体感模型厂商发布的评测榜单越来越漂亮工具调用准确率动辄 90% 以上多步推理成功率看着也很能打可一旦把同一套模型接到自己业务里跑真实任务表现立刻掉一个档次。我最早做 Agent 项目时也踩过这个坑当时选型完全依赖公开榜单结果上线后才发现榜单里那些标准任务和我实际要跑的流程根本不是一个东西。问题不在于模型本身造假而在于评测任务的设计和真实任务之间存在巨大的分布差异。公开评测集里的任务通常是单轮、边界清晰、工具数量有限、环境状态干净的而真实业务里的 Agent 面对的是多轮对话、工具几十个、上下文随时被污染、中间步骤失败要回滚重试的复杂场景。你在榜单上看到的 90 分是在实验室环境里考出来的换到野外环境自然水土不服。所以我后来干脆自己搭了一套 Agent 真实任务评测框架核心目标只有一个用贴近生产环境的任务把每个候选模型/框架拉到同一条起跑线上跑完看谁真行。这套框架不追求学术上的严谨性追求的是工程上的可复现和可归因——跑完之后我能明确知道某个模型是在哪一类任务上强、哪一类任务上弱、失败到底是模型能力问题还是我的编排逻辑问题。这篇文章就把这套框架的完整搭建思路拆开讲包括任务怎么设计、执行怎么追踪、评分怎么归因以及我在实际跑测过程中踩过的那些坑。适合正在做 Agent 选型、或者想给自己项目建立回归测试体系的同学参考。全文基于我自己的实操经验涉及具体参数和步骤的地方会说明背后的取舍逻辑你可以直接抄作业也可以按自己业务调整。2. 评测框架的骨架任务、执行器、追踪器、评分器四件套搭评测框架最忌讳一上来就写代码先把架构想清楚能省掉后面大量返工。我这套框架最终收敛成四个核心模块每个模块职责单一模块之间通过标准数据结构通信。这样设计的好处是换模型只动执行器换任务只动任务库评分逻辑调整不影响其他部分。2.1 四个模块的职责边界任务库Task Store负责存放所有评测任务的定义每个任务是一个结构化的 JSON 或 YAML包含任务描述、初始环境状态、可用工具列表、期望结果、评分规则引用。任务库是只读的评测过程中不会被修改保证每次跑测的输入完全一致。执行器Runner负责把任务喂给被测的 Agent驱动它一步步执行直到任务完成或触发终止条件。执行器要处理的核心问题是怎么把任务描述转成 Agent 能理解的输入、怎么把 Agent 的工具调用请求路由到真实的工具实现、怎么判断任务该结束了。追踪器Tracer负责记录执行过程中的一切每一步的输入输出、工具调用的参数和返回、耗时、token 消耗、中间状态变化。追踪器是后面做归因分析的数据基础记录得越细出问题时越容易定位。评分器Scorer负责根据追踪器记录的数据和任务定义的评分规则算出最终得分并且给出失败原因的分类标签。评分器要区分结果对不对和过程好不好两个维度很多 Agent 是结果对了但过程极其低效这种也要扣分。2.2 为什么不用现成的评测工具市面上确实有一些 Agent 评测工具我也试过几个但最后都放弃了。主要原因是它们要么任务格式固定死了没法塞进我业务里的私有工具要么评分逻辑是黑盒跑完只给一个分数不告诉我为什么扣分要么追踪粒度太粗只记录最终结果中间步骤全丢了。自己搭虽然前期投入大但换来的是完全可控。任务想怎么设计就怎么设计评分规则想怎么调就怎么调追踪数据想记多细就记多细。而且这套框架搭好之后是可以复用的以后每换一个模型、每改一次编排逻辑跑一遍就知道有没有退化相当于给 Agent 项目建了一套回归测试。2.3 数据结构的统一约定四个模块之间靠统一的数据结构通信这是整个框架能跑通的关键。我定义了三层结构层级结构名作用关键字段任务层TaskSpec描述一个评测任务task_id, description, tools, initial_state, expected, scorer_ref执行层TraceRecord记录一次完整执行trace_id, task_id, steps[], final_state, status步骤层StepRecord记录单步执行step_index, input, tool_calls[], output, latency, tokensTaskSpec 是输入TraceRecord 是输出StepRecord 是 TraceRecord 的组成单元。评分器只认 TraceRecord不关心执行器是怎么跑出来的这样执行器换实现也不影响评分。提示数据结构一旦定下来就尽量别改因为任务库、追踪数据、评分规则都会依赖它。我早期改过一次字段名结果历史追踪数据全部作废只能重跑血的教训。3. 任务设计怎么让评测任务既真实又可复现任务设计是整套框架里最花心思的部分也是最容易做砸的部分。任务太简单所有模型都能满分测不出差异任务太难所有模型都零分同样测不出差异任务设计得不真实测出来的分数没有参考价值。我前后迭代了三版任务库才找到比较合适的难度区间和真实度平衡点。3.1 任务分层的三个维度我把任务按三个维度分层每个维度分三档组合起来覆盖不同的能力面。维度一步骤复杂度。单步任务一次工具调用就能完成、多步任务需要 3-10 步串联、长链路任务10 步以上中间可能失败重试。单步任务主要测工具调用的准确性多步任务测规划和状态管理长链路任务测容错和恢复能力。维度二工具干扰度。干净工具集只有完成任务必需的工具、有干扰工具混入几个功能相似但用不上的工具、高干扰工具集几十个工具大部分是干扰项。这个维度测的是模型在工具选择上的判断力真实业务里工具往往很多选错工具是常见失败原因。维度三环境不确定性。确定性环境工具返回固定结果、有噪声环境工具返回带随机噪声、会失败的环境工具按概率返回错误。这个维度测的是模型的鲁棒性真实世界里工具调用失败是常态不会处理失败的 Agent 没法上生产。三个维度组合起来就是 27 种任务类型实际不需要每种都覆盖我一般选 8-10 种有代表性的组合每种组合设计 5-10 个具体任务总共 50-80 个任务构成一个评测集。这个规模跑一轮大概需要 1-2 小时频率上可以接受。3.2 任务描述的写法别写得太清楚也别写得太模糊任务描述是 Agent 唯一能看到的输入写法直接决定任务难度。我踩过的坑是一开始把任务描述写得特别详细把每一步该干什么都暗示了结果所有模型都能满分测不出差异。后来改成只给目标和约束让 Agent 自己规划步骤难度立刻上来了模型之间的差距也显现出来了。但也不能写得太模糊否则任务本身就有歧义评分时没法判断对错。我的经验是遵循目标明确、路径开放原则告诉 Agent 要达成什么结果、有哪些硬约束比如不能超过多少步、必须用某个工具但不告诉它具体怎么走。举个例子task_id: order_refund_001 description: | 用户反映订单 ORD-2024-8871 的商品有质量问题要求退款。 你需要核实订单状态确认是否符合退款条件如果符合则发起退款 如果不符合则给出明确理由。退款金额不能超过订单实付金额。 可用工具见 tools 列表请在 15 步内完成。 tools: - query_order - check_refund_policy - initiate_refund - send_notification - query_user_history expected: refund_initiated: true refund_amount: 299.00 reason_code: QUALITY_ISSUE这个任务描述给了目标和约束但没告诉 Agent 先查订单还是先查政策也没告诉它要不要看用户历史。这些判断留给 Agent 自己做正好测出它的规划能力。3.3 期望结果的表达别只写最终答案期望结果如果只写一个最终答案评分时只能判断对错没法归因。我的做法是把期望结果拆成结果断言和过程断言两部分。结果断言描述任务完成后的最终状态比如退款是否发起、金额对不对、状态码是什么。过程断言描述执行过程中必须满足的条件比如必须调用过某个工具、工具调用顺序有要求、不能调用某些工具、总步数不能超过阈值。过程断言是归因分析的关键。举个例子如果某个模型最终退款金额对了但过程中调用了query_user_history这个不该调用的工具说明它在工具选择上有问题虽然结果对了但过程有瑕疵评分时要扣分。这种细粒度的评分才能真实反映模型能力。3.4 任务库的版本管理任务库一定要做版本管理因为任务一改历史评测结果就没法直接对比了。我用的是最简单的方案任务库放在 git 仓库里每次改动打 tag评测结果里记录任务库的 commit hash。这样任何时候都能知道某次评测用的是哪个版本的任务库也能复现历史结果。注意任务库改动后之前跑的所有评测结果都要标记为不可直接对比需要在新版本任务库上重跑一遍才能横向比较。我见过有人改了任务库还拿新旧结果对比得出的结论完全是错的。4. 执行追踪把 Agent 的每一步都钉在案发现场追踪器是整套框架里我最看重的部分因为没有细粒度的追踪归因分析就是空中楼阁。Agent 执行失败时你只看到最终结果是错的但不知道错在哪一步、为什么错这种评测跑一百遍也没用。追踪器要做的就是把这个黑盒打开把每一步的输入输出、工具调用、状态变化全部记录下来。4.1 追踪粒度记到什么程度才够用追踪粒度太粗出问题定位不到太细数据量爆炸还影响性能。我试过几种粒度最后定在每次模型调用 每次工具调用这个级别。每次模型调用记录完整的 prompt包括系统提示、历史消息、当前输入、模型的原始输出、解析后的结构化结果工具调用列表或最终答案、耗时、输入输出 token 数。这些数据能回答模型当时看到了什么、它决定做什么。每次工具调用记录工具名、调用参数、返回结果、耗时、是否成功。这些数据能回答工具实际执行了什么、返回了什么。中间状态变化也要记但不用每步都记全量状态只记 diff 就行。比如订单状态从待处理变成已退款只记这个变化不记整个订单对象。4.2 追踪数据的存储格式追踪数据我用 JSONL 格式存每行一条 StepRecord一个任务的完整执行是一个 JSONL 文件。选 JSONL 而不是单个大 JSON 的原因是流式写入方便执行过程中就能落盘不用等任务结束单行解析快做归因分析时按行读就行出问题时单行损坏不影响其他行。{trace_id:t_001,task_id:order_refund_001,step_index:0,type:model_call,input_tokens:1240,output_tokens:86,latency_ms:1820,output:{tool_calls:[{name:query_order,args:{order_id:ORD-2024-8871}}]}} {trace_id:t_001,task_id:order_refund_001,step_index:1,type:tool_call,tool:query_order,args:{order_id:ORD-2024-8871},result:{status:delivered,amount:299.00,user_id:U_5521},latency_ms:45,success:true}这种格式的好处是任何一步出问题我都能精确定位到是模型决策错了还是工具返回错了。比如上面这个例子如果query_order返回的amount是错的那就是工具实现的问题不是模型的锅。4.3 追踪器要处理的三个边界情况实际跑测时追踪器会遇到一些边界情况处理不好会导致追踪数据不完整影响归因。情况一模型输出无法解析。模型有时候会输出格式不对的内容解析器解析失败。这时候不能直接丢弃要把原始输出记下来标记为parse_error评分时这类步骤要单独统计。我早期直接丢弃这类记录结果发现某些模型失败率很高但追踪数据里看不出来后来补上这个标记才定位到问题。情况二工具调用超时。工具执行超过阈值要中断记录超时状态和已耗时。超时是真实业务里的常见情况评测时必须覆盖。我一般把工具超时阈值设成 30 秒超过就中断并标记timeout。情况三Agent 主动终止。有些 Agent 会在认为任务完成时主动终止有些会一直跑到步数上限。追踪器要能区分正常完成和被迫终止前者标记completed后者标记max_steps_reached。这两种情况的评分逻辑不一样被迫终止的即使结果对了也要扣分。4.4 追踪数据的脱敏如果评测任务涉及真实业务数据追踪数据里可能包含敏感信息。我的做法是在追踪器里加一层脱敏对特定字段如用户 ID、手机号、金额做哈希或掩码处理。脱敏规则配置化不同任务可以用不同规则。这一步很多人会忽略觉得评测数据反正是内部用的。但追踪数据往往会被导出做分析、发给同事看、甚至贴到文档里一旦泄露就是事故。脱敏成本很低别省这一步。5. 评分归因从跑了个分到知道为什么是这个分评分器是整套框架的出口也是最能体现框架价值的部分。一个只会打分的评分器和一个能归因的评分器价值差十倍。我见过太多评测工具跑完给个 0.73 分就完事了你拿着这个分数根本不知道该改什么。我的评分器设计目标是跑完不仅知道分数还知道扣分扣在哪、为什么扣、怎么改。5.1 结果分与过程分的分离评分分两个维度结果分和过程分各占一定权重。结果分判断任务目标是否达成过程分判断执行过程是否合理。两个维度分开算最后加权。结果分的计算相对直接对照期望结果里的结果断言逐条判断。每条断言有明确的判断逻辑比如refund_amount 299.00就是精确匹配refund_initiated true就是布尔判断。所有断言都通过得满分部分通过按比例给分。过程分的计算复杂一些要对照过程断言逐条判断。过程断言通常包括必须调用的工具是否调用、禁止调用的工具是否没调用、工具调用顺序是否符合要求、总步数是否在阈值内、是否有重试行为等。每条过程断言也有明确的判断逻辑从追踪数据里提取对应信息来判断。5.2 失败原因的分类标签体系归因分析的核心是一套失败原因分类标签。每次评测失败评分器要给出一个或多个标签说明失败原因。我定义的标签体系如下标签含义典型表现TOOL_SELECTION_ERROR工具选择错误调用了不该调用的工具或没调用该调用的工具PARAM_ERROR参数错误工具调用参数格式错、值错、缺字段PLANNING_ERROR规划错误步骤顺序错、遗漏必要步骤、重复无效步骤STATE_TRACKING_ERROR状态跟踪错误忘记之前步骤的结果、状态更新错误RECOVERY_ERROR恢复错误工具失败后没有正确处理、没有重试或重试策略错HALLUCINATION幻觉编造了不存在的信息、工具返回里没有的数据MAX_STEPS_EXCEEDED超步数在步数上限内没完成任务TIMEOUT超时单步或整体执行超时这套标签体系覆盖了我遇到的大部分失败情况。评分器在判断每条断言失败时会同时分析追踪数据给出对应的标签。一个任务可能同时有多个标签比如既工具选错又参数错。5.3 归因分析的实现思路归因分析的实现分三步提取特征、匹配规则、生成报告。提取特征是从追踪数据里提取出判断所需的信息。比如要判断是否调用了不该调用的工具就要提取出所有工具调用记录和禁止调用列表做比对。要判断是否有重试行为就要找出连续两次调用同一工具且参数相同的记录。匹配规则是把提取出的特征和预定义的规则匹配。规则用配置化的方式写不硬编码在代码里方便调整。比如如果调用了禁止工具列表里的工具则打 TOOL_SELECTION_ERROR 标签就是一条规则。生成报告是把匹配结果整理成可读的报告。报告要包含总分、结果分、过程分、每个失败断言的详情、对应的标签、以及基于标签的改进建议。改进建议可以预定义比如 TOOL_SELECTION_ERROR 对应检查工具描述是否清晰、考虑在系统提示里加强工具选择引导。5.4 一个真实的归因案例说个我实际遇到的案例。某次评测里一个模型在退款任务上得分很低结果分只有 0.4过程分 0.6。光看分数不知道问题在哪看归因报告才发现结果断言refund_amount 299.00失败实际退款金额是 299.00 但多退了一笔 50.00 的运费过程断言不能调用initiate_refund超过一次失败实际调用了两次标签PLANNING_ERROR STATE_TRACKING_ERROR进一步看追踪数据发现模型第一次调用initiate_refund退了 299.00然后它似乎忘记了自己已经退过款又调用了一次退运费。这就是典型的状态跟踪错误——模型没有正确维护已退款这个状态导致重复操作。这个归因结果直接指向了改进方向要么在系统提示里强调状态管理要么在编排层加一层状态校验防止重复调用。如果没有归因分析我拿着 0.4 这个分数根本不知道该改什么。6. 跑测实操从零跑通第一轮评测的完整流程前面讲了框架的各个模块这一节讲怎么把它们串起来跑通第一轮评测。我会按实际操作顺序讲包括环境准备、配置、跑测、看结果以及中间容易卡住的地方。6.1 环境准备与依赖框架本身用 Python 写主要依赖几个库httpx做模型 API 调用、pydantic做数据结构校验、pyyaml读任务配置、rich做终端输出。工具实现部分按业务需要接我一般用fastapi起一个本地工具服务执行器通过 HTTP 调用。环境准备的关键是把模型 API 和工具服务都跑通别等到跑评测时才发现连不上。我的做法是先写一个最小的 smoke test用最简单的单步任务跑一遍确认模型能调通、工具能调通、追踪数据能落盘再开始跑完整评测集。# 目录结构 agent-eval/ tasks/ # 任务库 order_refund_001.yaml ... tools/ # 工具实现 server.py runner/ # 执行器 runner.py tracer/ # 追踪器 tracer.py scorer/ # 评分器 scorer.py configs/ # 配置 models.yaml scorers.yaml results/ # 评测结果 2024-xx-xx_run1/6.2 模型配置与并发控制模型配置放在models.yaml里每个模型一个配置项包含 API 地址、模型名、温度、最大 token 等参数。评测时温度建议设成 0保证结果可复现。如果模型支持把 top_p 也设成 1减少随机性。并发控制是个容易踩坑的地方。评测任务多串行跑太慢但并发太高又会触发模型 API 的限流。我的经验是先小并发试跑观察限流情况再逐步提高。一般从并发 2 开始稳定的话提到 4、8。同时要加退避重试遇到限流时指数退避别硬刚。models: - name: model_a base_url: https://api.example.com/v1 model: model-a-large temperature: 0 top_p: 1 max_tokens: 4096 concurrency: 4 retry: max_attempts: 3 backoff_base: 26.3 跑测流程与断点续跑跑测流程是加载任务库 → 对每个任务执行 → 记录追踪数据 → 评分 → 汇总结果。这个过程要支持断点续跑因为跑一轮可能一两个小时中间网络抖动、API 限流、机器重启都可能中断不能每次都从头跑。我的实现是每个任务执行完就把追踪数据和评分结果落盘跑测时先检查哪些任务已经有结果跳过已完成的。这样中断后重跑只跑没完成的省时间。提示断点续跑的判断依据要用任务 ID 任务库版本 模型配置的哈希三者任一变了都要重跑。我早期只用任务 ID 判断结果改了模型配置后没重跑拿旧结果做对比结论全错。6.4 结果查看与横向对比跑完一轮后结果汇总成一个报告包含每个模型的总体得分、各维度得分、失败标签分布、典型失败案例。我一般会生成一个 Markdown 报告方便直接看。横向对比是评测的核心价值。把多个模型的结果放一起对比能清楚看出每个模型的强弱项。比如模型 A 在单步任务上得分高但长链路任务差模型 B 反过来那选型时就要看你的业务更偏哪类任务。模型总体分单步任务多步任务长链路任务工具选择容错恢复模型A0.780.950.820.580.880.52模型B0.740.820.790.710.760.73模型C0.690.880.710.490.810.45这张表一眼就能看出模型 A 单步强但长链路和容错弱模型 B 更均衡模型 C 整体偏弱。如果你的业务是长链路复杂任务模型 B 可能比 A 更合适尽管 A 的总体分更高。7. 踩过的坑那些让评测结果失真的细节框架搭起来只是第一步真正跑测时踩的坑才是最有价值的部分。这一节分享几个我实际踩过、并且导致评测结果失真的坑都是常规文档里不会写的。7.1 工具实现的 bug 被算到模型头上最早跑测时我发现某个模型在退款任务上失败率特别高一度以为是模型能力问题。后来仔细看追踪数据才发现是check_refund_policy这个工具的实现有 bug对某些订单状态返回了错误的政策结果模型基于错误信息做了错误决策。这个坑的教训是评测前一定要先验证工具实现的正确性。我的做法是给每个工具写单元测试用固定的输入验证输出确保工具本身没问题。工具没问题了评测结果才能反映模型能力。7.2 系统提示的差异导致对比不公平不同模型对系统提示的敏感度不一样。我早期给所有模型用同一套系统提示结果发现某些模型表现特别差换一套更适合它的提示后表现立刻上来了。这说明用同一套提示对比不同模型是不公平的。后来的做法是允许每个模型用自己最优的提示但提示要固定下来评测时不能改。这样对比的是每个模型在最优配置下的表现更接近实际选型场景。当然如果你想测的是同一套提示下哪个模型更强那就统一提示但要明确这是另一种评测目标。7.3 温度设成 0 也不完全可复现理论上温度设成 0 应该完全确定但实际跑测时发现同一任务跑两次结果偶尔不一样。原因是模型服务端的实现细节、并发请求的顺序、甚至浮点运算的精度都可能导致微小差异。这个坑没法完全避免我的应对是关键任务跑三次取平均减少单次随机性的影响。同时记录每次的原始结果如果三次差异很大说明这个任务本身不稳定要考虑调整任务设计。7.4 追踪数据量太大导致分析困难跑完一轮评测追踪数据可能有几十万行直接看根本看不过来。我早期没做数据聚合分析时全靠 grep效率极低。后来加了一层聚合分析按任务类型聚合、按失败标签聚合、按模型聚合生成统计报告。分析时先看聚合报告定位问题区域再下钻到具体任务的追踪数据。这样效率高很多。7.5 评分规则的过拟合评分规则写得太细会导致模型针对评分规则优化而不是真正解决问题。我遇到过一种情况某个模型在评测里得分很高但实际用起来效果一般。查了才发现它的输出恰好符合评分规则的每一条但换到真实场景就不行了。这个坑的应对是评分规则要抓大放小别抠太细。核心的结果断言和关键的过程断言要有但不要为了区分模型而加一堆细枝末节的规则。同时要定期用真实业务数据验证评测结果如果评测高分但业务效果差说明评分规则需要调整。8. 框架的扩展方向从单轮评测到持续回归这套框架跑通之后我陆续做了一些扩展让它从一次性评测工具变成持续回归体系。这部分分享几个我觉得有价值的扩展方向。8.1 接入 CI 做自动回归把评测框架接入 CI每次 Agent 编排逻辑有改动就自动跑一轮核心任务集得分低于阈值就阻断合并。这样能防止改动引入退化。核心任务集不用全量选 10-20 个最有代表性的任务就行跑一轮几分钟不拖慢 CI。8.2 用真实业务数据补充任务库评测任务库最初是手工设计的后来我把线上真实失败案例脱敏后补充进任务库让评测更贴近实际。这些真实案例往往比手工设计的任务更能暴露问题因为它们是真实用户踩出来的。8.3 多轮对话任务的扩展最初的框架只支持单轮任务后来业务需要多轮对话就扩展了任务格式支持定义多轮用户输入和期望的 Agent 响应。多轮任务的追踪和评分更复杂要处理上下文累积、话题切换、意图漂移等问题但价值也更大因为真实业务大多是