☰
多Agent协作实战:从0到1搭建Agent触达层Agent-Reach
2026/10/8 5:02:18 网站建设 项目流程

身边做多 Agent 项目的人,很多都卡在同一个地方:单个 Agent 写起来很顺,一旦要让几个 Agent 协作了,要么服务之间互相找不到,要么消息格式谁说了算都吵不清楚,要么外部工具调不动、回调不回、任务死在半路。我去年下半年一直在搞这块,最后在项目里沉淀出一套东西,起名叫 Agent-Reach。它不做推理、不写行为逻辑,专门解决 Agent 系统里最容易被忽视的那一层:触达层——也就是 Agent 和 Agent、Agent 和工具、Agent 和人之间的连接问题。这篇文章把我当时的整体思路、核心模块、从 0 到 1 的搭建过程,还有踩过的坑都梳理一遍,给正准备上多 Agent 项目的人一个参考。

我见过太多团队把精力全烧在 Agent 的“脑子”上,结果系统上线后全死在“手脚”上。Agent-Reach 这个名字里的 Reach 其实就是“触达”,我当时想的很朴素:我要的不是让 Agent 更聪明,而是让每个 Agent 都能被快速找到、随时叫得动、结果能送回来。这套东西最后长成了一个独立的轻量层,和具体业务解耦,谁都能接。下面我把设计思路和整个落地过程完整展开。

1. 为什么需要一个 Agent 触达层

1.1 多 Agent 项目里最常见的三个崩点

先说第一个崩点:Agent 孤岛。一个项目里通常会有多个职责不同的 Agent,比如一个管订单查询、一个管售后登记、一个管风控复核。这些 Agent 如果各自独立部署,互相之间就得有人去拉 API、配地址、写客户端 SDK。刚开始只有两三个 Agent 还不觉得,等数量上到十几个,光维护“谁在哪、谁能干什么”就够喝一壶的。更麻烦的是,某个 Agent 换了地址或升级了协议,所有调用方都要跟着改。

第二个崩点是协议不统一。有人习惯用 gRPC,有人喜欢发 JSON 到 HTTP 接口,还有人直接往消息队列里丢对象。Agent 之间一旦用不同的协议对话,双方都得写一层适配的胶水代码。这种胶水代码短时间看没什么,等链路长了会变得极其恶心——每次加一个 Agent,就要给旧的每个 Agent 补适配器。我当时数过,一个中等规模的项目里,光协议转换代码就能占掉总代码量的两成到三成。

第三个崩点是最隐蔽的:失败没有兜底。Agent 调用工具、调用另一个 Agent 或者通知人的时候,如果对方暂时不可用,谁来重试?重试几次?超时多久算失败?失败之后要不要通知别的模块?很多人一开始根本没想这些问题,压测一上才暴露。事实上,分布式系统里单次调用失败是常态,没有重试和兜底的多 Agent 系统,跑起来就是一颗定时炸弹。

1.2 Agent-Reach 想做的事:打通最后一公里

所以我把 Agent-Reach 定义成“触达层”,核心就是把 Agent 之间的连接问题单独抽出来,用一种统一的方式解决。它不负责 Agent 该说什么话、该做什么决策,只负责三件事:让 Agent 能被找到,让消息能被送到,让结果能被带回。

为了讲清楚,我把触达拆成三层模型。第一层是 A2A,即 Agent 到 Agent:一个 Agent 需要另一个 Agent 的能力时,不用知道对方 IP、端口、实例数量,只要声明“我需要什么能力”,由触达层负责路由。第二层是 A2T,即 Agent 到 Tool:Agent 要调外部工具(数据库查询、支付接口、内部系统),也不用自己去拼 HTTP 和鉴权,通过统一的技能网关进。第三层是 A2H,即 Agent 到 Human:很多任务需要人审批、人确认,Agent 不能直接替代人做最终决定,触达层要提供一个可靠的人工通知和确认通道。

