ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent-Reach:让 Agent 从能聊到能干的工具调用中间层

Agent-Reach:让 Agent 从能聊到能干的工具调用中间层 事情是这样的前阵子我帮一个团队做内部助手模型换到最新的一版对话质量肉眼可见地涨了一截测试集上回答得头头是道。可一上线就露馅了——用户问帮我看下华东仓这批货还剩多少不够的话补一单助手回了一段非常礼貌、非常结构化、非常没用的分析最后加一句建议您登录后台查询。模型不笨它只是手够不着。这套东西后来我们内部就叫 Agent-Reach名字取自让 Agent 真正触达外部世界这个朴素的念头。它不是什么新模型也不是又一个提示词框架而是一层负责把模型和真实系统接起来的中间层。如果你也在做 Agent 落地被能聊不能干卡过或者正在纠结工具调用怎么写才不乱这篇可以当一份踩过坑的施工笔记看。1. Agent-Reach 想解决的够不着问题能力落地的三层断点1.1 模型再强手够不着也是白搭很多人第一次做 Agent 时的直觉是模型能力是瓶颈。所以拼命换模型、调提示词、加思维链。做完一轮发现效果确实好了但离能干活还是差得远。原因在于Agent 的能力上限并不由模型单独决定而是由模型能力与外部可达能力的交集决定。模型再会推理如果它的世界里只有一段上下文文本那它能输出的最好结果就是建议你去做某某事。我用一个生活化的类比模型像一位经验非常丰富的顾问坐在一间没有电话、没有网络、门还锁着的办公室里。你隔着门缝递进去一张纸条问他问题他能给你一份精彩的咨询报告但他没办法帮你打电话确认库存、没办法替你签合同、没办法查一下物流到哪了。Agent-Reach 干的事情就是给这间办公室装电话线、装内线、装一台能查资料的电脑同时还要在门口配一个保安确认他每次打电话的对象和内容是否合规。这个比喻里其实藏着两个关键点也是很多团队容易忽略的第一接通只是起点不是终点第二接通之后必须有人管。只做第一步不做第二步的项目我见过太多最后都变成了能连数据库的提示词注入漏洞。1.2 三类高频的够不着现场把过去一年多我接触过的项目梳理一下够不着基本落在三个类别里每一类的解法重心都不一样。第一类是数据够不着。用户的意图需要实时数据才能回答比如库存、订单状态、账户余额、工单进度。这类问题的特点是数据在业务系统里模型上下文里没有而且数据是实时变化的靠提前把数据塞进提示词根本不可行。我见过一个团队试图每天早上把整张库存表 dump 成文本塞进系统提示结果就是上下文爆掉、成本飙升、数据还是昨天的。他们的失败不是因为模型不行而是因为把取数这个动作错误地放在了离线环节。第二类是动作够不着。用户要的不是答案而是一个被执行的变更下单、退款、改地址、建任务、发通知。这类比第一类危险一个数量级因为读错了只是答错写错了是真金白银。很多团队在数据查询上尝到甜头后顺手把写接口也挂上去没有任何二次确认、没有金额上限、没有幂等最后出事只是时间问题。第三类是流程够不着。单个接口都能调但完成一件事需要五六个接口按特定顺序编排中间还要做条件判断和错误回滚。比如客户要退一件定制商品这件事背后要查订单、查商品是否可退、算退款金额、发起退款、改库存、通知客服、写日志。这一串如果让模型自由发挥去串成功率会低得让你怀疑人生。三类问题对应三种不同的工程重点数据类重点是取数时机与缓存动作类重点是权限与幂等流程类重点是编排稳定性。把它们混在一起用一套方案处理是新手最常见的结构性错误。1.3 Reach 层和普通 Tool Calling 不是一回事说到这肯定有人想问这不就是 Function Calling / Tool Calling 吗模型厂商早就支持了为什么还要专门做一层我的看法是厂商提供的工具调用是协议能力Agent-Reach 这一层是工程能力两者不冲突但后者解决的问题前者不管。我列了一张对比表是我自己在选型时整理的维度原生 Tool CallingAgent-Reach 层关注点把工具描述告诉模型让它输出结构化调用工具从哪来、怎么注册、怎么鉴权、怎么审计鉴权通常不涉及凭证由业务代码自己处理统一凭证管理、按用户身份代理调用失败处理返回错误字符串模型自己看着办分类重试、降级、熔断、兜底话术权限无分级授权、敏感操作二次确认、额度限制可观测无全链路 Trace、耗时、成功率、成本多 Agent每个 Agent 自己维护工具列表能力注册表共享按角色分配可见范围一句话总结原生工具调用解决的是怎么让模型表达出它想调什么Agent-Reach 解决的是这个调用该不该发生、发生了怎么保证不出事、出事了怎么查。前者是语言问题后者是系统工程问题。我见过最典型的翻车方式就是只做了前者工具描述写得很糙参数 schema 只有一个 string然后上线让模型自由发挥。结果模型把用户 ID 传成了用户名、把日期格式传成了下周三、把金额单位从分当成了元。这些坑我在第 4 节会展开讲排查过程这里先埋个伏笔。2. 拆解 Agent-Reach 的骨架能力注册表、适配器与权限闸门2.1 能力注册表让 Agent 先知道自己有什么整套东西里最不起眼、但最影响后面所有环节的是能力注册表Capability Registry。它的职责就一件事维护一份当前这个 Agent 在什么场景下可以调用哪些能力的权威清单。为什么强调权威因为很多项目的工具列表是散落的有的写在提示词里有的硬编码在调用函数里有的靠 if-else 判断。结果就是提示词里说有三个工具代码里实际实现了五个模型调了一个不存在的或者调了一个存在但当前用户没权限的。这类不一致的 bug 极难排查因为它是文档和实现对不上不报错只是行为诡异。我的做法是把注册表设计成一条记录一个能力字段大致是这样{ name: inventory.query, display_name: 库存查询, description: 根据 SKU 与仓库编码查询可用库存数量只读操作, params_schema: { type: object, properties: { sku: { type: string, pattern: ^[A-Z0-9]{8,16}$ }, warehouse: { type: string, enum: [east, north, south, west] } }, required: [sku], additionalProperties: false }, side_effect: read, timeout_ms: 800, rate_limit: 60/m, scopes: [assistant.readonly, assistant.operator] }几个字段值得单独说。side_effect标注这个能力是读还是写这是权限闸门做分级拦截的基础也是重试策略的依据——读操作可以放心重试写操作必须谨慎。additionalProperties: false是我强烈建议加的它能在模型编造多余字段时直接报错而不是把乱七八糟的东西透传到下游接口。timeout_ms和rate_limit写在能力定义里而不是代码里好处是运营和运维可以不改代码就调整出问题时改配置比改代码快得多。scopes这个字段是能力可见性的关键。注册表存的是全量能力但真正喂给模型的工具列表是按当前会话的身份过滤后的子集。给一个只读客服角色喂 30 个工具的完整清单不仅浪费上下文更重要的是给了模型越权的机会。我一般的经验值是把单次暴露给模型的工具数控制在 8 到 15 个之间超过这个量模型选错工具的概率会明显上升。2.2 适配器层把异构接口揉成统一形状注册表定义了能力的对外长相适配器层负责对内怎么接。这一层的价值在于隔离不管下游是 REST、gRPC、GraphQL、还是某个老掉牙的 SOAP 接口甚至是只能通过页面操作的内部系统对上层来说都应该是同一个调用签名。我见过的失败案例里有一个印象特别深团队把所有工具实现都写在了一个 2000 行的tools.py里每个函数里混杂着参数校验、鉴权取 token、HTTP 调用、错误处理、日志打印、以及业务字段转换。结果第一次换鉴权方式改了 40 个函数改漏了两个线上跑了三天才发现。适配器层的意义不是让代码好看是让变化只影响一个地方。我一般会把一次能力调用拆成四段入参归一化把模型给的参数转成下游要求的格式。日期统一转 ISO、金额统一转最小单位、枚举做白名单校验。这一段是防幻觉的主力。鉴权注入根据当前会话的用户身份去凭证中心换取有权限的临时凭证而不是用一套万能的服务账号。这一步决定了审计能不能落地。调用与超时统一封装的客户端带连接超时、读取超时、整体超时三层控制。出参裁剪下游返回 200 个字段模型只需要 5 个。裁剪不仅省 token更重要的是降低模型的注意力被稀释的概率——我实测过同一份数据从 80 个字段裁到 6 个字段模型提取正确答案的准确率能提升十几个百分点。这四段里最容易被省略的是最后一段。大家觉得多给点信息总没坏处实际恰恰相反。信息过载会让模型抓不住重点尤其是当返回结果里有一堆近义字段比如amount、total_amount、payable_amount的时候模型选错字段的概率高得吓人。2.3 权限闸门与审计给手装上刹车前面说过接通之后必须有人管。权限闸门就是那个管的角色它横在所有能力调用的必经之路上做三件事判断能不能调、限制怎么调、记录调了什么。第一件事是能力级鉴权。会话带着用户的身份进来闸门先看这个身份有没有对应 scope。这一步要在调用发生之前做而不是在模型已经决定之后再去问用户可以吗——后者体验很差而且用户大概率会一路点同意。第二件事是参数级风控。这里有些规则是硬编码的比如退款金额超过某个阈值必须走人工审批有些是动态的比如同一个用户 5 分钟内第 3 次调用退款能力直接拦截并告警。这类规则用代码写会比用提示词写靠谱得多因为提示词是建议代码是强制。我从来不信我在提示词里写了不要退款超过 500 元这种防护。第三件事是审计留痕。每次调用记下来谁、什么时候、调了哪个能力、传了什么参数、返回了什么、耗时多少、是成功还是失败。这份日志的价值在出事那天会体现得淋漓尽致。我经历过一次误退款的排查因为有完整审计十分钟就定位到了是某个测试账号的 scope 配错了如果没有日志可能要花一天。提示审计日志里尽量不要原样落库用户的敏感明文参数做一层脱敏或哈希只保留可追溯性即可。日志本身也是数据泄露的高危面。3. 跑通第一条 Reach 链路从环境准备到闭环验证3.1 环境与依赖少装一个包就能卡你一下午概念讲多了容易飘我们直接上手跑一条最小链路。目标很明确让 Agent 能回答华东仓某个 SKU 还剩多少库存这个问题并且是查真实数据不是编的。环境上我一般这么配Python 3.10 以上3.9 在类型标注和一些异步特性上会有小别扭、一个支持结构化输出的模型接口、一个 HTTP 客户端库。依赖装完之后第一件该做的事不是写代码是先单独验证模型的结构化输出能力。这一点很多人跳过导致后面把模型的锅和代码的锅混在一起排查。python -m venv .venv source .venv/bin/activate pip install httpx pydantic jsonschema然后用一段最小的脚本确认模型能稳定吐 JSONimport json prompt 你是一个调用规划器。根据用户问题输出 JSON。 只允许输出 JSON不要任何解释。 格式: {capability: 能力名, args: {...}} 可选能力: inventory.query(sku, warehouse) 用户问题: 华东仓 SKU 为 AB12345678 的货还有多少? # 这里替换成你实际使用的模型调用 resp call_model(prompt) print(resp) try: print(json.loads(resp)) except json.JSONDecodeError as e: print(解析失败:, e)实测下来如果这个最小脚本的失败率超过 5%那后面的编排怎么调都白搭得先在提示词或模型选择上解决。我一般会准备 20 条刁钻的测试用例反复打包括带口语、带错别字、带多个意图的输入观察输出是否稳定。这一步花的时间会在后面省下十倍。很多团队直接跳到写工具实现结果调不通时根本不知道是模型不听话还是代码写错了。3.2 写第一个 Capabilityschema 比实现更重要写能力实现之前先把 schema 定死。我见过太多项目实现写得飞快schema 随手写个 string 就交差最后全部返工。from pydantic import BaseModel, Field, field_validator import re class InventoryQueryArgs(BaseModel): sku: str Field(..., description商品 SKU8-16 位大写字母与数字组合) warehouse: str | None Field( None, description仓库编码可选值 east/north/south/west不填表示全仓汇总 ) field_validator(sku) classmethod def check_sku(cls, v: str) - str: v v.strip().upper() if not re.fullmatch(r[A-Z0-9]{8,16}, v): raise ValueError(fSKU 格式不合法: {v}) return v field_validator(warehouse) classmethod def check_wh(cls, v): if v is None: return v if v not in {east, north, south, west}: raise ValueError(f仓库编码不合法: {v}) return v这里有几个我踩过坑才加上的细节。一是自动规范化用户和模型都可能把小写 sku 传进来在入口直接 upper而不是在业务逻辑里到处处理。二是可选字段的语义要写清楚warehouse不填到底是不查还是查全部这个区别很大必须在 description 里说明否则模型会凭感觉猜。三是错误信息要能回传给模型校验失败时把具体原因返回模型有机会自己纠正重试一次这比直接抛 500 让整条链路崩掉体验好得多。实现部分反而简单就是一次 HTTP 调用加上前面说的四段处理。真正的重点在于校验逻辑必须放在 schema 层而不是散在实现里。放对了地方所有调用方自动受益放错了地方每加一个调用路径就要重写一遍。3.3 挂载到 Agent 并跑通闭环能力写好后注册进注册表然后按身份过滤再把过滤后的清单转成模型能理解的工具描述这一步我一般用统一的转换函数保证 schema 和描述永远同步避免手写两遍导致对不上。registry { inventory.query: { args_model: InventoryQueryArgs, handler: query_inventory, side_effect: read, scopes: [assistant.readonly], timeout_ms: 800, } } def build_tool_spec(scopes: list[str]) - list[dict]: specs [] for name, meta in registry.items(): if not set(meta[scopes]) set(scopes): continue specs.append({ name: name, description: meta[args_model].__doc__ or name, parameters: meta[args_model].model_json_schema(), }) return specs跑闭环的过程我建议按这个顺序验证不要跳步纯校验测试直接给能力传各种畸形参数确认校验能挡住。这一步不涉及模型。单轮调用测试固定一个问题跑 50 次统计模型选对能力的比例、参数正确的比例、最终答案正确的比例。这三个指标要分开看混在一起看不出问题在哪。多轮场景测试先问库存再追问那北仓呢看模型能否正确继承上下文里的 SKU。异常注入测试把下游接口手动改成超时、返回 500、返回空数据观察链路表现。第 4 步是分水岭。第 1 到 3 步跑通只说明正常路径能用第 4 步才说明能不能上线。我见过不少 demo 演示得行云流水一断网就整个崩掉。3.4 实测数据与几个反直觉的结论这条最小链路我前后跑过几轮有几个结论跟直觉不太一样记下来供参考。观察项直觉预期实测结果工具描述越长越好信息全模型判断准描述超过 3 行后准确率反而下降工具数量越多越灵活能覆盖更多场景超过 15 个后选错率明显上升参数 schema 越宽松越好减少调用失败加additionalProperties: false后失败率下降、成功率上升返回数据越全越好信息丰富裁剪字段后答案准确率提升明显让模型自己纠错省去工程处理无明确错误反馈时模型会反复犯同一个错最后一条我展开说一下。很多人指望模型会自己发现传错了然后重试实际上如果代码只返回一个笼统的调用失败模型没有任何线索知道哪里错了它下次大概率还是这么传。把校验失败的具体原因结构化回传是提升整体成功率性价比最高的改动之一。4. 踩坑实录参数幻觉、超时雪崩、重复副作用4.1 参数幻觉模型编造字段的排查链路第一个坑是我印象最深的因为它排查过程特别反直觉。现象是库存查询偶尔返回该 SKU 不存在但用户反馈那个 SKU 明明是有的。服务端日志显示确实收到了一次查询但 SKU 参数不对劲。第一步先怀疑数据。我们去查了那个 SKU 在数据库里是否存在存在。排除数据问题。第二步看服务端收到的原始参数。这里发现端倪收到的 SKU 末尾多了两位。这就说明不是数据库的问题是传进来的参数本身就错了。第三步看模型输出的原始调用。在中间层把模型的原始输出落一份日志发现模型输出的 JSON 里SKU 字段确实是完整的、正确的。第四步定位到转换环节。既然模型输出是对的服务端收到的是错的那问题一定在中间的转换。最后发现是入参归一化那段里有个字符串截断的逻辑是为另一个能力写的被错误地套用了。这纯粹是个代码 bug跟模型一点关系都没有。这个排查过程的价值在于顺序先排除数据、再确认模型输出、最后查转换。如果一开始就怀疑模型幻觉去调提示词会白折腾很久。我后来给自己定了个规矩凡是最先怀疑模型之前先把模型输出的原始日志拿出来看一眼。十次里有三四次会发现其实是代码问题。还有一个真正的参数幻觉案例是模型把日期格式编成了下周三。这个的根因是 schema 里日期字段只写了type: string没有任何格式约束。模型面对一个开放式字符串自然会用最符合自然语言直觉的方式表达。加上format: date和正则约束之后这个问题基本消失。大部分所谓的幻觉本质上是约束缺失。4.2 超时雪崩一个慢接口拖垮整条链路第二个坑更隐蔽。上线一段时间后整体成功率从 95% 掉到了 78%但看单个能力每个的成功率都在 97% 以上。这个矛盾的数据说明问题不在单个调用而在整体链路。排查从耗时分布入手。我拉了 P50、P95、P99 三个分位的链路耗时。P50 是 1.2 秒正常P95 突然涨到了 9 秒多P99 直接 30 秒超时。说明有一小部分请求特别慢把整体体验拖垮了。再拆开看是哪一段慢。加了两层埋点一层是模型推理耗时一层是能力调用耗时。发现慢的请求里模型推理只占 1 秒剩下的都是能力调用在等。具体是哪个能力呢一看是某个查询量特别大的报表接口平时 300 毫秒但每天整点前后会涨到 8 秒以上。根因是级联等待。一次用户请求里如果模型决定连续调用三次能力其中一次撞上了慢接口整条链路的等待时间就是三段之和。而且因为是同步串行慢的那一段会一直占着连接和线程。修复方案是分层的给每个能力单独设置超时并且超时值要明显小于链路总超时。比如链路给 10 秒那单个能力最多给 2 秒而不是让一个能力独自吃掉整个预算。超时后不让模型自由重试慢能力而是返回一个暂时查不到可以稍后再试的结构化结果让模型给出自然的兜底话术。硬重试只会让雪崩更严重。加熔断某个能力连续超时 N 次后直接短路返回降级结果避免持续冲击已经吃力的下游。把可以并行的调用并行化。有些能力之间没有依赖关系串行是浪费。改成并行之后P95 从 9 秒降到了 2.3 秒。这里有个经验值得单独拎出来链路超时预算要往下分配不能每层都假设自己有完整预算。常见错误是网关给了 30 秒模型调用给了 30 秒能力调用也给了 30 秒结果一个慢接口能把整条链路卡到 30 秒用户体验极差还占满了资源。4.3 重试引发的重复写入与幂等设计第三个坑最危险因为它涉及真金白银。现象是客服反馈有客户收到两笔一样的退款。查日志发现一次用户请求触发了两次退款能力调用。为什么会有两次因为其中一次是超时重试。第一次调用其实成功了但下游返回比较慢客户端在超时时间内没收到响应就按重试策略又发了一次。下游收到两次请求执行了两次退款。这是个经典问题超时不等于失败。超时只是我不知道结果可能成功、可能失败、也可能还在处理中。对写操作来说盲目重试是灾难性的。解决的核心是幂等键。每次写操作生成一个唯一键随请求一起发到下游下游如果发现这个键处理过直接返回上次的结果不重复执行。幂等键的生成有几个讲究不能随机生成否则重试时会生成新的键等于没做。要有稳定的来源比如会话 ID 加意图哈希。要有合理的有效期一般几分钟到几小时太短起不到作用太长会误伤正常的重复操作。下游必须真的支持如果下游是第三方接口不支持幂等键那就得在中间层做去重状态表。除了幂等写操作还有三条我现在的默认规矩注意写操作的能力默认不自动重试除非下游明确支持幂等写操作的超时值要比读操作更宽松宁可慢也不要重复涉及金额、库存、状态流转的写操作必须记录请求指纹用于事后对账。另外写操作返回的结果建议带上明确的确认信息比如退款单号 XXX金额 XX 元让模型能原样反馈给用户。这既是体验问题也是出了问题时的追溯凭证。5. 让链路稳下来缓存、降级与可观测性5.1 结果缓存与语义缓存链路一旦稳定下来第一个该做的优化就是缓存。但缓存不是无脑加用错了比不用还糟。第一层是精确缓存。同样的能力加同样的参数在很短的窗口内比如 30 秒到 5 分钟直接返回上次的结果。这一层对高频重复查询特别有效比如多个用户同时问同一个热销 SKU 的库存。键就是能力名加规范化后的参数哈希。要注意的是参数在生成键之前一定要先归一化否则AB12345678和ab12345678会被当成两个不同的键缓存命中率直接腰斩。第二层是语义缓存。这个更激进一些把用户问题的向量算出来如果和之前某个问题的相似度超过阈值直接复用上次的答案。这一层能省下大量模型调用成本但风险也大——相似的问题答案可能完全不同尤其是涉及具体数字、具体订单的时候。我一般的做法是只用语义缓存做能力选择的复用不用它复用最终答案。也就是说如果一个问题跟之前很像我可以跳过模型选工具这一步直接复用当时的工具调用计划但实际去查数据、生成回答还是走完整流程。这样既省了推理成本又不会拿旧数字糊弄用户。这个折中方案我用下来效果不错命中率大概在两三成出错率极低。提示任何跟用户身份、权限相关的结果一律不进共享缓存。缓存键里必须带上身份维度否则会出现 A 用户看到 B 用户数据的严重问题。这类事故我听说过不止一起。5.2 分级降级从全瘫到半可用降级这个概念大家都会说但真正做细的不多。我的经验是把降级分成三档每档对应不同的触发条件和用户可感知结果。第一档是能力级降级。单个能力出问题比如某个下游接口挂了。这时候链路还是能跑只是这个能力返回暂时不可用。模型收到这个结构化结果后会给用户一个自然的说明。对用户来说体验是这部分暂时查不了而不是整个助手坏了。第二档是链路级降级。模型调用本身出问题了比如接口超时或者限流。这时候可以把请求转成一个更简单的模式不做复杂编排只用一个精简的提示词和少量核心能力先保证能回答最常见的问题。质量下降但不至于完全不可用。第三档是兜底。全都不可用时的最后一道防线。这时候应该给用户一个明确的、可操作的信息比如当前服务繁忙你可以直接联系人工客服联系方式是 XX。这一档看起来简单但很多系统在这里给的是一个空白页或者一句冷冰冰的系统错误体验很差。降级的触发条件要自动恢复也要自动。我见过需要人工手动开关降级的系统结果是半夜出问题没人切换第二天才发现。用连续失败次数加时间窗口做自动触发用请求成功率回升做自动恢复是更省心的做法。还有一点容易忽略降级路径本身也要测试。很多团队从来没演练过降级真到需要降级的时候发现降级逻辑本身有 bug。我现在的习惯是定期做故障演练手动把核心依赖打挂看系统是否真的像设计的那样优雅降级。5.3 可观测性Trace、指标与成本核算可观测性是前面所有优化能不能落地的前提。没有数据所有优化都是拍脑袋。Trace 是最基础的一层。一次用户请求分配一个 trace id贯穿模型调用、能力调用、缓存命中、降级触发全流程。有了 trace id排查问题时可以一条命令捞出完整链路。我一般会记录每个节点的开始时间、结束时间、输入输出的摘要脱敏后、以及结果状态。这里的关键是摘要要够用但不要太大参数和返回值可以截断但结构要保留方便快速判断。指标是第二层。我关注的指标不多但每一个都要看趋势指标关注的异常信号可能的根因方向能力选择准确率突然下降工具描述变更、模型版本切换参数校验失败率某能力独高schema 描述不清或约束缺失P95 链路耗时台阶式上升某依赖变慢或出现串行等待降级触发次数频繁触发依赖不稳定或阈值设置过松单次请求平均成本缓慢爬升上下文变长或重试变多成本核算是第三层也是最容易被忽略的一层。Agent 类应用的成本不只是模型 token还包括能力调用的费用有些第三方接口按次计费、缓存存储、以及重试带来的额外调用。我一般会在链路结束时打一条成本日志把这次请求消耗的每一部分都算清楚。做一段时间后你会发现重试和长上下文往往才是成本大头而不是模型本身贵。有一点值得提醒成本和成功率要一起看。单纯降成本很容易少给点信息、少重试就行但成功率会掉。我见过为了省钱把工具描述砍到只剩一句结果选错率飙升最后算总账反而更贵因为错误处理的开销更大。找到曲线上的拐点比单点优化重要得多。6. 进阶多 Agent 协作下的能力共享与边界划分6.1 能力复用把 Reach 层做成内部能力市场单 Agent 跑顺之后很自然的下一步是多 Agent 协作。这时候能力注册表的价值会翻好几倍因为它天然支持复用。我的做法是把注册表当成一个内部的能力市场任何团队实现的能力只要按规范注册、通过评审就能被其他 Agent 复用。避免每个团队各自封装一套一模一样的数据查询接口——这种重复建设我见过太多三个团队做了三个库存查询逻辑还各不相同出了问题口径都对不上。要让这个市场真的运转起来有几个机制是必须的统一的注册规范命名、schema、错误码、超时、限流都要有约定否则注册进来一堆风格各异的能力接的人会很痛苦。命名我一般用领域.动作的形式比如order.cancel、ticket.create。能力版本化能力也要有版本。改了 schema 不要原地改发新版本老版本保留一段时间。直接原地改会让所有已接入的 Agent 同时出问题。使用情况可见哪个能力被哪些 Agent 调、调用量多少、成功率多少。这些数据能帮助判断一个能力的真实价值也能在准备下线时评估影响面。有了这些能力复用就不再是复制粘贴代码而是接入一个契约。这是规模化之后唯一的出路。6.2 能力冲突与职责重叠怎么解多 Agent 场景下最容易出的问题不是技术问题而是职责问题。典型的冲突有两种。一种是能力重复两个团队注册了功能重叠的能力比如inventory.query和stock.check做的事几乎一样。这时候模型面对两个高度相似的工具选错是必然的。解决办法不是靠调提示词区分而是在注册环节就做去重评审同一个语义只保留一个权威能力。另一种是职责重叠多个 Agent 都能执行同一个写操作。比如客服 Agent 和运营 Agent 都能改订单地址。这时候真正的问题不是技术而是谁在什么时候有权这么做。我的做法是给每个 Agent 一个明确的角色能力按角色分配 scope并且在同一资源的写操作上做互斥检测——如果一个订单正在被某个流程处理另一个流程要改它先做冲突检查。还有一种更隐蔽的冲突是状态竞争。两个 Agent 几乎同时读了同一个订单状态各自基于读到的旧状态做了决策然后先后写入后写的覆盖了前写的。这类问题的解法还是回到乐观锁读的时候带上版本号写的时候校验版本号不匹配就报冲突让上层重试或人工介入。在 Agent 场景里乐观锁比悲观锁更合适因为 Agent 的调用往往是异步、耗时较长的长时间持锁会把整条链路堵死。6.3 边界与安全Reach 越大风险面越大最后聊一个我认为最重要、但在项目早期最容易被忽略的问题Agent 能触达的东西越多出事的可能性就越大。这不是危言耸听而是一个很朴素的道理攻击面等于可达面。我在设计 Reach 层的时候会反复问三个问题第一这个能力真的需要暴露给模型吗有些能力完全可以在业务代码里按固定规则执行不需要模型来决定调不调。比如下单失败写一条日志这种事让它成为模型的一个可选项纯粹是增加不确定性。能确定的事不要交给模型判断。第二这个能力的最坏后果是什么如果最坏后果是不可接受的比如无法撤回的转账那要么不暴露要么必须在闸门层做强拦截比如金额上限、二次确认、人工审批。这个判断要在接入之前做接入之后再补限制往往会有漏网的调用路径。第三出问题之后能不能追溯和回滚每个写操作都应该有明确的标识能在事后定位到具体是哪次调用造成的影响并且有对应的补偿操作。没有这个出事时就只能干看着。另外权限这块我坚持一个原则永远用当前用户的身份去调用下游而不是用一套超级服务账号。用服务账号图省事代价是下游的权限体系完全失效Agent 变成了一个能访问全量数据的后门。用用户身份代理调用虽然多一层凭证换取的开销但下游的权限控制自然生效审计也有意义。注意从能查到能改是一个质变不是量变。跨越这条线之前务必把幂等、限额、审批、审计四件事都准备好缺一件都不要上。踩过几次坑之后我现在看一个 Agent 项目最先看的不是它用了什么模型、提示词写得多漂亮而是它的 Reach 层是怎么设计的能力清单控不控得住、写操作有没有刹车、出事了能不能查。工具层面的花活可以快速补齐但这套约束和边界的设计往往才决定了一个项目能不能从演示走到生产。
返回列表