ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agent-Reach:解决AI Agent工具调用与触达边界难题的实战指南

Agent-Reach:解决AI Agent工具调用与触达边界难题的实战指南 你敢说自己在做 AI Agent大概率逃不过一个怪现象模型能力已经很强了正常聊天对答如流可真让它去查个数据库、调个接口、跨系统协作的时候它就像突然失聪要么答非所问要么干脆自作主张开始编数据。我前后折腾了三个 Agent 项目最后把这类问题收敛成一个词——Agent-Reach。Agent-Reach 不是某个开源框架的名字也不是灵光一闪的产品创意它是我在实测里总结出来的一套关于“智能体触达边界”的设计思路与实操方法。简单说它衡量的是一个 Agent 在运行时到底能不能“够到”真正需要的外部资源该调的工具能不能被正确选中、该看到的记忆能不能被及时想起、该协作的另一个智能体能不能被顺利连通。把这三件事理顺Agent 才不会停留在“嘴上很厉害”的阶段。这篇文章会用我实际踩坑的经验把 Agent-Reach 掰开揉碎先聊清楚触达边界到底是什么再给一套三层架构设计思路然后落到工具注册、记忆压缩、多Agent通信这些能直接抄作业的细节最后附上排查实战。不管你是刚开始搭 Agent 原型还是已经被工具调用失败折磨了两周这篇都值得翻一下。1. 项目定位Agent-Reach 究竟在解决什么痛点1.1 触达边界决定 Agent 能不能真正干活的隐形指标我起初和大多数人一样以为 Agent 的能力上限由大模型的智商决定。GPT 级别模型聪明用它的 Agent 一定更强。但真正把 Agent 接进业务系统后我发现模型的“聪明”和 Agent 的“好用”之间缺了一大段传输管道。举个例子。我在项目里接了一个内部订单查询工具函数逻辑很简单参数是订单号返回是订单状态。模型在单测里能一字不差地调出来可系统一接入 30 个工具后它就开始“眼花”了用户问“帮我看看昨天的退款到哪步了”它先去调了订单列表工具、又试了库存工具绕了好几圈才落到退款进度查询。个别场景里它甚至直接忽略工具用记忆里的旧数据编了一段结果。这类问题跟模型智商没关系纯属触达边界设计不清晰。所谓触达边界就是 Agent 在特定上下文、特定权限、特定时机下能够有效调用的资源范围。一个 Agent 就算背后是千亿参数模型如果它的触达边界设置得乱七八糟实际能完成的工作跟一个只会聊天的机器人没什么区别。Agent-Reach 这个项目的出发点就是这么朴素把“触达边界”当成跟提示词、模型选型同等重要的一等公民来设计。先回答清楚你的 Agent 需要够到什么、在每个环节怎么够、够不到的时候怎么处理然后再去调模型。1.2 三个“够不着”工具、记忆、协作如果你也碰到过 Agent“间歇性掉线”的情况别急着换模型先把问题归到三个维度里看。第一类是工具触达。Agent 知道某个工具存在但调用时机不对、参数格式不对、或者工具描述写得含糊导致它压根没理解这个工具是干嘛的。我在项目中做过一次统计工具调用失败有接近六成不是代码 bug而是工具描述和上下文里的信息没有对齐。这个比例相当吓人。第二类是记忆触达。对话历史里明明有过关键信息比如用户上一条消息说了“只要本月的数据”结果下一轮 Agent 就开始统计全年的了。这不是模型忘了而是当上下文窗口接近上限、早期信息被截断之后Agent 的“有效触达记忆”已经退化到只剩最近几轮。模型还在但记忆已经“够不着”了。第三类是协作触达。多 Agent 系统里主 Agent 需要把任务派给数据 Agent、报表 Agent、审批 Agent可是怎么知道哪个 Agent 有能力处理这个请求又怎么避免两个 Agent 互相派活形成死循环我实测过最夸张的一次两个 Agent 互相对着调用了几十轮token 烧掉几万块什么问题也没解决。Agent-Reach 的设计目标很简单让这三类“够不着”变成可度量的、可优化的、可测试的问题。而不是每次线上出了岔子只能靠堆日志和拍脑袋猜。1.3 为什么叫“Reach”而不叫“Tool-Connect”很多朋友问我你这个概念说到底不就是工具调用吗叫 Tool-Call 不就完了。这里头有个命名上的考量也直接影响后续的设计角度。Connect 是建立连接是静态的、一次性的。比如我给 Agent 配好了 30 个工具从注册表上看它们全都“连接”上了。但 Reach 是动态的、运行时的概念。工具连上了不代表 Agent 在关键时刻真的能“够到”它。一个藏在一百个工具列表最底部、描述写得不痛不痒的工具对 Agent 来说就是不可触达的。Reach 这个词还天然带距离感很适合用来描述层级关系。“Agent 能触达一层数据、触达不了二层数据”听起来很像网络里的可达性分析。做网络的人都知道光有路由表还不够还得看路由是否可达、延迟多少、有没有环路。我把这套网络思维迁移到 Agent 系统里发现意外地好用。所以 Agent-Reach 这个名字提醒我别只盯着“接了哪些工具”要盯“运行中的每一轮对话里Agent 实际能不能够到它该够的东西”。这个视角的转变是项目后面所有设计决策的总开关。2. 方案选型与整体架构思路2.1 三层触达模型工具层、数据层、协作层想清楚要解决三类“够不着”之后我开始把触达能力拆成三层来看每层独立优化、独立测试。这套模型后来成为整个 Agent-Reach 项目的架构骨架。工具层解决“Agent 能不能调用到正确工具”。它关心工具注册表的组织方式、工具描述的可读性、路由策略设计。一句话概括让 Agent 像翻一本目录清晰的工具书而不是在一堆便利贴里翻找。数据层解决“Agent 能不能拿到足够的上下文”。它关心记忆怎么分层、历史怎么压缩、长文档怎么索引。这一层最容易被忽略因为模型表面上“记住了”很多实际一截断就失忆。我把数据层单独拎出来的原因就是不想让上下文管理混在 Prompt Engineering 里一带而过。协作层解决“Agent 之间能不能互通”。它关心智能体之间的能力声明、消息协议、超时与熔断机制。单个 Agent 再强协作层设计粗糙一超多强也会变成一超多堵。三层之间有先后关系工具触达不到数据层再多也白搭数据触达不到协作层的任务描述就会失真。我在架构评审时有个固定要求任何一次 Agent 交互出问题先定位是哪一层触达断了不要笼统地甩锅给“模型不行”。2.2 工具全部平铺的问题选择困难症不只是人有我最早的方案特别天真把所有工具函数一次性全部塞进系统提示词认为模型会在需要的时候自己挑选。第一个小项目只有 10 个工具效果还行之后业务一扩大工具到了 50 个系统立刻开始退化。直观表现有两个。一是模型在工具选择上的准确率明显下滑会频繁在相似工具之间犹豫甚至张冠李戴。二是在响应时间上工具描述占用的 token 越来越多每轮对话光看描述就要耗掉大量上下文留给真正业务数据的空间被严重挤占。同类工具越多问题越明显。我有几个查询类工具名字分别是“查询订单”“查询退货”“查询退款”三者的参数几乎一模一样区别只是内部接口地址和返回字段不同。模型经常把退货查询调到退款查询上返回的错误信息又模棱两可用户看到的就是一个功能不正常的“人工智障”。所以 Agent-Reach 的工具层设计原则第一条不要让 Agent 面对一个长长的扁平列表做选择。要在中间加一层路由机制先快速缩小范围再让模型在少量候选工具里做精细决策。这个思路跟搜索引擎先粗排再精排是一样的道理人脑面对十个以内的选项能处理得比较好模型其实也类似。2.3 路由分层与 Landmark 机制先定位再调用既然不能全量平铺我在 Agent-Reach 里设计了一套轻量级路由分层机制核心是 Landmark地标路由。Landmark 的概念取自城市导航你要去一个陌生商场的某家店不会从全城所有建筑里筛选而是先锁定商圈地标到了附近再看具体楼层和门牌。Agent 的工具调用也一样先定位到“业务域”再定位到“具体接口”。具体落到实现上我把工具注册表分成两级。第一级是领域路由表每个领域配一个简短描述比如“售后域”包含退货、退款、换货、维修查询第二级才是具体工具定义。系统每轮收到用户请求时先让模型只输出一个最可能相关的领域标签然后只把该领域下的工具定义注入当前上下文。这个方案需要的额外开销只是多一次小型模型调用或一次轻量意图分类成本很低。但在工具选择准确率上的提升非常明显。小范围工具列表里模型几乎不会再选错同类工具。我实测在 40 个工具的规模下路由分层后的选择准确率从 78% 提高到了 94% 以上。Landmark 机制的另一个额外收益是上下文节省。以前每轮都要全量加载 50 个工具描述现在只需要加载一个领域下 5 到 8 个工具的描述token 开销缩到原来的六分之一。省下的空间正好留给业务数据形成良性循环。2.4 工具描述规范给 Agent 一张“看得懂的地图”路由机制解决的是“看哪个工具”的问题但工具描述的质量直接决定 Agent 能不能看懂。同一个工具不同写法下模型的调用成功率差异巨大。这也是 Agent-Reach 项目里投入产出比最高的一项工作。我看过很多团队的函数文档会写“查询订单信息返回订单对象”完了。这种描述对人来说当然懂但模型分不清它是内部方法还是外部 API也不清楚参数之间什么该填什么不该填。尤其当一个接口支持多种查询条件时描述里没写清楚“订单号与手机号至少填一个”模型就会想当然地两个都填或者干脆不填。我后来总结出一套工具描述模板用途一句话、触发场景、参数说明、返回说明、典型调用示例、常见失败原因。其中触发场景和典型示例最值钱。触发场景能帮模型判断“什么时候该用这个工具而不是别的”典型示例则是给模型一个最直观的参考坐标。不要小看写描述这件事。为了把二十来个工具的描述重写一遍我花了整整两个下午但换来的效果是所有工具相关报错率降低了四成左右。给模型写文档比给人写文档要更具体、更少修饰、更多场景。人是模糊生物模型是字面生物文档风格必须区分。3. 核心细节解析与实现要点3.1 工具注册与参数 Schema调用失败的根源往往在描述Agent-Reach 的工具注册表不是简单的字典存函数而是一份围绕调用成功率设计的元数据结构。我建议每个工具至少包含 name、description、domain、parameters、examples、error_hints 六个字段。name 要短且不含糊。类似“processData”这种名字对模型毫无信息量远不如“calc_refund_amount”直观。domain 是路由分层的领域标签必须归属清晰一个工具只能归一个主领域不要搞多标签否则路由阶段模型会困惑。parameters 部分最容易被简化。有 JSON Schema 的用 Schema没有的话也要至少标注 required 字段和 optional 字段并且对每个参数写清楚取值范围。比如“status: 售后退款状态可选 pending / approved / rejected”这么一写模型基本不会再传一个中文状态进去。还有一个细节把 error_hints 也写进工具定义。描述里直接说明“该工具在订单不存在时返回 ERR_ORDER_NOT_FOUND”这样模型遇到错误码后能立刻知道是数据不存在还是调用方式错了不会反复重试同一个错误请求。这个字段是我在调度日志里发现大量无效重试后加的效果立竿见影。3.2 上下文触达策略记忆的分层与压缩上下文触达这个维度的核心问题是窗口是有限的业务信息是无限的。不做分层Agent 记住了后面的就忘了前面的做了分层就得考虑哪些信息值得被长期记住。我参考了记忆系统的思路把 Agent 的上下文分成工作记忆、短期记忆、长期记忆三层。工作记忆就是当前轮次和最近几轮对话不做任何压缩保留原始细节短期记忆是最近一段时间内对话的摘要每几十轮自动生成一次长期记忆则把需要跨会话保留的关键事实抽取出来独立建索引。实际落地时我用了一个比较原始但可靠的办法对话超过阈值后触发一次摘要任务把旧对话浓缩成结构化摘要比如“用户已确认本月预算为三万元重点关注华东区的投放效果”。然后在下一轮对话中把摘要放在当前对话之前替代被截断的旧日志。这样 Agent 的“有效记忆触达半径”就不会随着对话变长而急速衰减。这类压缩有个坑需要提醒直接让模型总结所有内容会损失细节尤其是数字和否定句。比如“用户不想要短信通知”这类信息摘要写得好好的“不想要短信”系统里一旦在后续处理中漏掉“不”字结果就完全反了。我现在的策略是强制摘要保留“变更类信息”和“否定类信息”的原文并在压缩后做一致性校验发现关键实体缺失就重新生成。3.3 多 Agent 协作的可达性协商与熔断多 Agent 系统的触达问题比单智能体更复杂因为它多了一层“知道谁有什么能力”的问题。我在 Agent-Reach 里为每个子 Agent 设计了一份能力声明文件类似一个人的简历。声明里写清楚这个 Agent 能处理什么任务、接受什么格式的输入、返回什么格式的结果、不支持什么。这套能力声明在系统启动时收集起来形成一张协作路由表。主 Agent 收到任务后不是直接去问每个子 Agent“你能做吗”而是先查路由表找到候选 Agent再把任务描述发给对方。这个机制取消了大量无效的广播式请求也避免了“所有 Agent 都以为别人会做结果没人做”的责任真空。协作层还有一个不得不提的问题死循环。我踩过非常惨痛的坑。A Agent 发现缺数据请求 B Agent 生成B Agent 认为这属于 A 的职责又把任务弹回去。两个 Agent 你来我往几十轮日志刷了上万行。查问题时我看着两个 agent_id 反复出现差点以为是监控脚本出 bug 了。后来我加了双层防线。第一层是请求深度限制任何一条任务链最多往下传两层超过就直接返回主 Agent 处理。第二层是调用熔断单个子 Agent 在三十秒内收到超过五次相同请求时自动标记为“不可用”不再接收转发任务。这两条规则写进路由表之后协作类的异常流量基本归零。3.4 触达可观测性日志与指标怎么埋Agent 交互跟传统接口调用最大的区别是它每次动作都有博弈性质没法靠打印几行日志就想清楚“为什么成了、为什么败了”。Agent-Reach 的可观测性方案围绕一条主线设计每一轮动作都要回答四个问题——谁发起的、想触达什么、触达成功没有、如果失败是卡在哪一层。我把四类信息固化在日志结构里request_id 串联一轮完整对话agent_id 标识发起者action 记录动作类型reach_status 记录触达结果。触达结果我会细分TOOL_MATCHED 表示工具选对了、TOOL_EXECUTED 表示工具执行成功、TOOL_FAILED 表示执行失败、REACH_TIMEOUT 表示超时、CTX_TRUNCATED 表示上下文截断导致关键信息丢失。这个字段是后续排查的一把钥匙。光有日志还不够我会把关键指标同步到监控面板上。最重要的三个指标是工具选择准确率、工具执行成功率、平均触达耗时。这三个数字一旦出现趋势性下滑就该回去查代码和工具描述而不是等用户反馈问题。可观测性最大的价值是让触达质量变得可以用数据说话而不是靠“感觉最近好像不太对”。4. 实操过程搭建一个具备触达能力的查询 Agent4.1 前置准备与选型建议动手之前先说一下工具链。Agent-Reach 的项目骨架不绑定特定框架我用的是 Python FastAPI 一个支持 function calling 的模型 API。如果你已经在用 LangChain、LlamaIndex 或者自研的 Agent loop也没有关系本文的核心思路可以直接迁移过去。依赖文本处理的部分我比较推荐用成熟工具解决。比如上下文摘要用模型自带的 summarization 能力向量检索用轻量级库。为了演示方便下面的最小实现里我会把向量部分简化为关键词抽取降低门槛。真实项目里建议换成真正的向量库思路不变。我需要先说明一个基础概念function calling 是什么。现在主流的大模型 API 都支持一种特殊输入允许你在请求里附加一批函数定义模型在回答时会多返回一个结构化的“调用哪个函数、参数是什么”的对象而不是直接输出文本。Agent 的工作就不再是“读一段话然后猜意图”而是“看用户的话然后从函数列表里选一个执行”。Agent-Reach 的工具触达就是建立在这个机制之上所以选模型时确认它支持 function calling 或 tool use这是底线。4.2 最小可用实现注册工具与路由配置第一步先定义工具注册表的元数据类。这个类不涉及复杂逻辑就是统一所有工具信息的数据结构。我用 Python 的 dataclass 写了一个简化版本。from dataclasses import dataclass, field from typing import List, Dict, Any dataclass class ToolSpec: name: str domain: str description: str parameters: Dict[str, Dict[str, Any]] required: List[str] examples: List[str] error_hints: Dict[str, str] def to_prompt_block(self) - str: 把工具定义转成模型能看懂的文本块 lines [ fTool: {self.name}, fDomain: {self.domain}, fDescription: {self.description}, Parameters:, ] for param, meta in self.parameters.items(): required_tag (required) if param in self.required else (optional) lines.append(f - {param} {required_tag}: {meta.get(type, )} - {meta.get(desc, )}) lines.append(fExamples: {, .join(self.examples)}) if self.error_hints: lines.append(Errors:) for code, hint in self.error_hints.items(): lines.append(f - {code}: {hint}) return \n.join(lines)这里有两个细节值得注意。to_prompt_block 方法把工具定义渲染成纯文本块是因为多数模型对纯文本格式的解析最稳定。不要试图用嵌套 JSON 塞工具描述那会浪费大量 token而且嵌套过深时模型很容易漏看字段。第二required 单独列出而不是放在 parameters 里打标记因为路由阶段我要快速过滤掉参数不完整的候选工具。第二步是路由函数。它的作用是根据用户输入先选出最可能的领域。实际项目里我会用一个小模型做分类但最小实现可以先做关键词匹配。DOMAIN_KEYWORDS { order: [订单, 发货, 物流, 下单], refund: [退款, 退货, 换货, 售后], customer: [用户, 客户, 会员, 资料], } def route_domain(user_input: str) - str: 简化的领域路由返回命中的领域名 user_input user_input.lower() for domain, keywords in DOMAIN_KEYWORDS.items(): if any(kw in user_input for kw in keywords): return domain return general第三步把路由和工具加载结合起来。每一轮请求根据 route_domain 的结果只注入对应领域下的工具定义。TOOL_REGISTRY: Dict[str, ToolSpec] {} def get_tools_for_domain(domain: str) - List[ToolSpec]: return [tool for tool in TOOL_REGISTRY.values() if tool.domain domain] def build_tool_definitions(domain: str) - str: tools get_tools_for_domain(domain) return \n\n.join(tool.to_prompt_block() for tool in tools)在真实的 Agent loop 里我会把 build_tool_definitions 的输出拼到系统提示词的尾部然后调用带 function calling 的模型 API。注意中间留一个分隔符比如用“--- 可用工具结束 ---”之类的标记帮模型划分清楚“提示词背景”和“即时工具列表”两个区域。4.3 构造三个典型工具与一份完整配置为了让演示能跑起来我定义了三个查询类工具全部归到售后域。注意我故意让它们的描述非常相似模拟真实场景里容易踩坑的同类工具情况。TOOL_REGISTRY[get_refund_status] ToolSpec( nameget_refund_status, domainrefund, description查询售后退款单据的当前进度。当用户询问退款到哪一步、退款多久到账、退款被驳回原因时使用。, parameters{ refund_id: {type: string, desc: 退款单号通常以R开头如R20250401}, mobile: {type: string, desc: 下单手机号后四位可选项}, }, required[refund_id], examples[查询退款进度 - get_refund_status(refund_idR20250401)], error_hints{ERR_NOT_FOUND: 退款单不存在请让用户核对单号}, ) TOOL_REGISTRY[list_refund_records] ToolSpec( namelist_refund_records, domainrefund, description按时间范围列出某个用户的所有退款记录。当用户想查看多笔退款、退款列表、历史退款时使用。, parameters{ mobile: {type: string, desc: 用户手机号}, start_date: {type: string, desc: 开始日期YYYY-MM-DD}, end_date: {type: string, desc: 结束日期YYYY-MM-DD}, }, required[mobile, start_date, end_date], examples[查看我上个月的退款记录 - list_refund_records(mobile..., start_date2025-03-01, end_date2025-03-31)], error_hints{ERR_NO_RECORD: 该时间段没有退款记录}, )第二份工具是用户信息查询归到客户域。这个工具在真实项目里通常会加权限校验这里演示时先在描述里提示模型只允许查询当前会话用户不做具体鉴权逻辑。TOOL_REGISTRY[get_user_profile] ToolSpec( nameget_user_profile, domaincustomer, description查询用户的基础档案信息包括姓名、会员等级、常用地址。仅允许查询当前会话中已认证的用户本人。, parameters{ user_id: {type: string, desc: 用户在系统内的唯一ID}, }, required[user_id], examples[获取当前用户资料 - get_user_profile(user_idU100023)], error_hints{ERR_FORBIDDEN: 无权查看该用户只允许查询本人资料}, )配置一份完整的 Agent 配置文件把路由策略、上下文压缩策略、超时时间都写进去方便统一管理和调整。agent: name: reach_demo model: 你的模型API名 temperature: 0.2 routing: enabled: true strategy: keyword_fallback max_candidates: 5 context: max_tokens: 8000 summary_trigger_tokens: 6000 summary_prompt: 请将以下对话浓缩为简洁的摘要保留所有数字、日期、否定表达、用户明确指令。 collaboration: max_chain_depth: 2 timeout_seconds: 30 circuit_breaker_retries: 5 observability: log_dir: ./logs/reach metrics: [tool_select_accuracy, tool_exec_success_rate, avg_reach_ms]配置文件里 temperature 我特意调低到 0.2为的是让 Agent 在工具调用环节尽量稳定输出少一点“创造性”。不要小看这个参数它直接关系到 Tool Call 的格式会不会频繁漂移。宁可让回复少一点花哨也要保证每一步调用格式不出错。4.4 集成后的典型运行链路与效果观察把上面的工具注册表和路由函数组装进 Agent loop 后系统的运行流程是这样的用户发来“帮我查下 R20250401 的退款进度”route_domain 识别出 refund 域只加载 refund 下的两个工具定义。模型看到两个候选工具迅速匹配到 get_refund_status生成结构化调用参数。执行完成后结果格式化返回给用户。这个集成后的第一个直观感受是之前那种同领域工具间互相拿错的“脸盲”问题基本消失了。模型面对的候选从几十个降到了两三个选择难度急剧下降。而且因为工具描述里带了触发场景和典型示例模型能够区分“退款进度”和“退款记录列表”这两种相似表达。另一个直观感受是上下文稳定性变好。以前全量加载 30 个工具描述时每轮都要占掉三千多 token模型还经常在长上下文尾部漏工具。路由之后每轮工具描述只有几百 token业务上下文充足模型在靠近上下文末尾时依然能稳定遵守工具调用指令。我在这个阶段会跑一组回归测试集把常见的二十种用户问法固定下来每次改动后都重跑一遍记录工具选对率。建议你也这么做。Agent 项目的改动经常牵一发动全身没有回归测试兜底改一次路由配置就可能悄悄引入新的触达盲区。5. 常见问题与排查技巧实录5.1 工具调用失败的典型排查路径我把自己遭遇到的高频故障整理成了排查路径按顺序检查基本能覆盖八成问题。第一看路由是否命中了正确的领域。如果用户的请求被路由到了错误的领域后续工具列表里压根没有正确选项模型必然瞎选。排查方法是在路由日志里回看 route_domain 的输出直接跟人工标注对比。第二看工具描述的触发场景是否覆盖了用户问法。比如用户说“这笔钱什么时候退回来”而工具描述里写的是“当用户询问退款进度时使用”。如果你没写“钱什么时候到账”“退到哪里了”这类同义说法模型就会迟疑。我的经验是把真实对话里出现过的问法不断补充进触发场景是打磨触达准确率成本最低的办法。第三看参数提取是否缺字段。模型在 function calling 里经常出现漏填必填参数的情况原因通常是工具定义里没有给参数附带足够的出处说明。比如退款单号这个参数如果描述里写“通常以 R 开头”模型就知道去用户文本里找 R 开头的字符串。如果只写“退款单号”模型有可能真的不知道去哪儿找。最后一个排查点是系统提示词跟工具列表的边界。你要是发现模型频繁在工具列表里“创作”出不存在的工具大概率是工具列表区域和普通对话区的边界不够清晰。加上分隔符并明确写一句“你只能使用下面列出的工具不要自己发明工具名”能解决大半幻觉式调用。5.2 上下文膨胀“够得着命令但忘了用法”上下文膨胀是另一个隐蔽杀手。对话跑了几十轮之后即使有摘要压缩也存在一个特殊场景工具列表本身被系统提示词挤到了很长的位置而模型对长上下文末尾的内容注意力会下降。表现就是用户说“按之前那个办法再查一次”模型能准确复述工具名却把参数格式给记混了。我的排查思路不是继续压缩系统提示词而是检查摘要里有没有保留足够多的操作背景。之前那个“用户明确指令要保留原文”的策略在这里起到了关键作用。操作背景在摘要里保持完整模型就能在有限的窗口里重建上下文而不是盯着被截断的残句硬猜。如果上下文仍然不够用建议再走一遍路由优化而不是临时砍工具描述。砍描述是负优化它会让模型对工具的理解变浅。宁可多花一次分类调用把领域缩小也不要让所有工具挤占窗口又都说不清。针对长对话场景我还会在每二十轮强制插入一次“状态快照”把用户已经完成的操作、待办事项、当前偏好记录成结构化条目。这样即便后面某轮上下文被顶掉至少关键的状态不会丢。5.3 多 Agent 协作异常循环、超时与责任不清多 Agent 协作里出现的异常绝大多数跟模型能力无关而是协议设计有漏洞。先说循环。我前面提了深度限制和熔断这里补充一下日志层面的识别技巧如果 trace_id 里同一个 agent_id 连续出现三次以上基本可以断定存在循环调用不用等抄完日志再处理直接触发熔断。再说超时。子 Agent 处理慢导致主 Agent 一直等是协作层最常见的故障现象。我的默认策略是三十秒超时超时后主 Agent 不再等待先返回用户一个“正在处理稍后通知”的占位结果再后台异步轮询。这样用户端体验稳定不会卡在那里转圈。对应地在协作路由表里我会把超时时间做成每个 Agent 可独立配置的字段因为数据查询和文件生成的合理耗时差异很大。最后一个值得重点说的是责任不清多个 Agent 都能处理同一类任务结果所有 Agent 都以为该对方去做。这个坑我在设计能力声明时吃过亏。解决办法很简单给每个能力声明加上一条“default_owner”标记明确某个能力在默认情况下由谁负责。只有当 default_owner 标记不可用或明确拒绝时才能转发给后备 Agent。这条规则在协作路由表里强制执行而不是靠模型自觉。5.4 权限边界触达能力越强越要管住能触达谁最后想聊一个越往后做越重要的问题权限。Agent 的触达能力本质上是一把双刃剑。工具路由做得越好它越能准确触达内部系统但一旦权限校验缺失准确触达就会变成乱触达。我在客户域工具上线后发生过一次安全事件Agent 在对话中拿到了一个用户 ID就开始查询该用户的手机号、地址、消费记录但完全没有校验当前会话是否属于该用户。模型忠实执行了工具调用从技术上看是一个非常成功的 function calling但从安全角度看是一次越权操作。还好当时只在内网环境没有造成实际数据泄露但它给我敲了一记警钟。所以 Agent-Reach 的权限原则是触达层只负责“够到”权限层必须独立于触达层之外强制校验。每次工具调用都要经过一个鉴权拦截器校验当前主体的身份、目标资源的归属、操作的等级。这层逻辑不能依赖模型的自觉更不能依赖工具描述里的“请仅查询本人”这种软性约定。在这套权限设计落地之后我的原则是系统提示词里可以正常展示个私有个性化信息但凡是涉及隐私数据的工具都必须先过权限层由后端程序强制决定允许还是拒绝不允许 LLM 自行判断。我试过把权限校验写进工具描述让模型自己处理结果就是时不时出点幺蛾子。现在这套代码层面硬校验的方案跑了一个多月再没出现过越权查询。关于 Agent-Reach 这条路踩坑踩到今天我个人最深的体会是一个能稳定触达正确资源的 Agent确实比一个“机智”但总闯祸的 Agent 靠谱得多。你接下来做 Agent 项目的时候不妨也从这三个维度出发逐个检查工具的触达成功率、记忆的触达完整度、协作的触达秩序。把每一个环节都变成可测试的指标你的 Agent 一定会比大多数“看着很智能但干不成事”的 demo 走得更远。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进