☰
多智能体协作中间层Agent-Reach:路由、注册与调度的工程实践
2026/10/28 21:40:38 网站建设 项目流程

搭建多智能体协作层时,我为什么选择了 Agent-Reach:路由、注册与调度的完整落地记录

现在做 AI 应用,基本绕不开“智能体”这三个字。但很多团队在从单 Agent 走向多 Agent 协作时,会突然发现一个很尴尬的现状:每个 Agent 都像一座孤岛,能力很强,却互相够不着。你要让一个搜索 Agent 把结果交给一个分析 Agent,中间要么靠人肉搬运 JSON,要么把代码写死成面条式调用,改一个接口恨不得重启全链路。

Agent-Reach 就是为了解决这个问题出现的。你可以把它理解成 Agent 之间的通信总线,或者说“电话交换机”——所有智能体都注册上来,通过它来发现彼此、传递消息、编排任务。它解决的痛点是:让多个 Agent 能像微服务一样被统一注册、调度和触达。适合正在做复杂 Agent 应用、多角色协作系统,或者是想把现有单 Agent 工作流升级成可扩展架构的开发者参考。全文不涉及任何外部网络依赖,全部基于本地环境的工程实践。

1. 项目背景:为什么多 Agent 协作需要一张“通信网”

1.1 “Agent 孤岛”是怎么出现的

先还原一个非常典型的开发场景。你可能已经有一个能查天气的 Agent,还有一个能做数学计算的 Agent,甚至有一个能调用内部 API 查询库存的 Agent。单独跑,每个都很好。但一旦要求它们协同完成一个复合任务,比如“查一下天气,再根据天气推算明天出行需要的交通时间”,问题就来了:第一个 Agent 的结果怎么传给第二个 Agent?如果未来再加入第三个、第四个呢?Agent 数量一多,调用关系就像一堆乱麻,A 调 B,B 调 C,C 又要调 A,代码里全是硬编码的 URL 和 API Key。

我在做这个项目之前,团队内部就已经被这种“手牵手”式的调用折磨过。于是干脆把问题抽象了一下:其实多 Agent 协作需要的是一个中间层,它干三件事——让 Agent 能“报名字”注册自己,让调用方“按名字找人”,以及把一次复杂的任务编排成一条可追踪的链路。Agent-Reach 就是按这个思路设计的。

1.2 Agent-Reach 在设计上到底做了什么

Agent-Reach 并不是一个 MRP 或者业务流程引擎那种重家伙,它的核心设计哲学是“轻路由、强协议”。也就是说,它不关心你的 Agent 内部用什么模型、什么框架,只要求你实现一个统一的接入协议。任何一个 Agent,只要按照协议声明自己的能力、接收消息的地址、以及返回结果的格式,就可以被全局发现和调用。这个思路其实借用了后端领域非常成熟的注册中心模式,类似于你手机通讯录里的联系人——你不需要记得每个人的电话号码,只需要知道名字就能拨通。

项目启动后,整体实现大概分成了四块:注册中心、消息路由、任务编排和日志追踪。前两个解决“找到人”的问题,后两个解决“把事情办完”的问题。后面的章节我会逐一拆开来讲我具体怎么实现的,每一步踩过什么坑也会详细说。

1.3 这套方案的适用边界

先泼一盆冷水:Agent-Reach 并不是所有场景下的万能药。如果你的 Agent 只有一两个,代码里 if-else 直接写死互相调用完全够用。但如果你要面对的是一群数量在五个以上、各自独立演进、甚至未来还要由不同团队维护的 Agent,那中间层就是刚需了。适用场景概括起来有三类:

  • 多个 Agent 由不同团队开发,需要统一接入和调用入口。
  • 同一个任务要跨多个 Agent 编排执行,且需要完整的链路追踪。
  • Agent 有可能增加、下线或迁移地址,不能容忍调用方跟着频繁改动。

我实际的体验是,引入 Agent-Reach 之后,新增一个 Agent 的成本从“改一堆调用方”降到了“注册一个名字”,这个体验是非常直观的。

