☰
Agent-Reach:多智能体可靠触达的调度基础设施设计与实践
2026/10/8 15:50:28 网站建设 项目流程

多智能体应用这几年被吹得很凶,但真正动手做过的人都会遇到同一个坎:单个智能体再聪明,也架不住业务一复杂就开始力不从心。真正让人头疼的往往不是模型能力本身,而是智能体之间的触达问题——谁有权限调用谁、怎么找到对方、调用过去之后上下文怎么传、失败了怎么兜底。Agent-Reach就是我在这个方向上折腾的一套基础设施,核心干一件事:让智能体能够被另一个智能体可靠地发现、调度和调用。这篇文章会把它的设计思路、落地过程和踩过的坑完整拆出来,适合那些正在做Agent平台、多智能体编排或者AI中台的开发者参考。

1. Agent-Reach到底在解决什么问题

1.1 你的智能体们其实是一座座孤岛

先说一个我观察到的普遍现象。大部分团队做AI应用,一开始都是单Agent形态:一个客服机器人、一个数据分析助手、一个工单处理机器人,各自独立部署,各自接自己的模型和工具。单看每个Agent,效果都还行。但业务流程一旦跨Agent,问题就立刻暴露了。

举个例子,一个用户找客服投诉“物流显示签收但我没收到货”,客服Agent查完订单链路,发现需要去仓储系统核实,而仓储那块功能是另一个团队用独立的Agent做的。这时候没有Agent-Reach这类东西,你只能硬编码API调用:客服Agent直接HTTP请求仓储Agent的某个接口。听起来简单,但实战里你会发现一大堆问题。仓储Agent的接口参数格式不透明,响应结构两套团队各写各的,调用失败没有统一的补偿机制,更别提调用链路上谁出了问题都很难定位。

说到底,这就是智能体孤岛化。每个Agent都像一个小岛,岛上自动化做得很好,岛和岛之间却只能靠人肉搭桥。Agent-Reach要做的事情,就是在这个岛上架一个统一的“调度总机”,让任何Agent都能通过标准化的方式触达另一个Agent,而不需要关心对方的内部实现。

1.2 为什么不能直接“做个API网关”了事

可能有人会想:这不就是API网关吗?Kong、APISIX都能做路由转发,再加个注册中心不就完事了?

这个想法对了一半。智能体之间的触达,底层确实是API调用,但上层多了一层非常关键的东西:语义路由和上下文感知。

传统网关转发的是HTTP请求,路径、方法是预先定义好的,/api/order/getById这种接口是死的。但Agent之间的调用,很多时候请求方自己都不知道该调谁。比如上面那个客服Agent,它手头只有一个模糊的诉求“帮我核实这个包裹的去向”,它需要先理解这个诉求,再去匹配到底是仓储Agent、物流Agent还是订单Agent能解决。这个过程不能靠写死的路由表,得靠语义匹配。

另外,智能体调用的还不仅仅是接口,是一段业务上下文加上一个目标。A Agent把用户意图、历史会话、业务数据传给B Agent,B Agent执行完还要把结果连同新的上下文回传。这比传统API的“请求-响应”复杂得多,它本质上是分布式多轮对话。普通网关模型根本不支持这种语义层面的透传和上下文管理。

所以Agent-Reach不是APISIX的替代品,它应该理解成APISIX之上的一层智能调度系统,专门处理“模糊诉求到具体Agent”的映射、“长上下文在Agent之间的安全传递”以及“调用链路的统一治理”。这个概念定下来之后,后面的架构设计就顺了。

2. 核心架构:Agent-Reach的四层设计

2.1 注册层:AgentCard是每个智能体的“身份名片”

想要让智能体被触达,第一步是先让平台知道它存在。传统服务注册用的是服务名+IP端口,但对智能体来说这远远不够。我参考了开源社区里智能体描述协议的一些思路,做了一份AgentCard规范,每个Agent接入时必须提供一份结构化描述,类似智能体的“身份名片”。

这份AgentCard长这样:

{ "agent_id": "logistics-warehouse-agent", "name": "仓储物流核验智能体", "version": "2.1.0", "capabilities": [ { "name": "package_location_check", "description": "核验包裹的物流轨迹与实际签收状态,返回异常原因", "input_schema": { "type": "object", "properties": { "tracking_id": {"type": "string", "description": "物流单号"}, "order_id": {"type": "string", "description": "订单编号"} }, "required": ["tracking_id"] }, "output_schema": { "type": "object", "properties": { "is_abnormal": {"type": "boolean"}, "reason": {"type": "string"}, "evidence": {"type": "array", "items": {"type": "string"}} } } } ], "rank": 0.82, "max_concurrency": 10, "timeout_ms": 30000, "permission_level": "internal", "cost_per_call": 0.002 }

