☰
AI Agent落地卡在系统触达?Agent-Reach连接层架构实践复盘
2026/10/28 19:18:03 网站建设 项目流程

最近大半年,我几乎所有业余时间都耗在了 AI Agent 相关的事情上。聊到 Agent 落地,圈子里有个很普遍的现象:Demo 都跑得飞起,一到接真实业务系统就集体卡壳——不是模型不够聪明,而是 Agent 根本“触达”不了内部那堆基于各种年代技术栈构建的服务和数据库。我自己做语言大模型应用越深入就越确认这点:大模型像一颗聪明的大脑,可大脑再强,没有一套可靠的神经系统把手脚和器官连接起来,它也只能干瞪眼。这套神经系统,就是我一直想解决的核心问题。断断续续折腾了几个月,我从零搭起了一套叫 Agent-Reach 的连接与编排基础设施。它不是一个新框架,不负责“怎么让 Agent 思考”,它只负责一件事:让 Agent 能够安全、可控、可观测地触达任何外部系统。这篇就当是我的工程复盘,把架构决策、真实接入链路、我在生产环境踩过的几个大坑,以及多 Agent 协作时的编排思路全部摊开来讲,希望能给同样卡在“Agent 接不上业务”这一步的朋友一些踩得实的参考。

1. Agent 项目普遍卡在“触达”这一步:问题剖析与我的切入点

1.1 一堆框架用下来,真正缺的不是模型能力而是系统接入

最初我做 Agent 的方式和大家差不多:LangChain 串一串,工具函数写一堆,ReAct 循环一跑,看着模型自己决定调用哪个工具,确实挺兴奋。但等兴奋劲过了,把这套东西从示例代码迁移到真实环境,麻烦立刻就出来了。真实系统的认证方式五花八门,有走 OAuth2 的,有走内部 JWT 的,还有不少老系统直接要求 IP 白名单加专用 Header;接口返回格式更是没法看,有的包装在{code, data, message}里,有的直接就是一份巨大的 XML;再加上字段命名混乱、超时设置随意、错误详情全靠肉眼观察,Agent 只要多调几个接口,上下文里就会塞满“看似成功但实际失败”的脏数据。

那时的我并没有去质疑大模型的能力,而是发现一个残酷的事实:几乎所有 Agent 框架都默认“tools 已可用”,然后就跳到“模型如何选择工具”这个环节。但现实里真正的成本恰恰被前置了——在让模型做选择之前,你得先让几十上百个工具接口能被稳定、可信地调用。没有这层打通,CoT 写得再细、Prompt 优化得再好也没用。这个问题属于典型的“集成工程”,我决定自己做一层专门解决这个问题的东西。

1.2 触达的三个层次:数据、工具、状态

把“触达”这个词拆开看,它其实包含三个层次,每一层都会让 Agent 从“能用”变成“难用”。

数据触达解决的是“模型能不能拿到需要的原始信息”。数据库在哪儿、表结构是什么、有没有数仓入口、文件系统里的资料怎么索引。这一层最容易被低估,尤其当你需要连的是一堆日志文件或者第三方 SaaS 的历史数据时,“取数”本身就成了瓶颈。

工具触达解决的是“模型能不能安全地产生副作用”。查订单是读操作,改库存、发邮件、创建工单是写操作,一个成熟的系统不可能把所有函数直接暴露给模型。就像你不可能把一个没上保险丝的电钻直接递给用户,触达层必须在“开放能力”和“真实副作用”之间做控制。

状态触达解决的是“多轮交互里状态怎么延续”。一次自然语言请求拆成多个工具调用,中间失败了怎么恢复?用户中途改了条件怎么重新路由?对话状态的保存、分支、回滚,这些如果都交给模型自己管理,逻辑很快就失控了。

想清楚这三点之后,我的目标就明确了:做一套基础设施,把这三个层面的触达通通标准化,让上面的应用层不用再操心连接和管控,让下面的实际系统不用感知 Agent 的存在。

1.3 Agent-Reach 的定位:连接层,不做新轮子