这三层链路是我在实际项目中一点点加出来的。最初我以为只要搞定 A2A 就够了,后来发现 Agent 调工具、找人工都缺不了一块统一出口。与其在每个 Agent 里各写各的,不如把这些公共能力下沉成一层,让所有 Agent 共用一个基础设施。这其实就是 Agent-Reach 存在的意义:把“最后一公里”的连接问题从业务代码里卸掉。

2. 核心模块拆解:四个必须做对的地方

2.1 服务注册与发现:让 Agent 能被找到

Agent-Reach 的第一个核心模块是服务注册与发现。每个 Agent 启动后,要主动到触达层注册自己,内容包括 Agent 名字、版本号、能力清单、基础地址(如果有需要直连的场景)以及当前优先级。系统会定期做心跳检查,超过一定时间没收到心跳,就把这个 Agent 标记为不可用,新的消息就不再往它那里发。

这里有一个很多人容易忽略的设计点:注册信息里必须带“能力元数据”,而不是只注册“名字”。因为实际场景里,同一个能力可能被多个 Agent 都声明了,而且这些 Agent 的处理能力不一样。比如“订单查询”能力,一个 Agent 查主库,一个 Agent 查缓存,优先级不同,触达层要能区分。我最初只按名字路由,结果经常把消息打到错误的实例上。后来改成能力字段做匹配源,加版本号做兼容判断,路由准确率一下就上来了。

这个模块还有一个容易被低估的价值:它天然地支持灰度发布。你新写了一个 Agent 版本,不想直接全量接流量,可以在注册信息里把权重调低,触达层做加权轮询,跑一段时间没问题再把权重提上去。不需要单独搞一套网关配置,注册表本身就是流量的总开关。

提示:心跳间隔建议设置成业务链路最长阻塞时间的三分之一,比如你允许每个任务最长等 3 秒,那心跳间隔控制在 1 秒左右比较合理。太频繁会白白消耗网络资源,太稀疏则会让故障发现变得迟钝。

2.2 统一事件信封:所有通信走同一种格式

第二个核心模块是统一事件信封,也就是所有 Agent 之间传递消息时,采用同一套 JSON 包装结构。这样做不是为了好看,而是为了在大规模协作场景下,谁能拿到消息都能一眼看懂上下文。信封里必须包含事件 ID、链路追踪 ID、源 Agent、目标能力、事件类型、携带数据、超时策略、创建时间这些字段。

一个比较典型的事件信封长这样:

{ "uid": "evt_20240123_00001", "trace_id": "tr_9f0d1c2b8a42", "type": "task.order_query.created", "source": { "agent": "order_assistant", "version": "1.1.0" }, "target": { "capability": "order_query", "routing_hint": "prefer_priority" }, "payload": { "order_id": "A1001", "user_id": "u_2002" }, "policy": { "timeout_ms": 3000, "max_retries": 2, "idempotency_key": "evt_20240123_00001" }, "created_at": "2024-01-23T10:00:00.000Z" }

信封设计中最重要的事情是幂等:retry 的时候,接收方必须能判断这条消息是不是已经处理过了。所以我让事件 ID 同时承担幂等键的角色,接收方可以把它存下来去重。很多新手会在重试机制里漏掉这一步,结果外部系统被重复扣款、重复发短信,出了大事故。

至于为什么采用事件驱动而不是让 Agent 之间直接 RPC,原因很简单:多 Agent 协作天然是异步的。一个 Agent 去请求另一个 Agent,对方可能要查库、可能要走审批、可能还要调用别的 Agent,整个过程超过几秒很正常。如果同步调用,调用方线程被挂起,整个系统的吞吐会急剧下降。用事件驱动,调用方发出消息就可以去做别的事,结果通过回调事件回来,系统吞吐和灵活性都高出不少。

2.3 技能网关:Agent 触达外部工具的通道