这里最关键的是capabilities,它描述了这个Agent能干什么、输入输出长什么样。注意description我建议写详细一些,因为后面语义路由要拿它和用户诉求做匹配,描述越精确,路由准确率越高。permission_level则定义了这Agent能服务的调用方范围,是实现权限控制的基础。

2.2 路由层:把意图解析交给大模型,但别全交给它

路由层是Agent-Reach最核心也最复杂的部分。请求进来之后,路由层需要回答一个问题:这个诉求应该交给哪个Agent?

我试过两种路线,踩了个大坑之后才找到平衡。第一种是纯关键词匹配,比如用户说“物流”,就路由给仓储Agent。但这个方案太脆了,用户说“我的货去哪了”就没有“物流”关键词,直接匹配失败。第二种是纯靠大模型做意图识别,把所有AgentCard一股脑塞给LLM让它选。试过之后发现,当注册的Agent超过5个,或者Agent能力之间边界模糊时,大模型的选型结果就开始飘,而且每次调用的Token消耗和延迟都不小。

最后我采用的是**“召回-打分-兜底”三级路由**:

  1. 先用自己的轻量Embedding模型,把请求文本和所有AgentCard里的capabilities.description做向量余弦相似度计算,召回Top 5候选。
  2. 把Top 5候选的AgentCard关键信息交给大模型,让它做最后裁决,返回一个带JSON结构的决策结果,包含选中的Agent和需要填充的参数。
  3. 如果大模型返回的Agent不可用(比如并发满了或熔断了),走兜底策略:降级到Top 2候选,或者返回“无法处理”并给出可尝试的Agent列表。

这三级路由的好处是:向量召回兜底了大多数常规请求,只有模糊度较高的请求才消耗大模型的推理成本。实测下来,这个方案把路由层的大模型调用量降到了总请求量的三成左右,单次路由延迟从原来的2到3秒压到了500毫秒以内。

2.3 执行层:统一协议收敛掉各Agent的“方言”

路由只是找到了目标,真正要触达的时候还得解决一个实际的问题:每个Agent的调用协议不一样。有的Agent内部是REST API,有的是gRPC,有的是WebSocket长连接,有的甚至只提供了一个Python SDK。如果让路由层去适配每一种协议,那Agent-Reach就变成了一堆胶水代码的集成品,根本维护不下去。

我的做法是在Agent侧加一个Agent Adapter。这个Adapter通常作为Agent的一个轻量边车组件,暴露一个标准的HTTP接口给Agent-Reach调用,然后由Adapter内部做协议转换,去调用Agent自己的核心代码。相当于给每个Agent配了一个“翻译官”。

执行层的调用流程大致是:

  1. 路由层确定目标Agent后,从注册中心拿到它的Adapter地址。
  2. Agent-Reach按规范构造执行请求,包含request_id、intent、parameters、context_ref。
  3. Adapter收到后转换协议,调用Agent核心逻辑,等待结果返回。
  4. Adapter把结果按标准格式封装回传,Agent-Reach记录链路的耗时、Token消耗、结果状态。

2.4 治理层:权限、审计和链路追踪缺一不可

多Agent协作最大的隐患是权限边界模糊。一个调用方Agent能调用的其他Agent,必须被显式授权。我基于AgentCard里的permission_level再加了一张访问控制表,类似这样:

调用方Agent被调用方Agent允许的能力生效时间
customer-servicelogistics-warehousepackage_location_check, return_apply2026-04-01至2026-12-31
>def route_request(request: AgentRequest) -> RoutingResult: candidates = recall_by_vector(request.intent, top_k=5) if candidates[0].score < 0.75: final_agent = llm_decide(request.intent, candidates) else: final_agent = candidates[0] if not check_permission(request.caller_agent_id, final_agent.agent_id): return RoutingResult(status="FORBIDDEN") if not check_available(final_agent): return fallback(request, candidates) return RoutingResult(status="OK", target_agent=final_agent)

这一步有个容易忽视的细节:权限校验必须放在路由确定之后。有些实现图省事,在召回阶段就过滤掉没权限的Agent,这个设计会导致调用方Agent从结果里猜出“存在另一个Agent但是不让我调”,属于信息泄露。我们在路由确定之后做统一鉴权,明确返回被拒,而不是让调用方感知到目标Agent的存在。

3.3 接入一个新Agent的完整流程

