☰
多智能体互联层核心设计:服务注册、能力路由与远程调用工程落地
2026/10/9 12:54:48 网站建设 项目流程

最近大半年,我把主要精力都放在一个内部代号叫Agent-Reach的智能体互联层上。说句实话,刚开始我并不想自己写这一套,市面上叫得上名字的多智能体框架我基本都试过,最后发现它们大多卡在同一个地方:Agent 与 Agent 之间没有一套像样的“注册、寻路、远程调用”机制,要么把地址写死在提示词里,要么靠人肉中转文本。Agent-Reach 做的事情,就是在这一层补上基础设施:每个智能体入网后声明自己的能力,系统负责把任务路由到正确的节点,再把工具调用的结果安全送回请求方。

这篇内容不是宣传稿,而是一份工程复盘。我会从选型逻辑讲起,落到部署步骤、接入代码和真实压测数字,最后是我调试过程中踩过的两个比较深的坑。适合谁看?如果你正在搭多 Agent 协作系统、想把 Agent 接入公司内部系统做自动化,或者只是单纯想搞清楚“Agent 与 Agent 之间到底怎么通信”,这篇文章应该能给你一些可以直接照着做的内容。

1. 为什么自己搭一套 Agent-Reach:多 Agent 协作的三个硬伤

1.1 拆出来的智能体,变成了另一种“分布式单体”

我一开始做客服自动化时,只想用一个模型 Agent 搞定所有事:它自己读订单、查库存、聊补偿。两周后提示词膨胀到几百行,上下文老是被无关技能占满,改一个流程要整段重发,还经常出现“上一条工具结果还没回来,它就开始回答客户”的时序问题。

所以我后来把这块拆成了四个智能体:意图分流 Agent 负责识别用户诉求,订单 Agent、库存 Agent、补偿 Agent 各自只处理一件事。拆分之后提示词轻了,单个技能迭代也快了,但新问题立刻浮现:如果没有统一的互联机制,拆出来的就是四座孤岛。最土的办法是在每个 Agent 的配置里写死另外三个的地址,用 HTTP 直接互相调。两个 Agent 这样玩没毛病,一旦加入区域维度——华东、华南各部署一套,配置的排列组合立刻爆炸。更难受的是,某个 Agent 扩容或者换了 IP,所有调用方都得跟着改配置。

我把这种状态叫作“分布式单体”:表面上看进程分开了,实际上耦合程度一点没降。这是我自己动手写 Agent-Reach 的第一个动机。

1.2 现成方案的三类妥协:任务表、直连队列、集中网关

动手之前,我试过的路线大概有三类。

第一类是共享数据库任务表,所有 Agent 定期轮询属于自己的任务记录。优点是接入简单,用 ORM 就能写;缺点是任务状态和业务状态搅在一起,还要自己处理“拉取后进程崩溃导致任务丢失”的补偿逻辑,轮询延迟也很影响体验。

第二类是直接连 RabbitMQ 或者 Redis 队列。消息能可靠传递,但它只管传输,不管能力发现:调用方必须提前知道“库存查询”应该发给哪个队列,Agent 扩容时队列名还是写死的。如果哪天补偿 Agent 新增了一套部署,你只能在代码里硬编码去适配。

第三类是集中式智能体网关,外部请求统一进网关,再由网关分发。这条路在调用方视角很舒服,但网关会慢慢变成一张需要人工维护所有 Agent 信息的路由表,Agent 的动态加入和退出在网关侧仍然要靠配置文件同步。

方案解决的核心问题我实际遇到的主要瓶颈
共享任务表任务可靠性状态耦合、轮询延迟、没有语义路由
MQ 直连消息传递能力发现缺失、队列名写死
集中网关全局入口动态 Agent 需人工配置、运维变成单点

我最终发现,自己需要的不是一个消息中间件,而是一层能理解“智能体能力”的互联服务:它既负责消息传输,也负责“谁会做这件事”的寻址。这个需求直接定义了 Agent-Reach 的定位。