第三个核心模块是技能网关,它负责把外部系统和工具统一接入,让 Agent 不需要关心工具背后的实现细节。在 Agent-Reach 里,我把它做成一套插件式网关:每个工具被封装成一个“技能”,有名字、有描述、有输入输出 Schema,内部可以对接 HTTP、数据库、消息队列或任何遗留系统。

为什么需要这层抽象?举个例子:一个风控 Agent 要查用户信用分,信用分系统是 Java 老服务,接口文档还是五年前的格式;另一个营销 Agent 也要查同样的数据,但走的是内部 Python SDK。如果让每个 Agent 直接对接,两边都要写各自的调用和鉴权逻辑,代码冗余而且容易在鉴权上出漏洞。技能网关统一接一次,后面谁要用都是走同一套入口,鉴权、限流、重试、日志全部在这个入口完成。

网关这层的实现上,我参考了最近比较通行的 MCP 思路,把工具描述统一成一张能力表。Agent 发起调用时,只需要指定技能名和参数,网关负责找到对应的工具执行器并返回标准格式的结果。可以把它理解成“万能插座”:不同国家的插头(工具协议)都能通过转换头(技能适配器)插到同一个插座(网关)上,Agent 只需要认识这一个插座就够了。

2.4 人工通道:永远保留一个人在上面的入口

第四个模块是我认为多 Agent 系统里最容易被遗漏、但最重要的部分:人工通道。很多人设计 Agent 系统时默认一切都可以自动化,实际业务里根本不是这样。退款超过某个金额要人工审批,对外发正式合同之前要法务确认,给用户发营销内容要运营审核。Agent 可以发起这些流程,但不能自己拍板。

Agent-Reach 的人工通道提供两类基础能力。第一类是审批确认点:Agent 发出“待确认”事件,系统把消息推送到相关的企业沟通工具或工单系统,等人在界面上点通过或拒绝,结果以回调事件的形式回到原来的 Agent。第二类是通知集成:Agent 完成任务后需要给人发结果,比如巡检异常、任务完成、风险预警,统一走通知渠道。

这里我踩过一个很深刻的坑:人工确认动作和程序处理动作之间的重复执行问题。审批的人点“通过”按钮,如果网络抖动,前端可能重发请求,系统就会重复触发后续流程。解决办法是在人工通道也引入幂等键,每一个待确认事件生成一个确认凭证,重复提交同一个凭证直接返回“已处理”,不重复执行。

3. 实操实录:从 0 到 1 跑通一个 Agent-Reach 实例

3.1 环境准备与启动容器

纸上谈兵讲完设计,实际跑一遍才是硬道理。我下面用一个最小可复现的部署方式演示整个流程。假设你的机器上已经装好 Docker 和 Python 3.10+,我们只需要两个组件:一个是 Agent-Reach 服务端,另一个是作为注册表缓存和事件存储用的 Redis。

先准备好 docker-compose.yml:

version: "3.9" services: agent-reach: image: agentreach/agent-reach:0.6.0 container_name: agent-reach ports: - "8400:8400" environment: REACH_BIND_ADDR: "0.0.0.0:8400" REACH_STORAGE_DSN: "redis://redis:6379/0" REACH_JWT_SECRET: "dev-reach-secret" REACH_HEARTBEAT_TTL_SECONDS: "3" REACH_DEFAULT_TIMEOUT_MS: "3000" REACH_MAX_RETRIES: "2" depends_on: - redis redis: image: redis:7-alpine container_name: reach-redis ports: - "6379:6379"

执行docker compose up -d之后,等服务端起来,可以用 curl 快速确认健康状态:

curl http://localhost:8400/health

正常会返回{"status":"ok","version":"0.6.0"}。把REACH_HEARTBEAT_TTL_SECONDS设成 3 秒是故意的,这样在实验环境里能很快看到节点失效后的路由剔除效果,方便验证心跳机制。

3.2 注册第一个 Agent

