Agent-Reach:让 Agent 真正触达目标资源的可达性工程
发布时间:2026/9/18 4:39:22来源:尧图网络
Agent-Reach 这个词第一次出现在我视野里的时候我脑子里冒出来的不是某个具体框架而是过去大半年里被问烂的一个问题我的 Agent 明明在演示里表现挺好怎么一到真实任务里就够不着它知道该去查订单但拿不到订单接口它能写 SQL但连不上那张表它推理链条走得挺顺走到第五步忽然发现自己没有那个权限。演示环境和生产环境之间那条缝十有八九就裂在可达性上。我把这一整套围绕Agent 能不能真正触达目标资源的诊断与补强方案统一叫 Agent-Reach。它不是又一个 Agent 编排框架而是一层专门盯住够不着的薄层管工具怎么注册、意图怎么路由、上下文怎么补、执行怎么兜底、失败怎么归因。框架负责让 Agent 会思考Agent-Reach 负责让它的手真的伸得出去。这篇文章适合三类人正在把 Agent 从 demo 往线上推的工程师、被模型没问题但任务失败折磨过的产品同学、以及想给自己团队搭一套可达性评估基线的人。不管你是刚接触 Agent 开发还是已经踩过几轮坑下面这些内容都能直接拿去用——包括我实际跑过的代码、算过的阈值、以及那些文档里绝对不会写的教训。1. 先把够不着这件事说清楚Agent-Reach 到底在解决什么在动手写代码之前我花了两周时间做了一件看起来不产出的事把过去半年所有失败的 Agent 任务捞出来逐条标注失败原因。这个动作的价值远超预期因为它把我原来模糊的Agent 不稳定直觉变成了一张可分类的清单。而这张清单里超过六成的问题跟模型推理能力关系不大全是可达性问题。Agent-Reach 这层东西就是从那张清单里长出来的。1.1 三个典型现场工具够不着、上下文够不着、执行够不着第一种叫工具够不着。用户问帮我看看上周那笔退款到账没Agent 需要调用退款查询接口。但注册表里只有订单查询和支付查询两个工具描述里还都写着查询交易相关信息。模型在两者之间反复横跳最后挑了一个参数填错报错回传它又换另一个循环三次超时。这类问题的根因不在模型在于注册表根本没覆盖到那个能力或者覆盖了但描述边界含糊导致模型没法判断该用谁。第二种叫上下文够不着。Agent 需要知道上周具体是哪几天、那笔退款是哪一笔。这些信息散在会话历史、用户画像、业务数据库三个地方。如果只把会话历史塞进上下文模型就只能靠猜。我见过最典型的一次Agent 把上周理解成了自然周而业务口径里上周指的是过去七个自然日结果查出来的数据完全是错的。模型没错是它拿到的上下文不足以支撑这个判断。第三种叫执行够不着。这一种最隐蔽因为前面几步都成功了。Agent 顺利识别意图、选对工具、参数也对执行到第六步需要写入一个下游系统时发现服务账号没有那张表的写权限或者需要调用一个耗时的批处理接口而调用链在第三十秒被上游超时掐断。任务链条越长这种中途断电的概率就越接近必然。我统计过一条七步的任务链每步成功率 0.95 的话整体成功率只有 0.70 左右这个衰减速度比大多数人直觉里快得多。1.2 为什么我把可达性单独抽一层来做很多团队的做法是把这些问题揉进编排逻辑里工具不够就多写几个工具上下文不够就把 prompt 写长一点执行失败就加重试。短期看能顶住长期看就是一锅粥。因为你没法回答一个基本问题这次失败到底是模型的能力问题还是资源没接上答不上来优化方向就是瞎猜今天调 prompt明天换模型后天加工具成本全花在试错上。把可达性抽成独立一层最大的收益是归因清晰。Agent-Reach 在每个环节都埋了结构化事件意图解析出来是什么、路由给哪个工具、路由时打了多少分、参数校验过没过、执行耗时多少、失败在哪一步、失败类型是什么。有了这些事件你就能把任务失败这一个笼统结果拆成工具未覆盖 32%、参数校验失败 18%、执行超时 14%、权限拒绝 9%、其余为模型推理问题。归因一清楚优先级自然就排出来了。我自己的经验是这套埋点做完之后团队每周花在 Agent 调试上的时间大概砍掉了四成。另一个收益是可回归。可达性是一组可以量化的指标能量化就能建回归集能建回归集就敢改。你可以放心大胆地重写工具描述、调整路由阈值、加一层缓存跑一遍回归集就知道有没有退化。这比改完上线看看有没有人投诉要靠谱太多。1.3 这套东西适合什么规模的团队拿去用我不想把它说成人人必备。如果你只是一个人写个玩具 Agent 跑几个固定任务直接用现成的编排框架就够了加一层抽象纯属给自己找事。以下三种情况我建议认真考虑把 Agent-Reach 这层搭起来你的 Agent 需要对接超过十个外部系统而且这些系统的接口风格、鉴权方式、错误码约定各不相同你的任务链普遍超过四步中途失败的成本开始变得明显比如涉及资金、工单、对外通知你需要向别人解释某次失败为什么发生而不只是说模型抽风了。反过来如果你的 Agent 任务集中在单轮问答、只读查询、单工具调用那这层的边际收益确实有限。工具选型的判断标准从来不是先进不先进而是当前痛点值不值得为它付维护成本。2. 整体设计Agent-Reach 的分层结构与选型取舍我见过太多项目死在一开始就设计得太完整上。第一版就想要插件市场、想要多租户、想要可视化编排器结果三个月没跑通一条端到端链路。Agent-Reach 的设计原则只有一条每一层都必须能在没有任何其他层的情况下独立跑起来。这条原则在后面救了我很多次因为当路由层出问题时我可以把它降级成关键词直匹配其余层照常工作至少服务还在。2.1 四层骨架注册层、路由层、执行层、反馈层注册层负责描述我有什么。每个工具在注册表里是一条结构化记录包含名称、功能描述、参数 schema、鉴权方式、限流配置、超时预算、副作用标记。这一层的核心产物是一份机器可读的能力清单而不是一份给人看的 API 文档。这两者的差别很大给人看的文档可以写支持多种查询场景给模型看的描述必须写只能查询单笔订单输入订单号不支持按时间范围批量查询。路由层负责决定这次该用什么。输入是当前意图和上下文输出是一个带分数的候选工具列表加一个是否够用的判断。我特意让它输出候选列表而不是单一结果因为当第一名失败了你手里得有第二、第三名可以退。很多系统一次只选一个工具失败后重新走一遍路由白白多花一轮 token 和延迟。执行层负责真的去调。它管参数校验、鉴权注入、超时控制、重试策略、熔断降级、响应裁剪。这一层是最脏最累的也是最容易被低估的。我见过团队在路由上花了两周在执行层花了半天结果线上事故九成出在执行层没设超时、重试没有幂等保护、响应体没裁剪导致上下文被撑爆。反馈层负责失败了怎么办、成功了留下什么。它把执行结果翻译成模型能理解的信号同时把结构化事件写到日志管道里。这里有个关键设计给模型的错误信息和给人看的错误信息必须是两套。给人看的要简洁友好给模型看的要包含足够多的可决策细节比如参数 order_id 格式错误期望 16 位纯数字收到的是 12 位含字母。2.2 选型取舍为什么不直接上大而全的编排框架市面上主流编排框架做得都不错图结构、状态机、断点续跑这些能力很全。但在可达性这件事上它们有两个不太适配的地方。第一它们的抽象层级偏高。框架关心的是节点怎么连不关心这个工具的鉴权 token 怎么在三分钟后自动刷新。可达性问题大量发生在框架视野之外的脏活里。你当然可以在框架里塞自定义节点但塞着塞着就发现真正的逻辑全在自定义节点里框架只剩个壳。第二它们的错误信息不适合模型消费。框架抛出的异常往往是一句人类可读的报错比如Node execution failed。这句话丢给模型模型除了重试没有别的选择。而复用 Agent-Reach 的反馈层你可以把它转成{step: refund_query, error_type: invalid_param, field: order_id, expected: 16位数字, retryable: false, suggestion: 先调用订单搜索获取正确订单号}。转完这一层模型的下一步动作准确率会明显不一样。我的选择是编排仍然交给框架可达性交给 Agent-Reach。两者通过一个很窄的接口对接框架在需要调用工具时问 Agent-Reach 要一个执行句柄Agent-Reach 返回结果和结构化事件。接口窄的好处是替换成本低今天用这个框架明天想换一个只改对接层就行。2.3 注册表字段设计多一个字段少一次事故下面这张表是我实际用的注册表字段定义跑了大概半年中间只加过一个字段idempotent因为一次重复扣款事故。字段设计的原则是每个字段都要能回答一个具体的决策问题回答不了问题的字段不要加加了没人维护就是负债。字段类型回答什么决策问题备注namestring模型怎么引用它全局唯一下划线命名descriptionstring什么时候该用它、什么时候不该用必须写清边界和反例params_schemaobject参数怎么填才算合法遵循 JSON Schema必填项显式标注auth_typeenum鉴权怎么注入service_token / user_delegated / nonetimeout_msint这一步最多等多久按 P99 数据再上浮 30%retry_policyobject失败了能不能重试含最大次数与退避曲线idempotentbool重试安不安全false 时禁止自动重试side_effectenum会不会改变外部状态read / write / notifyrate_limitobject会不会被限流QPS 与突发额度sample_callsarray少样本示例怎么写两三条真实调用样例这里我特别想强调description的写法。大部分团队写描述是照着 API 文档抄的写出来像这样查询退款信息。这行字对模型来说信息量几乎为零。我后来改成了一个固定句式做什么 输入什么 不做什么 什么时候该用别的工具。改完之后同一批测试用例上的路由准确率从 71% 提到了 89%一行代码没改纯改文案。3. 核心实现细节工具注册、路由与上下文补全这一部分是我踩坑最密集的地方。很多看起来是模型不行的现象拆到最后都是实现细节没做对。下面按我在项目里实际的处理顺序讲先把工具描述写明白再把路由打分调准最后补上下文。顺序不能反因为上下文补全的效果很大程度上取决于路由是否已经收敛。3.1 工具描述写得含糊就是给自己埋雷先给一个我实际改过的例子。改之前是这样name: query_refund description: 用于查询退款相关信息 params: order_id: 订单号改之后name: query_refund description: | 查询单笔订单的退款状态与退款金额。 输入必须是订单号16 位纯数字不支持按用户、按时间范围批量查询。 如果用户只提供了手机号或昵称先调用 search_order 拿到订单号再调用本工具。 如果只是想看订单当前的支付状态不涉及退款用 query_order_status。 本工具只读不会改变任何状态。 params: order_id: type: string pattern: ^[0-9]{16}$ required: true desc: 16 位订单号可从 search_order 的返回字段 orderId 获取差别在哪改之前模型只知道有这么个工具改之后模型知道了四件事输入的精确格式、不支持什么、遇到缺参数时该走哪条路、以及和相邻工具的边界在哪。这四件事恰好对应了前面说的四种失败模式。这里有个反直觉的经验描述不是越短越好。很多人担心描述太长占 token但一次路由错误带来的重试开销远大于多写两百字描述的成本。我算过一笔账一个中等规模系统单次路由平均消耗 600 token一次错误重试平均多烧 1800 token 外加一次工具调用延迟。把描述从 50 字扩到 200 字每次多花约 200 token只要能让错误率下降 10% 就回本了。实测下降的幅度远超 10%。还有一点给描述配样本。sample_calls字段里放两三条真实调用样例包含一次正确调用和一次应该走别的工具的反例。反例这条特别有用它比任何描述文字都能更快地把边界钉死。3.2 路由匹配的参数取舍与阈值计算路由这块我试过三种方案。第一种是让模型直接从全量工具里选工具数少于八个时可用超过十五个之后准确率掉得很明显。第二种是先用向量检索粗筛出前五再让模型在这五个里选这是我现在的主力方案。第三种是纯规则匹配只用在少数高频且格式固定的场景上比如查余额。粗筛阶段用的是工具描述和样本调用拼成的文本向量检索 top-k。k 取多少我做了组对比实验在 240 条覆盖全部工具的真实任务集上跑k 值路由 Top1 准确率平均候选 token 开销平均延迟382.1%低最低593.3%中中894.6%高高全部22 个88.7%很高最高k 从 5 提到 8准确率只涨了 1.3 个点但候选 token 开销涨了六成延迟也明显上升。更值得注意的是最后一行全量投喂时准确率反而下降到 88.7%这是典型的选项过载模型在太多相似描述之间被干扰了。所以我把 k 定在 5并且在粗筛阶段加了一层硬过滤把用户没有权限的工具、当前场景明显不适用的工具比如写操作在只读会话里直接剔除不进入候选。这层过滤之后有效候选往往只有三到四个准确率还能再提一档。模型精排阶段的输出不是单一结果而是带分数的列表。分数怎么用我设了一条双阈值规则第一名分数 ≥ 0.75 且与第二名差距 ≥ 0.15直接执行第一名分数在 0.5 到 0.75 之间或者与第二名差距小于 0.15走澄清分支向用户或上游系统确认一次第一名分数 0.5判定为工具未覆盖返回明确的我没有这个能力信号而不是硬选一个去试。0.75 和 0.15 这两个数是这么来的我在标注集上把阈值当参数扫了一遍以错误执行成本 : 澄清成本 5 : 1为权重算期望损失损失最小的点落在 0.74 到 0.77 之间我取了 0.75。澄清成本之所以只有 1是因为一次澄清对话大约花两秒和几百 token而一次错误执行可能触发写操作、产生脏数据、需要人工回滚成本差着一个数量级。这个比例因业务而异涉及资金和对外通知的场景应该把错误执行成本权重调得更高阈值相应往上提。3.3 上下文补全检索在可达性里的真实位置上下文补全最容易做成什么都往里面塞。我早期版本就是这样把用户画像、历史会话、知识库检索结果一股脑塞进去上下文常常撑到七八千 token效果反而变差因为模型开始抓不住重点。我的做法是按需触发而不是默认全量注入。具体分三步第一步识别缺口。在意图解析完成后检查这次任务需要哪些槽位哪些槽位在现有上下文里没填上。比如任务是查一下上周那笔退款槽位是order_id和time_range两个都缺。第二步给每个缺口指定补全来源。order_id的来源是会话历史里的实体检索time_range的来源是业务口径词典把上周映射成具体日期区间。这里的关键是来源必须显式声明不能靠模型自己去上下文里翻。我早期让模型自己找结果它经常从一段无关的历史对话里抓出一个订单号张冠李戴。第三步补全结果带置信度。如果order_id是从会话历史里唯一匹配到的一个置信度高直接用如果匹配到三个置信度低就不要猜直接走澄清分支。这一步帮我挡掉了很多隐蔽的错误——比错误更可怕的是看起来对的错误因为它不会报错只会静静地把结果算错。关于检索本身的实现我不想展开太多只提一个和可达性强相关的点检索的粒度要匹配槽位。整段历史做向量检索召回的往往是一大段混杂的对话里面同时出现三个订单号。更好的做法是把历史拆成结构化的实体记录谁在什么时候提到了哪个订单号检索直接命中实体而不是命中段落。我改完这个之后槽位填充的准确率从 76% 提到了 92%改的全是预处理逻辑。4. 实操从零搭一个最小可用的 Agent-Reach前面讲的是设计这一节讲怎么落地。我给的是一个最小可用版本大概三百行代码一两天能跑通。别一上来就想做完整版我第一版做得太全反而拖了一个月才上线而且上线后发现一半的抽象根本用不上。4.1 环境和最小依赖环境上我尽量克制只依赖几个基础组件# 运行环境 python 3.10 # 核心依赖 pip install fastapi uvicorn pydantic httpx # 向量检索粗筛阶段用也可以用任何你顺手的方式 pip install numpy # 可选缓存与限流计数 docker run -d -p 6379:6379 redis:7-alpine这里解释一下为什么选这些以及我踩过的坑。用 Pydantic 做参数 schema 校验是刚需因为注册表里的params_schema本质就是 JSON Schema用 Pydantic 可以直接生成和校验不用自己写一套。httpx 用来做异步调用超时控制比 requests 干净。Redis 用来存限流计数和短时缓存但不建议存会话状态因为一旦你把状态放进去重启之后会话就断了调试会很痛苦——这个坑我踩过后来把状态全部放在无状态的服务里通过请求携带。Python 版本要求 3.10 以上不是为了语法糖是因为结构化错误信息的类型标注用X | Y写起来清爽很多日志里也不会出现一堆 Optional 噪音。4.2 注册表加载与路由打分注册表用 YAML 存一个文件一个工具启动时加载进内存。这样做的好处是改描述不需要重启代码逻辑只要重新加载配置。下面是一个简化版的路由打分实现import json import numpy as np from dataclasses import dataclass, field from typing import Any dataclass class Candidate: name: str score: float reason: str class ToolRegistry: def __init__(self, tools: list[dict]): self.tools {t[name]: t for t in tools} # 每条工具把描述和样本拼成一个文本作为检索语料 self.corpus [ (t[name], t[description] json.dumps(t.get(sample_calls, []), ensure_asciiFalse)) for t in tools ] def hard_filter(self, tool: dict, ctx: dict) - bool: 硬过滤权限、场景、只读约束。返回 True 表示可用 if ctx.get(readonly) and tool.get(side_effect) ! read: return False if tool.get(auth_type) user_delegated and not ctx.get(user_token): return False return True def coarse_recall(self, query_vec: np.ndarray, ctx: dict, k: int 5) - list[str]: pool [(n, txt) for n, txt in self.corpus if self.hard_filter(self.tools[n], ctx)] # 这里用点积近似余弦相似度向量都已归一化 sims [] for name, _ in pool: sims.append((name, float(np.dot(query_vec, self.vec_of(name))))) sims.sort(keylambda x: x[1], reverseTrue) return [n for n, _ in sims[:k]]hard_filter这个函数看着简单但它是我认为整套方案里性价比最高的一段代码。加它之前粗筛经常把需要用户授权的工具排进候选模型选了之后在执行层才被拒白烧一轮。加它之后不可用的工具根本不出现路由准确率提升的同时延迟也降了。精排阶段把候选的描述完整拼进 prompt让模型输出一个 JSON 数组包含工具名和 0 到 1 的分数。这里有个实践细节要求模型给出分数比只要求它给出选择要好得多。因为分数可以被阈值规则消费选择不能。模型给分不一定校准得很准但同一模型在同一套 prompt 下分数的相对大小是有信息量的这就够用了。阈值判断的逻辑def decide(cands: list[Candidate], t1: float 0.75, gap: float 0.15): if not cands: return {action: no_capability, reason: no_candidate} top cands[0] second cands[1].score if len(cands) 1 else 0.0 if top.score t1 and (top.score - second) gap: return {action: execute, tool: top.name} if top.score 0.5: return {action: no_capability, reason: low_confidence} return {action: clarify, candidates: [c.name for c in cands[:3]]}no_capability这个分支特别重要很多系统不敢让 Agent 说我不会。但实测下来明确说不会比硬猜一个然后报错用户体验好得多而且不会产生脏数据。我甚至建议把我不会做成一个正式的、能触发人工接管的信号而不是一句普通的回复文本。4.3 执行层的超时、重试与熔断参数怎么定执行层的参数都是有据可算的别拍脑袋。以超时为例我的做法是先抓取该工具过去一个月的调用耗时分布取 P99记为 T99超时预算设为T99 × 1.3给它 30% 的余量来吸收抖动对整个任务链设定总预算各步骤的预算之和不得超过总预算的 80%剩下 20% 留给路由、澄清和最后的结果生成。为什么是 1.3 而不是 2因为超时设太长的代价是隐藏问题。一个接口偶尔慢到十秒你把超时设成二十秒它就永远不报警了但用户的等待时间实实在在变长了。宁可让它在十三秒处失败并留下一条清晰的超时事件也不要让它悄悄拖二十秒。重试策略绑定idempotent字段标记为幂等的可以重试两次退避曲线用 200ms、800ms标记为非幂等的绝不自动重试只能由模型显式决策后再调用。我在这里踩过一次坑一个扣款接口没标幂等网络抖动触发了一次自动重试扣了两次。那次之后我给自己定了条铁律——任何写操作必须显式声明幂等性声明为假的一律不允许自动重试。这条规则看着保守但省下的回滚成本远超它带来的不便。熔断用的是最朴素的滑动窗口某个工具在过去六十秒内连续失败五次或者失败率超过 40% 且样本数大于十就打开熔断器三十秒后放一个探测请求。熔断期间路由层会把这个工具从候选里剔除避免整条链路被一个坏掉的依赖拖死。这个参数我调过一次最初是十次失败才熔断结果发现一个下游服务挂掉之后前十个请求全都白等超时用户感知很差。改成五次之后体感明显好转。4.4 埋点把够不着变成可观测事件这一节是整篇文章里我最想强调的。没有埋点前面所有设计都只是感觉良好。埋点的目标是把每一次任务拆成一串可查询的结构化事件字段大致如下字段含义典型取值trace_id任务链路标识全局唯一step当前环节route / execute / clarify / fallbackintent解析出的意图query_refund_statuscandidates候选工具及分数[{name:query_refund,score:0.81}]decision决策结果execute / clarify / no_capabilityparam_valid参数校验是否通过true / falseerror_type失败类型invalid_param / timeout / auth_denied / upstream_5xxretryable是否可重试true / falseduration_ms耗时342有了这套字段你可以随时回答一些以前回答不了的问题这周工具未覆盖占比多少、哪个工具的参数校验失败最多、平均每个任务路由阶段消耗多少时间。我每周会花二十分钟看一遍这几张表基本上新出现的问题都能在用户投诉之前被发现。error_type的枚举值不要随性起要提前定好。我一开始是随手写字符串两个月后发现有十七种写法光超时就有 timeout、time_out、timeout_error 三种统计根本没法做。后来强制走枚举历史数据重新清洗了一遍费了不小力气。5. 指标与评估怎么判断够得着了设计做完代码跑通接下来要回答一个更硬的问题到底好没好我见过不少团队加了这层那层感觉上更稳了但拿不出数字。没有数字就没法判断优化方向对不对也没法在预算被砍的时候说服人。下面是我一直在用的四个指标以及一个我觉得比指标本身更重要的东西。5.1 四个核心指标怎么算才算准任务端到端成功率。这是最直观的但定义必须精确只有最终结果正确且用户没有二次纠正才算成功。中途重试成功的不算失败但也不算首次成功要单独统计。只看端到端成功率容易掩盖问题因为它是个复合指标涨了跌了都说不清原因。首次通过率。指任务在没有任何重试和澄清的情况下一次成功。这个指标最能反映路由质量。我自己的数据是端到端成功率 91% 的时候首次通过率只有 68%。两者差了二十多个点全花在重试和澄清上。所以提升首次通过率是性价比最高的方向因为它直接对应延迟和成本。可达覆盖率。定义为在一批真实任务样本中能够被当前工具集覆盖即存在至少一个候选工具得分超过阈值的比例。这个指标告诉你工具的缺口有多大。我建议每月跑一次当它低于 85% 的时候说明该补工具了。这个指标不需要跑完整链路只跑到路由阶段就行成本很低。失败归因分布。这不是一个数而是一张分布表。我每周看一次重点看趋势变化。如果工具未覆盖从 30% 降到 15%说明补工具的动作有效如果参数校验失败从 12% 涨到 25%多半是某次描述改动引入了歧义。这张表是指导优化的罗盘。5.2 用回归集给可达性做体检指标是结果回归集是手段。我维护着一套 240 条任务的回归集覆盖所有已注册工具每条包含用户原始表达、期望意图、期望工具、期望参数。每次改动工具描述、路由 prompt、阈值参数之前先跑一遍。建这个集子有几点讲究第一任务表达要保留真实用户的粗糙感包括错别字、省略、口语化。我一开始写的是查询订单号为 X 的退款状态这种标准句式结果回归集上准确率 96%线上只有 72%。后来我把表达全部改成真实用户语料回归集准确率掉到 78%这才和线上对上了。第二每条任务要标注可接受的其他答案因为有些任务确实有两种合理解法严格匹配会误判。第三定期补充线上失败样本。我每个月从线上捞二十条失败案例加进去让回归集跟着真实分布走。跑回归集的成本不高240 条任务只跑路由阶段的话大概三分钟。我把它挂在了提交前的检查里超过阈值退化就拦住。这个机制救过我至少三次——有一次我为了优化一个工具的召回改了它的描述结果让另一个相邻工具的 Top1 准确率掉了八个点回归集当场拦下来了。6. 常见问题与排查速查写到这儿前面基本都是应该怎么做。但实战里更有价值的往往是出问题了怎么查。我把过去半年处理过的问题整理成了一张表按现象、可能原因、排查动作三列组织遇到问题先查表大多数情况五分钟内能定位。6.1 排查速查表现象可能原因排查动作同一句话两次结果不同路由分数接近阈值处于边界抖动查 trace 里的候选分数看第一名与第二名差距是否小于 0.15模型反复调用同一个工具并失败错误信息对模型不可决策缺少 suggestion 字段检查返回给模型的错误结构补上错误字段名和期望格式任务走到一半突然结束某步超时后未产生明确失败信号查 duration_ms 分布确认是否触发了上游超时工具明明存在却总不被选中描述与其他工具重叠或粗筛阶段被硬过滤单独对该工具跑召回测试看它在粗筛 top5 里的排名参数总是差一位或格式错描述里的格式约束没写清或样本缺失补充 pattern 与 sample_calls加一条反例样本高峰期失败率陡增限流触发或连接池耗尽查限流计数与并发数确认是否触发了熔断上下文越长效果越差无关内容稀释了注意力关掉默认全量注入改为按槽位按需补全澄清分支触发过于频繁阈值定得过高在标注集上重扫一遍阈值看是否可下调写操作出现重复执行未标注幂等性却走了自动重试检查 idempotent 字段非幂等工具关闭自动重试某工具在某类用户上总失败权限模型不一致查该工具 auth_type 与用户授权范围是否匹配这张表我打印出来贴在工位上其实用久了会发现十种现象里至少有六种可以归到两类根因描述不清和错误信息不可决策。所以如果你刚接手一个 Agent 项目不知道从哪里开始排查我建议就先检查这两件事投入产出比最高。6.2 几条踩过坑才明白的心得第一条别让模型看见它用不了的工具。这是我在权限问题上最大的教训。早期我为了省事把所有工具都注册进去靠执行层报错来拦权限。结果是模型经常挑到没权限的工具报错重试再挑到另一个没权限的一轮下来用户等了两分钟得到一句抱歉我做不到。后来把权限过滤前置到粗筛阶段不可用的工具根本不进候选池问题直接消失。这个改动的代码量不到二十行效果却是所有改动里最明显的。第二条错误信息要给下一步而不只是哪里错了。这一点我改了很多轮。最早我返回的是参数错误模型只能瞎试。后来改成参数 order_id 格式错误好一些。最后改成参数 order_id 格式错误期望 16 位数字收到 12 位含字母建议先调用 search_order 用手机号获取正确订单号模型的一次性修复率从 40% 提到了 84%。多写那半句话的成本几乎为零收益却很大。第三条微调描述之后一定要跑回归。我吃过一次亏为了让一个低频工具的召回好一点把它的描述改得更宽泛结果它开始抢另一个高频工具的活线上首次通过率掉了六个点。六个点在 240 条回归集上就是十五条任务跑一遍三分钟就能发现。现在我给自己定了个规矩只要动了工具描述哪怕只改一个字也先跑回归再提交。第四条留一个可以随时降级的开关。Agent-Reach 的路由层我从第一天就留了降级路径一切换到关键词直匹配。这个开关平时不用但有一次向量服务抖动路由整体不可用切过去之后虽然准确率掉了一截但服务没停。这种设计在项目里看起来像是多余的防御真出事的时候才知道值不值。第五条不要为了让数字好看而放宽成功定义。我见过团队把用户没有继续追问算作成功指标一下涨到 95%但实际问题还在。指标是用来发现问题的不是用来交差的。宁可数字难看但真实也别要一个漂漂亮亮的假指标。最后分享一个我最近在试的方向把可达性指标反过来喂给工具注册表让系统自己发现哪些描述容易被混淆。具体做法是把回归集里所有路由错误的两两工具配对统计出来出现频次高的配对说明这两个工具的描述边界模糊优先去重写它们的描述。用这个方法我在一次迭代里定位出了三对高混淆工具改完之后首次通过率提升了四个点。这个思路还比较粗糙但至少目前看比人工逐个检查描述要高效得多。这套东西没有什么高深的技术难的是把每一层都想清楚、把每个失败都归到位、把每次改动的效果都量出来。Agent 这类系统最怕的就是感觉好像好一点了而 Agent-Reach 存在的意义就是让你不必再依赖感觉。
网站建设高端定制企业官网