1. 为什么需要“触达层”:Agent-Reach要解决的核心问题
1.1 从“会聊天的机器人”到“能干活的智能体”
过去两年我做过不少智能体(AI Agent)项目,从最开始的问答机器人,到后来的文档问答、客服辅助系统,总有一个绕不开的坎:模型很聪明,但“手”太短。它可以告诉你“我可以帮你查一下订单状态”,但它自己没有能力去查。于是我们只能做一堆胶水代码,在外部写死各种工具函数,再用关键词匹配去触发。那段时间我经常自嘲,做Agent感觉像是在给大模型当“嘴替”,它负责说话,我负责干活。
直到我接触了“Agent-Reach”这个概念,我才意识到问题出在哪:我们一直在优化Agent的“大脑”(推理能力),却忽略了它的“触达能力”(Reach)。所谓触达,就是智能体能通过什么方式、以多快的速度、在多安全的边界内,去对接真实世界的系统——查数据库、调接口、发邮件、操作浏览器、更新工单状态。这一个词,几乎概括了Agent从“演示阶段”走向“生产阶段”的全部关键。
Agent-Reach这个项目,就是想做一个统一的“触达层”方案。简单说,它不是一个大模型产品,也不是一个业务系统,而是位于智能体和外部系统之间的那层基础设施。它负责三件事:让Agent知道有哪些工具可以用、让Agent能安全地调用这些工具、让外部系统的反馈能顺畅地回到Agent的推理循环里。听起来简单,实际上坑特别多,我甚至可以说,这层做得怎么样,直接决定了你的Agent是“能落地”还是“只能写PPT”。
1.2 触达能力是智能体的分水岭
现在市面上谈论Agent,有两种极端。一种是只做聊天,本质上还是大模型套壳,对话框里聊得热闹,但它一个真实操作都做不了。另一种是特定场景的固定自动化,比如输入一个订单号就调一个接口返回结果,流程写死,换个场景全得重来。真正的Agent应该介于两者之间:它要有大模型的理解和规划能力,又要有灵活调用工具的本事。而这个“本事”,就是Reach层提供的。
我举一个很直观的例子。同样的一个指令:“把今天所有未处理的售后工单按紧急程度排个序,然后给对应的客服组各发一份待办提醒。”没有触达层的Agent,只能给你一段“详细的操作计划”。有一点触达能力的Agent,能直接调工单系统的接口、读取列表、调用排序算法、再通过消息机器人往多个群里发提醒。前者的价值是“建议”,后者的价值是“交付”。用户只会为后者付费,这一点我们团队在商业验证阶段深有体会:客户看到Agent真的把工单状态改了、把提醒发出了,才会相信它不是一个“高级玩具”。
所以Agent-Reach的核心定位,就是解决“触达”这件事的通用性问题。它不服务于某个具体业务,而是把触达过程中反复出现的技术工作沉淀成可复用的组件:工具注册、参数校验、权限控制、执行日志、异常回传。任何需要对接新系统的智能体,都可以直接复用这套机制,而不是每次从零搭建。
1.3 这个项目适合谁、能解决什么场景
如果你正在做智能客服、企业内部知识库、自动化运营助理、数据分析问答这类产品,你应该能很快在Agent-Reach里找到共鸣。因为它设计的出发场景,就是企业内部那些“数据散落在多个系统、规则复杂、操作必须留痕”的场景。
适合的人群大致有三类:第一类是Agent应用开发者,你们会发现很多能力是自己以前一遍遍重复写的,现在可以体系化了;第二类是平台架构师,你们可能正在头疼如何给多个Agent应用统一分配权限和审计能力,Reach层的设计能给你一套参考;第三类是对AI落地有真实需求的业务方,你们不需要读懂每行代码,但可以通过这篇梳理理解“为什么有的Agent项目做着做着就黄了”——很多时候不是模型不够聪明,是触达能力没跟上。
关于Agent-Reach的架构逻辑、核心机制、实操落地细节,我接下来分开讲。先把话放前面:这不是一个只能停留在理论图里的设计,我们已经在多个真实业务环境里跑过了,踩过的坑、趟出来的经验,我会尽量写进后面每一节。
2. 整体架构设计:一个大脑、一只手、一份清单
2.1 模块划分与分层设计
Agent-Reach的整体架构,我用一句话概括:Agent是大脑,Reach是手,工具注册表是工具箱清单,安全策略是使用规程。
从模块上讲,分为四层。最上层是Agent核心,也就是大模型的调度逻辑,它负责理解用户意图、规划任务步骤、决定下一步要调用哪个工具。第二层是触达调度层,这是Agent-Reach的主战场,我们在这里实现了工具发现、参数映射、调用编排、结果路由;大模型说出“我要用工具A,参数是XYZ”,调度层负责找到A、校验XYZ、执行调用、把结果送回给模型。第三层是连接器层,负责和各业务系统打交道——你可以理解为各种插件,有HTTP接口的、有数据库的、有消息队列的、有操作Excel的,每个连接器面向一种外部系统类型。最底下是基础设施层,包括权限中心、审计日志、配置中心、可用性监控,它们像地基一样支撑着上面三层。
一开始有人会问,为什么不直接把工具调用逻辑写进Agent里?当然可以,如果只有一两个工具,写死最方便。但一旦工具数量超过十个、权限角色超过三种,写死的代码就会变成一坨难以维护的“意大利面条”。拆出独立的触达层之后,好处就出来了:Agent不需要关心工具实现的细节,连接器不需要关心模型怎么规划,两边都只和触达层打交道。这带来了一个很实际的好处——大模型可以随时替换(GPT换到国产模型),业务系统也可以随时切换,因为中间有一层稳定的协议。
2.2 为什么选型这种架构:一次痛苦的“反模式”教训
这个架构不是一开始就定的,也是趟过来的。最早我们自己写Agent应用,图省事,直接在Agent代码里用if-else调用外部接口。做着做着,工具多了,问题开始爆发:工程师A写的订单查询函数叫search_order,工程师B写的物流查询函数叫query_logistics,命名风格五花八门;参数格式也互不统一,订单号有的传字符串有的传整数,大模型经常把参数类型搞错;更要命的是没有任何权限控制,只要大模型想到了,它就真的能调用——有一次测试时大模型居然自己去调了一个删除接口,虽然被下游拦截了,但吓得我们当晚就对架构做了重审。
那次的复盘结论,就是必须引入中间层。经过对比,我们最终采用了“工具注册表+统一调用协议”的方案:每个工具必须在注册表里登记一份“名片”,写明工具名称、功能描述、参数结构、权限要求、超时时间。大模型通过名片了解工具,触达层按照名片执行调用,权限中心根据名片上的权限要求做校验。这样做的好处是,工具的接入变成了一件“填表”的事情,而不需要写业务代码;大模型也不容易因为参数格式问题而乱猜,因为名片上有清晰的JSON Schema。
还有一个选型细节值得说:我们刻意把“工具名称”设计成带有命名空间的层级结构,比如crm.customer.search、crm.ticket.create,而不是简单的search_customer。原因是当工具数量超过一百个时,大模型靠名字区分相似工具的准确率会下降;有了命名空间,模型至少可以通过语义前缀缩小范围。我们实测过,工具数量从30个增加到150个时,工具选择的准确率从92%掉到接近80%,加上命名空间和描述优化之后,能稳定回到88%左右。
2.3 工具注册表设计:Agent的“工具箱清单”
工具注册表是整个Reach层的心脏,它做得不好,后面所有的调用都会出问题。我先把一张典型的名片结构列出来,然后逐个讲为什么这些字段必不可少。
| 字段 | 类型 | 示例 | 说明 |
|---|---|---|---|
| name | string | crm.ticket.create | 全局唯一,语义化,带命名空间 |
| description | string | 创建一条新的售后工单 | 给大模型看的,描述尽量说清使用场景 |
| parameters | JSON Schema | {orderId: {type: "string"}} | 定义参数结构,支持类型校验 |
| required_roles | array | ["agent", "operator"] | 哪些角色可以调用 |
| timeout_ms | int | 10000 | 调用超时时间 |
| idempotent | bool | false | 是否幂等,决定是否允许重试 |
| return_style | string | "json" | 返回格式,json还是text |
这里特别要说的是description怎么写,这直接决定了大模型会不会正确调用工具。很多项目为了省事,描述写得很模糊,比如“查询订单信息”,大模型接到一个具体指令“帮我看看上周买的那个吹风机发货没有”就懵了,它不知道该不该用这个工具,因为“订单信息”太宽泛了。我个人的经验是,描述必须写清楚:这个工具适合什么场景、输入什么、输出什么、有什么坑。比如这样写:“查询用户的订单状态,适合买家询问物流、发货、签收时间等情况。输入userId或orderId,返回订单状态和物流轨迹。注意:只能查询已入库订单,未支付的订单不返回数据。”大模型看到这段描述,匹配用户意图的准确率会显著提升。这个优化方法我们在多个模型上都验证过,工具调用命中率提升了至少15个百分点。
3. 核心机制实现:让工具调用稳、准、安全
3.1 统一调用协议与参数映射:给大模型一个“电子合同”
如果把大模型比作一个实习生,触达层就是那个负责帮他对接各部门的行政。实习生只需要写好申请单(这里指标准化的调用请求),剩下的事情由行政去跑。这里的“申请单”,我们定义了一套统一调用协议,每次请求都包含四个部分:工具名称、参数对象、调用方上下文、幂等键。前两个大家好理解,后两个是实战中必须加的。
调用方上下文记录的是这个调用来自哪个会话、哪个用户、哪个Agent实例。它的作用不仅是权限校验,还有多实例隔离。我们遇到过一种灵异现象:用户A在自己的页面上查订单,返回的却是用户B的数据。排查了一晚上,最后发现是不同会话复用了同一个数据库连接池,而且查询条件没有强制绑定会话用户信息。后来我们规定,任何工具的查询类操作,必须把当前用户标识作为隐式入参注入,不允许只靠大模型传参。这条规则救了我们很多次。
幂等键是我强烈建议每个人都要加的字段。举个例子,大模型调用“给客户发优惠券”这个工具,因为网络超时,模型重试了一次,结果同一个用户被发了两张券。原因就是第一次调用其实已经成功了,只是响应在网络传输中丢了,模型没收到结果,以为失败就重试了。解决思路很简单:每个调用生成一个唯一的幂等键,下游连接器在执行业务操作前先查一下幂等表,如果这个键已经执行过了,就直接返回上一次的执行结果,不再重复操作。这需要用MySQL或者Redis存一份幂等记录,成本很小,但在支付、发放、通知这类场景里价值巨大。
3.2 权限边界与操作白名单:给Agent的手上根“安全绳”
Agent的权限设计,和给一个实习生开权限是非常像的。你不会因为他是实习生就把公司的数据库root密码给他,但你也不会什么都不给他,让他什么都干不了。Agent也是这样,既要有能力干活,又必须被约束在边界内。
Agent-Reach的权限模型,我们用的是“角色+资源”的二维控制。角色定义“你是谁”,资源定义“你能操作什么”。比如一个“工单处理员”角色,能调用crm.ticket.query、crm.ticket.reply,但不能调用crm.ticket.delete;一个“数据分析师”角色,能调用data.report.generate,但不能调用finance.payment.create。这套RBAC(基于角色的访问控制)本身不新鲜,但和Agent场景结合后,有一个关键设计:权限校验发生在两个环节,一个是调用前、一个是调用后。
调用前校验很简单,看角色有没有权限,没权限直接拒绝,返回“ERR_PERMISSION_DENIED”,大模型收到这个错就知道要换个工具或者告诉用户“我没有这个操作权限”。调用后校验才是容易被忽略的:我们会在工具返回结果给大模型之前,做一次“出参脱敏”。什么叫脱敏?就是有些字段(比如用户手机号全号、银行卡号),调用方角色即使调用了查询工具,也不应该看到完整数据。我们加了字段级权限控制,比如能查到工单信息,但手机号显示为138****1234。这样即使大模型在推理过程中试图“套出”完整信息,也拿不到敏感字段。
关于“危险操作”的管控,我们用了一个很简单的分级策略:把工具分成“只读”“普通写”“敏感写”三个等级。只读工具(比如查询、导出)走正常流程;普通写工具(比如更新备注、发送普通消息)除了权限校验,要求弹一次人工二次确认;敏感写工具(比如删除数据、批量改状态、涉及资金的操作)直接禁止由Agent自主执行,必须转人工。这个策略的出发点不是对Agent不信任,而是对生产环境的敬畏——模型再强,也可能在极端情况下做出让人看不懂的决策,遇到大额资金、不可逆操作这种场景,把决策权握在自己手里永远没错。
3.3 状态上下文管理:让Agent记住自己干到哪了
很多人做Agent不成功,是因为把工具调用当成了一次性的“请求-响应”,忽略了“任务是有状态”的这个事实。Agent执行一个复杂的多步任务时,比如“帮我查几个平台的价格,然后对比一下,再把最低价的那个链接发给我”,它需要记住查了多少个平台、对比结果是什么、最后要发哪个链接。如果每一步都只把结果喂给大模型,而不做任何结构化保存,模型很快就会“失忆”。
Agent-Reach在这方面提供了一个轻量的状态上下文模块。每个任务会话会建立一个上下文流水线,每一步工具调用的输入输出快照都按序存储,并带有一个摘要。为什么要存摘要?因为完整的大模型上下文窗口有限,把每次调用的完整JSON都塞回去,可能几轮下来窗口就爆了。我们的做法是:维护一个“当前工作记忆”,里面只保留最近两轮的完整内容、历史步骤的摘要、以及一个目标清单。大模型每次进入新一步推理时,先用“目标清单+摘要”恢复全局视野,再基于最新的一两步做具体决策。这个机制大大减少了模型在长链路任务中的“迷路”概率。
我们还在状态模块里加了进度回滚的能力。如果某一步调用失败了,并且失败类型是“可恢复错误”(比如临时性网络错误),Agent会先尝试重试两次;如果还是不行,它会读取上一步的成功输出,尝试用备选方案绕过去。这里有个细节:重试不是简单地把同一个请求再发一遍,而是会给大模型一次“观察机会”。让它看看到底是参数不对、下游系统故障,还是这个工具根本不适合这个目标,然后重新规划下一步。这种“失败-反思-调整”的循环,是Agent和传统脚本自动化最大的区别。
4. 实操过程拆解:从零把一个工单系统接入Agent-Reach
4.1 真实案例背景与准备
理论讲了一堆,下面我把一个真实项目的接入过程完整写一遍,方便你照着做。案例背景:某电商公司的客服团队,日常要处理大量售后工单,他们有两个痛点,一是工单分类和优先级判断全靠人工,效率低;二是催单和通知动作滞后。我们的目标,是接一个Agent-Reach方案,让Agent能自己读取工单列表、判断优先级、给客服组长发送待办提醒。
准备工作分三块。第一块是API对接资料:工单系统是一个标准的Web系统,提供REST API,有查询列表、查询详情、修改状态、添加备注四个核心接口,都需要携带Token鉴权。第二块是消息通知:公司内部用的是飞书机器人,提供了一个Webhook地址,向这个地址POST一个JSON就能发送消息。第三块是数据权限:我们的Agent运行在一个服务账号上,它被授权可以读取所有未完成工单、但不能修改工单状态(修改权限保留给人工)。这三块准备好后,开始接入Reach层。
4.2 工具注册与权限配置实操
接入的第一步是配置工具注册表。就拿“读取未处理工单”和“发送飞书消息”这两个工具举例。
先说“读取未处理工单”这个工具,注册表里我们这样定义:
{ "name": "crm.ticket.list_pending", "description": "查询所有未处理完成的售后工单,按创建时间倒序返回,适合客服主管或AI助手查看待办列表的场景。只返回工单号、标题、优先级、创建时间、当前处理人五个字段。", "parameters": { "type": "object", "properties": { "limit": { "type": "integer", "description": "最多返回的工单数量", "default": 20 } }, "required": [] }, "required_roles": ["service_assistant"], "timeout_ms": 8000, "idempotent": true, "return_style": "json" }第二个“发送飞书消息”工具:
{ "name": "im.feishu.send_message", "description": "向指定飞书群发送文本消息,支持@指定用户。适合给客服组发送待办提醒、通知、催促消息等场景。", "parameters": { "type": "object", "properties": { "group_id": { "type": "string", "description": "群聊ID,必填" }, "text": { "type": "string", "description": "消息文本内容,不超过2000字" } }, "required": ["group_id", "text"] }, "required_roles": ["service_assistant"], "timeout_ms": 5000, "idempotent": false, "return_style": "json" }配置完注册表之后,重点来了:权限策略配置。我们给这个Agent分了两个权限组。一个叫“只读巡检员”,它只能调用查询类工具;另一个叫“运营助理”,它除了查询,还能调用消息发送、工单备注等普通写工具。实际运行中,Agent默认挂在“只读巡检员”角色下;只有用户明确说“帮我发消息”这类指令,并且经过人工确认后,才动态提升到“运营助理”角色。细心的读者会问,那Agent自己会不会偷偷“提升权限”?不会。角色提升在Reach层的调度器里是一个受保护动作,只能由外部用户操作或预设规则触发,大模型本身发起的任何“修改角色”请求都会被拒绝。这个边界我们在代码层面写得很死。
4.3 编排一个典型任务:从“看工单”到“发提醒”
配置好之后,我们来看一次典型的任务执行过程。用户(客服主管)对Agent说:“小李,帮我看一下现在有多少高优先级的未处理工单,顺便提醒一下王组长,A类工单超过24小时的该处理了。”
Agent接收到这条指令后,会经历这样几步。
第一步,规划。Agent判断这个任务需要两个能力:查询工单列表、发送消息提醒。于是它向触达层发出两次工具发现请求,模糊搜索工具。我们在这里做了一个“工具匹配增强”:大模型不一定能精确想起工具名字,所以允许它描述需求,比如“查未处理的工单列表”,Reach层用语义检索把crm.ticket.list_pending找出来。这个语义检索功能,是用工具名+描述拼成文本,然后用嵌入模型转成向量,在本地向量库中搜索。效果比纯关键词强很多,名字记不清也能找到。
第二步,执行查询。Agent确认使用crm.ticket.list_pending,参数limit设为50(因为它想一次性尽量多看一些)。触达层校验后,连接器调用工单API,拿到数据。我们发现克里工具返回的数据字段太多了,每个工单有二十多个字段,包括工单号、标题、状态、优先级、创建人、处理人、历史记录、备注等。如果把完整JSON一股脑丢给大模型,上下文很快被无意义字段占满。所以我们在连接器返回前做了一次字段裁剪,只保留注册表里承诺的那五个字段。返回给Agent的数据长这样:
[ {"ticket_id": "T20250001", "title": "商品破损申请补发", "priority": "high", "created_at": "2025-01-10 09:12", "assignee": "unassigned"}, {"ticket_id": "T20250003", "title": "少件申请退款", "priority": "high", "created_at": "2025-01-09 22:30", "assignee": "unassigned"} ]第三步,分析和汇总。Agent看到数据后,自动做了一个统计,“当前共有2个高优先级未处理工单,其中T20250003已经超过24小时”。这个分析是模型推理完成的,没有额外写代码。这一步的价值在于,它不是简单地罗列数据,而是完成了用户真正要的“看情况”这个意图。
第四步,发送提醒。Agent调用im.feishu.send_message,内容是:“王组长,当前有2个高优先级工单待处理,其中T20250003(少件申请退款)已超24小时,麻烦优先处理。”触达层在执行前发现这个工具属于“普通写”等级,于是按策略向用户弹了一次确认框。用户点击同意,消息发出。
整个流程走下来,我的体感是:Agent像是脚本、语义搜索、权限控制、人工审批四件事被有序地串起来了。程序的每一步都很稳定,最关键的是,无论哪一步出错,你都能在审计日志里看到清晰的记录:谁、在什么时间、调了什么工具、传了什么参数、返回了什么结果、被谁审批通过。这也是Reach层对企业客户最有说服力的部分——透明可审计。
4.4 联调与灰度上线经验
接入完成后,联调阶段有几句掏心窝子的建议。第一,一定要准备一套“哑数据”的测试环境,不要在联调时直接碰线上数据。我们就犯过一次错误,测试时Agent调的工单接口不小心连到生产库,直接给真实用户发了几条测试短信,客服主管差点报警。后来我们给测试环境单独配置了一套API域名和测试数据,彻底隔离。
第二,联调时多看看“失败场景”,别老盯着成功路径。比如飞书机器人Webhook地址如果配错了,会返回什么错?上游工单系统如果宕机,Agent的反应是什么?如果Agent抓取数据时字段类型不匹配,触达层能不能给出清晰报错?我们在测试时让Agent调一个故意不存在的工具名,最初它的反应是反复尝试四次同样的名字,浪费了很多时间;后来我们改进了工具匹配机制,找不到时就返回候选工具列表让大模型重新选择,效率提升明显。
第三,灰度上线建议先做“只读场景”和“人工审批场景”,不要一上来就开放全自动的写操作。我们第一批灰度只开放了“查询+汇总+发提醒”,而且是每个消息发出前都需要人工确认。等跑了两周确认稳定性不错,才逐步放开一些低风险写的自动化。这个节奏虽然慢,但客户信任度涨得快,后面反而省了更多说服成本。
5. 常见问题与排查经验实录
5.1 工具调用失败排查:先分清是“模型问题”还是“通道问题”
工具调用失败,是Reach层上线后最频繁的告警类型。但大多数情况下,问题根本不在Agent,而在连接器或下游系统。我总结了一个排查顺序,建议你照着走。
第一步,看触达层的调用日志,确认大模型到底发出了什么请求。这能帮你区分是模型压根没用对工具,还是用了正确工具但执行失败了。如果日志显示模型压根没找到工具,那是工具命名和描述的问题,优化描述、合并相似工具。如果模型调用了工具但返回错误,进第二步。
第二步,看错误码。我们统一设计了几个错误码:ERR_TIMEOUT(下游接口超时)、ERR_PERMISSION(权限不足)、ERR_VALIDATION(参数校验不通过)、ERR_BUSINESS(业务方返回的错误,比如查无此人)、ERR_CONNECTOR(连接器本身配置异常)。有了统一错误码之后,排查效率直线上升。我最怕的情况是连接器把下游原始错误直接透传,比如返回一段“ORA-00942: table or view does not exist”,大模型根本看不懂,也无法自我纠正。
第三步,对错误类型走不同策略。超时类错误做重试,带上幂等键;业务类错误不要盲目重试,而是把错误信息回传大模型,让它重新规划;权限类错误直接终止,不要试图绕过。这里强烈建议你不要在代码里写“遇到超时就一直重试”,一定要设置重试上限,我们用的是“指数退避+最多重试2次”,超过后转入人工处理。否则一个下游系统挂掉时,Agent会疯狂重试,把自己拖垮,还会连累下游系统被流量冲垮。
5.2 大模型“幻觉”导致的误调用:怎么拦截
大模型的幻觉不光体现在编造内容,还体现在编造工具参数上。我们遇到过几次比较典型的:模型在调用“发送通知”工具时,幻想出了一个并不存在的“收件人邮箱”字段,自己编了一个邮箱地址填进去;模型调用“查询订单”时,把订单号和手机号搞混,把手机号传给了订单号参数。这些错误如果没用参数校验,就会造成很大的混乱。
应对思路分两层。第一层是强校验:注册表里定义JSON Schema时,把类型、格式约束写严。比如订单号字段,我们规定必须以字母T开头后面跟8位数字,不满足直接校验不过。第二层是弱补全:大模型漏传了参数时,触达层会根据上下文自动补充。比如用户在这个会话里已经验证过了手机号,查询订单时就自动把手机号注入到入参里,不需要模型再传一遍。这两种手段结合,误调用的概率能压到极低。
还有一个值得讲的经验:对于“可选参数”,不建议在大模型可见的工具描述里写太多可选字段。因为模型倾向于把可选的也用上,一旦可用信息不充分,它就会“脑补”出一个值。我们做过一次统计,把所有可选参数都保留在Schema里时,参数编造率大约是12%;把非必要参数从Schema里移除、改由上下文自动注入后,编造率降到3%以下。所以我的建议是:给大模型的Schema,能少则少,只保留必要参数。
5.3 上下文长度与响应延迟的取舍:一个常见的性能误区
Agent接入工具之后,性能问题会变得很突出。最直观的就是延迟:大模型每做一次工具调用决策,就要多一次大模型的推理;推理的结果还要经过触达层解析、路由、调用外部系统、返回结果、再让大模型总结。整个过程下来,一个简单任务可能需要5到10秒。用户等不了那么久。
我们做过一轮性能优化,核心思路是“减少往返次数”。第一个手段是合并工具:把“查询工单详情”和“查询处理人信息”合并成一个“查询工单完整信息”工具,一次返回所有字段,避免模型为了拼一个答案来回复跑两次接口。第二个手段是并行调用:当任务里有两个互不依赖的工具调用时,我们允许大模型在一次推理中同时发出多个工具请求,Reach层并发执行,而不是串行排队。这个优化在“要查多个平台的订单价格”这种场景效果特别明显。第三个手段是让大模型做“轻量决策”:对于确定性的、不需要推理的分支,我们不经过大模型,直接用规则处理。比如收到的工具返回是空列表,就直接告诉用户“暂无待处理工单”,不需要让大模型再来一轮“思考”。
上下文长度的取舍也要小心。我们最初为了让模型有全局视野,把每一步工具调用的完整输入输出都保存并回传。结果任务是执行成功了,但模型越到后面越“迟钝”,响应越来越慢,而且经常把前面步骤的细节当成当前要处理的问题。后来改成“摘要+最近两步”策略,任务成功率反而上升了。我的建议是:不要贪多。上下文是为决策服务的,不是为存档服务的。你要让大模型看到的是“当前的最优决策信息”,而不是“所有历史数据”。
5.4 三个容易被忽视的坑
第一个坑:工具返回里的自由文本不要直接透传。比如工单系统有一个“处理记录”字段,里面是客服手动写的长篇大论甚至带表情符号。直接把这个丢给大模型,一方面浪费token,另一方面模型容易被一些奇怪格式干扰。我们的做法是,限制返回文本长度,超过N字的字段自动截断并加“(已截断)”标记;富文本统一转纯文本。
第二个坑:多个工具之间的“副作用”关联。比如Agent先调用了“修改工单状态”,接着又调用“发送状态变更通知”。如果第一调用失败,第二调用成功,用户会收到一个错误的“已更新”通知。我们上线了这个场景后,学到一个新原则:有依赖关系的操作,要合并成一个原子性的工具,尽量让下游系统在一个事务里完成状态变更和通知发送;如果做不到,就在前面加人工确认,减少连锁反应。
第三个坑:Agent的“过度承诺”。因为触达层能力太强,大模型会倾向于“什么都答应”——用户问“能帮我删掉这张订单记录吗”,Agent可能会说“好的,正在处理”。但删除操作在权限策略里是禁止由Agent自主执行的。我们后来在系统提示词里加了一段约束:“如果用户请求的操作属于敏感操作(删除、批量修改、涉及资金),你只能说明你可以支持,然后引导用户联系人工处理,不能直接调用或声称已执行。”这个软约束虽然不如硬权限可靠,但确实能减少用户认知层面的误会,不至于Agent说有权限结果没权限,用户体验太割裂。
6. 落地之后的一点心得体会:Reach层值得早做
做Agent-Reach这个项目的过程中,我越来越确定一件事:智能体应用能不能跑进生产,赢在触达层。大模型的能力会越来越强,各家之间的差距会越来越小,真正拉开体验差距的,恰恰是它对业务系统触达得有多稳、多快、多安全。
如果只让我留一句话给正在做Agent项目的读者,我会说:不要等到工具多到失控才开始做触达层,从一开始就把工具注册表、权限边界、调用协议、审计日志当核心基础来建设。这些资产会随着Agent接入的系统数量不断增加而复利式地发挥价值。当初我们三个人的小团队,愣是用这套机制管理了上百个工具,没有专职运维也能保证线上稳定,靠的正是前期把规范打扎实,后面都是在“填表”而不是“造轮子”。
这个方向后续还有很大的扩展空间,比如多Agent之间的协作触达、更细粒度的实时风险阻断、基于执行历史自动优化工具描述提示等,都是我们下一步想探索的。不过那些是后话,先把当前这版跑稳,比追新概念重要得多。