服务起来之后,下一步就是让一个 Agent 注册进来。我提供一个稍显完整的 Python SDK 示例,实际项目中你可以在 Agent 的启动逻辑里调用这段代码:

from agent_reach import ReachClient client = ReachClient( server_url="http://localhost:8400", token="dev-reach-secret", ) agent_id = client.register_agent( name="order_assistant", version="1.1.0", capabilities=[ {"name": "order_query", "priority": 10}, {"name": "order_refund_create", "priority": 5}, ], transport={"type": "webhook", "url": "http://order-assistant:9001/reach/callback"}, ) print(f"registered: {agent_id}")

这里transport字段是用来告诉触达层,事件到达这个 Agent 之后,用什么方式把消息送过来。比较常用的是 webhook 模式:触达层把事件 POST 到 Agent 暴露的回调接口,Agent 处理完之后再通过client.emit()把结果事件发回触达层,实现一次完整的链路。

注册完成后,在管理接口可以看到当前注册表里有哪些 Agent、能力、状态:

curl http://localhost:8400/v1/registry

会返回所有在线节点和它们的能力声明。这一步跑通之后,Agent-Reach 就已经能对“谁有能力处理什么”这件事有完整的认知了。

3.3 声明技能并把外部工具挂进来

有了 Agent,第二步是挂技能。技能网关通过配置文件定义外部工具。比如我要接入一个订单查询服务,可以建立一个技能定义文件 tool-order.yaml:

name: order_query description: "查询订单状态的只读接口" enabled: true backend: type: http endpoint: "http://biz-platform:8080/api/orders/{order_id}" method: GET headers: X-API-Key: "${ORDER_QUERY_API_KEY}" input_schema: type: object required: ["order_id"] properties: order_id: type: string policy: timeout_ms: 2000 retry_count: 2 rate_limit: 100

然后调用管理接口激活这个技能:

curl -X POST http://localhost:8400/v1/tools/apply \ -H "Content-Type: application/yaml" \ --data-binary @tool-order.yaml

这里值得注意的有两点。第一,endpoint里用了{order_id}这种模板变量,网关在真正调用外部服务时会把它替换成实际参数值,这样技能定义里的输入参数和外部接口的路径参数就自动对应起来了。第二,rate_limit字段非常有用,很多外部系统有 QPS 限制,如果多个 Agent 同时调一个技能,网关统一限流比每个 Agent 自己限流可靠得多。

3.4 配置路由规则并端到端验证

最后是路由规则。Agent-Reach 的路由规则核心是四件事:匹配什么事件、需要什么能力、优先发给谁、超时和重试怎么控制。

一个最简单的路由规则定义如下:

routes: - name: order_query_default description: "所有订单查询请求默认走优先级最高的 Agent" when: event_type: "task.order_query.*" required_capability: "order_query" target: strategy: "prefer_priority" fallback_strategy: "round_robin" policy: timeout_ms: 2500 max_retries: 1

然后,在业务代码里发一条事件看看整条链路是否通畅:

client.emit( event_type="task.order_query.created", payload={"order_id": "A1001", "user_id": "u_2002"}, )

如果一切配置正确,触达层会把事件路由给我注册的那个order_assistantAgent,它的 webhook 端点收到消息后,调通技能网关里的order_query工具,最后返回一个结果事件:

{ "uid": "evt_20240123_00002", "trace_id": "tr_9f0d1c2b8a42", "type": "task.order_query.completed", "source": {"agent": "order_assistant"}, "target": {"capability": "task.result.constants"}, "payload": { "order_id": "A1001", "status": "paid", "paid_at": "2024-01-22T18:30:00.000Z" } }

在这个演示里能很清楚地看出整个事件链路的形态:查询请求事件先进入触达层,路由到能力提供方,结果事件再出来,回给订阅方。整个过程里没有任何一个 Agent 去关心对方服务的 IP 或端口,它们只认识“能力”和“事件”,剩下的连接全部由 Agent-Reach 接管。

4. 实战问题排查与避坑记录