如果你在自己的项目里用Agent-Reach,接入一个新的Agent只需要四个步骤:

  1. 编写AgentCard JSON,描述清楚能力、输入输出和权限级别。
  2. 开发Agent Adapter,把Agent内部能力包装成标准HTTP接口。
  3. 把AgentCard注册到Agent-Reach的Redis注册中心,调用接入接口/agent/register。
  4. 在访问控制表里配置允许谁调用它,然后跑一条测试调用验证链路。

这些东西加起来,一个新手工程师大概两三个小时能完成接入。相比之前“两个团队撕API文档格式、约联调时间”的痛苦,效率提升是肉眼可见的。

4. 真实踩坑记录与排查思路

4.1 超时问题:LLM天生慢,不能用传统接口的期望

上线第一周遇到最多的问题是超时。客服Agent调用仓储Agent,路由很快,但仓储Agent内部对接了大模型做单据推理,一次推理要8到10秒。而Agent-Reach默认同步调用的HTTP超时我设成了5秒,结果大量请求直接失败。

这个问题的本质是:智能体调用不是普通API调用,它内部的LLM推理时间是天然开销,不能用传统微服务“几百毫秒返回”的预期去设计。我的解法是把同步调用改成异步化:Agent-Reach执行层收到请求后,先落库生成request_id,返回“已受理”,然后通过RabbitMQ把任务投递给目标Agent的Adapter,Adapter执行完成后再回调结果接口。路由层、执行层都彻底异步化之后,超时问题消了,系统吞吐也上来了。

4.2 上下文爆炸:把内容传给Agent是愚蠢的做法

另一个血泪教训是上下文传递。早期我让A Agent把完整对话历史一股脑传给B Agent,结果B Agent内部又要调大模型,Token直接爆炸,一个请求烧掉十几万Token,成本瞬间失控。

后来我做了两个改动。第一,Agent-Reach引入上下文仓库,Agent之间传递的不是对话内容本身,而是一个context_ref,也就是上下文的引用地址,被调用方按需从仓库里拉取。第二,给每次调用设置token限额,超过限额就截断或要求调用方精简上下文。这个设计和分布式系统里的消息引用替代消息复制是同一个道理,省下的成本和延迟非常可观。

4.3 死循环调用:必须有护栏

第三坑是死循环。测试环境里出现了一次诡异的现象:客服Agent调了数据分析Agent,数据分析Agent为了补充数据又调了客服Agent的某个报表功能,两个Agent互相调用停不下来,最后把下游系统打挂了。这个问题的根源是Agent-Reach完全信任了路由结果,没有任何护栏。

我现在给每个调用请求都加上两个保护参数:max_depth和call_chain。call_chain是携带的调用链路径数组,每次调用前检查目标AgentID是否已经在链路上出现过,出现了直接拒绝并报“循环调用”。max_depth是全局最大嵌套深度,超过限制同样拒绝。自打这两个护栏上线,循环问题再也没出现过。

4.4 常见问题速查表

最后把几个高频问题整理成一张表,方便你快速定位:

现象可能原因排查思路
路由结果不稳定,同样的诉求走到不同Agent向量召回的Top K候选质量不高检查AgentCard的description是否写清楚了边界词,必要时人工添加同义词或场景示例
调用成功率不高,但路由层日志正常目标Agent的执行层超时或资源不足看RabbitMQ队列积压情况、目标Agent的并发池配置
权限被误拦截访问控制表配置错误或AgentID不匹配核对caller_agent_id和表里的agent_id是否完全一致,注意大小写
审计日志丢失日志写入PostgreSQL失败,缓存队列满了检查数据库连接池配置,起一个独立的审计落盘进程
Agent批量注册后路由变慢召回阶段在每次请求时都全量计算AgentCard相似度把向量预计算出来放Redis缓存,定时刷新

我的几点体会

做了几个月Agent-Reach之后,我最大的感触是:这个项目本质上解决的不是技术问题,而是协作问题。技术上,实现一个路由、一个注册中心、一层协议封装都算不上难,真正难的是让所有接入的Agent团队愿意遵循同一套规范来描述自己的能力和边界。

我的做法是不搞强制统一,而是先接入两三个核心Agent做出样板,用事实告诉其他团队“接入Agent-Reach之后,别人能找到你的Agent了,你的Agent也能找到别人的了,互相调用的效能提升肉眼可见”。当一个平台能让参与方都获利的时候,规范就不再是负担,而是基础设施。

如果你也在做多智能体架构,建议别急着上复杂的大平台,先按Agent-Reach的思路搭一个最小可用版本,把注册描述、语义路由、执行异步化这三件事做好,剩下的事情会顺很多。后面的扩展方向我还在试:动态伸缩Agent实例、基于反馈的路由策略优化、跨组织间的Agent联邦触达,有机会再单独写一篇。

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

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

立即咨询