资讯详情

多智能体协作可达性设计:服务注册与消息路由的工程实践

📅 2026/10/29 9:20:03 | 华诺云谱 👁 阅读
多智能体协作可达性设计:服务注册与消息路由的工程实践
多智能体协作这几年被炒得很热但真正在项目里跑过几个Agent的人应该都有同感最磨人的从来不是模型能力不够而是Agent和Agent之间互相够不着。你设计了一个调度Agent它要去调用数据分析Agent的结果结果消息发出去了石沉大海你配置了一个工具Agent另一个Agent按文档调用它却一直超时重试。这些问题说到底都属于同一个范畴——智能体的可达性Agent Reachability。Agent-Reach就是我针对这个问题做的一套实操型方案它不是一个炫技的AI框架而是一个聚焦多智能体如何可靠地互相发现、触达、协作的项目。它解决的核心痛点很具体当一个系统里有几十个甚至上百个Agent时如何让任意一个Agent在正确的时间找到正确的另一个Agent并在网络抖动、服务重启、能力变更的情况下依然稳定完成调用。这篇文章我会从设计思路、能力注册、消息路由、坑位排查到权限控制完整拆一遍适合正在做Agent编排、或者打算把团队里的AI服务从单点走向协同的开发者参考。1. 为什么我会做Agent-Reach这个项目先说清楚背景。我之前在一个偏中台性质的团队负责把各种AI能力封装成服务给业务线用。一开始是单体Agent所有工具、模型、记忆逻辑都塞在一个进程里看起来一切正常。后来业务变多开始拆成独立的Agent服务有做意图识别的、有做知识库检索的、有做数据抽取的、有做内容生成的。它们之间需要互相调用这时候问题就来了。1.1 从一次失败的Agent协作说起那次故障我记得很清楚。主调度Agent需要调用一个行业报告生成Agent这个Agent本身又依赖一个数据查询Agent去拉取指标。我们当时用的方式很简单就是在配置里写死了对方的HTTP地址和接口路径。上线第一天就翻车了数据查询Agent因为一次内存问题发生了重启它的服务端口临时变了而行业报告生成Agent还对着旧地址狂发请求。调度Agent那边显示已派发任务但是下游一直在报连接超时最后整个任务链卡死用户等了三分钟拿到一个报错。这个事儿让我意识到Agent之间的协作不能靠大家商量好一个地址来实现。Agent是动态的它的状态、位置、能力都会变化你必须有意识地设计一套机制去解决它现在在哪、它还能不能干活、它用什么方式沟通这些问题。这套机制就是Agent-Reach要做的可达性保障。1.2 三个维度的可达性发现可达、契约可达、路由可达我在设计Agent-Reach时把可达性拆成了三个层面每个层面解决不同的问题。第一个是发现可达。一个Agent怎么知道另一个Agent存在它有证书吗有调用方式吗在一堆服务里能不能按能力找到对方这不是网络层面上通不通的问题而是信息层面上知不知道的问题。很多协作失败不是网络断了而是发起方根本不知道要找谁或者拿到的候选列表是过期的。第二个是契约可达。就算找到了对方你知道它的输入输出格式吗你知道它期望的参数是JSON还是XML字段是驼峰还是下划线调用是同步还是异步如果两边契约不一致就算网络全通消息到了对方手里也只是无法理解的一堆字符。我见过太多Agent对接的坑最后查出来是字段名大小写不一致这属于典型的物理可达但语义不可达。第三个是路由可达。找到对方、契约也对了但是调用链路上有中间层——可能是网关、可能是Agent Hub、可能是消息队列。这条链路上的节点是否有足够的权限转发消息是否有路由表规则把请求送往正确的实例是否会因为负载均衡策略把请求送到了一个已经不健康的节点Agent-Reach把这三个层面分开处理在注册阶段解决发现在配置阶段解决契约在运行阶段解决路由。分开处理有个好处就是出问题的时候你能快速定位是哪一个层面失效而不是所有错误都混在一起看起来都是调用失败。2. Agent-Reach 的核心设计能力注册与发现机制这一块是整个项目的地基。没有注册机制后面一切免谈。Agent-Reach在架构上做了一个轻量的注册中心每个Agent在启动时必须向注册中心上报自己的基础信息和能力声明。这个设计借鉴了服务注册发现的思路但同时补充了AI场景下特有的能力描述维度。2.1 能力注册表Agent的名片注册中心里维护的是一张能力注册表每一条记录对应一个Agent实例。字段不只是IP和端口还包括以下几类关键信息。第一类是身份信息比如agent_id、agent_name、版本号、运行模式同步/异步。agent_id是全局唯一的绝对不能复用不然会出现幽灵实例。第二类是能力声明也就是这个Agent能干什么。第三类是接入信息包括通信协议、接口路径、鉴权方式。第四类是运行时状态由Agent定时上报。下面是我在项目里实际用的注册数据示例用JSON格式展示一条注册记录{ agent_id: agent-report-gen-001, agent_name: report_generator, version: 2.3.1, mode: async, capabilities: [ { name: generate_industry_report, description: 根据指定行业和时间范围生成结构化报告, input_schema: { industry: string, time_range: string, include_charts: boolean }, output_schema: { report_id: string, status: string, download_url: string }, qps: 10, latency_sla_ms: 5000 } ], endpoints: { primary: http://10.2.3.4:8080/api/call, fallback: http://10.2.3.5:8081/api/call }, auth: { type: token, key_name: X-Agent-Token }, health_check: { path: /healthz, interval_sec: 15, timeout_sec: 3 } }这里有个细节值得强调capabilities里我专门写了input_schema和output_schema。这就是前面说到的契约可达的落地方式。只有把请求和响应的结构显式声明出来别的Agent在对接时才不需要靠猜。你写一个Python脚本去调别人的接口还得先读人家的文档呢Agent之间也一样只不过它读的是机器可解析的schema。2.2 能力描述文件怎么写直接用JSON往注册中心POST当然可以但在Agent-Reach里我推荐把能力描述做成独立的配置文件随Agent一起发布。原因很简单配置文件可以进Git仓库做版本管理发生人员变动时也不至于把注册逻辑搞丢。能力描述文件我用的是YAML格式因为可读性更好。核心是name、description、input_schema、output_schema、tags这几个字段。tags特别有用它用于能力检索。比如一个Agent需要查天气它不一定要知道具体哪个Agent提供这个能力只要向注册中心发起一个基于标签的查询注册中心返回所有打了weather标签的Agent即可。这就是从指定调用到按能力调用的转变。tags的粒度需要把控一下太粗了会匹配到一堆不相关内容太细了又难以命中。我一般建议tags控制在5到10个使用小写字母加连字符的命名风格比如data-query、report-generate、text-summarize。同一类能力的不同实现可以用version或者vendor字段区分而不是在tags里造一堆近义词。2.3 健康检查与心跳机制注册表不能只进不出必须有机制把死掉的Agent清出去否则调用方会不断尝试触碰那些早已不存在的实例。这是Agent-Reach里生命周期管理的基础。在Agent-Reach里健康检查采用了主动探测和被动心跳相结合的方式。主动探测是指注册中心定期向Agent的health_check.path发起HTTP请求看返回的状态码。被动心跳是指Agent自己每隔一段时间上报一个心跳包里面带上当前的负载、最近一次任务完成时间、内存使用率等指标。两条腿走路的好处是主动探测能发现进程活着但响应异常的情况心跳能反映进程正常但正忙于大量任务的情况。心跳间隔和超时阈值直接决定了故障发现的时效。我这里用过一个经验公式健康检查间隔不要超过业务SLA中允许的最大故障感知时间的一半。举个例子如果你的业务要求一个Agent不可用最迟30秒内必须被感知到那把健康检查间隔设为15秒连续两次失败才判定下线这样最坏情况是30秒出头基本踩线。如果要求再严格就缩短间隔并加入TCP层的快速探测。3. 消息路由与调用链从找到到用上注册环节解决的是我知道你存在的问题接下来要解决的是我怎样把消息成功送到你手里并拿到结果。Agent-Reach在调用链上设计了路由表和一套调用策略同时把超时、重试、熔断这些稳定性措施内置进去了。3.1 路由表与优先级路由表本质上是消息应该往哪走的决策依据。Agent-Reach的每个Agent都维护一份本地路由缓存这份缓存是从注册中心拉取后生成的包含目标Agent所有可用实例、协议类型、优先级权重。路由选择不是简单随机挑一个实例而是按照优先级顺序尝试。优先级由几个因素决定实例的最近健康状态健康权重最高、响应延迟越低越优先、负载情况负载越低越优先、以及发布版本灰度期间新版本可能降低优先级。我用一个加权公式计算每个实例的综合得分然后按得分从高到低排序。def score_instance(instance, current_time): health_score instance.health_factor latency_score max(0, 1 - instance.avg_latency_ms / instance.latency_sla_ms) load_score 1 - instance.current_load / instance.max_load version_score 1.0 if instance.is_stable else 0.7 return (0.4 * health_score 0.3 * latency_score 0.2 * load_score 0.1 * version_score)这几个权重的比例不是拍脑袋定的它反映的是我对Agent协作稳定性的优先级排序健康状态最重要因为调用一个不健康实例大概率会失败延迟第二因为Agent协作往往是链式调用每一跳延迟都会乘上去负载第三避免把消息打到一个已经很忙的Agent版本因素权重最低只在灰度发布场景下起作用。3.2 超时与重试参数的经验值调用超时设置是Agent协作里最讲究的环节。设太短慢一点的Agent永远被误判为不可用设太长链路一多整体响应时间就失控了。我在Agent-Reach里默认把单次Agent调用的超时设为目标Agent所声明latency_sla_ms的1.5倍。为什么是1.5倍而不是正好1倍因为SLA一般是理想状态下的P95值实际运行中网络波动、GC暂停、队列排队都会增加耗时留出50%的缓冲比较合理。重试策略上我的经验是只对可重试的失败类型做重试。连接超时、网络抖动、返回503这类错误可以重试而业务逻辑错误比如参数校验失败、数据格式错误绝对不能重试重试只会放大问题。重试的次数和退避策略也有讲究。我用的是指数退避加抖动exponential backoff with jitter基础退避时间200毫秒每次翻倍最多重试3次。加抖动的目的是防止多个Agent同时重试形成共振把下游打崩。下面的伪代码展示了Agent-Reach里的重试逻辑def call_with_retry(endpoint, payload, max_retries3): delay 0.2 for attempt in range(max_retries 1): try: return invoke_agent(endpoint, payload) except NetworkRetryableError as e: if attempt max_retries: raise sleep(delay * (2 ** attempt) random.uniform(0, 0.1)) return None这套参数在我的生产环境里跑了半年多调用成功率稳定在99.5%以上。要注意的是不同业务的容忍度不同如果是实时交互场景这个超时设置可能太长需要压缩到几百毫秒级别如果是离线任务型场景甚至可以放宽到几十秒。3.3 熔断和降级不要让一个Agent拖垮全局Agent协作最怕一个慢节点拖垮整条链。如果A调用B超时5秒B调用C又超时5秒两层下来就是10秒起步。更麻烦的是超时并不代表B停止等待了B可能在后台还在继续执行然后C的结果回来了B接着往下跑后面的调用又把任务执行了一遍代价不可控。为了避免这种情况Agent-Reach在每个调用节点上加了熔断器。核心逻辑是监控最近一段时间内的调用错误率一旦错误率超过阈值熔断器打开后续请求不再发往下游而是直接快速失败或者走降级逻辑。降级有几种方式返回缓存中的历史结果、调用一个功能相似的备用Agent、或者返回一个默认的包含错误信息的响应体由上层决定如何处理。熔断器的参数我一般这样设置滑动窗口时间10秒窗口内最少请求数20次错误率阈值50%。这些值不是普遍适用如果你的Agent调用频率很低比如每分钟几次20次最少请求数可能等很久都无法触发熔断需要相应调低如果调用频率很高又需要提高最小请求数以避免偶发抖动误触发。原则是熔断必须基于足够的统计样本不能因为一两次偶发错误就断掉整条链路。4. 落地过程中的坑与排查链路再好的设计也要经过真实的故障检验。这一章节我完整记录一次Agent-Reach上线后遇到的典型故障从现象、排查链到最终根因整个过程走一遍。你会发现很多问题的实质和表象完全不是一回事。4.1 现象协作任务一直卡在等待响应那次故障是凌晨2点开始出现的。线上一个舆情分析Agent调数据采集Agent的接口任务状态一直显示等待响应。从调用方的日志看请求确实发出去了但迟迟没有收到响应。最诡异的是以前偶发的超时都是秒级的这次直接卡死没有触发任何超时或者重试机制。我第一反应是网络问题直接ping数据采集Agent所在节点通了telnet它暴露的端口也通。那问题可能出在应用层。打开数据采集Agent的日志发现它确实收到了请求也进入了处理函数但处理函数卡在一个内部阻塞调用上。这个调用不是HTTP请求而是它向一个外部数据库发起的连接。4.2 排查链路从路由日志到健康检查我按模块逐层排查。先看路由日志Agent-Reach的路由层会记录每一次转发决策包括选择了哪个实例、得分是多少、是否触发了重试。日志显示数据采集Agent当时只有一个可用实例实例健康状态正常路由层没有做任何特殊处理选择了主实例。然后看健康检查记录。注册中心的健康检查显示数据采集Agent的/probe接口在故障前一直是200故障发生后依然有几次200——也就是说注册中心始终认为它是健康的。这就说明问题不在Agent-Reach的发现和路由层而是在数据采集Agent自身的业务逻辑内部。我又回去看它的进程线程状态。jstack打印出来的线程栈显示有一个线程阻塞在数据库连接获取环节正处于连接池等待状态。原因是数据库连接池的maximumPoolSize设置为了5而前一个晚上有一个批量任务同时启动了10个采集子任务每个子任务都要占用一个数据库连接。连接池被打满后来的请求只能排队等着空闲连接而前5个子任务又因为数据量特别大处理时间被拉得很长队列里的请求就一直在等。4.3 根因与修复根因找到了不是网络、不是路由、不是Agent-Reach的问题而是Agent内部依赖资源不足导致的线程阻塞。这类问题最坑的地方在于Agent的存活状态是正常的HTTP端口也能响应健康检查返回200但实际上它已经无法处理业务请求了。健康检查只探活不探业务可用性这是很多Agent系统的一个盲区。修复方案分两步。第一步是临时救火把数据库连接池的maximumPoolSize调大到15同时把阻塞的线程解除任务重新跑起来。第二步是长期优化在Agent的health check里增加业务健康判断——只有连接池使用率低于80%、线程池活跃度低于90%时才返回200否则返回503。这样注册中心就能及时发现半死不活的Agent把它临时标记为不健康路由层自然会把请求切换到其他可用实例。4.4 类似问题的快速定位清单经过这次故障我在Agent-Reach的运维文档里加了一份排查清单。当你遇到Agent调用异常时按照这个顺序逐层排查可以节省大量时间。第一层检查注册中心确认目标Agent是否还存在、状态是否为健康。如果状态异常直接分析下线原因这一步可以排除一大部分问题。第二层检查路由日志确认请求实际发往了哪个实例、用了哪个协议、走的哪个端口。这里能发现路由决策不符合预期的情况比如规则没加载、权重配置被修改过。第三层检查网络连通性确认实例的IP和端口从调用方所在节点是否可达。第四层检查目标Agent的应用日志看请求是否到达、处理过程有没有报错或者卡住。第五层检查目标Agent依赖的外部资源比如数据库连接池、缓存集群、外部API的调用状态。最后一层才考虑Agent-Reach自身的问题比如是不是注册中心有bug、路由表是不是没刷新、熔断器是不是误打开了。之所以把它放最后是因为从我遇到的故障比例来看绝大多数问题出在被调Agent的自身逻辑或者它依赖的资源上Agent-Reach这类框架层反而相对稳定。5. 安全边界与权限控制Agent之间可以互相调用了随之而来的安全问题就不能忽视。在企业内部环境里Agent持有的数据和能力可能涉及敏感信息如果不加控制任何一个被攻破的Agent都可能变成横向攻击的跳板。Agent-Reach在安全设计上做了几层防护。5.1 Agent身份与TokenAgent之间通信必须有身份凭证。Agent-Reach为每个Agent颁发一对Client ID和Client Secret启动时用它们换取短期访问Token。Token的有效期一般设为2小时过期后自动刷新。关键点是Agent之间的调用必须携带Token注册中心不做隐式信任即使请求来自内网IP。在实际部署中我见过很多团队图省事Agent之间的调用直接走内网裸HTTP觉得反正防火墙挡着呢。但现代微服务和Agent架构里攻击面非常大一个低权限Agent的日志泄露都可能被利用。Token化之后至少能保证消息来源可识别、调用可追溯。5.2 能力分级与授权不同Agent的能力灵敏度是不一样的。一个生成摘要的Agent相对低风险一个查询员工薪资的Agent就是高敏感。Agent-Reach引入了能力分级授权每个Agent在注册时声明自己的安全等级调用方必须拥有相匹配或更高等级的授权才能调用。我在项目里将等级分为L1到L4。L1是公开通用能力比如文本转写、通用搜索L2是内部业务能力比如生成销售报告L3是包含个人信息的能力比如查询某用户的订单记录L4是核心资产能力比如批量导出客户库里所有联系方式。路由层在转发请求前会校验调用方Agent的授权范围如果等级不够直接拒绝并记录审计日志。这套分级的实现并不复杂本质上是在能力注册表的每条能力里加一个security_level字段在调用请求的Token里附带调用方的授权等级路由层做一个简单的数值比较。难的是让各个Agent团队接受这个约束并正确为每个能力定级。我的建议是宁可初定偏高再用实际使用情况放宽也不要一开始就漏成筛子后期收紧时各个业务线会很不适应。5.3 审计与可观测性Agent-Reach从设计之初就把审计日志当作一等公民。每一次跨Agent调用都会记录下调用方Agent ID、被调方Agent ID、调用时间、路由决策、成功失败状态、耗时等字段。这些日志统一汇集到可观测性平台既用于排障也用于安全审计。发生了某个Agent异常调用另外一个Agent的告警时你可以快速回放当时的调用链。可观测性这块我用了三个维度的监控指标调用成功率、P90/P99延迟、以及错误类型分布。错误类型分布尤其重要它把连接失败超时业务拒绝熔断拒绝分开统计能直接反映系统健康状态的组成。如果某天熔断拒绝的比例突然升高虽然调用成功率还是99%实际上网络通信已经出问题了只是熔断器在兜底。没有这个维度你很难及时发现这些隐患。监控之外规则引擎也值得配置一套告警。我最常用的一条告警规则是在1分钟内同一个调用方Agent对同一个被调方Agent的重试次数超过10次立即触发告警。这种模式通常说明被调方存在持续性问题继续重试没有意义需要人工介入。另外新注册的Agent上线后前24小时内额外增加监控频次因为新上线Agent的配置和依赖往往不完善容易出幺蛾子。最后再说一点整个Agent-Reach项目给我最大的体会是做多Agent系统别把重心全放在模型怎么聪明、提示词怎么花哨上先把Agent之间怎么稳定地互相够到这件事做扎实。你设计的智能体网络里任何一个节点不可达、契约对不齐、权限挡路业务都会立刻感受到而这些都跟模型智商无关。如果你准备在项目里搭建Agent协作体系我建议从最小可用版本开始一个注册中心、一套能力声明、一份路由规则、一批健康检查。先让两个Agent稳定互调再逐步扩张。不要一上来就搞几十个Agent互相协作那样排障成本会吃掉你所有开发时间。Agent-Reach在我这边已经稳定运行了大半年中间迭代了超过六版配置规范每一次迭代都源于实际故障的教训。希望这套思路和细节能帮你在Agent协作的路上少踩几个坑。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