4.1 高频问题速查表

跑了一段时间之后,我把真实环境里碰到的高频问题整理成下面这张速查表,给读者直接抄作业用。

现象常见原因解决方案
路由后事件没被任何 Agent 消费路由规则中能力名写错;目标 Agent 心跳超时被踢下线先查注册表里实际能力名;再看节点是否在线
Agent 重复处理同一条事件消费方没有按事件 ID 做幂等去重在 Agent 入口使用消息事件的 uid 作为幂等键
外部工具调用时快时慢技能网关缺少超时控制,外部慢接口拖垮执行线程给每个技能单独配置 timeout_ms,并加熔断
流程卡死,没有后续事件忘记设置任务总超时时间,消息进入死循环为每条任务设置 max_retries 和 deadline
人工确认后流程执行了两遍确认回调缺少幂等凭证每次确认生成唯一 ticket,回调时做唯一性校验
新增 Agent 后流量反而下降新实例优先级过高,但能力元数据不完整检查 routing_hint 和能力版本匹配规则

上面这些坑,绝大多数不是 Agent 本身“智力”不够,而是基础设施不完善。多 Agent 系统里,稳定性主要取决于这些细枝末节,而不只是模型能力。

4.2 几个印象最深的坑详解

第一个值得单独拿出来说的是路由规则的通配符匹配问题。我最初写when.event_type用前缀匹配,比如task.order_query.*会匹配task.order_query.created和task.order_query.completed。看起来没毛病,但实际场景里这两个事件需要的处理能力完全不同:created 需要订单查询能力,completed 是查询结果,不该再被路由回去执行查询。如果不加区分地用一个规则匹配,就会产生“查询触发查询”这种递归风暴。解决办法是把路由规则的事件匹配做得刻意严格——要么直接写出完整事件类型,要么在规则里同时校验 target 的能力方向,避免事件自己触发自己。

第二个是超时参数设得太随意。我第一次部署时给技能网关统一设了 8 秒超时,结果一次外部数据库慢查询拖住了所有调用方,导致大量任务积压。后来我养成了针对每个技能配置独立超时的习惯,并且给整条任务链路设置总 deadline。用一个生活类比来解释:你要去赶高铁,不能只要求地铁站内走路 20 分钟,而不考虑地铁本身会不会延误;链条上的每一个环节都要有各自的时间预算,最后还要有一个“无论如何必须赶上车”的总时间线。

第三个是鉴权设计太晚了。一开始我把技能网关的鉴权放在了后端工具服务上,想着外部系统自己有鉴权,网关就做个透传,结果内部调试时还好,正式环境接入多个团队后,有人误调了线上支付接口,还好当时有权限管控才没出大事。后来我强制所有技能声明里必须带上所需角色权限,由触达层统一校验,Agent 的调用凭证不再透传,人和 Agent 的身份体系彻底分开。这一点建议所有做 Agent 协作基础设施的人提前设计,不要等出了乱子再补。

5. 方案选型:Agent-Reach 与其它协作方式的分野

5.1 横向对比

不少人问我:你这个 Agent-Reach 和我自己写编排,或者直接用消息总线,到底差在哪?我很难简单地用“好”或“坏”来划分,因为不同方案的适用范围完全不同。这里给出一个基于我实际使用体验的对比表:

对比维度自行硬编码编排通用消息总线(如 Redis Stream/NATS)Agent-Reach 触达层
开发速度初期快,后期协议维护爆炸需要自己设计消息结构和路由协议前期有学习成本,中后期最稳定
能力路由需要手动维护服务间调用关系只做消息存储和分发,不管能力匹配基于能力元数据自动路由
人工审批集成每个流程各写一套没有内置概念,需要自行扩展内置审批确认点
可观测性需要自行打点,日志格式难统一依赖总线自带的追踪能力事件信封天然带 trace_id,全链路透明
适合规模2-3 个 Agent 的小型原型通用服务间异步通信超过 5 个 Agent 的正式业务系统