1.3 五条验收标准,避免再造一个消息中间件

正式设计之前,我给自己定了五条验收标准,防止项目跑偏成又一个大而全的中间件:

  • Agent 能以声明式的方式接入,不能要求每个团队去学一套复杂的 SDK。
  • 能力匹配要基于语义,而不是固定字符串硬匹配。
  • 远程工具调用必须可审计,谁调了什么、结果是什么、耗时多少,要能拉出来。
  • 故障传播要可控,一个 Agent 失联不能导致整条链路雪崩。
  • 接入成本低到“30 分钟能跑通”,否则团队一定会放弃。

最后这条是我特别在意的。多智能体架构的推广障碍从来不是性能,而是每个团队的工程习惯不一样。如果接入需要改大量代码、读五十页文档,方案再先进也落不了地。顺着这五条标准,Agent-Reach 的架构才慢慢成型。

2. 架构总览:把每个智能体当成独立服务节点注册到总线

2.1 控制面与数据面分离,元数据与消息流各归其位

Agent-Reach 的整体思路是“总线”模式,但总线的实现被我拆成了两个面:控制面和数据面。

控制面负责 Agent 注册表、能力索引、令牌签发、健康状态,底层用 etcd 存储元数据。数据面负责任务请求、工具调用事件、结果回执,默认实现用 Redis Streams。我坚持分面,是因为两者的特征完全不同:注册表、能力元数据量小,但一致性要求高,需要强一致读;任务消息量大,但允许小幅延迟抖动,需要的是吞吐和水平扩展。把两者放进同一个存储里,最后一定会被各自的 SLA 拖累。

在这套设计下,Agent 不再是一段被调用的函数,而是一个拥有“服务名、端点、健康检查”的独立节点。AI 部分退回到决策器角色,它只负责判断“下一步该调用谁”,通信和协调交给 Agent-Reach 这一层来处理。

2.2 为什么消息层先用 Redis Streams,而不是一步到位上 Kafka

不少人看到我们没用 Kafka 会问一句“为什么”。我当时的估算逻辑是这样的:业务峰值每秒大约 200 个跨 Agent 任务,每个任务连 payload 平均 4KB,突发按 5 倍算,大约是 4MB/s 的写入量。这个量级用 Redis Streams 完全扛得住,而且部署简单,不用单独维护一套协调服务。Kafka 的优势在跨可用区容灾和长期日志回放,那是数据平台的事,不是多 Agent 互联层短期内必须解决的事。

我把自己当时的判断依据整理成了这张表:

对比项Redis StreamsKafka
部署复杂度单组件,运维成本低需要协调多个角色
4MB/s 写入场景实测无压力能力过剩
消费组水平扩展有,可扩展完善但有学习成本
持久化保留适合短期事件适合长期审计回放
我的选择第一阶段默认第二阶段视容灾需求再引入

我的观点很明确:先跑起来,等真正遇到“需要长期存证、需要多区域容灾”的问题时再换不迟。刻意在初期引入 Kafka,只会增加排查问题时的变量。

2.3 Agent Descriptor:智能体入网时的“自我介绍”

每个 Agent 接入时都要提交一份 Agent Descriptor,用来描述自己是谁、能干什么、怎么调用。我的设计里,它是一份 YAML 文件,包含名字、版本、能力列表、回调地址和 TTL。

能力列表是整份文件的核心。每一项都要写清楚能力 ID、自然语言描述、关键词、参数 schema 和风险等级。这份文件就是智能体在网络里的公开名片,Agent-Reach 会把它解析后写入能力索引。没有这一层,后面所有语义路由都是空中楼阁。

name: inventory-agent version: 0.4.2 ttl: 60 contact: type: http endpoint: http://10.20.3.15:9100 capabilities: - id: inventory.query name: 库存实时查询 keywords: [库存, 余量, 可售, stock] args: sku_ids: array[string] warehouse: string - id: inventory.reserve name: 库存预占 risk: high need_approval: true