2. Agent-Reach 整体架构与核心设计拆解

2.1 注册中心:让每个 Agent 都能“报个到”

注册中心是整个 Agent-Reach 的基石。我用了一张表来存储所有 Agent 的元信息,字段包括:Agent 的唯一标识(agent_id)、能力描述(capabilities)、访问地址(endpoint)、认证令牌(api_key)、健康状态和最后心跳时间。每个 Agent 启动时,调用/register接口把自己塞进这张表,然后每隔一段时间发一个心跳包。

这里有一个设计细节值得展开:我特意把“能力描述”做成了半结构化的标签列表,而不是一段自然语言。例如“weather.query”“calculator.advanced”,这样路由的时候做匹配效率高,也方便未来做能力矩阵的聚合展示。注册中心本身我用 Python FastAPI 写的,存储层用一个轻量的 SQLite,生产环境可以平替成 MySQL 或者 Redis。

先贴出核心的数据模型定义:

# registry/models.py from pydantic import BaseModel from typing import List class AgentInfo(BaseModel): agent_id: str name: str capabilities: List[str] endpoint: str api_key: str = "" status: str = "active"

这个类是理解 Agent-Reach 的第一个入口。agent_id 是全局唯一标识,endpoint 是其他 Agent 调它的地址,capabilities 面向路由匹配。我建议 agent_id 的命名尽量带上部门或者业务前缀,比如ops.weather.v1,排查问题的时候一眼能看出是谁。

2.2 消息路由:让请求精确找到对应的 Agent

有了注册表,下一步就是路由。路由模块存在的意义是:当一个外部请求进来,它自带一个意图,比如“查一下上海的天气然后算一下明后天温差”,路由模块必须负责把这个意图翻译成一个具体的 Agent 调用链。

在 Agent-Reach 里,我实现了一个“基于能力匹配 + 上下文传递”的路由器。请求进来时先按 capabilities 命中第一个 Agent;拿到第一个结果之后,再结合既定流程,传给下一个 Agent。整个过程是配置驱动的,流程定义长这样:

{ "workflow": "weather_and_analysis", "steps": [ {"agent": "ops.weather.v1", "input_mapping": {"city": "$request.city"}}, {"agent": "analysis.calculator.v1", "input_mapping": {"data": "$steps[0].output"}} ] }

这个 JSON 是一个“最小可用”的任务编排描述。每个步骤声明用哪个 Agent,以及输入数据从哪来。$steps[0].output这种写法,类似于模板引擎里的引用变量,让数据在 Agent 之间流动时可以明确溯源。我就是靠这个方式把原先硬编码的调用链变成了可视化、可修改的配置。

2.3 任务编排:从单次调用到跨 Agent 的流程

任务编排是 Agent-Reach 里稍微复杂的一块。它不是一个 UI 拖拉拽的编排器,而是一个执行引擎。核心接口只做三件事:接收一个 workflow 定义,按步骤串行或并行地调用 Agent,最后汇总结果。

实现上,我并没有去引入像 Celery 那样的任务队列——目前这个量级还用不上。Agent-Reach 采用的是轻量异步执行,基于 asyncio 的gather做并行调用,配合一个内存中的任务状态表来追踪执行进度。如果某一个步骤失败,默认策略是快速失败,返回错误步骤号和上下文;你也可以配置“失败后继续”的策略,用于不关键的可降级步骤。

这里我贴一段执行引擎的简化代码:

# core/executor.py import asyncio from typing import Dict, Any async def run_workflow(workflow: Dict[str, Any]): steps = workflow["steps"] results = {} for idx, step in enumerate(steps): agent_id = step["agent"] input_data = resolve_input(step.get("input_mapping", {}), results) result = await call_agent(agent_id, input_data) results[idx] = result if not result.get("success") and step.get("critical", True): return {"workflow": workflow["id"], "failed_step": idx, "results": results} return {"workflow": workflow["id"], "status": "done", "results": results}