定位这件事,最开始我有点贪心,想顺手把 Agent 的规划(Planning)也一起做了。做得越多越觉得不对:规划是模型和 Prompt 的强项,轮不到中间件插手。Agent-Reach 应该只做连接层,把自己限定在两个问题里:第一,如何屏蔽外部系统的异构性;第二,如何保证每一次触达都是安全、可观测、可审计的。一旦想清楚这个边界,后面所有功能选型都有了依据。

我给它画了一层非常直观的定位图:最上面是各种 Agent 应用,最下面是各种数据库、HTTP 服务、消息队列和文件系统。Agent-Reach 横在中间,左边是北向 API,把标准化的触达能力暴露给 Agent;右边是南向连接器,把异构协议翻译成统一 Schema。中间那层是策略引擎和链路追踪。这个结构决定了后续的所有演进,也让我在和朋友讨论时能一句话讲明白:Agent-Reach 就是给 Agent 装上一套标准化的“手和脚”。

2. Agent-Reach 架构拆解:连接器、触达协议与策略层

2.1 连接器层:把异构系统规范成同一种 Schema

既然核心是屏蔽异构,连接器就得设计得像充电器一样,不同设备的充电协议不同,但只要都插到同一个 USB 口上,就能统一供电。Agent-Reach 里的连接器就是那个 USB 口。每一个连接器对外暴露的是一组标准化的动作声明(Action Schema),动作的输入输出都遵循同一套字段约定,后端是 MySQL 也好、Salesforce 也好、一个只有远古 SOAP 接口的财务系统也好,对外呈现都是清一色的 JSON 结构。

我第一次写的连接器是一个只读 MySQL 数据源,配置大概是这样的:

name: order_db type: mysql version: 1.0 actions: - action: query_orders description: 按时间范围和订单状态查询订单列表 input: - name: start_time type: string required: true description: 起始时间,格式 yyyy-MM-dd HH:mm:ss - name: end_time type: string required: true - name: status type: string enum: ["pending", "paid", "shipped", "cancelled"] optional: true output: type: object fields: order_id: string customer_id: string status: string amount: number

关键在于“Action Schema”这套东西:描述用的是纯 JSON Schema,模型可以直接读;“query_orders”这个名字本身语义清晰,模型不需要额外理解 SQL 结构就知道该调什么。真正在连接器底层我封了一层查询构造逻辑,把 start_time 这类参数映射到 SQL 条件上。字段说明写得越细,模型选错参数的概率就越低。

HTTP 类连接器我一般直接用 OpenAPI 导入生成。把现有系统的 API 文档喂给 Agent-Reach,它会自动把每个 endpoint 转成一个 action,连参数枚举都由拉取验证。这一步省了我大量人工定义的时间,也降低了维护负担。

表格是很适合呈现多样性的场景:我用下面的对照表来说明三类连接器面对的核心挑战:

连接器类型典型系统难点解决方式
数据库类MySQL / PostgreSQL / ClickHouse表结构多、SQL 注入风险白名单 SQL 模板 + 参数化绑定
HTTP 类内部微服务、SaaS API鉴权方式杂、限流策略不同OAuth2/JWT/签名统一注入
文件类对象存储、日志目录文件遍历慢、格式不可控索引预扫描 + 按会话导出

2.2 触达协议:给 Agent 和工具之间定一套“通用语言”

连接器解决了“系统之间怎么对接”,接下来要解决的问题是“Agent 和连接器之间怎么对接”。HTTP 之所以成功,是因为它有统一的方法、状态码和头部约定,而 Agent 生态里最缺的正是这种约定。我不想每个团队都自己发明一套“用自然语言描述工具调用需求”的格式,所以在 Agent-Reach 里做了一层轻量的触达协议,我内部管它叫 RAP(Reach Action Protocol),处理的是“一次触达动作”的统一请求与响应包装。

一次请求的核心字段大概是这样的:

{ "reach_version": "1.0", "action": "order_db.query_orders", "action_id": "a3f1c2b1-9d4e-4a8f-b7c0-2e5a1d0f3c9b", "args": { "start_time": "2025-04-01 00:00:00", "end_time": "2025-04-30 23:59:59", "status": "paid" }, "context": { "user_id": "u_1024", "session_id": "s_77881" }, "timeout_ms": 3000 }