这也是为什么团队接入时不需要改核心业务代码:Agent 里的业务逻辑该怎么写还怎么写,只是通过一份描述文件把自己暴露到总线里。

3. 注册心跳与能力路由:让请求找到“能做这件事”的智能体

3.1 注册、心跳与租约:失联阈值为什么定成 60 秒

Agent-Reach 使用租约机制管理存活性。Agent 注册后会拿到一个 agent_id 和 token,然后以 15 秒间隔发送心跳;注册中心收到心跳后刷新租约,租约有效期默认 60 秒。

为什么要这样定?推导过程很简单:要保证一个 Agent 真正出问题时,系统能在 60 秒内发现,并把新任务切走,这是一个可接受的故障发现窗口;而 15 秒的心跳频率,即使同时有 200 个 Agent,每秒也才十几条心跳,不会给注册中心制造压力。

判定逻辑上我做了一点宽松处理:连续超过两个心跳周期没收到,先标记为 DEGRADED;连续超过三个周期未恢复,才标记 OFFLINE。这样能避免网络瞬时抖动就把 Agent 踢下线。存量任务在节点进入 DEGRADED 后有 30 秒宽限期,期间允许 Agent 自行完成处理并回执;宽限期结束还没有结果,任务进入重试队列。

3.2 能力指纹与路由打分:匹配不能只靠关键词

路由是 Agent-Reach 的核心动作。调用方发来一个任务请求,里面包含意图、必需能力、payload,路由层需要判断应该把这个请求发给哪个 Agent。

我用的方案是能力指纹:每个能力由结构化标签和语义描述两部分组成。结构化标签用于精确匹配,比如 region=cn-east、tool=inventory.query;语义部分则由一段文本 embedding 参与相似度计算。实际打分公式是:

score = 0.6 × 工具标签命中率 + 0.25 × 语义相似度 + 0.15 × 亲和性权重

亲和性权重用来解决分流问题:同一个能力有多套部署时,权重分配决定流量是均匀分布,还是偏向已经完成预热的那一台。

我还坚持一个原则:路由结果必须可解释。所以每个匹配结果都会附上“为什么选我”的理由,这份理由会写进审计日志。业务出问题时,不需要对着黑盒猜。

3.3 一次真实请求的完整链路:从意图到回执

举一个我们线上最常见的例子:意图分流 Agent 判断用户想“查一下缺货补偿标准”,于是生成一个跨 Agent 任务。完整过程是这样的:

  1. 分流 Agent 向 Agent-Reach 提交任务请求,声明必需能力是 inventory.query 和 compensation.advise,并附上订单号和商品 ID。
  2. 路由层计算能力指纹,锁定库存 Agent 和补偿 Agent 各自的回调地址。
  3. 任务请求进入数据面,一条写入分发队列,另两条分别投递给目标 Agent。
  4. 库存 Agent 收到请求,调用自己的数据库查询工具,把结果回执给 Agent-Reach。
  5. Agent-Reach 把库存结果作为上下文传给补偿 Agent,由它生成补偿建议。
  6. 分流 Agent 在超时窗口内收到两条回执,组装成最终话术。

对应的请求体大概是这样的:

{ "request_id": "req_8f23c1", "session_id": "sess_9011", "intent": "query_stock_and_advise_compensation", "required_capabilities": ["inventory.query", "compensation.advise"], "payload": { "sku_ids": ["SKU-1001", "SKU-1002"], "order_id": "ORD-20250311-042" } }

这种链路要是靠人肉写 HTTP 地址,第一个 Agent 扩容就要全量改配置;在 Agent-Reach 里,新增实例只需重新注册,路由层会根据能力指纹和健康状态自动收敛。这就是互联层带来的实际价值。

4. 远程工具调用的安全边界:不能把数据库连接直接交给 Agent

4.1 调用链路三段:意图识别、工具发现、远程执行