注意最后一行,这也是我说得最直白的地方:如果你的项目现在只有两个 Agent,用一个消息队列甚至直接函数互相调用都够用了,上一套触达层反而显得笨重。但业务一旦复杂到“需要知道谁有能力处理什么”这个程度,再开始补基础设施就晚了,因为那时候业务代码里已经到处都是直连调用,重构代价相当大。

5.2 什么时候应该果断上触达层

我的判断标准可以总结成三条。第一,Agent 数量接近或超过五个,且它们之间的调用关系已经不是一条直线而是一张网。第二,外部工具系统超过三个,而且每个工具都需要独立的鉴权、限流和日志。第三,业务链路上有强人工介入节点,比如审批、合规审查、二次确认,需要处理人机交互的异步状态流转。满足任意两条,就值得认真考虑引入 Agent-Reach 这类触达层。

反过来也有不需要的情况:如果整个系统就是一个单体脚本在本地跑,或者只有两条固定调用链,直接硬编码是最高效的,强行引入中间层只会增加部署和排障成本。做技术选型最忌讳的不是选错,而是用一个很重的方案去解决一个很轻的问题。

6. 场景延伸:从 Agent 技术触达走向业务用户触达

6.1 用统一事件化改造用户通知系统

Agent-Reach 能解决的不只是 Agent 内部协作,我后来发现这层触达网络也很适合直接对用户做触达。举一个例子:一个电商系统的用户运营团队,经常需要给用户发订单状态变更通知、优惠券提醒、物流异常预警。过去这些通知逻辑散落在各个微服务里,每个服务自己拼文案、自己调消息服务,重复代码严重。

借助 Agent-Reach,我把通知场景也事件化了。统一的“消息发送”技能被挂在技能网关上,任何业务 Agent 只需要发出一个notification.user.remind类型的事件,技能网关负责选渠道(推送、短信、站内信)、做频控、打日志。这样既不需要动原有业务系统,又能把所有触达行为集中在一个可控的通道里,运营同学还能从一个仪表盘里看到全量触达情况。

6.2 用 trace_id 把 Agent 系统真正管起来

还有一点我必须强调:Agent 系统的可观测性,比传统微服务更容易被忽略、也更难做。因为 Agent 的执行路径不是预先写死的控制流,而是根据事件路由动态组成的,问题出现时你不知道中间经过了多少节点。Agent-Reach 里的 trace_id 帮了大忙——所有事件信封都携带同一个 trace_id,日志系统按这个字段聚合,就能把一次完整任务走过的全部节点串起来。排查线上问题时,我从用户侧的报告反查,几秒钟就能定位到是哪个 Agent 没处理、哪个技能网关超时了。这个能力在系统出问题时能救命的,强烈建议每一位做 Agent 编排的人,无论用不用 Agent-Reach,都要在第一天引入全链路追踪,而不是上线后再补。

6.3 让触达层变成可扩展的连接协议

最后说一个我正在做的方向:把 Agent-Reach 从项目内的基础设施,慢慢往“标准协议”的方向演进。也就是说,它不应该只是自己项目能用,而是任何两个基于该协议的 Agent 都能互相触达。当前触达层如果要跨部门、跨系统协作,还需要双方各接一个网关;理想的情况是所有 Agent 都支持一套触达协议,注册、通信、鉴权、审计全部标准化,这样就可以像浏览器通过 HTTP 连接任意网站一样,让 Agent 之间也能即插即用地协作。

这条路要走很久,但我觉得方向是值得坚持的。毕竟,Agent 系统要真正落地成有业务价值的基础设施,靠的不仅仅是某个模型聪明,而是整条链路上的每个角色都能被可靠地连接在一起。

如果只让我留一条经验给后来者,我会说:先别急着让 Agent 变聪明,先把它们连成一个网。这个网越早拉起来,后面所有自动化流程的稳定性就有越多保障。

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

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

立即咨询