ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

多Agent系统如何真正“触达”彼此?轻量中间层的路由与治理实践

多Agent系统如何真正“触达”彼此?轻量中间层的路由与治理实践 如果有人问我最近在做什么我会说在做一件听起来很简单、做起来却极其磨人的事让多个AI Agent能够真正“够得着”彼此。这里的“Agent-Reach”名字里的“Reach”不是指网络链路那种拓扑可达而是指能力边界和通信边界的打通。过去我们把Agent当独立的聊天程序用模型上下文就是它的整个世界。但当系统里有十几个Agent分别负责日志检索、告警分析、知识整理、工单回复时最大的问题不再是“单个Agent聪明不聪明”而是“这个Agent能不能在需要的时候以可控、安全、可观测的方式触达另一个Agent以及Agent背后的工具和数据”。这不是一个聊天机器人项目也不打算重新发明大模型推理框架。Agent-Reach的定位非常明确它是Agent与Agent之间、Agent与外部能力之间的一个轻量中间层专注解决寻址、路由、鉴权、回传、可观测这几件事。适合谁看适合正在做多Agent系统、智能运维平台、需要把多个Agent接入现有系统但不想被某个重型编排框架绑死的开发者。1. Agent-Reach 真正要解决的是系统里“最后一公里”的触达问题1.1 单个Agent很强多个Agent却经常“各说各话”我最早做多Agent系统的时候踩过一个非常典型的坑单看每个Agent能力都很完整。负责日志分析的Agent能在测试环境把日志检索做得很好负责知识问答的Agent也能从文档库中给出准确答案。可是一旦把它们放进同一个业务流程问题就来了。问题不在于模型能力而在于“连接方式”。当时几个Agent之间靠什么通信靠硬编码的HTTP请求A服务直接把B服务的URL写死在代码里C要调用A的时候再把参数拼到URL上。这种做法的痛点是每个Agent都要自己维护“该调谁、怎么调、参数长什么样”的逻辑权限分散在各处没法统一控制“哪个Agent能碰哪些数据”链路一长出了问题根本定位不到是哪一环断了Agent-Reach把这些都收口成一个问题一个Agent在运行时如何找到另一个Agent并安全地拿到它的能力。听起来像服务注册中心有点像但又不完全是。Agent不是普通微服务它的入参和出参经常是非结构化的自然语言它的执行时长可能是几秒也可能是几分钟它还会在过程中自主决定调用什么工具。所以这套“触达方案”不能只做服务发现还要处理语义路由、异步回传、结果校验这些很Agent化的东西。1.2 用一句话定义Agent-Reach的职责Agent-Reach做四件事注册、寻址、路由、回传。注册每个Agent上线时描述自己“擅长什么、能调什么工具、以什么方式被调用”寻址调用方不用关心目标Agent部署在哪台机器、叫什么服务名只描述任务意图路由由Agent-Reach根据意图找到最合适的Agent必要时同时分发给多个Agent回传统一处理同步返回和异步回调并且把整条调用链路的追踪信息带上这四个词就是整个项目的骨架。我不需要每个Agent知道其他Agent的存在它们只需要知道Agent-Reach这个网关把自己的能力交出去把别人给的任务接回来。谁新加了Agent、谁下线了其他Agent完全无感。1.3 为什么叫“触达”而不是“集成”集成这个词通常意味着两个系统间预先约定好的配合。但在Agent系统里调用关系往往是运行时才确定的一个任务描述抛过来系统判断“这件事该谁干”。这与传统的接口对接有本质差异更像人与人之间的“找人办事”你不必知道对方叫什么名字只要描述“我要修打印机”别人就能帮你找到管打印机的人对方现在忙不忙、有没有权限接这个活、做完之后怎么通知你这套机制要提前约定好Agent-Reach做的就是把这套“找人办事”的机制工程化。这也是我在项目里反复强调“触达”的原因——比起集成它更关心的是“能不能找到、找对了没有、找完之后回不回得来”。2. 核心设计把“谁能被谁触达”这件事建模清楚2.1 能力注册Agent 上线先“自报家门”没有注册机制的Agent系统就像没有电话簿的公司。每个Agent都觉得自己很有用但别人不知道它有什么本事。Agent-Reach里每个Agent启动时要先向网关提交一份能力描述。我不把它设计成严格的结构化Schema因为Agent能力的边界本来就模糊。我采用“结构化字段 自然语言能力描述”的混合模式。一个典型的注册信息长这样agent_id: log-agent display_name: 日志检索Agent capabilities: - name: search_log description: 根据关键字和时间范围搜索应用日志支持模糊匹配 parameters: - name: keyword type: string required: true description: 搜索关键字 - name: time_range type: string required: false description: 时间范围如 1h / 24h / 2025-01-01~2025-01-02 - name: tail_log description: 实时跟踪最新日志输出 parameters: - name: lines type: integer required: false description: 返回行数默认50 endpoint: https://internal-agent/log-agent/invoke auth_token_ref: token-log-agent timeout_policy: ack_timeout: 5s max_wait_time: 120s async_callback: true注意我在注册信息里特意放了authentication_token_ref指向一个凭据引用而不是直接暴露密钥。Agent系统里有个很不好的习惯就是直接把服务账号的Token写在代码里导致权限边界形同虚设。Agent-Reach的注册中心只存凭据的引用编号真正的密钥由密钥管理系统统一保管网关在发起调用时用短期凭证换取访问权限。2.2 语义路由按“想干什么”找人而不是按“服务名叫什么”找人这是Agent-Reach和普通服务注册中心最大的区别。普通注册中心的服务名是唯一的调用方必须确切知道服务名才能发起调用。但Agent系统的调用方往往只知道任务意图不知道应该调谁甚至不需要知道该调谁。举个例子一个排障Agent收到用户指令“帮我看看订单服务为什么慢”它自己可能并不熟悉日志Agent的接口格式但它知道“我要查日志”。这时排障Agent把任务发给网关网关做语义匹配找到注册表里能力描述与“日志查询”相关的候选Agent。我的做法是三步筛选关键词预筛从任务文本中提取领域关键词缩小候选范围向量召回把能力描述和任务文本都转成向量用相似度取Top-5候选规则仲裁如果存在精确能力名匹配直接命中否则在Top候选里结合调用方身份、资源占用情况做最终选择整套流程对调用方完全透明。排障Agent只用发一个带意图标记的请求真正决定“谁来做”的是路由层。这样的设计带来一个意想不到的好处Agent系统可以随时上线“更擅长某类任务的新Agent”只要它注册了能力旧Agent无需改动路由会自动把新任务类别的请求分流过去。2.3 扇形分发一个任务同时找多个Agent协作单Agent能做的事终究有限多Agent协作才是这个项目的价值所在。Agent-Reach支持两种路由模式单播路由和扇形路由。单播就是上面说的一对一。扇形路由则是一条请求进来后网关同时把它分发给多个能力匹配的Agent等它们的结果回来后做聚合再返回给调用方。这个功能我一开始觉得很难——因为多个Agent的返回时间不可控有的快有的慢聚合策略怎么定后来我采用了一个务实的方案不追求严格的“全部到达才返回”而是支持两种聚合模式wait_all默认等待所有分支返回或超时后返回已有结果并标记丢失分支wait_quorum设定最小完成数比如3个分支里至少2个完成达到后立即返回不等剩下分支{ task: 分析订单服务最近1小时响应变慢的根因, route_mode: fan_out, aggregation_policy: { mode: wait_quorum, min_success: 2, overall_timeout_ms: 30000 }, target_capabilities: [search_log, query_trace, search_doc] }这样编排Agent就可以把一个复杂任务拆成几个子任务并发出去然后只关心整体超时时间和成功率。在我的实测中扇形路由比串行调用三个Agent在总耗时上能减少50%到70%。3. 跑通一个真实场景让“排障Agent”和“知识库Agent”协作一次3.1 场景设定我拿一个非常常见的内部运维场景做例子。假设系统配置了三个Agentlog-agent负责检索日志返回关键词命中的日志片段和统计信息wiki-agent负责检索内部运维知识库返回相关排障文档triage-agent编排角色接收用户对故障的描述综合分析日志和知识库输出根因报告如果没有Agent-Reachtriage-agent的代码里要同时维护log-agent和wiki-agent的地址、鉴权、超时逻辑。更麻烦的是如果日志Agent的域名换了triage-agent也必须跟着改。用Agent-Reach之后triage-agent只负责一件事把用户描述转成一个结构化任务发给网关。3.2 调用链路的完整时序完整的一次协作过程用户 - triage-agent triage-agent - agent-reach (POST /v1/tasks) agent-reach - log-agent (扇形分发第一个分支) agent-reach - wiki-agent (扇形分发第二个分支) log-agent - agent-reach (返回日志检索结果) wiki-agent - agent-reach (返回相关文档列表) agent-reach - triage-agent (聚合结果回调) triage-agent - 用户 (生成根因报告)核心代码在triage-agent这侧非常简单from agent_reach_client import AgentReachClient client AgentReachClient( gateway_urlhttp://agent-reach:8080, agent_idtriage-agent, api_keyos.environ[TRIAGE_AGENT_API_KEY] ) def handle_user_report(description: str): task client.fan_out( capabilities[search_log, search_doc], payload{ problem_description: description, time_window: 1h, service: order-service }, aggregation{ mode: wait_quorum, min_success: 2, timeout_ms: 30000 } ) result task.wait() return compose_report(result)这里最有价值的一句是client.fan_out(...)。它把“调用谁、传什么参数、怎么等结果”全部抽象掉了。triage-agent只能感知到“我发了任务我拿到了结果”中间任何一个环节替换了Agenttriage-agent都不用改。3.3 网关侧做了什么网关收到triage-agent的扇形任务后并不是简单地做转发它会完成几件关键动作能力解析把search_log和search_doc映射到已注册的Agent实例参数改写把problem_description统一包装成下游Agent能识别的输入格式。比如log-agent期望的是keyword字段网关会从描述里提取关键实体填入wiki-agent期望的是query字段网关会调用一个轻量级的关键词抽取函数生成超时预算分配整体超时30秒网关会给log-agent分20秒给wiki-agent分20秒允许它们并行跑但任何一个分支超时都只影响该分支不影响其他分支追踪上下文注入在每个子请求里附加trace_id和parent_span_id这样即便三个Agent在不同进程日志也能通过时序串联起来这一步是整个Agent-Reach最像中间件的地方调用方只需要按业务意图说话网关负责把意图翻译成每个Agent能听懂的语言。这个“翻译”工作如果丢给调用方自己做系统就会变成调用方和每个Agent的紧耦合多Agent协作就退化成了一种手工编配。3.4 实测效果与衡量指标我在内部环境用三个Agent跑这个排障流程日志Agent检索约50GB日志需要3秒到5秒知识库Agent向量检索大约1秒到2秒算上网关路由和聚合的开销端到端耗时在6秒到8秒之间。如果改成串行调用总耗时在12秒以上。当然这只是我自己的环境数据机器配置、日志索引方式不同数字会有差异但“并行分发比串行调用省一半以上时间”这个结论在多数场景下成立。衡量这个项目做得好不好我建议看三个指标路由准确率任务被分发到“确实擅长该任务”的Agent的比例端到端成功率从发起任务到拿回有效结果的比例我目前内部环境在95%左右新增Agent的接入成本一个新Agent从开发到被其他Agent触达是否只需要一次注册而不需要改任何既有调用方4. 生产环境里最容易翻车的三个细节4.1 超时策略别用普通接口的2秒超时来约束Agent这是我在项目初期踩得最深的一个坑。一开始我把Agent调用当普通REST接口来配超时统一设了3秒。结果发现即便是日志检索这种相对机械的任务在大规模日志上跑SQL聚合都可能超过3秒更不用说需要模型多轮思考的Agent任务了。Agent的调用超时必须分层处理。我把超时拆成两段ACK超时下游Agent是否在指定时间内确认“我收到了任务”这个必须非常短我的配置是3秒到5秒。如果连ACK都收不到说明网络或服务本身有问题结果等待超时从确认接收到返回结果的最长等待时间。这个只能根据任务复杂度来配机械检索任务20秒涉及模型推理的任务我给到3分钟再长就建议走异步回调如果Agent执行确实需要很长时间最好的做法是让网关立即返回一个task_id给调用方等Agent执行完后再通过回调或轮询拿结果。这样调用方的HTTP连接不会长时间占用整个系统的连接稳定性会好很多。timeout_policy: ack_timeout: 5s max_wait_time: 180s async_callback: true callback_url: https://internal-callback/triage-agent/results4.2 权限边界Agent之间不能“全透明”多Agent系统有一个很危险的想法Agent都是自己人互相之间可以完全信任因此每个Agent都配最高权限。这在内部Demo里还能跑一旦对上生产数据一定会出事。我在Agent-Reach里做了一层轻量的RBAC每个Agent都有一个角色角色决定了它能触达哪些能力。日志Agent的角色是log-reader它只能调用日志相关的工具拿不到知识库的写入权限编排Agent的角色是orchestrator它可以向其他Agent派发任务但不等同于拥有其他Agent的系统权限。关键原则是一个Agent的权限上限由它自己的角色决定而不是由“谁发起的请求”决定。我举一个实际教训。早期版本里排障Agent发现某台服务器日志里有异常它很“聪明”地调用了一个重启服务的工具想把问题解决掉。这在测试环境没出什么问题如果部署到生产环境一个模型误判就可能重启掉核心服务。后来我添加了两条铁律Agent只有显式声明了某能力才能被路由到该能力。编排Agent没有声明restart_service这个能力因此即使它想调用路由也会拒绝高危险操作默认拦截。重启服务、删除数据、修改配置这类的工具在注册时会被标记为高危操作网关默认不路由需要额外审批流程才能放行这套权限设计和传统API网关的权限校验非常像只是把校验对象从“用户”换成了“Agent身份”。你不做这层隔离Agent系统规模一大权限失控几乎是必然的。4.3 循环调用A问BB问CC又问A分布式系统里的死循环问题在Agent系统里格外严重。因为Agent有“自主决策”能力它的下一步动作由模型根据当前上下文来生成开发者在编码阶段根本想不到Agent会说出“我要再问问上一级的Agent”。我有一次测试时碰到过一个诡异现象一个排障任务发出去之后三个Agent互相追问产生了超过十次的调用链闭环把网关打到近乎阻塞。原因很简单Agent A发现信息不足就向Agent B发起了求助B觉得A也应该知道某个信息又回头向A发起询问C在中间被两边引用了多次。这一类问题在传统服务里通常靠“调用深度限制”解决Agent系统里还需要额外防递归。我在Agent-Reach里做了三步防护TTL字段每个任务生成时带一个最大跳数默认5跳。每经过一次Agent调用TTL减一减到0就强制终止并返回“调用链过深”环路检测网关会记录每个任务在当前调用链上的agent_id序列如果同一个agent_id在序列里重复出现说明形成了环网关会直接拒绝把任务再次路由给该Agent任务幂等键每次派发都带一个全局唯一的task_id下游Agent在执行前检查是否处理过这个任务处理过就直接返回缓存结果有了这三步我之后再也没有出现过“Agent互相玩坏系统”的事故。但这三个机制都必须做成网关内置功能不能指望每个Agent自己实现因为Agent的生成行为具有不确定性它自己都未必能感知到自己正在制造循环。5. Agent-Reach 不是什么“银弹”和同类方案的边界划分5.1 横向对比几种主流方案经常有人问我Agent-Reach和LangChain、CrewAI这类框架有什么区别也有人问我为什么不直接用消息队列让Agent之间通信。我用一张表说明它们的定位差异方案核心定位适合解决不适合解决Agent-ReachAgent能力触达与路由层多Agent找谁干活、权限收口、链路观测Agent内部推理、复杂提示词设计LangChainAgent构建与工具调用框架单个Agent串联工具和模型跨Agent的运行时路由与治理CrewAI多角色Agent协作框架定义角色、任务分解流程与既有系统的大规模非侵入集成业务消息队列异步事件传递高吞吐事件驱动通信语义路由、按意图匹配直接硬编码HTTP调用点对点集成Agent数量少、关系固定链路一长扩展和运维成本爆表我使用Agent-Reach的一个原则它不替代Agent框架而是放在Agent框架和外部世界之间。比如你用LangChain构建Agent这个Agent依然是你的业务逻辑载体但它的工具里会多一个“调用Agent-Reach”的工具上了这个工具之后你的Agent才真正获得触达其他Agent和外部系统的能力。5.2 为什么我不建议直接硬编码调用硬编码HTTP调用在只有两三个Agent时是最快的方案但随着Agent数量增长它带来的问题会以指数级放大。尤其是下面这些场景硬编码会非常痛苦地址漂移Agent部署在不同环境域名、端口不一样配置文件要改一串鉴权分散每个Agent都要维护对方的TokenToken泄露了都不知道在哪里链路不可见A调用B被谁记下来了吗超时了该找谁排查如果没有统一网关根本无从查起Agent-Reach选择了所有跨Agent调用都必须经过网关这个设计可能在某些人看来“多了一跳”但它换来的是全链路可观测和统一治理。我个人的判断是对Agent系统来说治理能力的重要性远高于减少一跳网络开销。5.3 什么情况下不建议使用Agent-Reach虽然我在做这个项目但我也要说实话不是所有场景都适合引入它。只有一个Agent的场景。你只做了一个文档问答机器人没有其他Agent要协作用Agent-Reach纯粹是给自己加复杂度毫秒级实时控制的场景。比如控制机械臂这种需要严格时序的系统不要用Agent做决策。这类场景需要的不是Agent触达而是确定性控制逻辑Agent之间没有实质性的能力差异。如果所有Agent都在调用同一个函数库只是模型思路不同那它们本质上是一个Agent不需要做Agent间的路由这套方案最舒服的适用区间是你有5个以上的Agent它们分别连接不同的数据源或工具且经常需要组合起来完成一个复杂任务。在这个区间里Agent-Reach带来的收益是压倒性的。6. 我在实际落地过程中的几个体会写到最后分享一些没有写进设计文档里的个人经验。第一个体会是Agent系统落地难最难的从来不是模型能力而是边界管理。模型每天都在进化Agent的推理能力越来越强但系统边界如果不清晰能力越强越危险。Agent-Reach的核心理念就是把“谁能触达谁”放在“Agent能思考什么”之前。第二个体会是路由准确性一定要用真实数据测试。我在测试环境用仿真数据测过语义路由准确率很高一上生产就露馅了。因为生产环境里Agent的业务描述往往是缩写、混合中英文、甚至带口语化的病句。关键词预筛和向量召回都要用生产环境的真实任务样本来调优不然就是刻舟求剑。建议建立一个“历史任务记录表”每周复盘路由分错的任务把样本补充到召回库里。第三个痛感很深的体验是埋点一定要做在网关上而不是做在每个Agent上。如果每个Agent自己记录调用日志格式五花八门最关键的路由决策过程为什么选了这个Agent而不是那个反而被漏掉了。Agent-Reach的每一条路由记录都包含输入任务的原文、候选Agent列表、每个候选的得分、最终选择结果、各分支的耗时和状态。排查问题的时候有这份记录和没有这份记录是天壤之别。最后分享一个打通监控的小技巧Agent-Reach的延迟指标和成功率指标用Prometheus格式暴露直接接入现有的监控大盘。这样每次模型升级或Agent提示词调整后你都能直观地看到“这次改动是否影响了端到端的触达成功率”。我一直觉得做Agent系统和做传统系统有一个共同点——数字不会骗人。能答对多少问题固然重要但一个连“该调的都没调通”的系统再聪明的模型也白搭。
返回列表