我见过最错误的姿势,是把数据库账号直接塞给 Agent,理由是“反正 Agent 能自己理解需求”。这句话在演示环境成立,在生产环境就是灾难。

Agent-Reach 里的远程工具调用被拆成三个独立阶段:

  • 意图识别:发生在调用方本地,决定“需要什么能力”。
  • 工具发现:发生在路由层,确定“哪个 Agent 暴露的工具满足请求”。
  • 远程执行:发生在被调用方,由 Agent 在自己的容器里调用真正的业务系统。

三个阶段都有独立的日志和临时票据,任何一步失败都能单独定位。拆开之后,Agent 永远不会获得比它实际需要更多的权限,这比在提示词里反复强调“你不要乱调用”靠谱得多。

4.2 双向认证、一次性票据与最小权限

Agent 与 Agent-Reach 之间默认启用双向认证:Agent 持有注册 token,Agent-Reach 则在响应里附加签名头。

每次远程调用,控制面会生成一张有效期 30 秒的一次性调用票据,票据里写明了请求 ID、目标能力 ID、允许访问的参数白名单。目标 Agent 校验票据后才执行工具。

为什么有效期定 30 秒?因为正常工具调用在 90% 的情况下 5 秒内能完成,30 秒足够覆盖慢查询,又不会让票据在更长的窗口里被复用。参数白名单则直接来自 Agent Descriptor 里的 args schema,路由层在派发前就把不在 schema 内的字段剔除掉了。

这套设计和微服务间的 token 换 API Key 是同一套逻辑:安全本质是“最小权限 + 短时凭证”。AI 的加入并不会改变这个原则,只会让权限边界更需要被显式管理。

4.3 没有审计的远程调用,迟早变成事故

远程调用最大的隐患往往不是外部攻击,而是内部权限失控。

我上线初期的审计日志只记录调用方和调用结果,后来补偿 Agent 出了一次误操作,排查时发现根本不知道是谁触发的、上下文是什么。从那天起,每条远程工具调用都必须记录五件事:调用方 Agent ID、请求 ID、目标工具 ID、入参摘要、结果状态。

这里需要特别注意,入参摘要不是存原始参数,而是把敏感字段脱敏后生成摘要,既能定位问题又不至于把用户订单明文堆在日志里。审计日志默认保留 30 天,一天大概几十万条,直接放 ClickHouse,查询也很快。

我的经验是:远程调用的安全设计不是“等出事再补”,而是从第一天就把调用链路的每一跳都当成可追踪事件来设计。

5. 30 分钟跑通最小集群:部署与接入实录

5.1 最小架构拓扑与硬件配额

很多人一听到多 Agent 互联就以为要上大集群,实际上,一个验证环境两台 2C4G 的机器就够了。我当时的配置是:一台 4C8G 跑 etcd、Redis、Agent-Reach 控制面和网关;另一台 2C4G 跑库存 Agent,补偿 Agent 暂时和库存 Agent 共用。想模拟跨区域,在容器里再起几个 Agent 实例就行,不需要真的跨云。

组件实例数规格建议用途
etcd1(验证)0.5C 512M注册表与元数据
Redis11C 2G消息流与队列
control-plane11C 2G注册、心跳、路由
gateway11C 1G对外 API 入口
业务 AgentN按业务需求执行实际工具

我用 docker compose 起基础组件,业务 Agent 用普通 Python 服务跑在宿主机上,互不干扰。

5.2 接入流程:从 YAML 到心跳保活

接入一个 Agent 的步骤核心就四步:写描述文件、注册、启动心跳、订阅任务流。

先把 Agent Descriptor 放到 Agent 仓库里,内容和前面 2.3 节的 YAML 一致;然后调注册接口拿 agent_id 和 token;接着在服务主循环旁边起一个心跳线程;最后通过 SDK 接收路由过来的任务消息。

心跳线程的代码量很小,核心就是循环发请求,失败时立即重连而不是干等下一个周期:

import os import time import requests AGENT_ID = "ag_inv_0f3a" TOKEN = os.getenv("AGENT_REACH_TOKEN") HEARTBEAT_URL = f"http://reach.gateway:8080/v1/agents/{AGENT_ID}/heartbeat" def heartbeat_loop(): while True: try: resp = requests.post( HEARTBEAT_URL, headers={"Authorization": f"Bearer {TOKEN}"}, timeout=5, ) resp.raise_for_status() except Exception: time.sleep(1) continue time.sleep(15) # 在主进程启动后单独跑一个线程

接入完成后,Agent 不需要了解 Agent-Reach 内部的 etcd、Redis 怎么部署,它只关心注册地址和 token。这种低侵入性对团队推广很重要。

5.3 用 curl 模拟一次跨 Agent 调用

接入阶段最容易验证的是“能力路由”是否生效。我通常让测试同学直接用 curl 模拟调用方 Agent,请求里写清楚必需能力和 payload,不需要真实的大模型参与:

curl -X POST http://reach.gateway:8080/v1/tasks \ -H "Authorization: Bearer $CALLER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "request_id": "req_demo_001", "intent": "query_stock", "required_capabilities": ["inventory.query"], "payload": {"sku_ids": ["SKU-1001"], "warehouse": "cn-east"} }'

如果路由和 Agent 都没问题,返回的任务回执里会带上目标 Agent 生成的工具结果和匹配理由。这一步走通,说明注册、心跳、路由、远程执行整条链路是通的。大模型只是后面才接入的“决策层”。

5.4 压测基线:P50 和 P99 到底是多少

我做了 60 秒、500 并发的基础压测,数据如下:

指标P50P99
注册接口12ms78ms
路由匹配8ms35ms
端到端(调用→工具→回执)226ms743ms

端到端延迟里,“路由匹配 + 消息队列 + Agent 工具”只占一部分,另一部分来自目标 Agent 对工具的 HTTP 调用和结果序列化。整体吞吐在单网关、500 并发下稳定在 850 次/秒左右,P99 会随并发上升缓慢涨到 1.4s,对客服场景完全够用。

我特意看 P99 而不只看平均延迟,是因为多 Agent 链路里任何一个节点变慢,都会在尾延迟上放大,平均值会骗人。这点在后面排查问题时也起了关键作用。

6. 踩坑实录:心跳“假失联”与消息乱序,两周排查全记录

6.1 坑一:Agent 每隔几个小时就被误判失联

上线后最常见的告警是“inventory-agent 失联”,点进详情又显示已经恢复。第一次出现时,我以为是 etcd 的问题,查了 etcd 日志一切正常;然后查 Agent 心跳线程,发现它根本没有退出,心跳也一直在发。

那为什么注册中心还是判定失联?后来抓心跳链路才发现,问题出在 TCP 空闲连接被云环境下的防火墙静默回收:Agent 到网关之间,除了每 15 秒一个心跳包,平时没有其他流量,防火墙会把超过一定时间空闲的 TCP 连接断开。连接断开后 Agent 侧没有感知,继续往同一个 socket 上发心跳,直到队列塞满触发异常重连。中间几次心跳全部丢失,就触发了租约过期。

这个坑的典型之处在于:链路上每个组件看起来都在正常工作,只有组合起来才会出问题。

修复分两层:底层给 HTTP 客户端配置 TCP keepalive,应用层把心跳策略从“定时间隔”改成“失跳即重连 + 连续窗口判断”,并容忍最多 3 秒时钟偏差。改完后,误报率从一天几次降到零。

6.2 坑二:补偿流程的消息乱序导致扣减异常

第二个坑更隐蔽。售后补偿 Agent 在一次流程里会连续收到两个关联请求:先是“记录补偿判定”,再是“执行补偿发放”。某天晚上我发现,发放动作跑到了判定记录前面,资金流水和判定单对不上。