action_id是全链路唯一的,我用它来做超时追踪和幂等;context会贯穿到审计日志里,方便回答“谁在什么会话里让 Agent 做了这件事”。响应侧也做了标准化,统一分为success、user_error(参数错误,模型可自行修正后重试)、system_error(系统故障,应该直接放弃或升级)和timeout(响应超时,需要触发幂等逻辑)。这四类区分很有必要,否则模型收到一段报错不知道是自己传参传错了还是对方的服务崩了,就会无限重试。

RAP 的价值不在于协议本身多复杂,而在于给了模型一个极低成本的解析模型:字段名固定、错误分类固定、状态字段永远是第一优先级。我实测下来,模型理解 RAP 响应格式的准确率,远高于解析“每个接口自己定义的错误体”。

2.3 策略层:权限、限流、审计一个都不能少

连接器让触达“可能”,策略层让触达“被允许”。这是我认为 Agent-Reach 和普通工具调用 SDK 之间最大的区别。接口一旦变成 Agent 可以调用的动作,就不再有“前端按钮”这种人为防线,策略必须前置到框架层。

权限模型上我做了最小粒度的控制:每一个 action 都可以绑定角色和白名单。比如order_db.query_orders只允许被销售场景的 Agent 调用,crm.create_ticket则必须要求请求上下文里的 user_id 具备对应权限,否则直接拒绝。策略决策发生在每次触达之前,有一票否决权,模型就算规划得再离谱也绕不过去。

限流和熔断也放在这层。单个 Agent 的 QPS 上限、单连接器的并发数、连续失败后的熔断开关,这些都能通过一段简单的配置表达:

policies: - action: order_db.query_orders limit: qps: 5 burst: 10 circuit_breaker: fail_threshold: 20 reset_after_sec: 60 audit: true

每个动作默认开启审计,记录动作名、参数、调用者、会话、耗时和结果。这套审计日志后来救了我很多次——有用户质疑“Agent 是不是擅自改了数据”,一查链路,发现所有写操作都被策略层拦截了,日志里清清楚楚。

3. 一项真实接入实验:让 Agent 查询 MySQL 并调用订单 API

3.1 环境准备与注册数据源

说了这么多架构,来点实操。这一节我演示一个最典型的场景:搭一个本地 Agent-Reach 服务,注册一个 MySQL 订单库和一个订单查询 API,然后用自然语言让 Agent 完成“查一下这个月付费订单超过 500 元的客户有哪些”。

部署 Agent-Reach 本身很简单,它是个无状态的中间层,可以单独跑一个进程,也可以集群部署。我本地用 Docker 起了一个 reachd 服务,配置里指定了监听端口和后端存储(用来存策略配置和审计日志,开发环境用 SQLite 就够了,生产环境切 PostgreSQL)。

启动之后就是注册数据源。我用 Agent-Reach 自带的命令行工具把刚才那份order_db连接器配置提交上去,再执行一条命令导入订单 API 的 OpenAPI 定义。整个注册过程会立刻做一次连通性校验,连接器会真的去试连一次数据库,失败会反馈具体的网络或认证错误,不会把坏配置假装注册成功。

3.2 Agent 自然语言请求的触达链路全流程

环境就绪后,我通过 Agent-Reach 的北向 API 发了一条自然语言请求:“帮我统计一下 4 月份已付款订单总共多少钱,顺便告诉我是哪些客户贡献的。”

这条请求的触达过程可以被拆成五步。第一步,Agent 先把自然语言映射成动作意图,它选择了order_db.query_orders;第二步,Agent-Reach 的策略引擎检查权限、限流和审计规则,通过;第三步,连接器把参数翻译成 SQL 并执行查询;第四步,结果按 RAP 格式返回,触达链路追踪信息也一并回传;第五步,Agent 基于返回的 JSON 生成最终的自然语言回答。

从技术角度,Agent-Reach 本身不负责第一步的意图理解和最后一步的语言生成,这两步由大模型完成。它管的是中间三步,保证任何触达都经过同一个受控管道。下面的 Python 示例展示了如何使用 Agent-Reach 的 SDK 在本地模拟这个过程:

from reach_sdk import ReachClient, ActionRequest client = ReachClient("http://localhost:8600", api_key="dev_xxx") # 一次标准触达 req = ActionRequest( action="order_db.query_orders", args={ "start_time": "2025-04-01 00:00:00", "end_time": "2025-04-30 23:59:59", "status": "paid" }, context={"user_id": "u_1024", "session_id": "s_77881"}, timeout_ms=5000, ) resp = client.execute(req) if resp.is_success(): total_amount = sum(row["amount"] for row in resp.data["items"]) print(f"4月已付款订单总额: {total_amount}") else: print(f"触达失败: {resp.error.category} / {resp.error.message}")

这段代码看着简单,但在没有 Agent-Reach 之前,我得给每个接口分别写调用客户端、处理各自的鉴权和错误格式,还要手工加审计日志。这套封装省掉的不是那几十行代码,而是“每一个新接口接入都要重新走一遍既有流程”的隐性成本。

3.3 用 Reach Trace 排查一次慢查询

任何系统接入真实环境一定会遇到性能问题,Agent-Reach 的链路追踪在这里发挥了很大作用。我在实验时执行了一次请求,明显感觉到返回慢,模型都开始胡言乱语了。换做以前,我只能在数据库侧慢慢看慢日志。这次我直接打开 Reach Trace,看到触达链路里每一步的时间分布:RAP 解析耗时 2 毫秒,策略检查 5 毫秒,然后连接器执行 SQL 阶段耗时 4.8 秒,连接器内部又细分为“建立连接 4.5 秒”和“执行查询 0.3 秒”。

问题一目了然:连接池没生效,每次查询都重新建立数据库连接。我修复了连接器的连接复用配置,整体触达耗时立刻降到 260 毫秒。Reach Trace 的价值就在这里——它把“Agent 答不好”的问题拆解为“具体哪一段链路慢了”,让优化不再靠猜。

我还做了一个不算复杂但很实用的小功能:把每次触达的参数摘要和返回条数也记录下来,方便在追踪页面上看到类似“这个 Agent 每次请求都会带一个超大参数,实际只有前 3 个字段是必须的”这种浪费模型 token 的坏习惯。

4. 触达链路的高危雷区:权限失真、重复副作用与上下文爆炸

4.1 身份失真:Agent 调用系统时到底该用谁的凭证

前几节演示的都是顺利路径,真正让我长记性的是在生产准备阶段踩的坑。第一个坑来自身份失真。

最初我把所有 Agent 都配置成一个服务账号,逻辑上“Agent 是系统的一个内部服务,只要这个服务账号授权的数据源它都能访问”。听起来没什么问题,但真实业务里立刻暴露了风险:服务账号回答了“以系统的名义查”,却回答不了“是哪个员工让系统去查的”。于是订单 Agent 可能因为某个客服人员的一句话,去调用了那个客服本不该看到的高级别数据。权限本质上没被绕过,但被“服务账号”这个抽象层给放大了。

解决思路是“身份传递”,触达时要带着末端用户的身份走完整条链路。Agent-Reach 在每个请求的 context 里强制要求带上 user_id 和权限范围,策略层把这个身份和具体的 action 白名单做二次匹配。这意味着触达动作的授权判定是“Agent 角色 + 用户的权限范围”双重叠加,不是其中任何一个。改完之后我再也没敢回到单纯服务账号的模式。

4.2 超时重试引发的订单重复:幂等键救场

第二个坑特别典型。某个调用下单 API 的 Agent 在线上跑了一阵之后,有客户投诉说订单被重复创建了。排查了一圈,问题出在 Agent 侧的自动重试:外部 API 响应太慢,SDK 触发了超时,Agent 觉得“这次动作可能没执行成功”,于是重新调了一次下单接口。实际上第一次调用在服务端已经成功了,只是响应报文超时没回来。Agent 出于好心重试一次,结果就多了一个订单。

Agent-Reach 的 RAP 协议里加了幂等机制,每个携带副作用的动作都会要求调用方传一个idempotency_key,服务端按这个键缓存执行结果。同一个键只执行一次,后续所有重复触达直接返回第一次的结果。这个设计其实借鉴了支付系统很早的幂等方案,但多数 Agent 框架都忽略了它。重试本身没问题,重试之前必须想清楚“如果上一次其实成功了怎么办”。

