ARTICLE DETAIL

资讯详情

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

多Agent协作实战:从0到1搭建Agent触达层Agent-Reach

多Agent协作实战:从0到1搭建Agent触达层Agent-Reach 身边做多 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 到 ToolAgent 要调外部工具数据库查询、支付接口、内部系统也不用自己去拼 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.ymlversion: 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_urlhttp://localhost:8400, tokendev-reach-secret, ) agent_id client.register_agent( nameorder_assistant, version1.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(fregistered: {agent_id})这里transport字段是用来告诉触达层事件到达这个 Agent 之后用什么方式把消息送过来。比较常用的是 webhook 模式触达层把事件 POST 到 Agent 暴露的回调接口Agent 处理完之后再通过client.emit()把结果事件发回触达层实现一次完整的链路。注册完成后在管理接口可以看到当前注册表里有哪些 Agent、能力、状态curl http://localhost:8400/v1/registry会返回所有在线节点和它们的能力声明。这一步跑通之后Agent-Reach 就已经能对“谁有能力处理什么”这件事有完整的认知了。3.3 声明技能并把外部工具挂进来有了 Agent第二步是挂技能。技能网关通过配置文件定义外部工具。比如我要接入一个订单查询服务可以建立一个技能定义文件 tool-order.yamlname: 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_typetask.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/NATSAgent-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 变聪明先把它们连成一个网。这个网越早拉起来后面所有自动化流程的稳定性就有越多保障。
返回列表