ARTICLE DETAIL

资讯详情

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

多智能体系统调度实战:Agent-Reach注册路由与执行网关设计

多智能体系统调度实战:Agent-Reach注册路由与执行网关设计 最近半年我和团队一直在做多智能体系统的产品化落地项目名叫 Agent-Reach。它是我们内部用来解决一个老大难问题的中间层当 AI 智能体数量从两三个涨到十几个分属不同小组维护、接口风格各异、能力边界模糊调度逻辑变成一团乱麻时你根本不知道该找哪个智能体干活。Agent-Reach 的核心思路很直接——把智能体之间的调用关系从硬编码的 if/else 里解放出来做成一个带注册中心、路由引擎和执行网关的调度框架。这篇文章我会把整个项目从设计到踩坑落地的过程完整拆出来包括能力描述怎么写、语义匹配怎么做、超时降级怎么配、性能和成本怎么控希望对正在做类似多智能体系统的团队有点参考价值。1. 当智能体数量超过五个团队就开始抓狂1.1 我遇到的实际问题最开始我们其实没有多智能体只有一个客服问答机器人。后来业务方陆续提出新需求团队就顺手加了几个独立智能体服务文档摘要、舆情监控、报表生成、运营分析。半年后数了数线上已经有 9 个用不同技术栈实现的智能体Python FastAPI 的、Node.js 的、还有两个是一个 LangGraph 工作流直接暴露成 HTTP 服务的。每个服务都有自己的一套 API 风格、参数定义和返回结构。起初路由很简单无非就是在调度代码里写一堆条件分支if 总结 in user_query: target_agent doc_agent elif 舆情 in user_query: target_agent social_monitor elif 报表 in user_query: target_agent report_agent else: # 兜底交给客服智能体但也经常答非所问 target_agent support_agent这套代码一开始能跑但随着智能体数量增长很快就失控了。典型场景是新来的同学问我想加一个“会议纪要智能体”他要改的不仅是新服务本身还要动调度服务里的 if/else、改一堆优先级判断、加一堆特例。时间一长这个调度服务就被戏称为“祖传大泥球”——没人敢随意改因为任何一次调整都可能影响线上流量。更麻烦的是智能体之间的能力边界开始重叠。文档摘要智能体声称自己能做“总结”舆情监控智能体也写了“总结”能力报表生成智能体能“汇总数据”运营分析智能体也能“汇总数据”。不同小组为了表现能力能力描述写得一个比一个全但真正执行时又会说“这不是我的职责”。这种情况不需要多发生两次你就会意识到缺一个统一的能力注册与路由层。1.2 Agent-Reach 想解决什么我当时想得很清楚团队需要的不是再写一个“更聪明的 if/else”而是一个能支撑智能体持续增长的基础设施。于是 Agent-Reach 的定位就变成了三层注册中心统一保存智能体的基本信息、能力描述、健康状态和版本号新智能体接入只需要在这里做一次登记。路由引擎根据用户请求或上层任务的描述自动计算候选智能体列表并排序不再依赖硬编码的条件分支。执行网关统一负责调用转发、超时控制、重试、限流和结果校验让每个智能体的内部实现细节不再暴露给上层。一句话概括Agent-Reach 做的事情就是“谁有能力谁上谁最近稳定谁优先”而调用方只要跟 Agent-Reach 对话就行。它不关心下游是一个 Python 服务还是一个 Node 服务也不关心智能体内部用了什么模型。这个抽象层的价值在智能体数量越过五六个之后会非常明显新接入智能体时注册一份能力描述就可以被路由到路由策略的调整也只发生在路由引擎内部不动任何业务方代码。到后面我们甚至没有智能体清单的硬编码Agent-Reach 成了所有智能体的“唯一电话号码本”。2. Agent-Reach 的总体设计与选型依据2.1 架构总览注册中心、路由引擎、执行网关整体调用链长这样调用方 - Agent-Reach API - 路由引擎打分排序 - 执行网关转发容错 - 目标智能体 ^ v 注册中心能力清单 健康检查/追踪/成本记录注册中心是源头。每个智能体上线前必须提交一份能力描述文件我下文会给出 SchemaAgent-Reach 启动时加载所有描述之后每当有新的注册或版本更新会对内存索引做一次热更新。注册中心还做一个轻量级健康检查每 30 秒探测一次每个智能体的/healthz接口状态不好的智能体会被标记为degraded路由时降权。路由引擎主要负责两件事一是把任务请求映射成可计算的匹配信号二是在匹配信号基础上加权打分选出前 N 个候选智能体。这块是整个系统最核心也最容易被做复杂的地方后面我会单独聊匹配算法。执行网关则是一个比较通用的转发层。它拿到路由引擎给出的候选结果后按照优先级依次调用智能体的 HTTP 接口同时处理超时、熔断、结果格式校验并把每次执行的 trace 信息写进日志。网关还承担限流职责——防止某个智能体因为粘性流量被打爆。执行网关的设计很大程度上是抄了微服务网关的思路但简化了很多因为智能体之间不需要微服务那样复杂的服务发现和负载均衡候选集本来就可以由路由引擎动态生成。2.2 为什么不用现成的编排框架在动手写 Agent-Reach 之前我们其实认真评估过一圈市面上的主流框架比如 LangChain/LangGraph、AutoGen、CrewAI。它们的定位不同用下来感受也不一样我列个对比表说明当时的选择逻辑框架/方案擅长场景在我们项目里的问题LangChain / LangGraph单个智能体内的工具调用、流程编排它更偏“一个智能体如何调工具”而不是“多个独立服务如何互相发现”。我们这些智能体已经是以独立服务形态存在了硬迁到 LangGraph 等于重写一遍AutoGen多智能体对话、角色扮演它的核心模型是多智能体会话适合研究原型。但生产环境里每个智能体都是有状态的 HTTP 服务让它们在一个会话里互相喊话运维责任太重CrewAI角色分工的任务执行同样偏角色协作但我们的智能体不是统一跑在 LLM 上下文里的“角色”而是各自有独立技术栈和部署单元这超出了它的设计边界自研 Agent-Reach能力注册、路由、网关三层它解决的本质问题是“智能体可达性”不是流程编排所以可以和任何流程引擎共存这个对比让我确定了关键判断我们的问题不是“怎么写一个工作流”而是“一群独立的智能体服务如何被稳定地调度”。这正是 Agent-Reach 要做的。它不是要替代 LangGraph而是可以放在 LangGraph 前面或后面——上游做流程编排到真正执行某个能力时把执行请求交给 Agent-Reach 去找具体服务。如果你们团队的情况是若干智能体都在同一个代码仓库里、由同一个人维护那确实不需要这种面向服务的路由层。但如果你和我们一样面临着跨团队、跨技术栈、甚至跨部门的智能体协同一个独立的注册与路由服务几乎是绕不开的。3. 核心实现能力描述、路由与容错的细节3.1 能力描述文件的 Schema路由能算得准前提是信息进得来、进得来还得标准化。我们给每个智能体定义了一套能力描述文件YAML 格式必须和代码一起走版本评审。下面是一个简化示例id: doc-summarizer version: 1.2.0 display_name: 文档摘要智能体 endpoint: http://doc-agent.internal:8001/v1/execute health_check: http://doc-agent.internal:8001/healthz timeout_ms: 30000 capability: action: summarize domains: - document - report - email input: required: - content optional: - max_len - format output: content_type: text max_length: 2000 confidence_weight: 0.9 tags: - 摘要 - 总结 - 提炼 keywords: - 总结一下 - 太长不看 - 一句话概括 languages: - zh - en conditions: - 输入必须包含正文内容不能仅传URL这里有几个设计时验证过很重要的点。tags和keywords必须由智能体开发团队自己填不能省。很多团队会觉得反正有 embedding 语义匹配了关键词不需要。但我们实测下来语义匹配对专有名词和缩略词非常不稳定比如“PRD总结”和“文档总结”在向量空间里未必足够接近。而显式关键词是有确定性收益的成本极低。这个字段千万不能省。conditions一开始我也不太理解它的价值后来栽过跟头才明白它有多重要。它相当于智能体预设的“前置条件说明”路由引擎在匹配时会把任务输入与conditions做简单的规则校验如果输入不符合条件即使相似度很高也会被过滤掉。比如文档摘要智能体说自己不接受只传 URL那路由引擎就不会把一个只有 URL 的任务路由给它宁可交给下一个候选或进入人工复核。字段里的confidence_weight是给路由引擎的一个先验权重。因为不同智能体对自身能力的描述有浮夸倾向我们允许在接入时人工设置一个 0~1 的系数但要求必须给出合理理由。这个系数最终会参与打分但不是决定性的。3.2 路由匹配算法从精确匹配到语义匹配路由引擎的打分我们分了三条线分别覆盖确定性匹配、语义匹配和运行状态匹配最后加权得总分def score_candidate(agent_desc, task): # 第一层规则匹配关键词/标签命中情况 rule_score rule_match(agent_desc[keywords], agent_desc[tags], task.text) # 第二层语义匹配文本向量余弦相似度 semantic_score semantic_match(agent_desc[capability], task.embedding) # 第三层状态因子健康度越高的智能体分数越高 health_factor agent_desc[health_factor] # 0~1 final_score ( 0.3 * rule_score 0.5 * semantic_score 0.1 * agent_desc[confidence_weight] 0.1 * health_factor ) return final_score我解释一下权重为什么这样配。semantic_score权重最高因为用户请求的表述千变万化规则匹配永远会有漏网之鱼但纯靠语义又容易误召所以把规则分拉到 0.3 来稳定输出。confidence_weight和health_factor各占 0.1主要起微调作用避免某个智能体因为写了个高权重描述就一直霸榜。这里有个容易被忽视的细节任务文本的向量计算。我们一开始用的是简单的将整段任务描述整体向量化后来发现效果不理想因为一个请求里往往包含多个意图比如“帮我把上周的日报总结一下然后发给运营团队”这句话里既有摘要意图又有发送消息意图。后来优化为用一个小模型先做意图分类和关键词抽取再用抽取出来的关键片段去做向量匹配。这一步大概让整体精确率提升了 8% 左右代价是路由时延增加约 30 毫秒完全可接受。候选集生成我们用的是 Top-K 方式默认取前 3 名。最终判定规则是最高分 ≥ 0.75直接执行该候选。最高分在 0.55~0.75将候选列表返回给上层由上层决定是否确认。最高分 0.55认为没有能力匹配的智能体返回“未找到可用能力”进入人工复核通道。第一条阈值设得比较保守是故意为之。宁可多让上层确认一次也不要因为分数虚高就乱执行。毕竟在真实业务里一个任务被错派给错误的智能体浪费的不只是那一次调用成本还可能引发连锁错误。3.3 超时、重试与降级策略路由找到候选只是开始真正执行时各种幺蛾子比匹配不到能力还多。这里我列一套我们在 Agent-Reach 里沉淀下来的参数不一定适用于所有人但可以直接抄作业参数默认值说明timeout_ms30000每个智能体端点上配置长任务可以单独调大max_retries2最多重试两次只在任务幂等时才允许重试retry_backoff指数退避基础 1s加随机抖动避免雪崩式重试打垮下游circuit_breaker失败率 50% 且持续 60s 则熔断熔断窗口 30s期间该智能体降权到 0.1fallback按候选集依次尝试首选失败后自动尝试第二候选重试逻辑有几个坑。第一不是所有智能体都幂等。比如“发送通知”这个动作被重试一次就意味着发两条通知。我们在能力描述里增加了一个idempotent: true/false字段网关读取后才能决定是否对失败请求做重试。第二重试时一定要在请求头里带上同一个x-reach-request-id让下游能识别这是同一次请求方便做去重和排查。第三指数退避不能只有固定倍数要加抖动否则多个并发请求的重试会形成节奏一致的流量波峰非常容易把下游服务直接震挂。降级路径我们也考虑过。如果候选集里所有智能体都不可用Agent-Reach 不会直接返回失败而是把任务放进一个manual_review_queue同时给值班人员发一条通知。人工处理结果可以回填到路由日志里作为后续调参的数据。这个“失败不落地”的设计在真实运营中很重要因为多智能体系统里任何一个环节的抖动都可能让最终用户得到一个莫名其妙的错误而我们希望至少保证有兜底。4. 实测中的意外情况与排查过程4.1 智能体互相“甩锅”任务无人处理上线后遇到的第一个棘手问题不是匹配不上而是匹配上了但智能体拒绝执行。有一次线上突然出现大量“任务失败”告警业务方反馈说有些数据报告任务被自动路由到一个数据分析智能体结果这个智能体返回的错误信息是“本智能体仅处理结构化数据无法处理非结构化文本请转人工。”我们追 trace 发现路由打分环节这个智能体确实排名第一因为它的能力描述里写了“数据分析”和“文本处理”两个 tagconditions却没有限定输入格式。它的能力描述写得比实际执行能力宽泛导致它自己给出的“能力承诺”和真实边界不一致。根因很清楚能力描述是智能体开发团队自己写的人都有高估自己能力的倾向。我们的解法是在能力描述里增加了一个审核环节要求每个新版本的能力描述必须附上“负例场景清单”——也就是这个智能体明确不处理什么。这个负例清单会参与路由过滤比如数据分析智能体写明“不处理非结构化文本”那路由时遇到非结构化文本任务就直接过滤掉。这个改动上线后类似甩锅问题降低了七成。4.2 语义匹配的误召率问题两个智能体能力太像另一个高频问题是语义匹配误召。运营团队有两个智能体一个做“日报摘要”一个做“周报复盘”。从用户视角看两者的语义描述确实非常接近向量相似度在 0.9 以上。结果出现一个需求“把本周的运营总结发我”路由引擎连续几次都选中了日报摘要智能体返回的内容是每日颗粒度根本不是业务方想要的周报维度。这个问题的本质是语义空间里两个能力挨得太近但实际执行语义差异很大。我们第一版想通过提高精确阈值来解决但阈值调高了又会让许多本应正常命中但表述不那么标准的请求掉出候选集影响体验。后来我们的方案是在能力描述里增加action和domains两个维度作为硬过滤条件。action可以理解为一等能力分类比如summarize、analyze、generate_reportdomains是领域分类。路由时先按 action domains 过滤缩小候选范围再做语义打分。只要日报和周报的 action/domains 配置不一样哪怕语义接近也很难跨错。这本质上是“宁可多写一点结构也不依赖纯向量”。当然这个方案需要每个智能体在接入时就把 action 归类想好。我给团队的建议是不要自创太多 action系统预置一份常用分类表比如summarize、analyze、search、generate、transform、notify有特殊需求再走评审扩展。保持 action 集合小误召率会低很多。4.3 排查链路把请求链路可视化出来多智能体系统的排障比单体服务难一个数量级。尤其是在半夜告警的时候你根本不知道一个任务到底经过了哪些智能体、在哪个环节卡住、哪个服务超时。上线早期我们遇到过这样的场景明明网关日志显示已经调用了目标智能体但目标智能体日志里根本没有这条请求两边各说各话。后来我们全面接入了 OpenTelemetry 风格的追踪。Agent-Reach 为每个路由请求生成一个全局唯一的trace_id这个 ID 通过 HTTP header 传给下游智能体要求所有接入方在日志里带上它。这样在日志平台里直接搜 trace_id就能把一次任务的完整链路拉出来路由打分用了多久、选中的是哪个候选、网关转发了几次、每次执行耗时多少、最终结果从哪里返回的。我们还搭了一个简单的可视化页面按 trace_id 就可以链式查看各个智能体的调用顺序和时间线。这个追踪体系上线之后排障效率提升非常明显。有一次我们发现某个任务的实际耗时几乎是预期的一倍通过 trace 看到是网关因为超时重试了两次但重试都打到了同一个故障节点上。如果当时没有这条链路这个排查过程至少要花半天。需要提醒的是能力接入的时候就要把 trace_id 传播的规范定下来不要等出了问题再补。我们就是因为最开始接入的智能体没有统一规范后来挨个改造接口和日志格式花了很大力气。作为中间层Agent-Reach 承担了唯一的上游入口这让 trace_id 的传播规范变得非常简单下游只需要把一个 header 解析出来打日志就行。5. 性能与成本扣细节的经验5.1 延迟分布与瓶颈定位Agent-Reach 第一版上线时的路由时延大概在 600ms 左右听上去不多但对上层服务来说这是纯新增的固定开销用户体感非常明显。我们用性能分析工具打了一版延迟分布发现大头在四处任务文本向量化约 180ms候选向量相似度计算约 220ms健康检查同步请求约 150ms这个完全不该阻塞路由其余序列化和网关转发约 50ms优化方案分三步。第一把健康检查从同步请求改成异步缓存网关侧每 30 秒拉一次各智能体状态路由时直接读缓存不再现场等探测结果第二把向量化结果按文本 hash 缓存起来同一个任务反复出现时直接命中缓存第三也是最关键的——当候选智能体数量少于 50 个的时候根本不需要上向量数据库直接做暴力线性计算就好反而比引入索引快得多。优化后路由部分延迟稳定在 120ms 以内网关转发部分另算。这个数据对大多数业务场景够用了。如果你预期智能体数量会快速涨到几百个建议在三方存储里启用向量索引但不要把路由延迟的预算设计得太紧张毕竟它只是一个调度开销不是执行业务逻辑本身。5.2 并发控制与限流Agent-Reach 作为中心调度层还有一个很大的潜在风险如果某个智能体非常热门所有任务同时涌向它会把下游直接打挂。我们虽然没有在企业级 API 网关上做那么复杂的流量治理但还是实现了一个按智能体维度的令牌桶限流。核心规则是每个智能体在注册时可以声明max_concurrency和max_qps。路由引擎在打分时会参考当前智能体的实时负载如果负载已经超过阈值该智能体会被降权任务优先调度给次优候选或者放入等待队列而不是直接往热点上堆。这套机制上线后有好几次上游流量突增下游智能体没有出现被打挂的情况。实际配置中要注意max_qps不宜设得太死建议留 20%~30% 的缓冲余量。因为任务执行时长波动很大同一个智能体处理 3 秒级任务和 30 秒级任务的负载差异巨大纯 QPS 不够全面。我们在网关里统计了每个智能体当前正在执行的请求数inflight把这个数值和 QPS 一起作为限流依据比只看一个指标靠谱得多。5.3 成本台账把每一次调用变成可审计的记录做多智能体系统老板最关心的问题之一就是成本。每个智能体背后可能是不同的模型、不同规格的 GPU 或 API 服务调用一次到底花了多少钱很多时候根本没数。Agent-Reach 从设计之初就要求网关在每次请求完成后记录一条成本明细包括调用的智能体 ID、模型名、输入 token、输出 token、执行耗时、重试次数、最终状态。这些明细沉到日志系统里我们每周会跑一张“智能体成本账单”报表按智能体维度汇总总调用次数、总耗时、总 token 消耗。这个数据帮我们发现了不少问题比如有个智能体执行成功率极高但单位调用 token 消耗是其他智能体的三倍因为它的系统提示词写了一万字。后来我们把提示词压到两千字以内成本直接降了一半。没有成本台账这种问题你根本定位不到。有一点要特别提醒路由引擎自身也有成本主要体现在任务文本的向量化调用上。如果所有任务都走向量化这部分费用会随着请求量线性增长。我们给路由引擎加了一个降级逻辑如果任务文本能通过规则层的关键词匹配得出明确候选就不再计算语义层分数直接走快速通道。这个优化让路由引擎本身的成本下降了约 40%且几乎没有影响路由准确率。6. 一些可以少走弯路的建议6.1 小规模团队的 Agent-Reach 适配清单Agent-Reach 不是所有团队的必需品。如果你只有两个智能体而且都由同一个后端服务维护那完全没必要引入一个独立路由层反而是过度设计。但从我实际经验来看出现下面任意两条就值得考虑智能体数量 ≥ 5且预计还会持续增加智能体由不同小组或不同技术栈独立开发和部署核心业务开始因为调度逻辑混乱而出现互相协调的问题有人在你面前提“要是能自动找到能处理这个任务的智能体就好了”。刚开始不要一上来就做全套。我建议的落地顺序是分三步走第一步只做能力注册 基于关键词/标签的硬匹配验证“把调度从代码里抽出来”这个思路是否成立第二步补语义匹配和候选打分让路由效果上一个台阶第三步再上执行网关的重试、熔断、限流和成本台账这时候系统已经能扛住生产流量了。一步到位反而容易因为太多不确定因素导致上线反复受阻。6.2 后续可以扩展的方向Agent-Reach 目前的形态已经能解决核心的“可达性”问题但后面还有一些明确值得做的东西。比如路由引擎的人工反馈闭环把人工复核队列里那些被纠正过的路由结果收集起来定期重新衡量打分权重甚至可以微调一个小模型来优化排序。又比如跨组织的智能体注册协议让外部团队的智能体只要声明能力描述就能接入这会极大提升生态扩展性。还有智能体之间的协商机制——如果某个任务需要多个智能体协同完成Agent-Reach 负责把任务拆解后的子请求分别路由给对应服务再把结果合并返回。最后说一点个人心得。做这个项目时最开始的挫折感很强总觉得“智能体路由”听起来太基础好像不值得做成一个独立系统。但真正跑起来以后我才发现恰恰是这种基础能力最容易被忽视也最容易成为瓶颈。一个多智能体系统能不能规模化不取决于单个智能体有多聪明而取决于它们之间能不能被高效、可靠地连接起来。Agent-Reach 的诞生就是这样朴素的结论。
返回列表