ARTICLE DETAIL

资讯详情

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

Agent-Reach:打通多系统能力边界的企业级AI Agent接入架构

Agent-Reach:打通多系统能力边界的企业级AI Agent接入架构 上个月我接了一个需求把公司内部的工单系统、知识库还有数据分析平台统一接到一个 AI Agent 里让业务同事用一句大白话就能查数、找文档、走流程。听起来很常规对吧真正动手才发现单体 Agent 的能力再强手伸不到的地方它就是够不着——三个系统的接口风格完全不一样鉴权方式各走各的其中一个甚至没有官方 API只能靠内部网关转发。我最初的想法是写一堆胶水代码硬怼结果越写越乱。后来我干脆重做了这个项目名字就叫 Agent-Reach核心思路是不追求单一 Agent 什么都会而是让 Agent 真正够得着外部世界里的每一项能力。这篇文章就把我从设计、开发到落地的完整过程写出来。它适合正在做 Agent 应用落地、被接口打通和多系统协作折磨过的开发者也适合技术负责人参考——你可以直接拿这套思路去评估自己的项目要不要引入类似框架。我会把架构设计、路由策略、部署顺序、实测数据以及我踩过的三个比较深的坑都讲清楚尽量说人话。1. 为什么做 Agent-Reach单体 Agent 撞上够不着的墙1.1 我遇到的具体问题不是模型不够聪明先说背景。当时我们选型的是业内比较成熟的 LLM Agent 方案模型本身调用工具的能力不差写 Prompt、配 Function Calling 也能跑通 demo。demo 里让 Agent 查个库存、生成一段周报效果还挺惊艳。但一旦接真实业务问题就全冒出来了。第一个问题是接口语义不统一。工单系统是 Java 老项目提供的接口是 REST 自定义状态码知识库是内部 Wiki走的是 SSO 登录后的 HTML 页面数据平台倒是开放了 API但用的是另一套签名机制。Agent 要调这三家就得写三套完全不同的请求逻辑。更麻烦的是每个接口的入参格式都不一样有的要 JSON有的要表单有的要求头里带时间戳签名。这些细节全堆在 Agent 的 tool 定义里Prompt 长得吓人模型时不时就记岔。第二个问题是鉴权散落。工具函数散落在不同服务里有的用 Token有的用签名有的需要短时有效的一次性凭证。Agent 本身没有状态管理每次调用都要临时现要凭证这一层逻辑写出来极其啰嗦而且容易在并发场景下相互覆盖。第三个问题最致命Agent 无法感知自己到底有没有能力做这件事。你问它帮我查一下上个月的退款率它如果没注册相关工具会一本正经地编一个数字出来。这不是模型笨而是它根本不知道自己的能力边界——这是 Prompt 层面很难根治的问题。1.2 Agent-Reach 的设计初衷能力边界显式化我在调研了一圈之后发现社区里解决这类问题有两种主流思路。一种是拼 Prompt把所有工具的说明、参数、鉴权方式全部塞进去让模型自己判断使用另一种是给 Agent 套上记忆和规划外壳让它学会拆解任务。但我觉得这两条路都绕开了真正的核心矛盾Agent 和外部系统之间缺一个统一的能力接入层。Agent-Reach 的思路就是把能力接入单独抽出来做成一层中间件用一套统一的注册模型把所有工具、接口、服务收敛起来。每个接入 Agent-Reach 的能力都带着自己的元信息做什么、入参结构、鉴权策略、调用方式、输出格式。Agent 不需要知道目标系统长什么样它只需要对着 Agent-Reach 的能力表点菜。这样一来能力边界变成了一张可以随时查阅的清单模型不知道就查查不到就老老实实说做不到不再瞎编。这套设计对单人项目和团队项目都适用。单人项目的好处是代码不用散落在 Prompt 里维护成本低团队项目的价值更大不同小组可以各自注册自己的服务能力由 Agent-Reach 统一调度互相之间不会踩到接口变更的地雷。1.3 它和现成编排框架的差异市面上其实有不少 Agent 编排框架比如 LangChain、AutoGen 之类。坦白讲这些框架擅长的是让模型规划与调用但它们默认你已经有现成的工具函数。Agent-Reach 不是要替代它们而是补上它们缺失的一块从系统能力到Agent 工具的最后一公里。你可以把 Agent-Reach 理解成一个带路由和鉴权的能力总线前端接 LLM后端接各类服务中间只做一件事——让一切能力可达Reach。后面我会详细拆这个能力总线的内部结构。2. Agent-Reach 的核心架构注册表、路由器和执行链路2.1 能力注册表一切皆可被描述Agent-Reach 的第一个核心组件是能力注册表Capability Registry。所有能被 Agent 调用的东西不管是 REST API、内部函数、数据库查询还是第三方 SaaS都必须在注册表里登记一条能力记录。我设计的注册记录结构大致长这样{ capability_id: wiki.search_docs, name: 知识库文档搜索, description: 根据关键词搜索内部知识库中的文档返回标题列表和原文链接, input_schema: { query: {type: string, required: true, description: 搜索关键词}, limit: {type: integer, required: false, default: 5} }, output_schema: { results: {type: array, items: {title: string, url: string}} }, auth: {type: sso, credential_ref: wiki_sso_cred}, endpoint: {method: POST, url: http://wiki.internal/search}, timeout_ms: 3000 }这条记录最关键的部分有三块。第一是description它决定了 Agent 能否在路由阶段正确选到这个能力所以必须站在使用者视角来写而不是写内部实现细节。第二是input_schema它不仅是给模型看的参数说明Agent-Reach 还会拿它做运行时校验参数缺失直接拦截避免请求打到目标系统才报错。第三是auth我们把鉴权全部收归到中间件目标服务根本不需要关心 Agent 怎么拿到的凭证。这个注册表我一开始用静态 JSON 文件后来服务多了就迁到了数据库并加了版本号。每次能力变更都会生成新版本Agent 查到的永远是当前生效的版本不会出现Prompt 里写的和线上实际的接口对不上这种问题。2.2 路由器让模型做选择题而不是填空题第二个核心组件是路由器Router。它的职责是拿到用户的自然语言请求分析出意图然后映射到一个或多个能力记录上。这里的实现细节值得说一下。路由有两种做法一种是在代码里写死关键词规则比如包含文档就调知识库这种方案又快又稳但覆盖不了复杂的口语化表达另一种是完全交给模型自由选择灵活但容易出现幻觉模型可能选一个语义沾边但其实不对的能力。Agent-Reach 采用的是混合路由先让一个轻量分类器粗筛候选能力再把候选列表交给 LLM 做最终选择。粗筛阶段我用的是 embedding 相似度把所有能力的description预先向量化用户请求进来后算出 top-K 候选。这样即使 LLM 偶尔抽风它也只能在候选池里选犯错空间被压得很小。实测下来路由准确率比纯 LLM 自由选择高不少而且速度更快——embedding 计算通常在几十毫秒内完成真正需要 LLM 参与的只是从 5 个里挑 1 个这种简单任务。路由还有一个容易被忽略的环节参数提取。模型选定了能力接下来要做的是从用户话里抽出结构化参数。这一步我建议和能力选择分开做否则单个 Prompt 任务太重模型容易顾此失彼。Agent-Reach 里参数提取是跟着能力记录走的每个能力有独立的提取模板比如查工单要抽工单号、查数据要抽时间范围。分开之后错误率肉眼可见地降了下来。2.3 执行链路与状态回传选定能力、抽好参数之后请求进入执行链路。这一层做三件事鉴权注入、参数校验、统一响应包装。鉴权注入是我们做得比较重的一部分。每个能力在注册时可以挂多种鉴权策略支持 API Key、OAuth2 客户端模式、SSO Cookie 转发等。Agent-Reach 维护了一个凭证管理中心凭证按能力维度隔离存放Agent 拿不到原始凭证只会在调用时由链路自动注入。这样做既安全又省心Agent 的上下文里完全不需要出现密钥相关的内容。参数校验则完全依赖能力记录里的input_schema。校验失败的请求会直接返回参数缺失的确定性错误而不是把错误请求转发到目标服务最后拿回一个莫名其妙的 500。这个前置校验帮我节省了大量排错时间——以前 70% 的失败都是参数没对上现在这问题在入口就拦住了。所有执行结果都会被包成统一的响应结构再附带上一个trace_id。这样 Agent 拿到的不再是一堆格式各异的原始接口返回值而是统一格式、带元信息的结果。出了问题也可以顺着 trace_id 一路查到执行链路。3. 从零部署 Agent-Reach环境准备到跑通第一个能力3.1 环境要求与依赖选型Agent-Reach 本身是一个偏轻量的服务我没有为它引入特别重的组件。依赖列表如下Python 3.10FastAPI 作为 HTTP 服务框架Redis 作为路由缓存和会话状态存储PostgreSQL 存能力注册表和审计日志可选向量数据库用于路由粗筛阶段的 embedding 检索也可以用 pgvector 代替选 PythonFastAPI 纯粹是为了团队技术栈统一和快速迭代。如果你团队是 Go 或者 Node.js完全可以用自己熟悉的语言重写中间层——Agent-Reach 的核心设计是概念模型和接口规范语言不是重点。安装上没什么特殊之处pip install依赖包建好数据库表启动一个uvicorn进程即可。但我强烈建议一开始就部署两个实例一个路由一个执行中间用 Redis 做队列解耦。这不是为了性能而是为了出问题时能快速定位——路由挂了不会影响正在执行的任务执行链路崩了也不影响新请求进路由。3.2 最小可跑配置一个让人有信心的 Demo我习惯先做一个最小闭环让 Agent-Reach 把一个单机函数暴露成能力再通过命令行调用它。跑通之后信心就有了后面接真实服务其实是在这个骨架上填肉。最小能力定义我直接写在一个 Python 文件里from agent_reach import register_capability register_capability( capability_idgreet.say_hello, description向用户问好生成一句简单的问候语, input_schema{ name: {type: string, required: True, description: 用户的名字} } ) def say_hello(name: str) - dict: return {message: f你好{name}这是 Agent-Reach 的第一个能力。}启动服务之后通过 Agent-Reach 的 HTTP API 发起一次调用curl -X POST http://localhost:8000/v1/router/invoke \ -H Content-Type: application/json \ -d { request: 帮我跟老张打个招呼, session_id: test-001 }返回结果里会带上路由选择的能力 ID、提取出的参数、执行结果和 trace_id。看到这条链路完整跑通我基本就知道这套框架的每一块大致在做什么了后面接业务系统只是增加能力注册记录而已。3.3 核心 API 的使用逻辑Agent-Reach 面向调用方暴露的 API 很克制主要有四个POST /v1/capabilities/register注册或更新一个能力GET /v1/capabilities/list查看当前可达能力清单POST /v1/router/invoke发起一次带意图路由的调用POST /v1/executions/{trace_id}/cancel取消一个正在执行的任务对 Agent 应用层来说最常用的就是/v1/router/invoke。它接受自然语言请求和会话 ID返回执行结果。这背后其实已经完成了意图识别、能力选择、参数提取、鉴权注入和调用执行的全流程。Agent 不需要知道任何一个目标的内部细节它只需要发起这一个请求。心法上有一点想提醒不要让 Agent 拿到全部能力清单后再自己选而是让 Agent 先描述要做什么事由 Agent-Reach 的路由层去匹配能力。这样可以最大限度把选择这件事控制在确定性更高的链路里。我在早期版本里就是把能力清单全塞给 Agent结果模型经常被大量描述干扰选错能力后来改回路由层做主选准确率立刻上来了。4. 实测场景让 Agent 跨三个系统完成一个真实任务4.1 场景设计一张表看清三个系统的差异为了验证 Agent-Reach 不是纸面架构我设计了一个真实场景用户说帮我查一下上周张三提交的退款工单处理到哪一步了顺便把相关客服话术找出来最后生成一段 50 字以内的客户回复摘要。这个请求从人类视角看很自然但对 Agent 来说需要依次访问三个系统系统能力接口风格鉴权方式工单系统按工单号/提交人查询工单状态老式 REST自定义状态码Token知识库按关键词搜索客服话术HTML 页面解析SSO Cookie数据分析平台查询退款率趋势新版 REST签名鉴权HMAC 签名三个系统没有一个是省油的灯。工单系统返回的状态码跟 HTTP 语义毫无关系200 的响应体里可能藏着 error1知识库压根没有开放 API需要走内部网关抓 HTML 再解析数据平台虽然规范但签名算法是 Java 写的跨语言验证花了我不少时间。4.2 在 Agent-Reach 里注册这三个能力的实际过程我逐个将它b们注册进 Agent-Reach。工单系统这条最顺利因为接口是现成的我只需要把请求转发逻辑包一层然后写清楚description和input_schema。知识库那条最折腾。因为没有 API我只能把抓取 HTML 页面 → 解析正文 → 提取搜索结果的逻辑封装成一个函数再把这个函数注册成能力。这里 Agent-Reach 的优势体现出来了Agent 完全不知道自己在访问一个 HTML 页面在它看来这就是一个普通的搜索文档能力。底层实现再怎么丑陋只要注册表里写清楚了输入输出对 Agent 就是透明的。数据平台的签名逻辑我放在鉴权注入层写了一个小的签名拦截器注册能力时指明auth类型为hmac执行链路自动完成签名计算并附加到请求头。这样做的收益是后续每扩展一个数据平台接口都自动获得了签名能力不用重复写签名逻辑。4.3 联调过程和效果对比联调那天出了一些小问题主要集中在参数提取上。比如用户说上周到底指自然周还是最近 7 天这属于语义歧义不是 Agent-Reach 能解决的所以我加了一条规则涉及时间范围的能力参数统一由前置的日期解析器交给 LLM 确认一次默认取自然周用户没特别说明就以自然周计算。全部注册完成后的执行链路是这样的Agent-Reach 先路由到工单系统能力拿到工单状态再根据工单里的商品关键词路由到知识库能力取回客服话术最后把两个结果拼成上下文路由到数据平台能力做退款率补充。三个步骤串完后生成摘要的任务我留给了 Agent 应用层没有走 Agent-Reach——因为生成类任务不是外部能力不适合放进去。实测效果很直观。优化前Agent 直接面对三个系统一次完整任务的成功率大约只有 40%而且失败了很难定位——是参数错了、鉴权失败了还是模型选错了工具排查一次至少半小时。接入 Agent-Reach 后同一批测试用例的成功率提升到 78%剩余失败大部分是源系统本身的超时或数据异常Agent-Reach 已经能给出具体原因而不是一堆乱码堆栈。排查问题的时间也从半小时压缩到了几次 trace 查询。5. 跑通之后踩过的坑以及对应的补救措施5.1 坑一路由到错误的能力根因不是模型而是描述上线第一周我最头疼的问题是这个用户问退款率怎么样Agent 却调了知识库文档搜索因为知识库里有篇文档标题含退款率。表面上是模型选错了本质上是能力描述写得太像路由层面没法区分查数据和找文档这两个意图。我一开始试图让模型更聪明加了各种上下文提示效果甚微。后来改了三处才解决问题第一每个能力的描述里明确加上动词前缀比如计算并返回指标数值而不是退款率相关第二注册表增加了category字段把数据查询和文档检索分成不同类目路由粗筛阶段先按类目过滤第三对容易混淆的能力在描述里加反例说明比如文档搜索的描述里补充本能力不返回任何统计数据仅返回文档标题和链接。改完之后混淆率降了七成左右。血的教训是能力描述的质量基本决定了路由质量写描述一定要站在使用者在什么场景下会想起用这个能力的角度而不是这个能力返回什么的角度。5.2 坑二长链路任务的状态丢失Agent-Reach 一开始设计成无状态服务每个请求独立处理这在大方向上没错但遇到多步骤任务就成了问题。用户说先查工单再根据结果查知识库如果第一步和第二步之间隔了很久或者第一步的结果没有传给第二步链路就断了。我试过把中间结果都塞到会话上下文里让每次路由都携带完整历史结果 Prompt 越来越长成本迅速升高而且模型容易被历史信息干扰。后来我用 Redis 做了一层轻量级的会话状态存储只在会话里保存每一步的关键输出摘要和对应的trace_id后续步骤需要引用前序结果时通过 trace_id 去取完整结果而不是把全文塞进上下文。这样设计之后状态丢失的问题基本解决同时也没有把 context 撑爆。还有一个额外好处是如果某一步失败可以直接从 Redis 里拿到前一步的结果重新发起后续步骤不用整条链路重跑。5.3 坑三成本失控模型调用量比想象中大得多Agent-Reach 自己做了大量的路由和参数提取工作按理说 LLM 调用量应该比直接让 Agent 乱调省得多。但上线后我发现总 Token 消耗反而涨了原因是每一步的参数提取都单独调用模型来回几次就比一次大 Prompt 还贵。这个问题的解法比较朴素能用确定性代码解决的绝对不调模型。比如上周这类时间表达我写了一个基于规则的时间解析器覆盖了上周/上个月/最近 N 天/本月至今等常见表达只有规则解析不了时才回退到 LLM。再比如工单号提取优先用正则只有正则应不上才找模型。经过这一轮优化单次任务的 Token 消耗降到了原来的四分之一。另外Agent-Reach 的路由粗筛阶段用的是 embedding 而非 LLM这一层成本本来就很低。所以瓶颈基本集中在参数提取规则兜底的效果立竿见影。5.4 排错的具体链路从用户报错到 trace 定位最后说一下排错。以前排错基本都是对着日志猜现在 Agent-Reach 给每条执行链路生成了 trace_id我会按照固定的顺序排查第一步看路由阶段确认模型选的能力对不对。不对就是描述或路由策略的问题。第二步看参数提取阶段确认提取出的参数对不对。不对就是提取模板或规则的问题。第三步看执行阶段确认请求是否正确到达目标系统。到达了但报错就是目标系统的问题没到达那就是鉴权或网络配置。这个顺序很重要。因为大部分问题的根因都在前两步如果一上来就盯着目标系统的接口日志很容易被带偏。我甚至把这套排查顺序固化成了文档团队里新同学照着做基本能独立定位问题。6. Agent-Reach 的进阶扩展多 Agent 协作与能力自描述6.1 从单 Agent 到 Agent 群每个人只负责自己的可达范围Agent-Reach 跑顺之后我开始琢磨多 Agent 协作。之前的模式是一个 Agent 借助 Agent-Reach 调用所有能力但实际业务里不同角色的 Agent 应该有不同的权限和可达范围。财务 Agent 不该有调内部 Wiki 编辑接口的能力客服 Agent 也没必要碰数据平台的删除类接口。Agent-Reach 在能力注册表上天然支持这个需求每个能力可以挂allowed_roles列表路由阶段会根据调用方的身份过滤候选能力。这比在 Prompt 里写你无权调用 XX可靠得多——模型不会因为提示而真的守规矩但路由层过滤是强制性的。我实际搭建了两个 Agent 共用同一套 Agent-Reach 的架构一个管客服问答一个管数据分析。两者各自的路由范围互不重叠效果立竿见影客服 Agent 的上下文更短了错误率下降数据分析 Agent 也不用再担心被客服相关的能力描述干扰。6.2 扩展插件把自己团队的服务包装成标准能力Agent-Reach 的价值随着接入的服务数量增加而指数增长。我们团队后来的标准做法是任何系统接入都先整理一份能力清单文档然后照着能力记录模板逐个注册。这个过程没有太多技术含量但有几个细节很关键超时和重试策略不要一刀切。查询类接口可以给 3 秒超时写操作类接口建议给更长的超时并且默认不重试避免重复提交。输出结果要做截断和脱敏。有些接口返回全文或敏感字段Agent-Reach 可以在响应层统一截断然后再交给 Agent 上下文。能力下线要有优雅流程。注册表里保留deprecated字段切换新能力时保留旧能力运行一段时间确认无人调用后再下线避免上午还能用下午突然断了的尴尬。6.3 后续方向和可以自己动手做的改造Agent-Reach 下一步我打算做两块。第一是能力依赖编排目前一个请求只能映射到一个能力虽然 Agent 应用层可以串多个请求但我希望 Agent-Reach 内部支持定义能力组合模板比如查工单 找话术 汇总摘要可以直接注册成一个复合能力一次调用自动串联省掉在应用层写编排逻辑的功夫。第二是能力自描述校验现在写错的description只有上线后靠路由错误率反馈才能发现。我想加一个测试集回流机制每次注册新能力时自动跑一批历史路由样本分数不达标就拒绝注册。这有点类似 CI 的思路把路由质量变成可测试的指标。如果你也想在自己的项目里实现类似的东西我的建议是先别急着写框架。把现在系统里所有需要 Agent 调用的工具列一张表标清楚输入输出和鉴权方式你会立刻发现 Agent-Reach 解决的那些问题在自己项目里同样存在。想通了这一点再决定是直接用现成编排框架还是自己抽一层能力总线思路会清晰很多。我个人在整个项目里最大的体会是Agent 应用真正的复杂度不在模型侧而在连接侧。把连接做好了Agent 的能力自然就伸展出去了——这就是Reach这个名字的含义。希望这篇文章能给你一些启发少走一点我走过的弯路。
返回列表