1. 为什么是“Reach”:Agent评测缺的不只是一个分数
做AI Agent相关应用的开发者,大概率都经历过这样一段模糊期:Agent看起来“挺聪明”,能理解任务,会调用工具,多轮对话也有来有回,但一旦要回答“它到底行不行”,大家都只能支支吾吾。我去年搭建一套内部智能体应用时就卡在这个问题上。不是不知道要测,而是没有一个合适的维度去测。最终结果能不能跑通只是最粗的一个信号,真正让我头疼的是:一个任务失败了,我根本说不清楚是模型理解错了,是工具调用错了,还是中间某一步链路断了。
那段时期我做了大量“感觉型评测”——换个模型试一下,觉得效果好一点;改一版提示词,觉得更听话了。但这类反馈没法沉淀成可复用的结论,换个任务场景一切归零。所以我后来动手做了一个叫Agent-Reach的评测脚本,专门用来回答一个问题:给定一条任务链路和一个智能体,它能从初始状态出发,成功触达多少个关键节点?
1.1 只盯最终结果是不够的
大多数团队评测Agent,习惯看“完成率”,也就是最终目标有没有实现。这个指标最容易统计,但也是最迟钝的指标。打个比方,你让一个智能体去处理一笔异常订单,链路包含:读取订单号、查客户信息、核对库存、生成处理建议、更新订单状态。如果最终状态没更新成功,完成率就是0,可是这0背后可能是五种完全不同的原因——可能订单号读错了,可能客户信息查不到,可能库存接口返回了空数据,也可能模型压根没想过去查库存。
只用一个完成率,所有这些失败模式全被混在一起,你拿不到任何定位信息。我做Agent-Reach时第一个决定就是:不要只盯着最终输出,而是把任务链路拆成里程碑,逐个检查智能体到没到过、有没有做对。
1.2 从图论“可达性”借鉴一个思路
“Reach”这个词,说白了借用了图论里的可达性(Reachability)概念。在一个有向图里,从一个节点出发,沿着边能走到哪些节点,哪些走不到,这是有明确答案的。Agent执行任务的过程,在形式上非常像在图上做状态迁移:每个状态是一个节点,每次工具调用是一条边,模型每走一步,就决定了下一条边通向哪里。
如果把“智能体完成任务”抽象成“从起点走到终点”,那么评测的核心就变成了:它在每一步到底能走多远,哪条边断了,哪个节点永远到不了。Agent-Reach要做的,就是把这种抽象还原成工程上的度量——给你一份指标报告,告诉你智能体覆盖到哪个里程碑,卡在哪个里程碑,瓶颈是什么。
1.3 Agent-Reach 到底测什么、适合谁用
Agent-Reach本身不是一个产品,而是一套评测思路加配套脚本。它适合下面几类场景:
- 你在对比两个模型在Agent任务上的表现,不想只靠“体感”。
- 你在迭代提示词,想确认改动到底提升了哪部分能力。
- 你在搭建Agent应用,需要知道哪段链路是薄弱环节。
- 你想给外部交付一个可量化的Agent效果说明,而不是一句“效果不错”。
我在这篇文章里会把Agent-Reach的完整设计思路、评测流程、指标计算方式、以及我在实际跑测中遇到的三个高频问题全部摊开讲。如果你正在被“Agent效果无法量化”这个问题困扰,这套东西可以直接抄作业。
2. Agent-Reach的评测对象与里程碑设计:一条工单处理链路的拆解演练
2.1 为什么选“工单处理助手”当评测样本
评测一个Agent,首先要有一个足够有代表性的任务场景。我选的是“工单处理助手”,原因是它天然具备一条完整的工具调用链,而且每一步都有清晰的业务语义:读工单、查客户、查库存、给建议、更新状态。这个链路在真实业务里非常常见,无论是客服系统、运维工单还是售后流程,结构都差不多。
评测场景太短,没有区分度,所有模型都是满分;太长,噪音太大,很难定位问题。工单处理链路处在“中等长度”——五个关键节点,既能暴露模型差异,又能让失败模式可以追溯。
2.2 五个工具与五种失败模式
我在Agent-Reach的第一个版本里定义了一个模拟工具集,让智能体在沙箱环境里操作,而不是直接对接真实业务系统。这样做的原因是评测环境必须可控,真实系统有太多不确定性,你没法分清楚失败是因为Agent能力不足,还是因为上游接口抽风。
| 工具名称 | 功能说明 | 评测中注入的失败模式 |
|---|---|---|
read_ticket | 根据工单ID读取工单基本信息 | 部分工单缺少产品SKU字段 |
lookup_customer | 根据客户ID查询客户档案 | 部分客户地址字段为空 |
check_inventory | 根据产品SKU查询库存 | 部分SKU返回库存为0或不存在 |
generate_action | 根据已有信息生成处理建议 | 当输入信息不完整时,该节点视为不可触达 |
update_ticket | 更新工单状态并留备注 | 状态字段非法时返回错误,需要重试 |
每个工具都预留了失败分支,而这些分支就是评测的“陷阱”——Agent必须在信息缺失、数据为空、结果非法的情况下做出正确判断,无论是重新请求、请求补充信息,还是明确说明无法继续。评测真正想看的,就是智能体面对这些分支时怎么走。
2.3 里程碑(Milestone)怎么定义才算不留后门
设计里程碑是整个Agent-Reach里最需要较真的环节。定义得太宽松,起不到筛选作用;定义得太死板,又会把允许绕路的情况误判成失败。
我最终把一条工单处理链路拆成五个里程碑:
- M1:正确读取了工单编号和工单基本信息。
- M2:成功查询到客户档案,并在后续推理中实际使用了客户信息。
- M3:成功查询到库存状态,并正确关联到工单中的SKU。
- M4:基于M1-M3的信息生成处理建议,没有凭空捏造字段。
- M5:成功调用更新状态工具,工单状态变更合法。
特别要注意M2里的“实际使用”。如果一个智能体查了客户信息但完全没有在后续决策中参考它,这个里程碑只能算“经过”,不能算“触达”。Agent-Reach记录的不只是“有没有调用过某个工具”,而是“这个工具返回的信息有没有进入后续决策链”。这一条,是防止评测分数虚高的关键。
2.4 评测对象可以横向对比什么
在评测对象上,我同时对比了两个维度:
- 模型维度:一个商用闭源模型,一个开源小参数模型,看它们在完全相同任务集的条件下覆盖率差距。
- 提示词维度:同一模型下,对比自然语言风格的工具描述和结构化三段式的工具描述。
模型对比能帮你做选型,提示词对比能帮你做迭代。两件事放在同一套评测框架里跑,效率非常高。我第一次跑完就发现,开源小模型在节点独立触达率上和商用模型差距并没有想象中那么大,真正的差距出现在“把信息串联起来”的环节——这直接改变了后来我对提示词工作的投入方向。
3. 一次完整评测的执行流程与核心指标计算
3.1 一份评测任务的完整执行序列
Agent-Reach的执行流程并不复杂,但每一步都有讲究:
- 构造评测任务集:我准备了10个工单场景,覆盖正常场景和各类异常场景。
- 每场景跑多轮:每个场景跑3轮,一共30条独立的Agent轨迹。
- 记录完整过程:每一轮都保存完整对话历史、每次工具调用的请求与返回、每一步的时间戳。
- 里程碑判定:用规则脚本对每条轨迹做自动标注,标出M1-M5各节点是否触达。
- 人工抽查:随机抽取20%的轨迹做人工复核,防止规则漏判或误判。
跑一轮完整评测,总耗时大概40分钟到1小时,大头都花在模型推理上,脚本本身的性能开销可以忽略。评测脚本只做“记录”和“判定”,不干预Agent的决策过程,这是保证结果可信的前提。
3.2 三个核心指标:Reach@K、里程碑覆盖率、节点独立触达率
在指标设计上,Agent-Reach最终沉淀出了三个核心指标,各有分工。
Reach@K:借鉴信息检索里Recall@K的思路,定义为“在K次工具调用之内,智能体触达目标里程碑的比例”。这个指标回答的问题是:给智能体足够多的尝试机会,它能不能摸到那个关键节点。我在评测里默认取K=15,也就是一条轨迹里最多允许15次工具调用。
里程碑覆盖率:整条链路从头到尾全部触达的比例,也就是M1到M5全部完成的轨迹数除以总轨迹数。这个指标等价于“成功完成率”,但它比最终状态成功率高得多,因为每一步都被显式确认过。
节点独立触达率:单独看每个里程碑在多少条轨迹中被触达,用于定位瓶颈。比如M3的触达率只有40%,而其他节点都是80%以上,那问题大概率出在“查库存”这个环节。
三个指标配合使用,才能完整刻画一个Agent的能力。单独看覆盖率,你会误以为它“还行”;单独看某个节点的触达率,你又会忽略链条整体可能很差。它们的组合逻辑是:Reach@K观察上限,覆盖率观察整体,节点触达率观察瓶颈。
3.3 判定逻辑的工程化:机判为主、人工抽查为辅
里程碑判定是整个评测里最容易被低估的工作。规则定好了,但执行规则也需要工程化,否则大量轨迹靠人肉看,效率太低而且主观性太强。
我采用的是“日志结构化+规则机判+人工抽查”的方案。每一轮评测里,Agent每调用一次工具,脚本就自动记录一条结构化的调用记录,大致长这样:
{ "round_id": "case_03_run_02", "step": 7, "tool": "lookup_customer", "source": "read_ticket", "target": "customer_profile", "result_status": "ok", "milestones_reached": ["M1", "M2"], "timestamp": "2025-06-12T14:33:21Z" }之后用一段判定逻辑去跑这些结构化记录,判断每个里程碑是否真正触达:
def check_milestone(trace, milestone_id): if milestone_id == "M2": # 必须实际调用过lookup_customer calls = [s for s in trace if s["tool"] == "lookup_customer"] if not calls: return False # 且后续步骤里出现过客户信息的引用(例如传入generate_action或update_ticket) customer_info_used = any( "customer" in str(s.get("input", "")).lower() for s in trace[trace.index(calls[-1]):] ) return customer_info_used return False人工抽查用于兜底。规则脚本覆盖不了的情况,比如模型在自由文本里提到了某个数据但没体现在工具调用里,这种就需要人来判断。最终抽查下来,机判和人工评判的一致率在90%以上,遇到不一致的案例,我会回头调整规则,让定义更精确。
3.4 为什么每个场景需要跑多轮
这条经验是我在第一版评测里吃了一次亏之后学到的。最开始每个场景只跑1轮,结果一个模型在某些任务上表现出色,换一批同样的任务又一塌糊涂。后来发现,Agent存在随机性,单次成功可能只是运气好,单次失败也可能是偶发抖动。
每个场景跑3轮,可以把“能力问题”和“随机波动”分开。如果一个节点3轮里触达了2次以上,说明智能体具备这项能力但不够稳定;如果3轮全失败,才能确认这是一个真瓶颈。稳定性本身也是一个隐藏指标——有些模型上限很高,但方差奇大,在真实业务里这种模型其实很难用,因为不可预测。
4. 跑测中踩到的三个高频问题与完整排查链路
4.1 问题一:模型在工具返回缺失字段时“脑补”数据
第一次拿到Agent-Reach的报告,数据显示M3(查库存)的触达率异常低。我翻了轨迹日志,发现一个诡异的现象:智能体确实调用了check_inventory,但传入的产品SKU从来没有出现在之前的任何工具返回里。也就是说,模型自己编了一个SKU出来。
这类问题我当时判断为“生成式幻觉”。模型在上下文里找不到某段信息,但它不愿意承认自己不知道,于是基于概率推导出一个“看起来合理”的值。排查链路是这样的:
- 先确认数据源:检查工单原始数据,确认缺失SKU。
- 再比对调用链:用日志搜索SKU是否在
read_ticket和lookup_customer的返回里出现过,结果没有。 - 最后做控制测试:同样场景换一个提示词版本,要求模型“拿不到确切字段时必须明确说明”,结果触达率显著回升。
解决方案是在工具返回结构里加入一个完整性标记字段,大体思路是:
{ "status": "ok", "data_quality": "complete", "is_truncated": false, "record": { "ticket_id": "T-2025-0117", "product_sku": null } }当is_truncated为true,或者某个关键字段为null时,Agent必须主动请求补充信息,而不是自己“圆”。评测脚本也会把这类情况单独标记出来,不记为正常触达。经验总结:工具返回里主动暴露“缺失”,比让模型自己猜测省心得多。
4.2 问题二:在同一个工具上无限循环空转
另一个高频故障是无限循环。我翻日志时发现某条轨迹连续调用了十几次同一个工具,参数几乎不变,模型像是在撞墙但始终不死心。这个现象在当时很让人崩溃,因为一轮评测卡在一个循环上,后续所有步骤全部停摆。
排查链路:
- 第一步,用一段简单脚本统计每条轨迹里相同工具名的连续调用次数,找出数值异常的轨迹。
- 第二步,回看触发循环前最后一条正常消息,确认上下文里已经拿到了足够信息。
- 第三步,检查工具描述,发现原文只写了“根据SKU查询库存”,没有说明重复调用不会带来新信息。模型判断不出“这个工具已经没用了”,就会试图靠反复调用来蒙一个结果。
修复方案分两层。一是把工具描述改成更加明确的版本:“检查指定SKU的实时库存。注意:如果最近一次调用已完成,重复调用不会返回新的信息,请勿多次重试。”二是加了一道硬约束,在Agent-Reach里设置单轨迹工具调用深度上限,默认15层,超限直接终止该轮评测,判定为“未触达”。这道硬约束不是给模型用的,是给评测框架用的——防止无限循环拖垮整个评测任务。
4.3 问题三:模型对工具的“偏好性绕路”
第三个问题最隐蔽,跑数据的时候你没觉得哪里坏了,但统计结果就是不对劲。我对比两个模型时发现,开源小模型在M3上的触达率明显偏低,但它并没有报错,也没有循环,而是总在绕路。
回看轨迹,这个模型非常偏好使用read_ticket,几乎每走一步都想重新读一次工单,却很少主动调用check_inventory。这不是技术上做不到,而是模型在策略层面有偏好——它“习惯”了某个工具,就会倾向于反复使用它,哪怕当前任务更需要另一个工具的数据。
这种事情用覆盖率指标看是“性能差”,但必须区分一种失败类型:
- 触达失败:模型调用了工具但结果错误,或者根本没想过去调用。
- 策略性绕路:模型有能力调用工具,但因为策略选择问题导致多走了弯路。
区分这两类的办法是看结果:如果绕路之后还是完成了任务,说明能力没问题,是策略问题;如果绕路导致任务失败,那就是策略能力不足以支撑任务。Agent-Reach的指标设计里,我把节点独立触达率和“首次触达时机”分开记录——后者专门用来识别这种偏好性绕路,毕竟一个节点虽然最终触达了,但如果中间多浪费了8次调用,稳定性必然受到影响。
对于这个问题的修复,我在测试里调整了工具描述的交代表达,明确在generate_action的说明里写上“生成建议前,优先检查库存,库存为0时不可建议发货”。这相当于给模型一个决策顺序上的提示,把偏好往正确的路径上引。
5. 用评测结果反推Agent调优:我把工具描述重写了一遍
5.1 差距主要藏在中段节点,而不是终点
第一轮完整评测跑完后,我最直观的结论是:两个模型在最终里程碑覆盖率上的差距确实存在,但并没有大到“一个能用、一个废掉”的程度。真正的差距集中分布在M2和M3这两个中段节点,也就是“查客户”和“查库存”之后的信息串联。
这其实颠覆了我一开始的直觉。我最开始以为模型差距会体现在最难的那个节点M4(生成处理建议)上,但数据告诉我,几乎所有模型都能生成一段像模像样的建议,难的是“敢不敢在信息不完整的时候停下来”。商用闭源模型相对更擅长处理M2/M3这类“先取数再判断”的场景,遇到缺失字段时更倾向于显式求助;开源小模型则总想靠生成来弥补不确定性。
这个发现直接影响了我后续的调优策略——我没有急着换更大的模型,而是先去改工具描述、改交互约束,因为成本和收益的性价比更高。
5.2 工具描述的三段式重写:触发条件、入参约束、返回值承诺
根据Agent-Reach的评测结果,我总结出了一套工具描述写法,也就是“三段式”。拿check_inventory举例:
改写前:
查询库存。输入SKU,返回库存数量。改写后:
tool: check_inventory trigger: 当需要确认某SKU是否有货、是否可承诺发货时,必须调用此工具 params: sku: string, 必填,必须来自工单记录或库存查询返回中的原样字段,禁止自行生成 returns: - { sku: string, stock: int, status: "in_stock" | "out_of_stock" | "not_found" } - 若sku不存在或库存为0,返回status=not_found/out_of_stock,此时不得建议发货 constraints: - 同一SKU短时间内重复调用不会返回新结果,请勿连续重试这套写法每个字段都有明确作用:
trigger告诉模型“什么时候该用这个工具”,解决偏好性绕路。params里明确“禁止自行生成”,直接在源头压制脑补问题。returns里把异常状态显式列出来,让模型知道“遇到这种情况该怎么走”。constraints里的重复调用提示,是为了减少空转循环。
只改这一处工具描述,同一个模型在M3节点上的触达率提升了15个百分点左右。这是Agent-Reach带给我的最大收益——评测不只是用来打分的,它能反推出最值得修改的那一个点。
5.3 评测场景设计的两个边界教训
设计评测场景的过程中,我也踩过一些认知上的坑,这里值得单独说明。
第一个坑是“把评测场景设计成只有一条正确路径”。最开始我构造的任务集里,每个工单的“最优解”都是唯一的。这样评测起来确实干净,但反过来也会掩盖模型的真实能力——现实世界里的问题有很多种合法解法,智能体可能绕了一点路但结果完全正确,这种轨迹在严格路径判定里会被误杀。
后来我在Agent-Reach里加入了“策略合法性”判定,只要最终结果正确且没有违反业务约束的路径,都算有效触达。代价是规则稍微复杂了一些,但评测结果的可信度明显上升。
第二个坑是“正常数据太多,异常数据太少”。如果评测语料里全是顺利场景,任何模型触达率都会虚高,区分度也就没了。我在任务集里故意塞了大约三成的异常场景——缺失字段、库存为0、状态不合法——这样才能真正拉开差距。实际做Agent应用的时候,难点从来都不是正常流程,而是那些“不知道该怎么办”的边界情况。
5.4 把Agent-Reach从模拟场景扩展成真实业务链路的做法
模拟场景跑通后,把Agent-Reach接到真实业务系统是一种顺理成章的扩展。核心做法是把模拟工具替换成真实服务的接口,把里程碑定义成业务操作日志里的关键事件,判定逻辑则对齐真实系统的状态变更。
我在这个扩展过程中踩过一个实际的坑:真实系统里不是每类操作都有明确的“操作日志”可查,很多结果只能从自由文本里推断。但自由文本恰恰是大模型最容易犯错的领域。后面我强制要求Agent在每一步都输出结构化中间结果,比如确定一个动作时先输出一个JSON片段,再执行对应工具。这个习惯养成了之后,判定逻辑一下子变得简单可靠。
另外,如果要在真实场景里长期跑评测,数据里必须留一小部分“脏数据”,比如故意损坏的字段或模拟超时的接口返回,让Agent知道系统不是每次都配合。这听起来像是在给评测加难度,但它恰恰保证了覆盖率不会虚高。毕竟一个真实可信的Agent,不是那种只能在顺境里表现完美的系统,而是知道在数据不好的时候该怎么兜底的系统。
对我个人来说,Agent-Reach最大的价值不是给出了分数,而是把评测从“玄学”变成了“工程问题”。每次改动之后跑一遍,哪个节点变好、哪个节点变差,一目了然。如果你也在跟Agent打交道,卡在调优没有方向的状态里,建议你也建一套类似的可视化链路评测,先搞清楚智能体到底能走到哪一步——剩下的调优就只是力气活了。