多Agent系统触达层设计:Agent-Reach的注册、调度与容错实践
做Agent应用最开始我以为最难的是模型本身。后来真上了生产环境才发现最磨人的是Agent之间怎么相互触达——一个对话Agent要调用数据Agent取数一个任务编排Agent要把活儿派给三个不同技术栈的执行Agent大家各自部署、各自维护接口、协议还不统一全靠人肉拉群对接口。为了把这个痛点彻底解决掉我搭了Agent-Reach一套专门给智能体用的“连接、发现、调度”中间层。这篇文章就聊聊这个项目的完整设计思路、核心模块的实现细节、踩过的坑以及上线之后的真实效果和建议。想给多Agent系统做架构选型的朋友可以直接参考这套方案去改。1. 项目定位与设计思路拆解1.1 为什么要做一套Agent的触达层先说背景。我们团队当时有三十多个Agent在并行开发有做自然语言理解的、有做SQL生成的、有做流程审批的、有做数据可视化的。前后端调用关系一开始是网格状的每个Agent都存了一堆别的Agent的HTTP地址。改动一个Agent的接口牵连出来七八个调用方要跟着改。更麻烦的是有些Agent内部还做了异步任务队列调用方拿不到完成信号全靠轮询触达路径长得离谱。Agent-Reach的定位就是把这些混乱的调用关系收敛起来。它解决三个具体问题第一Agent能力的可见性让每个Agent知道当前环境里有哪些兄弟节点可以提供什么服务第二Agent之间的通信安全不能让随便一个服务都来调核心Agent第三触达路径的稳定性当目标Agent负载高了、临时下线了、协议变了调用方不能直接崩掉。1.2 触达不止是打通网络很多人一听说Agent互联第一反应是“这不就是个API网关吗”。实际上网关解决的是南北向流量而Agent之间多数是东西向流量业务语义更强。Agent触达要理解的是“找谁办事、什么条件、怎么办、办了之后怎么确认”。比如一个Agent的响应说“稍后再试”调度方需要决定是重试还是换一个供应商Agent这已经超出了转发层能做的范围。所以Agent-Reach本质上是一层智能调度总线。它对外暴露的是两件事一幅实时的Agent能力地图以及一套可编程的触达策略。调用方只需要告诉Reach“我要一个能做某类事情的服务”Reach负责匹配、调度、鉴权、超时管理、结果返回调用方不应该关心目标到底是谁。1.3 为什么没有直接选开源的注册中心方案调研阶段我们对比过几类方案。纯粹的服务注册中心比如Consul、Nacos解决“谁活着”的问题但它不理解Agent的能力语义工作流引擎比如流程编排平台解决“怎么编排”的问题但它太重Agent之间的轻量调用套进去非常啰嗦干脆自己写个配置文件维护Agent列表方案更不靠谱变更频繁、没有限流、没有容错。最终选择了自研中间层的方式核心原因是触达语义这件事必须按Agent业务来定。注册中心管“容器”Agent-Reach管“能力”。注册中心告诉我某个服务实例存不存在Agent-Reach告诉我某个能力当下能不能用、谁来提供、什么代价、什么优先级。这套语义是通用中间件给不了的。2. 系统架构与核心模块解析2.1 三横两纵的整体结构Agent-Reach的整体架构可以分成三横两纵。三横是接入层、调度层、执行层接入层负责把Agent以标准方式接入Reach调度层负责能力匹配和路由选择执行层负责最终触达和结果回收。两纵贯穿全链路一条是观测与日志一条是权限与安全。接入层对Agent的唯一要求是提供一个HTTP健康检查端口和一个能力描述文件。Agent不需要额外开发SDK也不用换框架接入成本控制在半小时以内。调度层是无状态设计多实例水平扩展所有调度决策依赖一份在内存中实时刷新的能力路由表路由表由Reach自己从注册信息中构建。执行层处理具体协议转发、超时控制、熔断降级。权限与安全单独拉了一条线因为Agent之间的信任边界必须收紧。每个接入的Agent会拿到租户Token和角色标识Reach在调度前先做一次RBAC校验只有调用方具备目标Agent的访问权限才放行。2.2 触达模型里的三个核心对象Agent-Reach有三个核心数据对象Agent节点、能力项、触达策略。Agent节点是物理维度记录Agent的实例ID、机房、版本、健康状态、负载指标。能力项是业务维度描述一个Agent能对外提供的具体功能包括能力名称、输入参数Schema、输出参数Schema、语义标签、调用协议。一个Agent可以注册多个能力项一个能力项也可以由多个Agent同时提供。触达策略是决策维度描述调用者在某个业务场景下如何选择目标Agent包括优先级、权重、超时、重试、熔断阈值。这三层分开设计好处是解耦。Agent扩容了不需要改能力项能力升级了不需要改节点列表业务方的触达偏好变了只调整策略配置。2.3 关键设计取舍控制面与数据面分离设计时的一个关键取舍是把控制面和数据面分离。控制面负责Agent注册、心跳维持、策略下发、健康监测数据面负责实际触达流量转发。两者分离后策略配置的变更不会影响数据面的吞吐数据面的抖动也不会拖垮控制面的稳定性。控制面用了独立的存储和一个定时同步任务每30秒把Agent注册表和策略配置同步给所有调度实例。调度实例启动时先加载全量快照之后订阅增量更新。这样一个数据面实例挂了其他实例依然能根据最近一次快照继续处理流量不会因为控制面短时不可用而导致全链路瘫痪。从部署结果看这套设计让单实例调度能力轻松跑满千级QPS而控制面CPU占用几乎可以忽略不计。这也是我会推荐在类似场景沿用“控制面做慢决策、数据面做快转发”的根本原因。3. 实操过程与核心环节实现3.1 Agent接入注册流程接入注册是整个项目里决定成败的第一步。Agent上线之前必须完成三个动作声明身份、声明能力、证明自己还活着。声明身份时Agent提交一份注册信息到控制面接口核心字段包括agentId、namespace、version、endpoint。namespace用于隔离不同的业务域比如“客服域”“供应链域”是绝对不能互调的。endpoint就是Agent对外提供触达的HTTP地址。能力声明使用一份JSON描述文件我直接贴一个当时用的示例结构{ agentId: data-service-agent, namespace: bi, capabilities: [ { name: query_dataset, inputSchema: { dataset: string, dimensions: array, metrics: array }, outputSchema: { columns: array, rows: array }, tags: [data, query, bi] } ] }能力声明的关键在于inputSchema和outputSchema。Reach在调度时不直接理解Agent内部实现它靠Schema来校验调用方的参数是否匹配。这样做的好处是调用方传错参数时Reach能在触达之前就把错误拦住避免脏调用污染下游Agent。注册完成后Agent进入“待验证”状态。Reach会发起一次主动连线测试请求Agent的健康检查端口并获得响应验证通过后才会标记为“可用”。这一步非常重要因为很多Agent网关配置之后自己测没问题但Reach所在网络环境根本访问不到提前验证可以避免路由表里出现大量假活节点。3.2 健康检查与实际可用性评估健康检查是Agent-Reach里最容易被低估的模块。早期我们只做了简单的TCP连通性检查结果线上出现过TCP能通但业务请求全部超时的情况。后来改成两层健康评估。第一层是探活探测每10秒一次请求一个固定的探活路径要求Agent在2秒内响应一个标准JSON。第二层是质量评分Reach会基于实际触达结果给每个Agent节点打一个滚动加权分数指标包括过去5分钟的成功率、平均响应时间、排队超时次数。质量评分的作用是给调度器做参考。比如有两个Agent提供同样的“翻译文本”能力A成功率高但慢B速度快但偶尔失败评分模型可以通过权重配置来引导流量偏向更符合业务目标的节点。我用的计算逻辑是这样的综合分 成功率权重项 延迟权重项具体公式为 base_score success_weight * success_rate - latency_weight * normalized_latency其中base_score默认100success_weight取60latency_weight取40。延迟归一化采用5秒作为阈值超过5秒该项记为满分负向。这个公式落地后调度选路比之前纯随机和最少连接都要稳定尤其在目标Agent负载波动时能明显减少错误率。3.3 路由调度与触达策略配置路由调度的核心是策略优先、权重兜底。每条触达策略包含四个要素能力名称、标签匹配条件、候选Agent列表、调度参数。我把一个实际用过的策略配置简化一下{ capability: translate_text, matchTags: [production, low_cost], candidateAgents: [agent-a, agent-b, agent-c], routing: { mode: priority_ratio, priorities: [agent-a, agent-b], ratio: [80, 20], fallback: agent-c, timeoutMs: 3000, retryTimes: 1 } }这段配置的意思是当调用方请求“translate_text”能力时优先把80%流量分给agent-a20%给agent-b如果两者都失败就直接兜底到agent-c。timeoutMs设为3秒只重试1次。实际运行中有一个需要特别注意的点重试只能用于幂等操作翻译这种接口还好但如果是创建订单的Agent重试会造成重复单据。所以Application里应该对不同能力项做幂等标记没有幂等标记的能力项强制关闭自动重试。触达执行的过程是调度器拿到请求后先做一次规则匹配再根据当前健康评分过滤掉已经熔断的节点然后按配置的比例随机选路最后通过数据面按目标Agent的协议格式把请求发出去。这里看起来简单但真实落地时对协议适配的灵活性要求很高——有的Agent吃Restful JSON有的Agent要传表单参数有的Agent内部是消息队列消费数据面必须支持多种适配器不能把协议差异暴露给调用方。3.4 超时、熔断与降级的联动超时、熔断、降级这三个动作在链路里是联动的不是各自独立。Agent-Reach的熔断逻辑不是简单地看到一个错误就断开而是基于滑动窗口计数。在一分钟的时间窗口内如果某个Agent节点的错误率连续超过40%或者平均延迟超过阈值调度器会自动把该节点从候选列表里摘除并触发降级策略。降级策略要提前配置好。我常用的方式是把触达目标从“实时Agent”降级为“本地缓存结果”或“后备Agent”。例如报表Agent挂了降级就返回上一次的缓存报表而不是返回一个错误给前端。降级配置必须在Reach控制面上列清楚否则调度器不知道该从哪个维度兜底。这里有个经验要分享熔断恢复不能太积极。我最初设置的恢复探测是每5秒扫描一次后来发现目标Agent重启时往往需要一分多钟加载模型导致短暂恢复后又有大量请求涌入再次触发熔断。最终把冷却时间改为“连续成功探活30次且成功率回升到80%以上”才允许重新加入候选列表。效果很稳没有再出现振荡。3.5 观测系统与告警配置没有观测的触达层等于盲飞。Agent-Reach里为每次触达都做了一份独立的链路追踪记录字段包括请求ID、调用方Agent、目标Agent、选路原因、耗时拆解、结果码。这个字段设计的价值在于当出现慢请求时能立刻判断是调度器决策慢还是目标Agent执行慢还是网络传输慢。告警规则我设置了三条一是单个Agent错误率超阈值立即告警二是某个能力项全局触达成功率低于99%告警三是调度器选路时出现“无候选Agent”的次数超过告警阈值。第三条很多团队会忽略但它在灰度下线Agent时非常好用能在业务方感知异常之前就提醒管理员去调整策略。日志输出统一用JSON格式单独抽了一个日志主题不跟业务日志混在一起。查询时按requestId检索能在十秒内拉出整条触达链路的完整日志。4. 常见问题与排查技巧实录4.1 高频问题速查表我把上线过程中遇到的典型问题整理成了一张速查表适合直接贴在监控台上看。现象可能原因排查手段部分流量报“无可用Agent”触达策略的标签条件过严候选全部被过滤查看策略匹配日志放宽matchTagsAgent看起来是活的但请求超时健康检查探活路径未覆盖核心业务依赖检查探活路径是否只验证了框架心跳未验证数据库连接调度器频繁切换目标Agent评分权重设置过敏感小波动就翻盘调低延迟权重增大滑动窗口时长某类请求重复执行非幂等能力被自动重试检查能力声明的幂等标记强制关闭retry注册表里出现大量离线节点下线Agent未调用注销接口接入流程强制要求下线时先注销且控制面定期清理超过2小时没心跳的节点跨namespace强调成功但响应体为空目标Agent拒绝了非本域请求检查RBAC权限配置和namespace隔离规则速查表的好处是处理日常告警不用每次从头看日志。我们的值班同学按这张表能定位八成的异常。4.2 三个典型实战案例第一个案例是Agent-A在某个版本里升级了接口返回格式从纯JSON包了一层code字段。由于没有改能力声明的outputSchemaAgent-Reach这边还按旧Schema做校验调用方拿到的数据解析直接走了老的逻辑现场表现为页面数据字段全部错位。后来定位到问题后我把规则改成“outputSchema必须跟随Agent版本一起变更否则版本号不允许发布”。最终是结构校验和版本联动一起生效这类问题几乎绝迹。第二个案例是调度器把大量流量导到了刚重启完的Agent-C节点。当时重试冷却时间没设充足Agent-C重启后模型还需要加载几十秒探活是通的但业务请求全部排队。我把探活路径升级为“完整执行一次轻量级推理并返回耗时”效果立竿见影冷却期之后的流量才真正有意义。第三个案例是权限隔离问题。一个内部测试Agent拿到了管理员Token在调试时不小心调用了支付域的Agent接口直接把测试环境的支付流程带崩了。问题在于权限模型中默认放行了同环境资源而环境隔离和租户隔离是两套维度。修正后Agent-Reach把每个Agent的Token绑定角色最小权限集任何跨域调用都必须显式审批。4.3 避坑清单和验收标准基于这些案例我总结了一份避坑清单。新Agent接入时必须逐条核对探活路径必须是业务真实依赖链路不能只测框架存活能力声明的Schema要和接口文档强绑定变更必须走上线流程所有非幂等接口必须显式标记幂等字段所有自定义Header必须在能力声明里登记防止参数穿透失败Tag不能乱打Too many tags导致路由匹配变慢少而精更好。对Agent-Reach这类中间层建议建立一套上线验收标准否则业务方会让你无限加规则。我的做法是验收只看三件事触达成功率能达到99.9%以上P99延迟相对原有人肉调用链路损耗小于30ms任何Agent节点异常下线不影响总体成功率超过5分钟。三个指标不过就不得上线。5. 落地效果与个人体会5.1 上线后的观测数据Agent-Reach上线运行三个月后几个关键数字可以参考一下。全平台五十多个Agent接入能力项超过一百二十个触达平均P99延迟已经稳定在80ms以内相比原来网格状直连的平均600ms降了一大截。原因主要是去掉了调用方反复重试的连锁效应以及减少了大量无效轮询。成功率方面在最稳定的一个月里全局触达成功率在99.5%以上。发生故障时由于有兜底和熔断机制绝大部分异常都限制在一个局部域内没有出现过跨业务域的连锁故障。这个稳定性指标是这套触达层存在的最大价值。5.2 做这个项目最深的三个体会第一个体会是Agent触达层本质上是对“契约”的管理。Agent之间只要接口约定清楚了技术选型反而不那么重要。比起整天纠结grpc还是http不如先把每个Agent的能力Schema管好。第二个体会是不要把调度规则写死在代码里。触达决策必须是动态可配、可灰度、可回滚的。我们在生产环境调整过很多次路由比例都是改配置发布代理层完全不需要重启。第三个体会是中间层的价值一直被低估。很多团队给Agent做了很强的模型能力却让Agent之间的互调停留在“写一个API网关”的层面。如果多Agent不是几十个、而是几百个的时候没有触达层是走不了多远的。5.3 后续扩展的三个方向Agent-Reach后续的扩展空间我个人比较看好三个方向。第一个方向是语义触达让调用方不再显式指定能力项名称而是用一句话自然语言描述需求由触达层通过语义索引自动匹配Agent能力。这意味着Agent接入门槛进一步降低因为调用方连API文档都不用看问一句“帮我查一下上周销量”就能被路由到对应的查询Agent。第二个方向是对Agent执行结果的质量评估。现在触达层只负责把请求送到并把响应带回来但响应本身是不是高质量的基本没有判断。后续应该接一个质量评估模块对于低质量执行结果进行重试或者切换目标Agent形成闭环。第三个方向是跨站点、跨组织的Agent触达。当Agent所有权属于不同团队甚至不同公司时需要一套联邦式的注册和信任机制触达层只传递必要信息不落地业务数据而Agent身份由各自控制面背书。这是规模化协作的前提。这个项目做到现在这个状态我最满意的一点是它并没有变成一套复杂的重系统而是成为Agent之间相互协作的“轻契约”。后续如果你们的Agent数量也涨上来了我建议从最小的注册表开始做先把“找一个Agent”这件事标准化再逐步叠加调度策略不用一上来就把所有模块做齐。