这段代码虽然简单,却是整个 Agent-Reach 最核心的执行骨架。我刻意没有引入外部任务框架,就是想让项目保持“拿来即懂”的体量。你要是把这段改成用消息队列驱动,也只是替换掉call_agent这一层的实现逻辑而已。

3. 实操:从零实现一个 Agent-Reach 实例

3.1 准备基础环境与依赖

动手之前,先把环境理清楚。我这边是本机 Ubuntu 22.04,Python 3.10 版本。你完全可以在 macOS 甚至 Windows 上跑,因为依赖很少。核心就三个库:FastAPI 用于提供注册和路由接口,uvicorn 作为服务器,httpx 做 Agent 之间的 HTTP 调用。如果有要求,再加 pydantic 做数据校验,不过 FastAPI 本身已经携带了。

安装命令就一行:

pip install fastapi uvicorn httpx

建议提前建一个虚拟环境,免得污染系统 Python。我自己管理项目依赖有个习惯:无论项目多小,都单独建 venv。这个习惯在 Agent-Reach 的多 Agent 联调阶段救了我很多次——有个 Agent 依赖的是旧版 pydantic,如果全都装到同一个环境里,那基本是没法跑的。

3.2 动手实现一个最小注册中心

注册中心的实现,说穿了就是两张路由表。第一张是/register,接收 Agent 上报的信息并保存;第二张是/heartbeat,接收心跳。为了让后面的 Agent 能够发现彼此,我额外加了一个/lookup接口,按 agent_id 返回地址信息。

关键代码在这里:

# registry/server.py from fastapi import FastAPI, HTTPException import uvicorn app = FastAPI() agents = {} @app.post("/register") async def register(info: AgentInfo): agents[info.agent_id] = info return {"status": "ok", "agent_id": info.agent_id} @app.post("/heartbeat") async def heartbeat(agent_id: str): if agent_id not in agents: raise HTTPException(status_code=404, detail="agent not found") agents[agent_id].status = "active" return {"status": "ok"} @app.get("/lookup/{agent_id}") async def lookup(agent_id: str): agent = agents.get(agent_id) if not agent: raise HTTPException(status_code=404, detail="agent not found") return agent if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)

这段代码看着简单,但它是整个 Agent-Reach 的基础。注册中心一旦活不起来,后面所有 Agent 互相“打电话”就全没戏。我建议你写完这段代码后,先启动服务,然后手动用 curl 试一下/register和/lookup,确认基本链路通了再往下走。

3.3 如何让一个 Agent 接入 Agent-Reach

先拿一个自带的“天气 Agent”来做示例。它内部逻辑很简单:接收城市名,返回一份模拟天气数据。重要的是怎么接入 Agent-Reach 的协议。每个 Agent 需要做三件事:启动时向注册中心报到;暴露一个 HTTP 接口供其他 Agent 调用;周期性地发送心跳。

我给天气 Agent 写了一个独立的 FastAPI 实例,监听在 8001 端口,它的注册代码如下:

# agents/weather_agent.py import httpx, uvicorn from fastapi import FastAPI app = FastAPI() @app.post("/invoke") async def invoke(req: dict): city = req.get("city", "") # 这里是模拟的天气数据 return {"success": True, "city": city, "temperature": 26} if __name__ == "__main__": with httpx.Client() as client: client.post("http://localhost:8000/register", json={ "agent_id": "ops.weather.v1", "name": "Weather Agent", "capabilities": ["weather.query"], "endpoint": "http://localhost:8001/invoke", }) uvicorn.run(app, host="0.0.0.0", port=8001)

这里有个小但关键的细节,Agent 启动时注册用的是 httpx 同步客户端,在 uvicorn.run 之前完成注册。这样做的好处是,Agent 在开始对外提供服务之前,就已经在注册中心报到过了,不会被调用方短暂地“发现不到”。如果你把注册代码放到服务启动之后再去做,就会有一段窗口期,别人的请求过来会发现查无此 Agent。

