首页 / 资讯中心 / 文章详情

Agent-Reach:多智能体协作的注册与消息触达框架实践

Agent-Reach:多智能体协作的注册与消息触达框架实践 ★ FEATURED ARTICLE
在折腾了大半年多智能体协作之后我越来越觉得“Agent-Reach”这个词本身就点破了分布式 AI 应用里最疼的一个问题你手里的 Agent 再聪明如果它触达不了该触达的节点、拿不到该拿的数据那它跟单机脚本没什么区别。我们今天聊的这套 Agent-Reach 项目核心就是解决智能体之间的“通讯录”和“快递系统”——让每个 Agent 知道别人在哪、怎么找、消息怎么安全送达。我搭建这套系统的初衷其实很现实。团队正在做一个多智能体的任务编排平台早期 Agent 之间用网络请求直连代码里写死的 IP 和白名单节点一多直接乱成一锅粥。新 Agent 上线要改配置旧 Agent 下线了其他节点还在拼命重试消息偶尔丢失也没人管。Agent-Reach 就是在这个背景下从零手搓的一套轻量级智能体注册与消息触达框架今天我把完整的设计思路、关键实现、踩过的坑一次讲清楚希望对正在做类似架构的你有点帮助。1. 为什么多智能体协作需要一张“通讯录”从一次线上事故说起先说一个让我下定决心重构的线上事故。当时我们有三个 Agent 节点协同处理用户查询A 节点负责意图识别B 节点负责知识库检索C 节点负责生成答复。一次发布后 B 节点因为配置错误悄悄挂了但 A 节点完全不知道还在按老地址持续给它发请求。结果就是每个请求都在等待超时前端转圈转满 30 秒用户投诉电话被打爆。事后复盘时我们发现问题的根源不是代码逻辑而是节点之间缺乏一套“存在性感知”机制。1.1 原生实现的问题清单在那个阶段我们用的是最原始的 HTTP 直连方案问题积累到一定规模后集中爆发地址硬编码每个 Agent 的配置里写死其他 Agent 的 IP 和端口换一台机器就得全部改配置。无健康检查接收方挂了发送方完全无感知只能靠超时重试每次失败浪费大量时间。无消息确认发出去了就认为自己任务完成但对方到底收没收到、处理成功没成功完全没有反馈。无动态扩缩容想临时加一个 Agent 节点分担压力需要手动通知所有现有节点操作极容易出错。你可以把 Agent 想象成一家公司的员工。早期公司只有三五个人你扯着嗓子喊一声就能沟通不需要什么组织结构。等团队扩到几十上百人没有通讯录、没有前台转接你要找财务得挨个办公室敲门问这种效率迟早完蛋。Agent-Reach 就是给这群 Agent 装一套内部通讯录和中转系统。1.2 Agent-Reach 的设计边界在动手写代码之前我先明确了 Agent-Reach 要解决的问题边界这个很关键不然容易越做越臃肿只管 Agent 之间的“互认”和“互达”它负责让新 Agent 能注册进来、让其他节点能找到它、让消息能可靠投递。不管 Agent 内部的业务逻辑Agent 拿到消息之后怎么处理、用不用大模型、选什么模型这些不在 Agent-Reach 的职责范围内。提供同步和异步两种通信模式实时性要求高的调用走同步接口任务型、耗时长的请求走消息队列。两者使用同一个注册发现机制避免维护两套系统。明确了边界之后整体架构就清晰了。Agent-Reach 由三个核心模块构成注册中心Registry、消息交换层Exchange、客户端 SDKReachlet。下面的章节我会逐个拆解它们在真实项目中是怎么设计和落地的。2. 注册中心的实现细节Agent 状态感知的“心脏”注册中心是整个 Agent-Reach 系统里最容易做但最难做对的部分。它表面上只是一个存储 Agent 元数据的表但“元数据的一致性和实时性”直接决定了整个系统触达成功率。2.1 数据模型与注册协议我给每个 Agent 定义了一套简洁的注册信息用 JSON 格式表示字段如下{ agent_id: agent-query-01, agent_type: intent_recognizer, host: 192.168.1.105, port: 9011, capabilities: [text_classification, slot_filling], replica_of: agent-query, ttl: 15, rating: 0.85 }这里几个字段是后来实际运行中验证过的“刚需”agent_id全局唯一标识格式由“业务名-实例序号”组成方便日志排查时一眼看出是哪个 Agent 的哪个副本。capabilities能力标签数组。我最初没用它后来发现有业务方想按“能力”而不是“名称”去路由请求比如“找一个能做文本分类的 Agent”这个字段就成了动态路由的依据。ttl生存时间单位秒。Agent 必须在这个时间内发送心跳续约否则注册中心将该节点标记为不健康。这是实现故障感知的核心机制。注册协议我设计成了三个极简接口走 HTTP 或 gRPC 都行我最终选了 gRPC 因为团队内部已经统一使用了Register(agent_info)首次启动时调用带完整元数据。Heartbeat(agent_id, timestamp)周期性续约只带最小字段。Deregister(agent_id, reason)优雅退出时调用主动告知大家“我要走了”。2.2 为什么心跳周期定在 TTL/3这里有一个非常容易踩坑的参数设计心跳周期和 TTL 之间应该保持什么比例我一开始让 Agent 每 1 秒发一次心跳TTL 设 3 秒想着这样故障发现最快。结果注册中心一台 4 核 8G 的机器扛 50 个 Agent 就很吃力了——每秒 50 个心跳请求虽然不算多但 gRPC 连接保持、状态加锁、超时检查加在一起CPU 损耗比预想高很多。后来把心跳周期调成 5 秒、TTL 15 秒性能压力立刻降下来故障发现也才慢 5 秒完全在可接受范围内。我现在的经验公式是心跳周期 TTL3TTL 设为 15 秒时心跳周期 5 秒。这样即使网络抖动导致连续丢 2 个心跳包节点也不会被误杀而第 3 个心跳没到就基本可以判定节点真出问题了可靠性有保证。2.3 注册中心的存储选型早期我直接用 Redis 存 Agent 元数据key 是 agent_idvalue 是 JSON 字符串配合 expire 机制实现 TTL 淘汰。但后来发现一个致命问题Redis 的过期删除是惰性的Agent 已经失联了但注册中心返回的状态可能还是健康因为 Redis 还没执行淘汰。后来切成了内存注册表 Raft 日志持久化的方案。每个注册中心节点自己维护一个并发的 mapAgent 元数据存内存读路径极快变更操作注册、心跳、注销通过 Raft 复制到其他副本保证高可用场景下的强一致。这个设计简洁可靠实测 100 个 Agent 同时注册注册中心的内存占用不到 20MB读写 P99 延迟都稳定在微秒级。3. 消息交换层的触达策略从同步阻塞到异步可靠注册中心解决了“Agent 在哪”的问题但“消息怎么送过去”是另一个维度的问题。Agent-Reach 的消息交换层设计了三种模式分别对应不同的业务场景。3.1 三种投递模式怎么选模式适用场景实现方式可靠性同步请求-响应意图识别、在线问答等需要实时结果的调用gRPC 双向流请求发出后等待响应中等依赖超时重试异步任务投递离线计算、批量向量化、文档处理等耗时任务持久化消息队列消费完成后回执高at-least-once 投递广播订阅事件通知、状态同步、配置刷新发布-订阅模型中高按消费者确认进度管理同步模式不用多讲就是普通 RPC 加上注册中心的服务发现唯一需要注意的是调用链路的超时时间一定要比下游的 TTFB 长。我遇到过客户抱怨“请求总是超时”查到最后是上游设置了 3 秒超时但下游 Agent 用的模型推理一次就要 4 秒这已经不是网络问题而是业务层面的超时配置问题。异步模式是 Agent-Reach 的重点尤其适合那些“一生成大段文本、处理大文件”的 Agent 任务。我基于 etcd 的 Watch 特性封装了一个简化版任务队列结构大致如下{ task_id: task-8f2a9c, producer: agent-orchestrator, consumer: agent-summarizer, payload: { doc_url: s3://..., params: { max_length: 2000 } }, status: pending, retries: 0, created_at: 2025-06-01T10:00:00Z }消费者消费任务后必须写回一个完成状态我把它叫作“回执”。回执上会记录处理开始时间、结束时间、结果摘要以及异常信息。没有回执的任务会被认为投递失败进入重试队列。3.2 消息丢失的兜底方案就算有队列消息还是可能丢。我遇到过两种情况一种是生产端把任务写进 etcd 后进程崩溃还没等消费者拉取任务就永远停在 pending 状态另一种是消费者处理完成后写回执时 etcd 集群抖动回执没写成功任务被重复投递。针对第一种我设计了“孤儿任务扫描器”每 5 分钟扫一次所有 pending 超过 10 分钟的任务重新投递。针对第二种要求消费者在处理逻辑上做幂等——这个非常重要尤其涉及“写数据库”“发通知”这类操作一定要用任务 ID 做去重索引。老实讲这套设计最初我认为已经“足够可靠了”直到现实中发生了一次消息重复投递导致 Agent 把同一篇文档向量化入库了三次、用户搜索时看到三个相同结果的问题。从那以后“幂等优先”成了这个项目的铁律这不是设计上的锦上添花而是分布式系统生存的底线。4. 踩过的坑与对应修复策略Agent-Reach 实际运行中的三场硬仗从框架跑通到稳定上线我至少经历了三轮比较硬核的排错。这些坑的根因并不深但在没踩过之前光靠看文档和设计图是根本看不出来的。4.1 心跳风暴注册中心为何 CPU 飙到百分百系统刚上线一周注册中心所在节点 CPU 连续几天在 Scan 状态下跑满。起初我以为是机器配置不够准备盲目扩容但在加机器前先抓了一下 goroutine 和网络连接数发现一个诡异的现象连接数一直在涨但 Agent 总数只有 30 个。顺着排查发现某个 Agent 的 SDK 心跳逻辑有 bug——它把Heartbeat调用写在了重连循环里服务端每次返回失败它就越等越短最后变成了死循环高频发送。这个心跳包又触发了注册中心的续约逻辑续约的同时要更新内存表里的last_heartbeat加锁、对比、刷新形成了典型的活锁。修复方案分两层客户端限制心跳最小间隔为 1 秒连续失败后指数退避而不是疯狂重试服务端加了一个滑动窗口限流器单个 Agent 的单位时间心跳次数超阈值直接丢弃并告警。这个经历让我意识到分布式系统的很多故障根源不在设计图上标出的主链路而在于各种异常路径和自愈尝试偶然撞在一起。心跳这种看起来最微不足道的机制反而是故障放大效应最明显的环节。4.2 节点状态不同步注册信息的“脑裂”有一次上线 10 个新 Agent 节点之后老节点频繁报“目标 Agent 不可用”但注册中心查询明明显示目标节点是健康的。两边对着日志吵了半天最后发现是两边各连了一个注册中心节点而新注册的 Agent 信息只被其中一个注册中心接受了。原因很简单注册中心组的高可用用的是 Raft 一主多从模式但客户端 SDK 里的注册逻辑写错了——它向从节点发起了Register请求从节点返回了自己的状态信息而客户端没有重定向到主节点以为注册已经成功。结果主节点上根本没有这条记录。修复方法很直接所有写操作必须经过主节点。SDK 收到NOT_LEADER响应时要根据响应中的 leader hint 自动重新发起请求。同时注册中心的主节点还要支持把最新注册表定期快照推给从节点这样新接入的客户端不管连到哪台都能拿到全量的 Agent 信息。这里我给所有做注册中心类系统的朋友一个建议一定不要把“写成功”和“生效”混为一谈写操作的返回必须明确告知客户端“你的数据现在在哪个节点生效了”。4.3 消息重复触达任务队列的幂等保卫战项目跑了两个月后一位用户反馈他上传了三遍同一份文档系统竟然生成了三份摘要并发送了三封邮件。抓日志发现是任务队列的分区再平衡引起的Consumer 节点扩容时原本分配给旧节点的任务分区会被重新分配旧节点在关闭前处理了一部分任务但没来得及写回执新节点接手后又重新消费这些任务。那段时间我试过直接清空队列、重启 consumer但问题总是反复出现。后来是跟同事一起在任务表里加了一个processed_flag消费时先尝试用task_id去更新这个标记更新成功才算真正拿到任务否则跳过。这不光解决了一次线上事故也让我想明白了一个道理任务队列本身能保证的是“至少一次投递”但业务层必须用自己的手段把“至少一次”变成“恰好一次”。对 Agent 场景来说最常见的恰好一次手段就是任务 ID 幂等表因为 LLM 推理这类操作天然不可回滚所以消费端去重是最省成本的办法。5. Agent-Reach 的扩展玩法基于能力标签的动态路由注册中心落地之后我们又往上加了一层进阶功能基于能力标签的动态路由。这东西一开始不在计划里是业务方被逼出来的需求。他们有一个场景同一份文本需要做实体抽取、情感分析、语义相似度计算三个子任务而这些子任务分别由不同团队的 Agent 提供能力。入口 Agent 不可能在代码里硬编码“情感分析找谁”因为能力集群是动态变化的新能力上线、旧能力下线都要自动感知。Agent-Reach 在注册信息中已经带了capabilities字段所以动态路由做起来不算难。核心逻辑是三步调用方声明需要的 capability例如task_score text_embedding。注册中心从内存表中筛选所有声明了该 capability 且状态健康的 Agent。如果匹配到多个副本按rating加权随机选择一个作为目标。这里评分rating是我埋的一个“伏笔”。我建议每个对外服务能力的 Agent 在上报元数据时带上自己的最近服务质量指标例如平均响应时间、任务成功率、被用户点赞的次数。路由层根据评分做平滑加权轮询既能做到负载均衡又能自动规避那些服务质量突然崩掉的节点。实际效果怎么样比如我们的情感分析能力有 3 个实例在用不同的开源模型提供服务其中一个小模型的准确率近期明显下降但响应速度极快。如果单纯轮询用户就可能随机分配到差模型用 rating 加权后质量高的模型分到的流量更多系统整体体验立刻提升。这种方式远比复杂的人工配置要符合 Agent 世界的真实情况——每个 Agent 都是独立的、动态变化的个体它们互相之间应该靠机制而不是配置来择优协作。6. 压测实录一套可以照抄的触达性能基准光说架构和排错还不够一个系统能不能真正投入生产最终还是要看数据说话。我整理了一套 Agent-Reach 的触达基准测试方法和结果给你做性能评估时参考。6.1 测试环境与用例设计测试环境是三台 8C16G 的云主机一台跑注册中心两台跑 Agent 节点。Agent-Reach 客户端 SDK 和模拟业务代码部署在同一批主机上尽量走内网低延迟链路。压测工具用的是 ghzgRPC 压测工具直接对同步触达接口打流量。测试分三组组一10 个 Agent 节点同步请求-响应调用消息体 1KB。组二50 个 Agent 节点同步请求-响应调用消息体 10KB。组三100 个 Agent 节点异步任务投递单任务负载 100KB。6.2 关键指标与结果指标组一10节点组二50节点组三100节点异步每秒完成触达数320024501180触达延迟 P508ms12ms20ms触达延迟 P9942ms68ms145ms失败率0.02%0.08%0.15%注册中心 CPU12%35%64%注意看组三异步模式的延迟比同步高出不少是因为消息投递包含了进队列、落盘、消费端回执确认三个环节。这个延迟换来的是极高的可靠性我专门模拟过注册中心节点宕机异步任务一条没丢。调优过程中最有价值的一次改动是把同步触达接口的 JSON 序列化换成了 protobuf。开始我贪图调试方便直接用了 JSON over HTTP压测到 2000 QPS 左右服务端 CPU 就到 80% 了。换成 protobuf 之后同样配置轻松跑到 3000 QPS这个优化在规模上来以后是躲不掉的。7. 从 Agent-Reach 到更广阔的场景一些可以继续深挖的方向Agent-Reach 目前的形态已经足够支撑中小规模的多智能体协作平台但距离我理想中的“智能体互联网”还有不少路要走。以下几个方向是我觉得最有潜力也最值得投入精力去深挖的。单从“触达”视角来看下一个值得突破的点是内容感知的触达决策。现在的路由还是基于能力标签和评分但实际场景中用户的一个模糊意图可能需要多个 Agent 协同才能完成。Agent-Reach 未来如果能在触达层做一点语义分发——比如根据请求内容向量去匹配最合适的 Agent——那系统的智能化程度会上一个台阶。另外我还在计划把 Agent-Reach 和外部事件源打通。目前它假设所有 Agent 都在一个私有的网络信任域内但实际的智能体生态一定是跨组织、跨平台的。如果能在注册协议里加入“可信网关”的概念让外部 Agent 通过安全隧道注册进来同时保留现有的权限控制整个系统的触达范围就会被放大。最后是记忆与上下文的触达。这听起来偏应用层但我发现触达机制如果能把上下文片段随消息一起传递会大幅减少接收方 Agent 对用户意图的揣摩成本。比如一套文档问答系统里A 节点已经识别出用户想查“合同违约条款”把它作为元数据附在消息里B 节点拿到后就不用重新解析整段对话。Agent-Reach 的消息格式目前已经有metadata段的预留位我可以顺着这个方向继续扩展。踩过这么多坑之后我最大的一个体会是做智能体系统的人很多时候把精力都扑在模型效果上觉得提示词写得妙、模型选得强系统就成功了一半。但真实跑起来你会发现模型再好如果 Agent 之间触达不到、配合不起来一切都是空中楼阁。Agent-Reach 给我的最大启发并不是某个具体的算法突破而是让我意识到智能体的“社会性”和“互操作性”才是这类系统从 demo 走向生产要迈过的第一道坎。布局好这张网后面的生长空间自然会打开。
阅读完成 · 觉得有帮助?
咨询建站