因为加了幂等校验,Agent-Reach 的action_id还多了一层用途:既能做链路追踪,又能天然充当幂等键。同一会话内重试同一个 action_id,旧结果直接复用,不给外部系统造成二次副作用。

4.3 返回结果撑爆上下文:输出裁剪与摘要路由

第三个坑藏得最深,也最让人头疼。有一次我给数据分析类 Agent 接入一个数据仓库连接器,测试阶段一切正常;放到真实环境后,模型开始频繁输出无关内容,甚至复述数据库字段名。追到触达日志里才发现:一次查询返回了 8000 多行数据,JSON 序列化后大约有 1MB。这 1MB 全部塞进了模型的上下文窗口,不仅让模型“选择性失明”,还让整轮对话的 token 成本暴涨。

原因很直白:连接器返回的原始数据是给程序看的,不是给大模型看的。Agent-Reach 的处理方式是在连接器层增加“输出回流”能力:对于大数据量结果,先做聚合、摘要、截断,而不是原样返回。现在每次触达返回的默认上限是 200 行结构化摘要,如果模型确实需要原始明细,它可以再发起一个分页触达动作去取下一页。

这个改动带来的效果非常明显。模型不再被无关字段干扰,输出的相关性和稳定性显著提升。也是这次改造让我坚信:触达层不能只做“传输”,它还得做“翻译”,把程序世界的原生数据翻译成模型世界能消化的小份信息。

5. 多 Agent 编排的触达网络:从单兵作战到协同联动

5.1 为什么要做多 Agent 触达:两类真实需求

在 Agent-Reach 稳定支撑单 Agent 场景之后,很自然的下一步是多 Agent。这个需求的驱动力来自两类真实场景。一类是“纵向分工”:一个复杂工单大概率不是某个 Agent 能独立搞定的,需要客服 Agent 判断意图、工单 Agent 创建记录、库存 Agent 核验可用量、通知 Agent 发消息,每个 Agent 有相对单一的能力边界。另一类是“横向扩展”:某个高频 Agent 的负载已经很高,与其打破单 Agent 的能力边界,不如保持一套能力语义部署多个实例,用路由把请求分散到不同实例上。

这两种需求都指向同一个底层能力:Agent 之间需要像“服务调用”一样实现互触达。Agent-Reach 的多 Agent 编排,本质上就是把这个互触达变成可路由、可追踪、可管控的内部调用关系。

5.2 能力路由表驱动的请求流转

我不想把多 Agent 编排放到 Agent 自身内部逻辑里去实现,那样会把编排和被编排的对象混在一起,复杂度很快就失控了。Agent-Reach 的做法是维护一张能力路由表:每个 Agent 在接入时提供一份能力描述文件,声明“我能做什么、需要什么输入、产出什么输出”。路由服务收到上级 Agent 的请求时,根据能力描述匹配目标 Agent,再把请求转发过去。

能力描述其实也是一种“触达声明”,和连接器的 action 声明很像。我把每个 Agent 当作一个特殊的“连接器”,这带来一个很自然的统一抽象:无论下游是数据库、外部 API,还是另一个 Agent,从调用方的视角看都是一次 RAP 请求。这大概是我在整套架构里最满意的一个简洁决策——编排不需要发明新协议,复用同一套动作协议即可。

5.3 扇出与聚合:多 Agent 结果如何合流

一旦请求可以被转发,马上就出现第二个问题:一次上级请求需要同时触达多个 Agent,结果如何聚合后再回给模型。

我做了两种模式。第一种是“扇出-聚合”,适合多个 Agent 的工作相互独立。上级 Agent 一次性向三个子 Agent 发触达动作,三个结果全部收集成功后,统一汇总成一份合并结果,任何一个子 Agent 失败都可以决定整体失败或部分成功。第二种是“串行流水线”,适合有依赖关系的动作,比如必须先创建工单、再核验库存、最后发通知。串行流水线的调度状态全部记录在链路追踪里,任何一步失败都能明确看到卡在哪。

