今年做AI应用,我最大的感受是:模型能力已经溢出,但绝大多数Agent项目都死在了“够不着”这三个字上。客户要的是一个能自己查库存、比价、下单、再发通知的助手,结果我交付的Agent连公司内部系统的数据都读不出来,最后只能做PPT演示用。后来我把整套方案推倒重来,做了一个专门解决“Agent触达能力”的项目,代号Agent-Reach。这篇文章不聊抽象概念,就把我在这个项目里踩过的坑、重写三版才稳定的工具调用层、以及真实压测数据一次性倒出来。
如果你也在做AI Agent、智能客服、RPA替代方案,或者正为大模型只能聊天不能办事发愁,这篇应该能解决你一半以上的问题。
1. 我第一次做Agent时“够不着”的真实教训:Agent-Reach的起点
先讲项目背景。去年我接了一个供应链场景的智能化需求,业务方提得很具体:让AI Agent根据库存水位自动补货,缺货时找替代供应商并发起审批。听起来不难,但真正动手才意识到,Agent的能力边界根本不在模型智商上,而在它能不能触达企业内部散落的系统——ERP、WMS、审批流、消息网关,每一个都是独立的体系,接口风格完全不一样。
1.1 为什么聪明模型做不了实事
我当时用的方案很粗暴:把所有API的调用说明塞进Prompt,让大模型自己决定“现在该调用哪个函数”。演示环境里一切正常,模型能认得出函数名,也能生成参数,但只要把环境换到生产数据,问题立刻暴露。最典型的一个场景是:模型要从ERP取库存数,又从WMS取在途数,再结合供应商主数据去判断“该补多少”。这三步要连续调用三个不同系统,但Prompt里塞了三十多个函数说明,模型开始选择困难,要么调用错函数,要么把参数格式写错。有一次它把supplier_id传成了supplier_code,下游系统直接报错,整个工作流卡死。
数据对比很扎眼:只有5个函数时,模型选对工具的成功率是92%;函数超过30个时,成功率直接掉到68%。而且每增加一个函数,Prompt的token消耗就涨一截,响应延迟从1秒拉到3秒以上。这条路走不通。
1.2 “触达”应该是一个独立工程问题
我后来想明白一件事:人做事靠手脚去够东西,Agent做事靠什么?靠工具调用、靠上下文读取、靠和其他Agent协作。这三件事本质上都是“触达”(Reach)。传统做法是把触达逻辑散落在提示词里,让模型即兴发挥,这等于让一个新手司机在没有路标的高速公路上一边开车一边认路。
Agent-Reach的核心思路,就是把“触达”从模型对话里剥离出来,做成一个独立的工程层。它负责三件事:把外部世界抽象成一组稳定可控的“触达端点”;把模型选工具的决策过程用规则和校验兜住;把每一次触达行为记录下来,方便复盘和优化。你可以把它理解成给Agent装了一副“长臂”——模型负责思考,Reach层负责够东西,各干各的活。
2. Agent-Reach的触达层设计:会话、工具与多Agent三条通路
Agent要真正做事,触达的对象无非三类:对话里的上下文、外部系统和数据、以及其他协作单元。Agent-Reach针对这三类对象设计了三条并行的通路,互不干扰,又能组合使用。
2.1 会话触达:让模型“看得见”所有该看的信息
很多Agent连已有的业务数据都“看不见”,不是因为权限不够,而是上下文里根本没带。Agent-Reach的会话触达层做了一个轻量级记忆管理:每个业务实体会维护一个动态摘要,Agent开始干活前,先把与当前任务相关的摘要拉到上下文里,而不是把所有历史记录全部倒进去。
我用一个零售库存场景举例。Agent被问到“A商品还能卖几天”,它需要知道日均销量、库存余量、采购在途量。这些数据分散在三个系统里,传统做法是把这三张表都喂给模型。Agent-Reach的做法是先把原始数据处理成一条结构化摘要,再附上数据来源和时间戳,让模型既拿到答案,也拿得到可靠依据。这样上下文占用从2000多token降到了300多token,回答准确率反而更高。
2.2 工具触达:把API包装成“可信端点”
这是Agent-Reach最核心的部分。它不要求模型懂API细节,而是把每个业务操作包装成一个端点,端点声明自己需要什么参数、返回什么结构、有哪些限制条件。模型只需要从“端点目录”里选择,参数校验和错误处理全部由触达层接管。
我们做了一个工具描述规范,核心字段包括:工具名称、一句话说明、入参Schema、出参Schema、访问级别、超时设置。模型被约束为只能看名称和说明来做选择,等选中之后,参数解析走的是程序化校验,不靠模型自由发挥。这一步看起来简单,实际效果立竿见影:工具选择准确率从68%回到了93%以上。
2.3 多Agent触达:协作时“谁找谁、怎么找”要有约定
单Agent能做的事情终究有限。我测试过一个采购流程:一个Agent管需求分析,一个Agent管供应商寻源,一个Agent管合同生成。三个Agent各有专长,但它们之间如果只靠“聊天”协作,信息损耗特别大。Agent-Reach的多Agent触达层给每个Agent注册了能力清单和消息格式,协作不再靠自然语言聊天,而是走结构化消息总线——A Agent发出“寻源请求”,里面带上商品类目、数量、交期、预算;B Agent消费这个请求,处理后返回候选供应商列表和评分依据。整个过程消息结构固定,任何一环出错都能快速定位。
3. 工具调用层重写三版之后才稳定的核心实现
我估计很多团队会走到我原来的老路上:先试自然语言直连,再试JSON Schema,最后才想到要做独立触达层。下面把我在Agent-Reach里最终稳定运行的实现拆开讲,每一步都附上为什么这么做。
3.1 第一版:函数全暴露,模型自己挑——失败典型
第一版代码看起来很美:
functions = [ { "name": "get_stock_quantity", "description": "Get stock quantity by sku", "parameters": { "type": "object", "properties": { "sku": {"type": "string"}, "warehouse": {"type": "string"} } } }, # 共30+个函数... ] response = llm.chat(messages, functions=functions)问题出在描述冲突和参数幻觉。几个函数的语义边界模糊时,模型会选错;参数格式写得稍微含糊,模型就编造值。有一次我没有约束warehouse的枚举值,模型生成了“华东大仓”,而系统里实际叫“EAST-CHINA-01”,调用直接失败。
3.2 第二版:加参数校验,但还是在模型端打补丁
第二版我在模型层加了校验规则,比如枚举值检查、必填项检查,失败就重试一次。这个改动把参数格式类的错误降了一半,但解决不了根本问题:模型依然要在几十个工具里做选择,选择本身就容易错。而且每次重试都是额外的token开销,一次成功的调用平均要烧掉将近5000 token,成本实在扛不住。
3.3 第三版:声明式端点加路由仲裁,最终方案
Agent-Reach最终落地的架构有三层。第一层是端点目录,每个端点带精细的入参校验逻辑;第二层是一个路由仲裁器,它接收模型的工具选择结果,但不盲信,而是根据上下文的相关性打分,低于阈值就主动向模型追问一次;第三层是可观测管道,每一次调用的入参、出参、耗时、失败原因全部落日志。
核心的端点声明长这样:
{ "endpoint": "inventory.query_balance", "summary": "查询商品实时可用库存", "params": { "sku": {"type": "string", "required": True, "pattern": "^SKU-[0-9]{6}$"}, "warehouse": {"type": "string", "enum": ["EAST-CHINA-01", "NORTH-02", "SOUTH-03"]} }, "output": {"schema": "InventoryBalance", "expiration": 60}, "access": "internal.read", "timeout_ms": 2000 }模型看到的只是一句话“查询商品实时可用库存”,参数和规则全部由仲裁器掌控。好处是:模型的选择负担变轻了,token消耗少了,参数错的概率也大幅下降。如果模型传了非法参数,仲裁器直接拦截并返回可选值,不需要模型自己猜。
三版对比下来,最终方案的综合表现最好:
| 版本 | 工具选择准确率 | 参数错误率 | 平均单次耗时 | 平均token消耗 |
|---|---|---|---|---|
| v1 全函数暴露 | 68% | 18.5% | 3.2s | 6200 |
| v2 加校验重试 | 79% | 9.2% | 2.6s | 5400 |
| v3 声明式端点+仲裁 | 94% | 1.8% | 1.1s | 1900 |
别小看这个设计,它的本质是把“模型的自由发挥”限制在了一个安全笼子里:模型只做决策,不碰执行细节。
4. 一次压测复盘:Reach能力在极限情况下的衰减与应对
架构定了不等于万事大吉。Agent-Reach在受控环境里跑得很顺,但一上真实业务流量,各种边界问题就冒出来了。这里分享一次完整的压测复盘,整个过程挺有代表性。
4.1 压测场景与指标设定
压测场景选的是电商订单履约Agent:从用户下单开始,需要依次执行库存预占、优惠计算、地址校验、支付状态确认、物流单创建,共涉及7个不同系统、12个端点调用。测试流量模拟了真实业务分布,其中20%的请求包含历史遗留脏数据,比如非法SKU、缺失地址、重复优惠券。
我盯的核心指标有四个:任务成功率、单任务平均延迟、错误恢复率(一次失败自动转人工或重试的成功比例)、极端情况下的可用性。
4.2 数据结果:最大的坑是上下文长度
压测开到并发50时还好,任务成功率在90%左右;并发升到200,问题开始出现。第一批请求还没跑完,第二批请求就已经把Agent的上下文撑爆了——每个任务执行过程中都要读取订单详情、库存信息、优惠明细,这些内容叠加在一起,很快突破模型的上下文窗口限制。
数据很直观:上下文长度超过60K token时,Reach层的路由准确率开始小幅下降;超过100K时,任务成功率从90%跌到55%。模型不是变笨了,而是上下文太长之后,它开始遗忘早期步骤里的约束条件,偶尔会反复调用同一个端点,甚至把之前的工具结果当作新的查询参数。
4.3 解决方案:把“触达过程”和“触达产物”分开存
Agent-Reach后来引入了执行记忆压缩机制。每个任务进行中时,系统会实时把已完成的工具调用结果提炼成一行摘要,旧上下文从几十条完整JSON压缩成几条文本描述,模型需要追溯细节时再按ID去取详情。这个改动上线后,长任务成功率回到了87%,上下文峰值从110K降到25K左右,模型反而更专注了。
我还给每个端点设了独立超时与熔断阈值。下游系统偶发抖动时,Reach层会快速失败并触发备选路径,不让单点故障拖死整条链路。比如查库存服务超时,就让Agent先读取昨天的缓存快照,哪怕数据差了30秒,也比卡死在99%的进度上好。
5. 能力边界的“栅栏”:安全与外联控制,防止Agent摸太宽
Agent的能力越强,越要克制。我在Agent-Reach里最看重的一项设计,不是让Agent能触达多少东西,而是让它“不该碰的绝对不能碰”。
5.1 权限的最小化:工具白名单与数据脱敏
所有端点默认都是不可用的,只有进入白名单才允许被调用。白名单的粒度不是“系统级别”而是“动作级别”,比如Agent可以读库存,但不能改库存;可以查供应商联系方式,但不能直接发送对外邮件。权限配置写死在触达层,不放在Prompt里,这样即使模型被诱导,也有最后一道闸拦住。
数据脱敏我也放在了触达层。只要Agent请求的字段属于敏感范围,比如真实手机号、银行账号、个人身份证号,返回前自动打码,模型拿到的永远是脱敏后的数据。宁可让Agent多问一次“需要完整号码请联系人工”,也不要让它在对话里复述敏感信息。
5.2 外联审批与防滥用机制
Agent不止要“读”,还要“写”。我测试过让Agent自动发出采购订单,结果它差点真的给一家供应商发了PO邮件。虽然参数都对,但业务方根本没批准这笔采购。Agent-Reach因此加了一条铁律:所有外联动作分为自动执行和人工审批两类,下单、删数据、发对外消息这类一律走审批流。触达层拦截后生成审批链接,由人来决定放不放行。
我也记录过模型访问频次和模式,用来识别异常调用。某个Agent单日重复调用同一端点超过正常水平十倍,Reach层会直接限流并告警。这么做不是为了防范恶意外部攻击,主要是防自己人,毕竟模型在极端情况下可能陷入循环调用,烧token是小事,打垮下游业务系统才是大麻烦。
5.3 一个真实的误用案例
有一回测试“数据归档”功能,业务方说“把三年前的历史订单清理掉”。Agent正确识别出了归档目标,但参数解析时把“历史订单表”误映射到了“订单全量表”,要不是触达层的范围校验发现删除行数远超预期,险些把最近三个月的真实订单一起清理。这个教训让我坚定了一个原则:Agent能触达的范围必须是人先画好的圈,模型只能在这个圈里做最优选择,绝不能自己扩展边界。
6. 从Reach到Sustain:后续扩展与给同行的一套落地清单
Agent-Reach目前已经跑通了供应链场景里的大部分主流程,但离一个通用的“触达层基础设施”还有距离。我下一步准备做三件事:一是把端点目录从手工登记改成半自动发现,连接企业内部API网关后自动生成端点Schema;二是给多Agent协作增加更细的信用评分,避免某个Agent长期“划水”拖累整个任务;三是把压测能力内置到触达层里,每次改完端点配置自动跑一遍全链路仿真。
如果你也想在自己项目里引入类似思路,这里有一套可以照搬的清单:
- 把所有工具调用收口到一个服务里,不要散落在各个业务代码中
- 工具描述用“一句话说明+Schema”,别让模型读大段文档
- 参数校验必须程序化,不能指望模型自觉
- 每次调用落日志,至少保留入参、出参、耗时、失败原因四样
- 外联写操作一律走人工审批,再可靠的Agent也不配拥有“自动下单”的权限
- 上线前做一次超长上下文压测,看看你的Agent在窗口边缘还醒不醒
按这个清单执行,哪怕你完全不用Agent-Reach的代码,只把这套原则落地,也能避开我在第一版里踩过的那些大坑。
我个人在实际操作中的体会是:Agent项目能不能成,模型只占三成,剩下七成都在“触达工程”上。把Agent-Reach做扎实之后,我发现团队的关注点从“怎么让模型更聪明”变成了“怎么让模型不被环境绊倒”。这种转向带来的稳定性提升,远比调几个Prompt参数来得实在。最后再分享一个小技巧:给每个关键端点起一个“人话名称”,比如把inventory.query_balance叫做“查可用库存”,模型选工具时会更少出错。这种命名上的小改动,很多时候比优化模型温度参数更管用。