ARTICLE DETAIL

资讯详情

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

Agent最后一公里:自建触达层让大模型真正连接业务系统

Agent最后一公里:自建触达层让大模型真正连接业务系统 去年下半年我一直在折腾一件事把那些能聊天、能推理、能写代码的大模型真正变成能替我干活的员工。模型本身没让我头疼真正让我头疼的是它们“够不着”东西——查不了订单、改不了工单、读不了库存、发不了消息。你给它再多工具描述它也只能在一堆 API 文档里打转落不到真实系统上。很多团队的 Agent 项目最后就死在这一公里不是模型不够聪明而是触达层太脆。后来我把这套触达层单独抽出来做内部给它起了个名字叫 Agent-Reach。Reach 这个词很直白就是“够得着”——让 Agent 的动作可以被表达、被路由、被执行、被追踪而不是让每个业务系统裸露一堆接口给模型乱调。这篇文章不打算讲某个神秘开源框架而是把我在自建 Agent-Reach 过程中的分层设计、连接器适配、可靠性治理和实际踩坑完整拆一遍。无论你是正在做智能客服、自动化助手还是想把 Agent 接进公司内部系统这套思路都能直接拿来对照参考。1. 为什么模型再强也“够不着”业务系统Agent 的最后一公里问题1.1 能力很强但落不了地典型的 Agent 接入困境先看一个特别常见的场景。你做了一个 AI 客服助理它已经能流畅理解用户“帮我查一下上个礼拜那单物流到哪了”这种模糊表达也能正确识别出意图是查物流、参数是订单号。然后呢你让它调用订单接口发现要处理的问题一个接一个订单系统走的是内部 RPC 协议物流系统暴露的是老式 XML 接口用户权限在 CRM 里另有一套体系还有一堆接口需要先申请令牌再走审批流。于是最常见的做法是给 Agent 的 prompt 里塞了一堆接口文档写一堆 Python 函数直接调 SDK再让模型用 function calling 去选。刚跑通两三个接口时确实很爽但等到接入第十个系统问题就失控了——每个系统有自己的鉴权方式、超时配置、错误码语义Agent 的上下文里塞满了碎片信息模型开始混淆参数格式、选错接口、把生产环境的写操作按测试环境的方式处理。这时候你想审计一下“这个 Agent 到底对订单系统做了什么”发现日志里全是散落的 print根本串不起来一条完整链路。我管这个问题叫 Agent 的最后一公里问题模型负责认知但系统交互的工程复杂度全堆在了接入层而大多数团队没有把这一层当工程认真做。1.2 为什么不能把工具直接暴露给模型三个被忽视的代价很多人第一反应是直接给模型一堆函数不就行了OpenAI 的 function calling、各家框架的 tool use不都在做这件事吗但把工具设计成“模型随便选、代码随便调”实际上埋了三个坑。第一个坑是上下文污染。每个接口的完整 schema 动辄几百行十个系统就是几千行。模型的能力再强注意力也是有限的塞太多无关字段它就开始把 A 接口的参数往 B 接口里套。实测下来schema 越大幻觉率越高这不是玄学而是上下文窗口里有效信息密度下降的直接结果。第二个坑是权限失控。业务系统的接口通常按“人”的身份设计权限但 Agent 不是人它是一段可以并行、可以重试、可以被 prompt 注入影响的代码。如果你直接拿人的 token 去调系统一旦 prompt 注入把“帮我查一下张三的工资”这种指令混进来接口的权限校验根本反应不过来。Agent 的身份必须独立设计权限边界必须比人更窄。第三个坑是治理失效。工具散落在各个业务代码里你没法统一管控哪些 Agent 能调哪些接口也没法对关键动作做审计、限流和熔断。出了事故只能靠翻日志找线索响应速度永远慢半拍。所以 Agent-Reach 的核心思路很简单把“Agent 的动作”抽象成一种标准化的消息经过一个独立的触达层去做路由、鉴权、连接器适配、可靠性保障而不是让 Agent 直接跟每个系统谈恋爱。2. 把触达做成独立层Agent-Reach 的分层设计与动作链路2.1 控制面与执行面分离Agent-Reach 的整体架构先说一下我最终沉淀下来的分层结构总共四层每层只做一件事。接入面Agent 侧接收来自各种 Agent 框架的动作请求统一收口。不管是 LangChain、自研框架还是最简单的 HTTP 调用都走同一套动作协议。控制面策略层负责动作鉴权、参数校验、路由决策、限流熔断。这一层不碰具体业务只回答“这个动作能不能做、该由谁执行”。执行面连接器层真正连接外部系统的部分。每个连接器只负责“把一个标准动作翻译成目标系统能理解的调用”翻译过程中的协议差异、字段映射、错误转换都在这一层消化。观测面追踪层记录每一个动作从进入触达层到最终结果返回的完整链路包括耗时、参数、结果、错误、审批状态形成动作血缘和审计日志。把控制面和执行面分开是我做过一轮重构之后才想明白的事。一开始我把路由逻辑直接写在连接器里每个连接器都要自己判断“我能不能处理这个动作”结果新加一个连接器就要翻一遍之前的代码权限规则也散得到处都是。后来把策略全部上移到控制面连接器退化成一个纯粹的执行单元加新系统就只是“写一个新连接器 注册一组路由规则”清爽很多。2.2 一次动作的完整旅程从意图到系统响应以我之前做的“客户助理”为例它接了一个动作叫“查询订单物流”。用户在对话框里说“我的包裹到哪了”模型推理出需要调用order.query于是构造了一条标准动作消息发给 Agent-Reach。这条消息长这样{ action_id: act_8f3a2c9d, type: order.query, payload: { order_id: SO-2025-0412-001 }, idempotency_key: agent-ca-v1|order.query|SO-2025-0412-001|2025-04-12, actor: { type: agent, id: customer-assistant-v1, delegated_by: user_1024 }, timeout_ms: 5000, trace_id: tr_9f81c2e4 }这条消息进入控制面后先做几步很关键的处理。第一步是鉴权查规则表确认customer-assistant-v1这个 Agent 身份是否有order.query的权限以及它被允许访问的数据范围比如只能查delegated_by对应用户自己的订单不能查别人的。第二步是校验参数order_id是否符合订单号格式必填字段是否齐全防止模型传了个空值过来。第三步是路由根据type字段匹配到对应的“订单连接器”。连接器拿到这条标准消息后把它翻译成订单系统能识别的调用格式——可能是 HTTP 请求、也许是查询一个只读视图再拿到结果后做一个反向翻译转成标准动作结果返回给控制面。最终控制面把结果回传给 AgentAgent 再组织语言回复用户“包裹已到本市分拣中心预计明天下午派送。”整个过程拆开来看没有多高深的技术但每一步都让系统变得可以治理动作契约统一了权限有地方管了链路上每一个环节都能打日志了。这就是 Agent-Reach 存在的全部理由。2.3 注册表驱动所有动作和连接器一目了然触达层正常运行后最需要防的一件事是“失控”——不知道系统里到底有哪些动作、哪些 Agent 能调、哪些连接器连着哪些系统。我的做法是弄一个中心化的注册表把动作、连接器、授权策略全量登记进去用 YAML 维护进 Git走评审。actions: - type: order.query connector: order-service auth: agent-scoped timeout_ms: 3000 retry: true retry_policy: max_attempts: 2 backoff_ms: 500 schema_ref: schemas/order_query.json - type: order.cancel connector: order-service auth: human-approval timeout_ms: 10000 retry: false schema_ref: schemas/order_cancel.json connectors: - name: order-service transport: http endpoint: http://order-svc.internal:8080/api auth_mode: delegated-token idempotent: true这里有一个反直觉但很重要的细节读操作的超时设得短3 秒写操作反而设得更长10 秒。因为读操作追求的是快速失败、换一种方式重试而写操作一旦发出去了系统可能要处理后续的级联流程催得太紧容易误判为失败导致重复提交。这些参数后来都被证明是对的方向后面在可靠性那章我会细讲为什么。3. 统一触达协议与三类连接器REST、消息、人工审批怎么各归其位3.1 统一协议是“翻译器”的前提先定义规范再写代码连接器要能即插即用靠的是一套统一触达协议——进入触达层的动作消息长一个样出来的结果消息也长一个样差异全部封装在连接器内部。这套协议本质上就是前面那段 JSON 的结构化定义包括动作元信息、调用方身份、幂等键、超时、链路 ID以及标准的结果结构。标准结果结构也很重要。我的做法是连接器只返回三种东西——success包含业务数据、error包含错误码和可解释的错误信息、pending表示动作已受理但还在异步处理中。为什么要有pending状态因为不是所有系统都能在几秒内给出确定的答案。比如订单系统提交取消请求后可能要走审核队列三五分钟后才落定。如果触达层坚持同步等待Agent 就得在超时边缘反复试探。有了pendingAgent 可以先向用户回复“申请已提交”等系统通过回调或轮询把最终状态推回来体验会好很多。3.2 REST 连接器最顺手也最容易失控的一类REST 连接器是我最先实现的也是接入系统数量最多的类型。顺着 HTTP 加 JSON几乎没有接不通的系统。但恰恰是它“太顺手”反而容易出事。第一个问题是参数映射。模型的推理结果是结构化的但每个系统的接口字段风格千差万别。有的用order_id下划线有的用orderId驼峰有的调getOrder有的调queryOrderDetail。连接器必须在这里做一层明确的字段映射绝不能把映射规则留给模型去猜。我的做法是给每个动作绑定一个 JSON Schema连接器按照 schema 做输入校验和字段转换模型只需要输出语义正确的内容不需要知道目标系统的命名习惯。第二个问题是 OpenAPI 导入的诱惑。很多 REST 连接器都支持直接导入 OpenAPI 文档自动生成工具描述这看起来很省事但实际用下来我不建议对 Agent 暴露大而全的 schema。一个订单服务可能有三十个接口、上百个字段Agent 根本不需要全部了解。只把实际会用到的两三个动作、十几个字段暴露出来反而准确率更高、幻觉更少。这个“schema 越小越稳”的经验是我被幻觉折腾了很长时间后总结出来的。第三个问题是网关超时。很多内部系统的网关默认上游超时是 60 秒但 Agent 调用场景里等待 60 秒是不可接受的。连接器必须自带超时控制并且遵循“调用前设超时、调用后及时取消”的准则避免线程池被慢接口拖垮。3.3 消息连接器接异步系统的正确姿势除了同步 HTTP还有很多业务系统的核心交互是异步的发消息通知、提交数据到大数据平台、触发定时任务、写入消息队列。对于这些系统建一套“消息连接器”会让整个触达层轻非常多。消息连接器的思路是连接器把标准动作转成一条消息推到 MQ立刻返回pending状态并注册回调地址或轮询任务等下游系统处理完再回传结果。这里有两个容易踩的坑。一个是“你以为异步就是快”——恰恰相反异步连接器的端到端耗时通常比同步更长因为它把控制权交给了下游队列排队、消费、回调任一步都有可能推迟。所以对 Agent 来说异步动作要尽量配合“进度查询”类动作一起暴露让用户体验是逐步推进的而不是干等一个永不到来的结果。另一个坑是回调地址。生产环境里回调地址写错是最低级的错误但出现频率意外地高。我的做法是把回调地址写进注册表配置而不是让连接器初始化时从环境变量里随便猜同时回调查验必须校验trace_id防止外部伪造回调消息污染链路数据。3.4 人工审批连接器最容易被忽略但价值最高的类型聊完两类技术连接器我想重点说一个很多人一开始根本不会想到的类型人工审批连接器。Agent 的能力越强意味着它可能执行的写操作越危险取消订单、改价、删数据、转账。如果这些动作也只走接口权限校验出事的概率只是被降低了并没有消失。我在 Agent-Reach 里把“需要人来确认”建模成一种连接器——它不是连某个系统而是连接“人这个决策节点”。动作进来后控制面判断需要人工审批就把审批请求推给指定的审批人可能通过 IM 机器人卡片、邮件或内部审批系统审批通过后连接器才真正调用目标系统执行。审批人不在线没关系动作带着pending状态等待就好Agent 在超时范围内可以如实告诉用户“修改申请已提交等待主管确认”。这套机制的价值在后期体现得特别明显。有一次 Agent 在测试环境里错误地触发了生产环境的批量改价动作如果没有人工审批节点卡住几秒钟就能把几百个 SKU 的价格改错。后来我把所有“金额变更”“批量删除”“权限修改”类动作全部强制挂上人工审批这一条规则基本杜绝了 Agent 误操作导致的高危事故。别嫌它打断流程Agent 的可靠性先于效率。4. 可靠性不是事后补的权限、幂等、重试与可观测的一整套设计4.1 Agent 身份独立与最小权限从机制上防住越权前面提到过Agent 不能用人的身份直接调系统。我在 Agent-Reach 里给每个 Agent 单独建一个委托人身份通过 OAuth 2.0 的委托流程获取受限令牌并且令牌的权限范围在执行前会被控制面再校验一次做到“人给 Agent 授权 → Agent 用自己身份调系统 → 系统侧再校验权限范围”的三层防线。这里有一个具体经验注册表里配置权限时尽量用“动作级别”而不是“接口级别”来授权。比如“允许customer-assistant-v1调用order.query”是动作级别授权而“允许访问/api/orders/*”是接口级别授权。动作级别的好处是控制面可以在动作执行前做更精细的判定比如“是否允许查未支付订单”“是否允许查其他代理人的订单数据”这些规则用接口粒度很难表达。最常被忽略的是数据行级权限。接口权限管得住“能不能调这个接口”但管不住“能看哪条数据”。我给连接器加了一个scope参数控制面会把delegated_by用户 ID 注入到连接器的查询条件里确保 Agent 只能操作与之绑定用户的数据。这不是可选项而是安全底线。4.2 幂等、超时与重试写操作防止重复读操作快速失败Agent 和用户不一样它在调用失败后很容易因为重试逻辑被反复执行。如果重试的恰好是一笔转账或一次扣款结果就是灾难。所以我把幂等作为硬性要求所有写操作必须携带idempotency_key控制面用这个键做去重重复的请求直接返回上一次的执行结果而不是再次执行。重试策略也有讲究。我维护了一张表按动作类型区分对待动作类型是否重试策略理由读操作查询、列表可重试最多 2 次指数退避 300ms 起快速失败换一种方式再试写操作创建、更新需配合幂等键最多 1 次且必须幂等防止重复提交危险操作取消、删除不自动重试直接失败转人工避免错误操作被放大异步提交只重试提交动作提交成功后等待回调下游执行状态由回调决定不任务务端重复发超时的设置同样要分级。我的建议是同步 HTTP 动作默认 35 秒写操作且带幂等键的可放宽到 10 秒左右异步动作则直接给 60 秒以上的“受理确认”窗口。注意超时不只是连接器的参数它还会影响 Agent 后续的行为——如果超时上限超过 Agent 的等待耐心模型可能误判为失败并尝试换一种方式反而跳出你预期的流程。所以触达层在返回超时错误时附带上“不建议重试”的提示字段模型可以参考这个提示决定下一步动作。4.3 可观测与审计没有链路追踪触达层就是黑盒等接入的系统越来越多你会发现自己最需要的不是新功能而是一个能回答“这个动作是谁触发的、经过了哪些环节、为什么失败了”的系统。Agent-Reach 在观测面上做了两件事我都觉得是必须的。第一件事是全链路追踪。每个动作从进入触达层起就分配一个trace_id后续无论走到控制面、连接器还是回调都携带这个 ID。日志系统按trace_id聚合就能还原一次动作的完整生命周期。排查问题的时候不需要再靠猜测和搜索关键词。追踪日志里至少要记录动作类型、调用方身份、幂等键、路由到的连接器、耗时、返回状态、错误信息。第二件事是动作审计日志。与性能追踪不同审计日志侧重“记录事实”谁在什么时间对什么数据做了什么操作、操作结果是什么。审计日志要独立存储不依赖普通业务日志且不允许普通开发者直接修改。做合规或者排查纠纷时这份日志是不可抵赖的证据。审计日志里有一个字段特别值得保留模型的原始输出。这样你能看到 Agent 为什么要发起这个动作——即使模型的理解是错的你也能看清楚错误的源头出在哪个环节。在这里我还想多提一句限流熔断也要纳入可靠性体系而不是一维的防刷配置。Agent 的行为模式和人类用户不同它可能因为一个 bug 出现请求风暴瞬间把一个下游服务打挂。所以我设置了全局令牌桶限流同时按动作类型设置独立配额。一旦连续失败率超过阈值熔断器直接切断对应连接器优先保护下游系统而不是让 Agent 的循环调用反复冲击快要挂掉的服务。5. 从最小闭环到全量接入落地节奏与踩过的坑5.1 最小闭环先跑通一台服务、三个连接器、手动触发如果你准备在团队里上 Agent-Reach 这套思路我强烈建议不要一上来就追求大而全。我当时是从一个“最小闭环”开始的一台部署服务、三个连接器REST 查单、REST 改单、人工审批、一个手动触发的测试入口。当时甚至没有接 LLM而是写了一个简单的脚本模拟 Agent 发动作消息。为什么先不接大模型因为我要先验证触达层的链路是通的、权限是准的、审计是齐的。用脚本模拟的好处是请求稳定可控排查问题不需要跟模型的随机性纠缠。等三个动作全跑通日志链路全能看到再接入真实的 Agent 框架最后才放开给业务方试用。这个节奏让我后来少走了很多弯路——底层不稳定的时候把模型接进来你根本分不清是模型理解错了还是触达层处理错了。最小闭环阶段的另一个产出是明确动作清单。把所有 Agent 可能执行的操作全部列一遍标出类型读/写/危险写、所属系统、是否异步、是否需要人工审批。这份清单就是注册表的雏形也是后续权限配置和连接器开发的依据。5.2 扩展接入时的顺序先读后写、先低频后高频接入实际业务系统时我遵循一个原则先接读操作再接写操作先接低频动作再接高频动作先接可回滚流再接不可回滚流。读操作风险低、链路短适合用来磨合协议和数据映射写操作要等幂等和审批机制都验证过再接入。高频动作的重要性在于它能快速暴露性能问题。有一个动作是查询订单状态上线后日调用量迅速过万很快就发现连接器的线程池太小下游系统高峰期响应变慢触达层的等待线程堆积严重连带其他动作也变慢了。这个坑如果不经历一次真实流量光靠预估很难发现。高峰扛住了再去接那些一周只调用几次的冷门动作就很从容。5.3 四个让我记忆深刻的坑参数名、回调地址、注册表膨胀与模型预热最后分享几个具体到可以当检查清单的坑都是我被反复折磨出来的。第一个坑是参数名幻觉。模型在生成动作参数时常常不按 schema 定义来而是更倾向于“自己看着顺眼”的命名。比如 schema 里定义的是order_id模型可能输出orderId或OrderID。连接器严格校验时就报错不严格校验时就可能带错参数进系统。解决方式是在 schema 校验后加一层“相近字段自动归一化”的小工具把常见的驼峰、下划线、首字母大小写变体统一映射到标准字段同时记录告警日志倒逼模型侧优化工具描述。第二个坑是回调地址配置。前面提过一次但我还想强调另一面回调接口本身的稳定性。回调地址对应的 API 如果挂了下游系统执行完结果回不来动作就卡在pending状态永远不结束。我的补救方案是给异步动作加“超时未回调自动告警”定时任务超过设定时间就查一遍连接器状态人工介入补触发回调。生产环境里真的靠这个机制捞回来过好几个卡死动作。第三个坑是注册表膨胀。动作越来越多之后权限配置开始出现“顺手放宽”的倾向——为了图省事有些人会直接把某类动作的全部接口权限授给某个 Agent。这非常危险。我的做法是定期审计授权列表重点关注写操作和危险操作要求每一条授权都必须有关联的业务场景审批记录。没有记录的授权一律清除。第四个坑是模型预热。听起来像运营的事但技术上也躲不开。刚接入一个新 Agent 时模型对注册表里的动作理解还很生疏容易在第一步就选错动作。解决办法是上线前先用一批典型用户问题做回放测试把模型输出与预期动作核对一遍发现偏差就调整工具描述或补充少样本示例。这个环节能过滤掉大量线上才会暴露的调用错误。我在实际使用中还有一个体会越来越深Agent-Reach 这类触达层本质上不是在给模型造工具而是在给整个系统画边界。它让模型的自由度被约束在可控范围内——知道什么能做、什么必须审批、什么失败后不能盲目重试。把这条边界守好Agent 才会从“聪明的演示品”变成“可靠的工作伙伴”。如果你也在做 Agent 应用我建议先别急着让模型学会所有接口先花几天时间把触达层这扇门立起来。后续再扩展新动作、新连接器你会回来感谢当时那个自己做对了决定的晚上。
返回列表