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, tokens |
TaskSpec 是输入,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 反过来,那选型时就要看你的业务更偏哪类任务。
| 模型 | 总体分 | 单步任务 | 多步任务 | 长链路任务 | 工具选择 | 容错恢复 |
|---|---|---|---|---|---|---|
| 模型A | 0.78 | 0.95 | 0.82 | 0.58 | 0.88 | 0.52 |
| 模型B | 0.74 | 0.82 | 0.79 | 0.71 | 0.76 | 0.73 |
| 模型C | 0.69 | 0.88 | 0.71 | 0.49 | 0.81 | 0.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 响应。多轮任务的追踪和评分更复杂,要处理上下文累积、话题切换、意图漂移等问题,但价值也更大,因为真实业务大多是