3.4 让两个 Agent 通过 Agent-Reach 互相触达

一个 Agent 不算协作,我们再加一个计算 Agent,负责处理温差。它注册到 Agent-Reach 时,capabilities 写的是calculator.advanced,地址在 8002 端口。现在问题来了:天气 Agent 算出了今天的温度,怎么让计算 Agent 拿到这个温度再去算温差?

在 Agent-Reach 的架构里,这两个 Agent 不需要知道对方的存在。外部请求只需要发给路由模块,路由模块按照 workflow 配置,先调 8001 拿到温度,再把温度当作参数调 8002。整个过程相当于一次“投递”:外部请求是信封,workflow 是快递单,路由模块是快递员。

我在本地实测时使用的调用结果是,天气 Agent 返回温度 26 度,计算 Agent 自动把它减去一个基准值,返回差值。整个链路在日志里非常清楚:先显示dispatch -> ops.weather.v1,然后显示dispatch -> analysis.calculator.v1,哪个环节出了问题一眼就能定位。这也是 Agent-Reach 相比硬编码调用“分布式链路清晰”的优势。

4. 常见问题与排查技巧实录

4.1 Agent 注册成功但调用时 404

这是个人第一次接触 Agent-Reach 时会踩得最多的坑。现象是:注册接口返回成功,但调用时路由模块报 404。排查后发现,80% 的情况是 Agent 的 endpoint 写错了地址——尤其常见于把127.0.0.1和localhost混用。注册时写的是localhost,但 Agent 服务实际监听在某个局域网 IP 上,导致路由模块无法访问。

解决思路很简单:统一约定 endpoint 的写法,并且拿到注册中心之后,立刻用curl命令做一遍连通性验证。不要同时混用容器网络和宿主机网络,如果 Agent 跑在 Docker 里,endpoint 就要填容器网络的地址而不是容器的内部地址。这个经验在第 3 章那段注册代码里体现不出来,但实际部署时 90% 的问题都出在这里。

4.2 Agent 之间出现循环调用

多 Agent 协作中,最隐蔽的问题就是循环调用。A 请求 B,B 在处理过程中又去请求 A,如果没有任何保护机制,两个 Agent 就会互相发消息,直到资源耗尽。Agent-Reach 里我加了一个调用深度计数,每次进入路由模块时给请求加一个头部标记,比如x-agent-reach-depth: 1,每往下一层深度加一,超过设定的最大深度(我这里设的是 5)就直接拒绝,然后返回“调用链路过深”的错误。

这个检查和防重用的思路是一样的。因为 Agent 编排的调用链在多数情况下是有限的 DAG,绝对不该出现深度无限增长的情况。如果你在实战中发现了深度超限的报错,首先要做的不是调大阈值,而是去检查 workflow 定义是不是出现了环形依赖。

4.3 认证和可控性如何取舍

Agent-Reach 目前内置了简单的 api_key 校验。每个 Agent 在注册时可以带上一个 token,路由模块在调用它的时候,会在 HTTP 头部带上这个 token,Agent 收到请求后校验一下。团队里做联调的时候,这个机制能有效地拦住很多误调用。但注意,这只是基础认证,真正上生产之前,建议在网关层做更完善的鉴权,比如引入 OAuth2 或者 mTLS。

我把这个取舍写在这里,是希望你清楚:Agent-Reach 的设计重点是消息路由和编排,而不是安全边界。安全是一个完整体系,不要把一个小中间件当成全功能安全网关。如果你部署的环境对安全要求很高,建议结合公司的统一网关、审计日志和网络策略一起用。

4.4 性能瓶颈与优化实录

用 Agent-Reach 做联调的过程中,我压过一次简单的并发测试。场景是 100 个并发请求同时调用一条包含两个 Agent 的 workflow。结果发现,注册中心虽然是异步的,但 SQLite 的写入锁开始成为瓶颈,表现为偶尔的超时、日志里有 database is locked 的报错。