这两种模式我都写成了配置化的编排模板,而不是硬编码在 Agent 逻辑里。业务方想改流程顺序,改一下模板配置就可以重新生效,完全不用动代码。从工程运维角度,这种“流程与逻辑分离”的架构把 Agent 应用本身的复杂度降了一个量级。

6. 上线后的运维视角:可观测性、告警与成本控制

6.1 核心监控指标:成功率、P95、错误分布

Agent-Reach 上了生产之后,我把它当成一个正经的基础设施来运维,首先建立的就是监控体系。核心指标我不看太多,就看三组。

第一组是触达成功率,按 action 维度和连接器维度分别统计。成功率下跌往往直接对应外部系统或者权限配置变更。第二组是延迟分布,尤其 P95 和 P99。Agent 对慢系统特别敏感,模型在等待期间可能在“自由发挥”,所以延迟指标直接关联回答质量。第三组是错误分布,按 RAP 协议里的错误类别(参数错误、系统错误、超时、限流)分层统计。限流触发率过高可能意味着某些 action 的并发配置不对;参数错误率过高往往指向连接器的 schema 描述不够清晰——后者模型根本不会自己修复,需要人来优化声明。

告警规则我配置得很克制,只对成功率低于 99%、P99 超过 3 秒、限流拦截率超过 2% 这三种情况发告警。宁可少收到一些噪音告警,也不能让团队对告警产生疲劳。

6.2 触达链路追踪与日志关联

监控指标负责“告诉我出问题了”,链路追踪负责“告诉我哪里出问题了”。Agent-Reach 每次触达的 action_id 会贯穿整条链路,包括 Agent 内部生成的 token 消耗、模型的推理记录,以及框架侧的触达记录。这三个来源的数据以 action_id 为关联键,可以拼出一条完整的“用户请求模型决定触达外部响应生成回答”的全链条日志。

排查问题时最大的幸福感来自于不用看三方日志对时间线。我只需要输入 session_id,就能拉出这次会话里所有触达动作的完整时间线、参数摘要、返回码、耗时和 token 消耗。Agent 应用在运维侧能像传统的分布式调用链一样被追踪,这在早期的 Agent 项目里是几乎做不到的,而现在被我视为 Agent-Reach 上线的必备组件。

6.3 成本优化三板斧:缓存、批处理、模型分级

最后聊成本。大模型和传统后端的成本逻辑很不一样:一次低质量触达不仅浪费了外部系统资源,还浪费了大量 token 和用户耐心。所以 Agent-Reach 在成本控制上做了一些机制,核心是把无关信息挡在触达之前。

缓存是第一板斧。相同参数的热点查询直接走缓存,不进入连接器,也不消耗模型 token。这个改动让热门查询的平均响应时间缩短了 70%,同时大幅降低了 token 消耗。批处理是第二板斧。某些场景一次会话里会有多个结构一致的查询需求,我用连接器层面的批量动作把多次请求合并为一次调用,再拆分返回。对数据库这类连接型系统,一次大查询远优于二十次叠加查询。模型分级是第三板斧,也是最有 Agent 特色的:简单可控的触达动作,比如查明细、算总和、转换格式,我直接用一个小参数模型做参数提取;需要复杂推理的多步规划才调用满血大模型。因为两次触达对模型档位的需求不同,Agent-Reach 的 action 定义里允许标注 recommended_model_level,Agent SDK 会优先按这个级别选择模型。

做完这三板斧,整体成本下降了大约一半,而生成质量没有可感知的下降。成本优化的核心思路始终是:不要为用不上的信息付费,不要让大模型做小模型就能做的事。


最后再分享一个我在整个过程里最深的体会。Agent 能不能落地,往往不看模型层,而看触达层的可靠程度。Agent-Reach 做完之后,我拿同样的数据源分别跑过普通函数调用方案和带连接器方案,同样是查询订单,前者的错误率明显更高而且很难回溯——因为错误都散落在模型层和外部接口的各种角落里。真正把 Agent 从“玩具”推向“工具”的,是背后的连接、策略、追踪这些基础设施。如果你也在搭 Agent,我建议你尽早把“触达”提到和“规划”同等重要的位置来设计,哪怕一开始只有一层简单的封装,也比事后补要省太多时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询