多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

多智能体协同的触达层:Agent-Reach架构设计与实践

多智能体协同的触达层:Agent-Reach架构设计与实践 从今年年初开始我陆续在公司内部做了几个独立运行的智能体应用。一开始跑单个Agent的时候还没有太多感觉等数量上到五六个、分散在不同的服务器和团队手里问题就冒出来了想查一个订单状态得先搞清楚该调哪个服务想在这个Agent里触发另一个Agent的动作得硬编码对方的接口地址甚至还要自己处理超时和重连。这个状态明显是快到瓶颈了所以当我在社区里看到Agent-Reach这个名字的时候第一反应就是“对缺的就是这个”。它的思路很直白把分散的智能体统一纳入一个可发现、可路由、可协作的触达网络让业务侧不用关心Agent具体部署在哪、用什么协议暴露能力通过一个协调层就能找到、调用、编排它们。这篇文章会从Agent-Reach要解决的核心问题说起讲清楚它的三层架构设计再给出第一版跑通的实现细节和我在踩坑过程中总结出来的排错思路。1. 为什么我需要一个Agent触达层多个智能体协同的真实痛点先说一个具体的场景。我们内部有一个订单履约Agent负责解析客服工单并生成处理建议还有一个库存查询Agent负责对接ERP接口返回实时库存。单独看这两个都挺好用但订单Agent在判断“建议是否可以执行”时需要实时知道某仓库存是否充足这时候它就不得不自己去查ERP、自己去解析库存Agent那张结构化的数据表。问题在于订单Agent和库存Agent是两个团队开发的接口约定没有统一。库存Agent内部换过一次表结构订单Agent直接写死字段解析结果悄悄坏了一周才被发现。两个Agent都有各自的鉴权方式业务方要记住两套调用规则。这些问题的本质不是Agent本身有多难写而是Agent之间、Agent与业务系统之间缺少一个统一的“寻址与触达”机制。Agent-Reach恰好就是补这一块它专注于解决“当一个Agent需要触达另一个Agent时怎么找到、怎么调用、怎么保障可靠性”这组问题。Agent-Reach这个名字我理解得比较直白——Agent代表被管理的智能体Reach代表触达能力。它不是一个完整的Agent开发框架而是一层位于Agent之上的协调与调度通道。你仍然用自己熟悉的方式去实现Agent内部逻辑只是把对外暴露能力和发现别人能力这件事统一托管给Agent-Reach这一层。适合参考这套方案的人我总结为三类手上已经有几个独立运行Agent、正在为互相调用写一堆胶水代码的开发者。打算在团队内搭建Agent统一入口但还没定拓扑结构的架构师。对“意图驱动路由”感兴趣希望把用户请求自动分发到正确Agent的AI应用开发者。我自己其实更看重它的抽象方式它把“能力”提升为第一公民让Agent对外呈现的不再是一堆裸接口而是带语义描述的能力单元。后面所有路由、编排、权限校验都围绕“能力”来做。2. 第一版架构选型注册中心加能力声明的三层拓扑Agent-Reach的第一版我没设计得太复杂核心就是一个三层拓扑。最上面是接入层统一接收业务请求中间是协调层负责能力寻址和任务路由最下面是节点层部署着各类Agent。协调层里最重要的东西是一个能力注册表——每个Agent在启动后向协调中心注册自己的地址、协议类型和一份能力清单。整个架构的工作流程大概是Agent启动时调用注册接口把自己对外提供的能力元数据上报。协调中心收到注册信息后把Agent节点标记为“在线”并纳入路由备选池。业务请求进入接入层先被解析成“需要什么能力”的意图。协调中心在能力注册表中搜索相似度最高的Agent节点。选好节点后协调中心将请求转交给对应Agent并把结果回传。它和微服务架构里的服务注册中心有相似之处但多了一个关键设计注册表里存的不是简单的服务名和IP列表而是一组带语义信息的能力描述。这样做的好处是路由可以更加灵活比如业务方不用精确知道“库存Agent”这个名字只需要表达“查一下SKU732的实时库存”协调层就能根据能力描术匹配到对应节点。被Agent-Reach隐藏掉的是具体哪个Agent、哪套协议、哪份鉴权细节。我为什么没有在首版引入消息队列原因很简单第一版最重要的是先打通全链路。如果一开始就用异步消息中间件会把故障定位的复杂度抬得很高。首版采用同步HTTP回调模式让协调层对请求响应路径有全局视野等稳定了再加异步通道也不迟。以下是第一版里Agent节点的注册数据结构用JSON表示比较直观{ node_id: agent-inventory-01, host: 192.168.10.32, port: 9011, transport: http, capabilities: [ { name: query_stock, description: 按SKU编码查询实时库存数量支持多仓盘点, input_schema: { sku: string, warehouse: string? }, output_schema: { stock: integer, warehouse: string } } ] }transport字段我特意留成了字符串而不是枚举当时就猜到后面会出现grpc或者自研协议场景提前留好扩展位让接入新协议时不用改库表结构。3. 从请求到能力的路由链路注册表匹配与节点触达的落地实现3.1 能力匹配不是字符串相等而是语义相似度这是Agent-Reach整个设计里含金量最高的一环也是我第一版最容易画蛇添足的地方。一开始我直接用规则匹配让Description精确命中关键词。比如订单Agent发送一个actionquery_stock我就去注册表里找某个Agent的capability。这个方案看似简单但真实业务中很容易翻车订单Agent那边发过来的能力名依然是check_remaining_number两边命名习惯不同匹配就失败了。后来我把匹配层改成“语义相似度优先、关键字兜底”的双路策略。先在协调层为每份能力描述生成向量请求过来时把请求文本也向量化然后计算余弦相似度。只保留一个及格线以上的候选集在候选集内部再按字符串相似度、调用权重做排序。这个改动直接让路由准确率上了好几个台阶。如果不想为向量计算引入架构负担也可以先用最简单的方式跑通把每个能力描述拆成一堆关键词做成倒排索引请求进来后先算词频重合度。这个方案足够应付初期几十个Agent的规模等量大再考虑升级到Embedding。3.2 节点触达的可靠性设计超时、重试与熔断协调中心找到目标Agent后只是完成了第一步真正触达Agent远端调用涉及到超时、重试和熔断。我首版没有引入额外依赖直接用HTTP客户端标准组件做封装。核心参数我按照下面的建议值去配置参数推荐值说明连接超时800ms快速失败避免请求堆积在协调层请求超时5s给Agent足够的处理时间但不无限等待重试次数2次只对幂等操作开启非幂等默认不重试熔断阈值连续失败10次触发后将节点摘除30秒有一个细节一直很容易被忽略非幂等的操作在重试时可能造成重复下单或重复扣费。Agent-Reach的能力注册表里我额外加了一个idempotent布尔字段由Agent声明操作是否幂等协调层在决定重试策略前先检查这个开关。几个老系统对接时吃了大亏后来才补齐这个设计。3.3 同步链路的一版调用序列首版跑通的最小同步链路并不复杂核心就是“接入层收到请求→协调层匹配能力→触达具体节点→回传结果”。我在协调层网关里设置了统一的请求入口对外暴露一个带能力Id的POST接口。接入层往里丢目标意图协调层做完路由后把请求转发给最终Agent。用伪代码表达这个核心处理过程会更直观def route_request(request): capability_id match_capability(request.intent) if capability_id is None: return {error: no_matching_capability} target_node registry.find_best_node(capability_id) if target_node is None: return {error: no_available_node} response http_client.post( urlf{target_node.host}:{target_node.port}/invoke, payloadrequest.body, timeouttimeout_config ) return response这段代码看起来简单实际生产环境中我会在“find_best_node”前后加入对节点健康状态、当前负载和熔断状态的判断。Agent节点会在协调中心上维护一个滚动窗口连续几次出现连接失败或响应超时就不再参与新路由也就是我刚才说的熔断逻辑。4. 跑通了第一版之后仍然很容易忽视的稳定性细节第一版跑通通常只需要两天真正需要大量精力的是让它稳定运行。Agent-Reach和传统微服务最不一样的地方在于Agent节点内部可能会调用LLMLLM本身响应时就呈长尾分布有时候1秒返回有时候8秒才返回。如果协调层超时配置太紧大量请求会积压重试把Agent打崩。因此我在第一版上线后的稳定性调优上做了一系列改动。心跳机制改成了动态间隔。Agent启动后以5秒一次的心跳速率上报存活状态协调层在连续三次没有收到心跳后把节点标记为失联。稳定运行一段时间后我会让Agent自动把心跳间隔拉长到15秒减少无效流量。这个思路来自一个很朴素的想法一次稳定的连接不需要频繁确认不稳定的连接靠频繁确认也没有意义。容器化部署在这个网络里很关键因为这些Agent节点经常被编排系统批量拉起或停止。每次新节点上线时协调层要有能力在几秒内把流量切过去。我在Agent启动逻辑里增加了一个预注册阶段节点在真正对外提供服务前必须执行健康检查检查通过后才把状态从“注册中”转为“在线”。不然分发到还处于启动中的容器结果全超时用户体验会非常糟糕。还有接口鉴权这一块很容易漏。Agent-Reach内部虽然协同方便但不能让内部Agent网络等同“完全信任网络”。每个Agent节点之间调用时我用了一个签发短期Token的方式协调层帮调用方签发只对某个目标节点有效的短时Token节点侧校验时不需要维护长期密钥。这个模式和很多平台里的STS临时凭证思路一致好处是就算Token泄露几分钟后也会自动失效。不可否认第一版试错过程中最有价值的收获是我把“发送请求”和“等待结果”的边界彻底想清楚了。后续在给Agent-Reach引入异步化支持时很多设计都能复用第一版已经验证过的路由与鉴权基础。5. 我在实际部署中踩过的三个典型坑5.1 心跳抖动引发的大面积误摘除第一次把Agent-Reach部署到跨机房环境时我遇到过一个看起来很奇怪的故障不固定某一个节点随机出现“节点失联”的告警然后业务请求偶发报错。查了半天才发现问题是机房之间的主链路到晚上会频繁抖动几百毫秒心跳包在抖动期间延迟超过阈值协调中心判断连续三次心跳超时就节点摘除。摘除后链路恢复节点又重新注册小范围内反复抖动。原因清楚之后修复很直接心跳超时阈值从“连续三次固定时长超时”改为“连续三次超过动态基准线”基准线按每个节点近五分钟的平均RTT自动调整。这样跨机房延迟高但稳定的节点不会被误杀只有真正出现持续异常的节点才会被摘除。5.2 能力描述里出现同义词匹配召回不足这个问题在Agent-Reach的语义匹配上特别明显。某个依赖库存的Agent在请求时使用“货物余量”这个意图而库存Agent注册的capability描述里写的是“stock inquiry”。词面完全对不上语义相似度也不够高结果路由失败。我没打算一上来就重构匹配算法而是先给心智收敛留下一层“同义词扩展表”。在语义匹配之外预置了一组查询扩展词映射让路由时同时考虑原始词和扩充词。这个扩展表是持续维护的团队每遇到一次匹配失败的案例就往里补充一组同义词。这个办法简单却解决掉了大约八成以上的召回问题。5.3 Agent之间的链式调用产生循环调用在Agent-Reach网络规模变大后业务侧开始配置链式编排一个Agent触发另一个Agent再叠加第三个。配置不当就会“A触发B、B里又触发A”形成循环调用。我当时通过两层措施解决了这个问题一是在协调层为每个调用链生成唯一的TraceId同一链条里某个节点只允许被执行一次重复请求直接返回上游已有结果二是给编排深度设置硬上限默认最多5层超出即拒绝。这两条加上日志链路追踪才能在上生产后很快定位到是哪一侧改配置导致的循环。6. 从第一版稳定到千人协同Agent-Reach后续可扩展的思考方向第一版Agent-Reach跑了大半年后我逐渐发现它真正提升的不是“请求成功次数”而是“让不同团队之间不需要反复口令式对齐也能协作”的能力。业务方不再背每个Agent的接口细节只管在接入层声明意图协调层去匹配和调度Agent。这带来的协作体验变化很大尤其是跨部门的联调成本降了不少。如果接下来继续演进我会优先做两件事。一是把异步消息通道补上让Agent之间不仅支持同步请求还能支持“扔一个事件进去多个监听Agent各自处理”的发布订阅模式。二是把协调中心做成集群摆脱单点依赖。这两个方向都需要先打好基础注册表的最终一致性、请求拓路的安全性都值得充分验证后再动手。用人话说Agent-Reach并不是一个炫技的项目它做的是一件看起来很笨但无比踏实的事把“谁能干什么、怎么找到他”这两件事沉淀成基础设施。当你的Agent数量从三五个涨到三五十个时会发现提前做的这个触达层特别值得。很多时候分布式问题的根子不在于单机性能而是节点之间没有统一的触达语言。Agent-Reach至少让这一层在我这边不再失控。
返回列表