我当时做了两个优化。第一,注册中心的写操作从同步改为异步并加内存缓冲,心跳更新先在内存里做,定期批量刷到 SQLite;第二,日常查询走内存副本,避免高频读操作打到底层数据库。优化之后,压测结果提升明显,数据库锁报错完全消失。这是所有带持久化状态的中间件都会遇到的问题,你一旦在 Agent-Reach 上做生产部署,内存缓冲几乎是必选项。

4.5 调试一个小技巧:结构化日志

Agent-Reach 的日志我统一走 JSON 格式,每条日志带上请求 ID、Agent ID、步骤号。这个方法在排查问题时效率极高——因为跨 Agent 的调用链,如果只是普通的文本日志,你根本没法把一串关联的操作串起来。有了请求 ID 之后,你可以用一条命令过滤整条链路的日志:

grep "req-20240601-abc123" agent_reach.log

这个习惯我从写第一行代码就保持了,实际排障时帮了很大的忙。很多项目问题难查,不是代码逻辑复杂,而是日志根本看不懂因果链条。

5. 工具选型解析与后续扩展思考

5.1 为什么用 FastAPI 而不是 Spring 或 Node.js

很多做后端的朋友看到我用 Python + FastAPI 会问为什么不用 Spring Cloud 那一套。其实道理很简单:Agent-Reach 的核心用户是 AI 应用开发者,团队里最熟悉的语言大概率是 Python;而且 Agent 生态里大部分组件(LangChain、LlamaIndex 等)都对 Python 友好,选 FastAPI 可以把整个技术栈的认知成本降到最低。

FastAPI 的异步性能也足够支撑中等规模的 Agent 调度。一次简单的消息路由,耗时基本在几十毫秒级别,除非你到了每天千万级请求的体量,否则 FastAPI 完全够用。技术选型的一个经验是选择团队维护成本最低的方案,而不是性能最强的方案。

5.2 从 Agent-Reach 可以扩展出的三个方向

Agent-Reach 目前是一个“可用”的中间件,但距离真正的生产级编排平台还有距离。我自己梳理了三个可行的扩展方向,这里列出来仅供参考。

第一个方向是增加“人工审核”节点。某些流程涉及关键决策时,不应该完全交给 Agent 自动执行。你可以在 workflow 里增加一个human_approval: true的步骤,Agent-Reach 执行到这个步骤时挂起,通过 webhook 通知人去审批,审批通过后自动继续。这在自动化办公、自动化运维场景里非常实用。

第二个方向是引入“多版本灰度”。每个 Agent 可能都有新老版本,在交接期你想让一部分流量走新版本,一部分继续走旧版本。现在 Agent-Reach 的注册表是按 agent_id 唯一识别的,我建议未来可以改成支持agent_id加version的双字段,并在路由层做按比例分发。

第三个方向是提供“能力地图”的可视化。现在只能在代码里看注册表数据,不直观。如果做一个简单的管理面板,把所有 Agent 的关系、调用频次、错误率展示出来,对线上运维会有很大价值。我自己下一步就打算给 Agent-Reach 加这个功能。

5.3 Agent-Reach 在团队协作中的真实感受

最后聊点软性的东西。这个项目做完后,我最大的感受是:多 Agent 项目真正的复杂度不在单个 Agent 的智商,而在它们之间怎么配合。Agent-Reach 不是那种能让你“哇”一下的炫酷技术,但它能持续帮你省下那些无休止的接口沟通、联调和排障时间。当一个新的 Agent 加入到系统里,团队只需要让它报个到,不需要去戳其他组的代码,这种体验是可以直接转化为交付速度的。

如果你也在做 Agent 相关的应用,我的建议是尽早把路由、注册、编排这层抽象建起来。不用做得像我的这个项目那么完整,哪怕只是先把你两个 Agent 之间的调用改成“通过一个名字找到对方”,后面扩展新 Agent 时你就会发现收益远超预期。

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

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

立即咨询