
上个月有个同事跟我诉苦说他负责的智能客服 Agent 要给业务部门订饭结果每次都得人工去另一个系统里查供应商列表。两个 Agent 明明都在同一个公司内网却像身处两个大陆想打通一次得排两周工期。我听完一点也不惊讶因为自己这几年折腾过不少 Agent 集成太清楚其中的问题。后来我花了大概六周时间把手里一堆临时脚本整理成了现在叫 Agent-Reach 的开源项目核心就一句话让任何一个 Agent 都能稳定、安全、可观测地“触达”到另一个 Agent 或者外部系统。它不替代你的业务代码也不绑定任何模型而是一套面向智能体的注册、路由、鉴权、观测设施。如果你正在做企业内部的多 Agent 协作或者想把多个 AI 能力组装成一个联合工作流这篇文章应该能帮上忙。这个项目最让我上头的点是它把一个老生常谈的问题换了个角度解决不是给 Agent 做接口文档而是给它们做一套“能力电话本”。谁来注册自己能干什么谁来负责转接出了问题谁知道全部交给 Agent-Reach 统一处理。下面我把当时的设计过程、踩坑经历、以及可以直接抄作业的配置方式完整写出来。1. 项目到底在解决什么问题1.1 现在的 Agent 已经成了新的孤岛团队里最常见的场景就是各业务线各搞各的 Agent客服线上了问答机器人数据线上了分析助手运营线上了内容生成工具。每个 Agent 单看都挺好一旦想让它们协作麻烦立刻出现。客服 Agent 想让数据分析 Agent 查一下昨天的转化率听起来简单但两个 Agent 的接口文档、入参格式、返回结构完全不一样甚至部署语言都不同。更麻烦的是Agent 之间传输的不只是结构化参数还有上下文和意图。举个例子一个 Agent 说“帮我分析一下最近三周华南区的销售趋势”目标 Agent 需要理解的不仅仅是“销售”、“趋势”这两个字段还得知道这是不是只读请求、会不会触发写操作、要不要等待结果直到完成。这些语义信息普通 REST API 网关根本不会替你考虑。你会发现传统微服务那套固定路径加参数校验的模式在 Agent 场景里特别别扭。1.2 为什么不能直接套 API 网关我在一开始也想过既然有成熟的 API 网关直接把 Agent 注册成微服务不就行了但试了几天就放弃了。核心差异有三点。对比维度传统 API 网关Agent-Reach 需要的机制服务发现依赖固定地址和版本Agent 动态上下线频繁需要按能力而非地址发现接口语义方法路径基本固定Agent 交互依赖意图、能力和上下文路径不稳定鉴权维度用户角色 / 权限要区分人类调用、Agent 调用、工具内部调用粒度更细响应模式同步请求为主经常有几十秒甚至几分钟的长任务还有流式输出传统网关假设客户端知道“我要调用哪个服务、哪个方法”但 Agent 遇到的场景往往是“我不知道谁有这个能力但我要完成这个目标”。这就像你手机里的电话本只能记熟人号码但你需要的是一个能查“附近谁会修水管”的能力黄页。 Agent-Reach 最早的核心就是把这个“能力黄页”做扎实。1.3 Agent-Reach 的核心定位名字取了 Agent-Reach是因为它双向“触达”对发起方来说它帮一个 Agent 触达它能想象到的任何能力对能力方来说它可以把自己的能力安全地触达给整个网络而不用把自己绑死在某个系统里。实际落地时Agent-Reach 由四块拼起来能力注册中心、智能路由决策、执行会话管理、完整可观测性。2. 整体架构与核心设计思路2.1 四层架构每层只做一件事Agent-Reach 的架构没有玩复杂概念就是四个清晰的分层避免一上来就被分布式架构拖死。接入层给 Agent 提供 SDK 和标准 HTTP/SSE 入口统一处理协议转换和认证。这一层最重要的原则是“接入方式简单到可以让一个刚入职的工程师十分钟跑通”。控制层包含能力注册中心、路由引擎、规则配置中心。所有 Agent 上线时向这里登记自己的能力标签和调用地址路由引擎根据请求中的目标能力描述匹配最合适的节点。这部分是整个系统的核心大脑。执行层负责实际转调 Agent、管理长任务、处理重试和回退。因为 Agent 之间经常是异步的长耗时操作执行层必须支持异步任务状态查询而不是傻等一次 HTTP 响应回来。观测层统一收集调用日志、耗时指标、链路追踪信息。每个请求生成一个 trace_id贯穿到所有 Agent 环境出了问题能按着 ID 查到上下游每一步。这四层之间用消息通道解耦控制层和执行层之间通过任务队列异步交互确保某个 Agent 响应慢了不会拖垮整个路由服务。一开始我也觉得自己写消息队列不现实后来发现直接用 Redis List 做简单任务队列完全够用后面运行稳定了再换正式 MQ 也不迟。2.2 两个关键数据模型能力和路由Agent-Reach 里最重要的数据结构有两个。第一个是 AgentInfo描述一个 Agent 是谁、能干什么、怎么联系。简化后的结构长这样{ agent_id: data-analyzer-01, agent_name: 数据分析助手, version: 2.3.1, capabilities: [ { skill: sales_analysis, scope: read_only }, { skill: trend_forecast, scope: read_only }, { skill: send_report, scope: write } ], endpoint: http://data-agent.internal:8301, status: active, max_concurrency: 5, metadata: { owner: data-team, sla_ms: 5000 } }第二个是 RouteRule决定“谁可以触达谁、在什么条件下触达、超时多久、失败了怎么办”。它不等于一个具体 URL而是一条带条件的语义规则。我当时设计成 YAML 配置方便非后端的人也能看懂和修改。一个典型的活例子是让数据 Agent 算完趋势后自动交给内容 Agent 生成周报。这条规则配置是这样的route: name: sales_trend_to_report description: 数据Agent算出销售趋势后交给内容Agent生成周报 source: report-agent target_skill: trend_forecast condition: context.mode read_only priority: 10 timeout_ms: 30000 fallback_action: notify_scheduler这里最值得琢磨的是condition字段。它不是写死某个 IP而是允许你在请求上下文中带出“这次调用的模式是只读还是写”、“是不是允许访问敏感数据”等标签路由引擎按标签放行或拒绝。这个设计帮我们后面解决了很多权限上的边界问题。2.3 为什么选“能力路由”而不用“方法路由”一开始我也犯过技术洁癖想定义一套 RESTful 的 Agent API/agents/{id}/invoke每个能力对应一个 POST 接口。结果开发到第二个 Agent 就崩了。因为 Agent 不是数据库表它面对的是模糊的用户请求它需要在执行过程中自己拆解意图。如果路由层非要提前定义死每个方法等于把 Agent 的动态能力重新塞进静态接口的盒子里。所以我决定用能力路由请求方只需要声明“我想完成某个能力”由 Agent-Reach 根据当前在线 Agent 的能力标签来匹配。这就像你打车不需要打给某个具体司机而是告诉平台“我要去机场”平台自动匹配在线的运力。能力路由天然适合 Agent 上下线频繁、接口经常调整的环境因为它把“谁来做”和“怎么做”彻底解耦了。匹配策略上我用了简单的两层第一层精确匹配能力标签比如trend_forecast必须有完全一致的标签第二层是模糊匹配用关键词相似度做兜底防止标签命名不一致导致找不到服务。实测下来两层匹配能让命中率达到 99% 以上而误匹配率没有明显上升。3. 落地实操从零搭一套 Agent-Reach3.1 环境准备与技术选型Agent-Reach 的核心运行时我选的是 Python 3.11 FastAPI。原因很简单团队里大多数 Agent 本身就是 Python 写的FastAPI 对异步并发支持好写路由和中间件也很顺手。注册中心和配置缓存用 Redis审计日志用 PostgreSQL任务队列早期直接用 Redis List等量上去了再迁移到更重的 MQ。这套组合最大的好处是部署简单一台 4C8G 的机器就能把控制层和观测层都跑起来。需要准备的组件清单如下Python 3.11 及以上版本Redis 6.2 以上用于存 AgentInfo 和待执行任务PostgreSQL 12 以上用于审计日志和路由规则持久化一个内部 DNS 或直连 IP保证各个 Agent 服务能互相访问OpenTelemetry 相关 SDK用于链路追踪安装 main package 的时候我用的是一个轻量 SDK名字就叫agent-reach提供 Agent 注册、心跳、取消注册和发起调用这四类核心方法。这里不建议把录 Agent 的核心逻辑写太重SDK 只要做好网络层封装就行。3.2 注册一个 Agent 的完整流程假设你有一个数据分析 Agent部署在内网地址http://data-agent.internal:8301想接入 Agent-Reach。第一步是安装 SDK 并实例化 Agent 对象然后调用 register 方法。代码里长这样from agent_reach_sdk import Agent, Capability data_agent Agent( agent_iddata-analyzer-01, endpointhttp://data-agent.internal:8301, capabilities[ Capability(namesales_analysis, scoperead_only), Capability(nametrend_forecast, scoperead_only), Capability(namesend_report, scopewrite), ], version2.3.1, heartbeat_interval15, ) # 连接到 Agent-Reach 控制面 data_agent.connect(http://agent-reach-control.internal:8080) data_agent.register()register 调用成功后会建立一个持久连接之后每 15 秒发一次心跳。如果连续三次心跳失败控制台会自动把这个 Agent 标记为离线并从路由候选里摘掉。这样就不会出现在 Agent 宕机后其他 Agent 还在傻乎乎地往一个死地址上打请求的问题。还有一个必须做的操作是优雅下线。你在发布升级的时候绝不能直接 kill 进程而是先调用data_agent.deregister()deregister 会通知控制台控制台立刻把该 Agent 从路由表中摘除然后你再安全重启。这步听起来简单但我在第一次灰度发布时偷懒没做结果升级期间产生了上千条超时报警后来才加上了强制检查。3.3 配置一条“触达路由”Agent 注册好了下一步是配置路由规则让别人能触达它。我习惯在控制台里用 YAML 维护所有路由规则维护起来清晰也方便做 Code Review。前面已经贴了一个路由示例这里我再拆开讲讲每个字段怎么定。首先source字段我写的调用方 Agent ID如果不填就表示所有 Agent 都能调用但生产环境里我强烈建议填上防止有人无意间调用到敏感能力。target_skill不是 Agent ID而是能力标签这样目标 Agent 以后换机器、换地址都不影响调用方只要能力标签不变就行。condition支持一个简单的表达式引擎你可以从请求旁路上取context里的任意字段比如context.mode read_only。配置规则时要记住一个原则先限定来源再限定条件最后限定超时不要把权限的口子开太大。fallback_action是出错后的兜底动作。我常用的有notify_scheduler、return_error、try_next_agent三个值。其中try_next_agent简直是人生救星当目标 Agent 超时或宕机时路由引擎会自动找下一个拥有相同能力标签但在线的 Agent业务调用方基本无感。3.4 一次完整调用链路演示配置完成后调用方的代码其实非常干净。比如运营周报 Agent 想调用数据分析能力它只需要from agent_reach_sdk import Agent report_agent Agent(agent_idreport-agent, endpointhttp://report-agent.internal:8201) report_agent.connect(http://agent-reach-control.internal:8080) result report_agent.invoke_skill( skilltrend_forecast, payload{ region: south_china, weeks: 3, metrics: [sales_amount, growth_rate] }, context{mode: read_only}, timeout_ms30000, ) print(result.task_id)调用方发出请求后Agent-Reach 控制层会生成一个全局唯一的task_id然后经过路由、鉴权、转发三步。最终返回的并不是直接结果而是一个任务号。因为 Agent 处理这种分析请求经常要跑几十秒如果直接同步等结果调用方连接早就断了。所以 SDK 内部支持两种方式wait_result()做阻塞等待或者主动query_status(task_id)做轮询。我当时在设计这个接口时把同步超时上限设置成了 5 秒超过 5 秒的全部走异步任务。实际操作下来客服和查询类场景基本 5 秒内都能返回而报告类长任务全部异步化用户体验反而更稳定。这个参数可以根据自己业务调整但核心思路是永远不要假设 Agent 会像常规接口一样秒回。4. 常见问题与排查技巧实录4.1 Agent 注册成功但路由永远匹配不到这是我被问得最多的问题也是最坑的问题。很多 Agent 明明在线、能力也有但其他 Agent 就是调不到。我排查到最后发现绝大多数原因是能力标签不统一。比如数据团队注册的标签是sales_analysis但调用方写的是sales-analytics中间一个下划线变连字符路由匹配不到。我后来在路由引擎里加了模糊匹配虽然能解决一部分问题但不彻底。真正有效的办法是在控制台提供“能力标签词典”上线前必须从这个词典选择禁止自定义新词。所有 Agent 的能力描述都走同一个标准词表匹配率立刻上来了。另外检查时可以先调控制台 APIcurl -X POST http://agent-reach-control.internal:8080/v1/route/preview \ -H Content-Type: application/json \ -d {source:report-agent,skill:trend_forecast,context:{mode:read_only}}这个preview接口会告诉你当前这条请求到底匹配到了哪些 Agent、被哪条规则拒绝、有没有候选节点。它是我调试时最常用的工具比看日志快多了。4.2 长任务总是超时怎么办刚开始我把 Agent 调用当成普通 HTTP 请求处理默认超时设置成 3 秒结果生产环境里一片哀嚎。Agent 内部有时要做工具调用、要跑好几轮模型推理根本不是普通接口能比的。最久的一次我遇到过数据分析 Agent 要跑 47 秒才返回结果。后来我把超时管理做了分级任务类型参考耗时建议同步等待超时建议任务轮询超时简单问答1-3 秒5 秒30 秒数据分析10-30 秒不做同步等待5 分钟报告生成30-90 秒不做同步等待15 分钟这里有个容易忽略的细节同步等待超时如果设置太长会让调用方线程一直占着不释放服务性能直线下降。所以我在 SDK 里统一限制同步调用上限 10 秒超过的一律返回 task_id 让调用方自己轮询。轮询接口返回任务状态包括pending、running、succeeded、failed。我建议在状态查询接口上做一层缓存不要每次都打到目标 Agent 上直接查 Redis 里缓存的任务状态就行。4.3 权限配置过严导致正常触达失败权限这个东西配置不好很容易出现两种极端要么全放开变成裸奔要么卡到连正常业务都跑不了。我一开始图省事给所有 Agent 账号配了全部权限结果没过多久就有人来报告一个只读分析 Agent 不小心调了写文件的技能差点改了线上配置。后来我收紧权限把所有能力默认设为不可跨 Agent 调用结果又出现运营 Agent 调不了任何报表能力的情况业务那边炸了。最终我采用了两步授权机制。第一步在路由层判断这条规则是否有权发起调用第二步在执行层判断目标 Agent 是否有这个技能的执行权限。而且把“路由规则”和“权限策略”分开管理路由规则负责“能不能找到”权限策略负责“能不能执行”。这样做的好处是就算路由规则里写了很多条执行层也能精确控制到 Agent 级。举个例子只读场景下路由允许调用trend_forecast但执行层配置>