最初我怀疑是消息队列乱序,但查看 RabbitMQ 类中间件的常识是单队列内通常保序;我们用的 Redis Streams,其实不具备业务键级别的保序能力。

排查链路是这样的:

  1. 查看 Agent-Reach 写入 Stream 的两个 request_id,顺序是 A 在前、B 在后,没有问题。
  2. 看消费组拉取情况,发现 B 被消费者 worker2 先拉走,A 被 worker1 后拉走。
  3. 两个 worker 并发处理,最终导致时序颠倒。

根因是消费组水平扩展后,同一个 session 的消息被多个 worker 并行处理,业务顺序被打破了。解决方案是在数据面加一个按 agent_id + session_id 哈希分片的保序层:同一个业务键的请求永远进入同一个有序队列,同一时刻只有一个 worker 去消费;同时在消息里携带 prev_request_id,消费者执行前先校验前序任务已完成,双保险。

6.3 复盘:传输顺序不等于业务顺序,存活性要按窗口判断

这两个坑给我最大的启发,是基础设施层只能保证尽力投递和最终一致性,业务顺序必须由业务插件自己维护;Agent 的存活性判断不能依赖单个心跳是否到达,而要看连续窗口内的到达模式。

很多分布式系统问题看起来是“中间件故障”,实际上都是边界条件设计不到位。现在 Agent-Reach 的代码里,我已把这两个修复固化成默认策略:心跳包含客户端时间与服务端时间的偏差计算;任务执行允许通过插件声明“需要保序”的业务键。团队接入时不需要理解底层细节,默认行为就是正确的。

7. 运行三个月后的取舍判断:Agent-Reach 该用在哪儿

7.1 真正受益的场景

运行三个月后,我对“什么时候值得上这套东西”有了明确判断。

第一,当多个 Agent 需要互相调用工具、且拓扑频繁变化时,Agent-Reach 的价值最大,比如客服、订单、库存、补偿的协作。第二,当每个团队的 Agent 有不同的发布节奏和部署位置时,统一注册和路由让我们不用为每次扩容去同步配置文件。第三,当业务要求远程工具调用有审计和权限边界时,集中式的调用票据比每个 Agent 自己签一套 token 更可控。第四,做区域容灾和动态切换时,健康检查与租约机制能让流量自动避开故障节点。

这些场景的共同点是:拓扑变化成为常态,静态配置根本追不上变化速度。

7.2 不建议用的场景

反过来,如果只是单 Agent 的简单工具调度,或者两个固定部署的 Agent 之间的低频调用,硬上一套互联层只会增加复杂度。延迟敏感的特征匹配、需要 5ms 以内响应的高频动作,也不适合走这种消息总线架构,Redis 和 HTTP 叠加的延迟规模通常在几十毫秒级。

还有一点要提醒:如果你的 Agent 数量少于三个,现阶段优先把业务流程跑顺,比追求架构优雅更重要。技术选型要匹配问题规模,我见过不少项目在三个 Agent 都没有的情况下先把 Kafka、K8s 全上齐,最后全压在维护成本上,业务反而没跑起来。

7.3 如果重新做一遍,我会改进的三件事

复盘下来,有三件事会从一开始就调整:

第一,把 gRPC 接入列为第一优先,而不是等到第二版。当时为了兼容团队习惯先用 HTTP,导致部分低延迟场景不得不绕开总线直连。第二,初期就给能力指纹加上 embedding 版本和模型来源字段,后续模型更新时可以平滑重建索引,不用像现在这样手工重建。第三,把保序策略设计为协议层的标准能力,而不是事后补丁。

另外,我个人最大的体会是:想清楚“接入成本”再动手。如果接入需要读五十页文档,任何先进设计都推不动。多 Agent 互联最后拼的不是能力列表有多长,而是能不能让人在 30 分钟内跑通最小路径——Agent-Reach 在这条路上走通了,希望这篇复盘也能帮你少走几步弯路。

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

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

立即咨询