多智能体系统上线半年之后,我最大的感受是:单个Agent的能力已经不是瓶颈,瓶颈变成了“我不知道哪个Agent能干什么、该找谁”。团队里十几个Agent各管一摊,客服Agent、售后Agent、库存查询Agent、报表生成Agent,业务方每次要干一件事,都得来问“这个你们能做吗”,连我们自己都要翻半天文档才能答上来。后来我们做了一个叫 Agent-Reach 的内部项目,专门解决智能体触达能力的度量、注册和调度问题。这篇文章就是把从设计到落地、再到踩坑的过程完整梳理一遍,给正在做多智能体平台或者准备做Agent治理的同学一个参考。
这个项目到底解决什么问题?简单说,它做了三件事:让Agent的能力可以被登记、被度量、被路由。你可以把它理解成一份“Agent能力地图”,知道每个Agent能触达哪些工具、哪些数据源、哪些业务场景,也知道当用户请求进来时,应该把任务派给谁。它更适合正在从“单个Agent做demo”走向“多Agent做生产系统”的团队,也适合所有被“Agent太多但不知道该选谁”折磨过的同学。
1. 项目整体设计与核心思路
1.1 从单Agent到多Agent,问题本质变了
以前做一个Agent的时候,事情很简单:一个模型、一套提示词、一个工具集,所有请求都打到这一个入口,性能好坏、效果高低都取决于这一个Agent自己。但一旦变成多个Agent协同,问题就从“模型能力”变成了“系统治理”。
我们遇到的最直观的问题是能力不透明。某个Agent接入的时候明明配置了可以查订单、查库存、查物流三个工具,但上线三个月之后,连负责人都记不清它到底能不能查售后单了;另一个Agent虽然文档里说支持数据分析,但实际跑起来,只要数据量大一点就开始丢字段,等于半残状态。这种“名义能力”和“实际能力”之间的差距,导致上层调度根本没法做可靠决策。
Agent-Reach的设计逻辑是从一次故障复盘里逼出来的。当时有业务方投诉,说用户问了一句“我上个月的退款什么时候到账”,系统把请求路由给了一个主营营销活动的Agent,那个Agent完全不懂退款单的逻辑,兜底语术从早上八点说到晚上十点,用户等了半天等来一句“这笔退款正在处理中”,彻底把用户惹毛了。复盘时我们意识到,路由层在做决策时没有任何关于Agent可达性的依据——它只知道“这个Agent看起来关联了售后工具”,但不知道“它真实能处理退款查询”。所以Agent-Reach的第一个核心思路就是:先把触达能力变成可量化的数据,再谈调度。
1.2 Agent-Reach 的核心能力拆解
整个项目围绕三个核心能力展开,这也决定了系统架构的基本形态:
| 能力 | 做了什么 | 解决了什么问题 |
|---|---|---|
| 能力注册 | 每个Agent启动时向注册中心上报自身元信息,包括能触达的工具、数据源、业务域、并发上限、超时配置 | 解决“能力不透明”,让系统有一份实时更新的Agent能力清单 |
| 触达度量 | 通过探活任务和真实流量采样,定期计算每个Agent对各业务域的触达成功率、平均耗时、稳定性 | 解决“名义能力”和“实际能力”的差距,让能力数据真实可用 |
| 触达路由 | 请求进来后,基于能力注册信息和触达度量结果,把任务分发给可达且稳定的Agent,支持降级和故障转移 | 解决“该找谁干活”的问题,提升整体成功率和用户体验 |
在实现的时候,我们把这三个能力拆成了四个组件:Agent注册中心、触达矩阵计算模块、触达路由器和监控面板。Agent注册中心负责元信息的登记与心跳维护,触达矩阵计算模块周期性跑探活任务并产出触达矩阵,触达路由器根据矩阵做实时分发,监控面板负责对全流程做指标可视化。后面的实操部分会逐个讲每个组件的落地细节。
1.3 为什么不做“让Agent互相调用”的方案
最早我们也讨论过一个看起来很性感的方案:Agent之间互相发现、互相调用,A触达不到的B自动补位,完全去中心化。当时团队里有人提议用Agent自己的推理能力做路由,但被我否了。原因很简单:路由决策是需要稳定性的,推理模型天然有概率性失败。如果让Agent自己去判断“我该找谁”,那么路由本身的错误率会叠加到业务错误里,出错之后还不好回溯。用规则和度量数据做路由,虽然看起来“不智能”,但每次决策都有据可查,出了问题能明确说是矩阵数据过期了还是注册信息错了,这对生产系统太重要了。
2. 触达矩阵:Agent能力可视化的核心
2.1 触达矩阵的数据模型
触达矩阵是整个项目里最关键的抽象。它就是一个二维表格,行是Agent实例,列是业务能力维度,单元格里存放的是这个Agent对这项业务能力的触达状态和触达质量。
我们业务能力列的具体定义和取值逻辑如下:
- 业务域:从实际业务场景抽象出的能力域,比如订单查询、售后处理、库存查询、数据分析、营销配置。每个Agent在注册时必须声明自己覆盖哪些业务域。
- 触达状态:取值只能是
reachable、unreachable、unknown三种。reachable表示探活和采样都通过;unreachable表示连续多次探活失败;unknown表示Agent在线但长时间没有覆盖该业务域的实际请求。 - 触达质量:0到1之间的加权得分,由探活成功率、近7天真实请求成功率、平均响应延迟三个子项加权算出。权重不是拍脑袋定的,我们通过历史数据拟合时发现,真实请求成功率的权重至少给到50%,探活成功率占30%,延迟占20%比较稳,因为探活环境跟真实环境总是有差异,完全靠探活容易把状态打分打得虚高。
元信息注册采用JSON Schema约束格式,保证每个Agent上报时字段齐全。我们用的核心注册字段大概是这样的:
{ "agent_id": "agent-orders-001", "name": "订单查询Agent", "business_domains": ["order_query", "logistics_tracking"], "capabilities": [ { "domain": "order_query", "tools": ["order-search-service", "refund-status-service"], "data_sources": ["order-db", "refund-db"], "timeout_ms": 3000, "rate_limit_per_min": 120 } ], "fallback_agents": ["agent-general-cs-001"], "status": "active" }这里有个容易忽略的坑:capabilities里的timeout_ms和rate_limit_per_min不能照抄Agent开发文档,必须由路由层实测校准。我们后来加了一个“注册信息复核”环节,Agent上线后先跑一小时的灰度探活,探活数据反哺校准超时配置,否则超时设太长会把整个路由链路拖垮。
2.2 触达矩阵的计算和刷新策略
矩阵数据不是一次算完就固定不变的,Agent的状态随时可能变化——工具依赖的服务挂了、数据源权限改了、大版本升级后行为变了,这些都会导致触达能力变化。我们采用“周期探活 + 动态采样”双轨刷新。
周期探活由触达矩阵计算模块执行,每5分钟对每个Agent发送一组轻量探针任务。探针任务会覆盖该Agent注册的所有业务域,比如对订单查询Agent发送一个真实的订单号查询请求,对库存Agent查一个测试SKU的库存。这里有个设计细节:探针请求必须构造得足够逼真,又不会产生脏数据。我们给探针任务打了专门的trace标记,业务侧看到这个标记就知道是探活流量,不写库、不推送通知。
动态采样则是从真实业务流量中捞数据。触达路由器每次分发完任务之后,会把请求结果异步回传给计算模块,由计算模块把成功、失败、超时这些结果累计进触达质量的统计窗口。真实流量采样有一个好处,就是能发现探活发现不了的问题——比如并发一高某个Agent就频繁超时,单发探针的时候根本测不出来。我们最终发布矩阵采用的是“探活结果 + 真实采样结果”按7天窗口聚合,每小时重算一次并写到内存缓存,保证路由器读到的永远是最新一份。
2.3 触达矩阵的可视化
可视化是给管理者和业务方用的,但搞不好就会变成自嗨。我们第一版画了一个巨大的力导向图,Agent节点和业务域节点连线密密麻麻,好看是真的,一点用都没有。后来砍掉重做,就保留两个视图:
- 矩阵热力图:行是Agent,列是业务域,颜色表示触达质量从红到绿。业务方想看“售后这件事谁能干”,一眼扫过去就能看到哪一行是绿的。
- 触达率趋势折线:按天展示每个Agent的整体触达率,用于发现周期性劣化,比如某个Agent每周三触达率骤降,后来排查发现是定期数据归档任务把查询权限临时改了,这类规律性问题是靠趋势图发现的。
个人经验是,这类内部工具的UI越朴素越好,数据密度比花哨重要,能用热力图表达的信息就不要额外做交互。
3. 触达路由:把请求分发给“真能干”的Agent
3.1 路由决策的完整流程
触达路由是Agent-Reach里跟线上体验直接相关的一环。每次业务请求进来,路由器按下面这个顺序处理:
- 语义识别业务域:请求先经过一个轻量分类器,识别出请求属于哪个业务域。我们没用大模型做分类,用的是微调的短文本分类模型加关键词规则兜底,因为纯大模型分类的延迟抖动太大,不适合高并发入口场景。
- 查询候选Agent集合:从注册中心取出覆盖该业务域、且状态为
active的Agent集合。 - 过滤不可达Agent:查最新的触达矩阵,把状态为
unreachable的Agent剔除。 - 按权重选择主Agent:余下的候选Agent按触达质量得分加权随机选择。这里用加权随机而不是直接取最高分,是为了防止所有请求都打到同一个Agent上,把它的限流阈值打爆。
- 执行调用并记录结果:调用结果异步回传触达矩阵计算模块,用于滚动更新质量数据。
- 失败降级:主Agent调用失败或者超时,自动降级到候选集合中得分次高的Agent,并打上降级标记,方便后续统计。
每一步的日志都必须带上request_id、agent_id和decision_reason三个字段。decision_reason尤其重要,它记录这次路由是因为什么做的选择——是矩阵得分最高、还是主Agent失败后的降级。没有这个字段,出问题的时候你根本分不清是路由决策错了还是Agent本身执行错了。
3.2 三种路由策略的选型对比
我们在实现过程中试过三种路由策略,最终是组合使用而不是只用一种:
| 策略 | 机制 | 适用场景 | 我们的结论 |
|---|---|---|---|
| 规则路由 | 按会话来源、用户类型、固定Agent映射 | 渠道隔离、白名单客户 | 稳定但僵化,覆盖不了长尾请求 |
| 语义路由 | 分类模型识别业务域后匹配Agent | 通用请求入口 | 覆盖率高,但分类错误会直接路由错误 |
| 质量加权路由 | 结合触达矩阵质量得分做权重分配 | 多Agent可互相补位的场景 | 最能提升整体成功率,但依赖矩阵数据实时性 |
最终在线上我们把三者组合成“规则优先+语义识别+质量加权”的链路:先判断是否命中强规则,比如VIP用户必须走专属Agent;没命中规则才走语义识别;识别出业务域后,再由质量加权路由做Agent分发。这套组合的好处是,兜底的规则减少了语义模型错误对头部客户的影响,质量加权保证了长尾请求也有合理去处。
3.3 降级策略和人工兜底
降级不是简单的“A挂了就换B”,还要考虑降级链路的深度和兜底质量。我们的降级链默认是两层:主Agent失败后降级到候选集里分数第二高的Agent;第二层再失败,才会落到全局兜底Agent。全局兜底Agent用的是通义千问这类通用大模型加精简业务FAQ,它的触达范围最广,但业务准确率不高,只能用来保证用户不会面对“无响应”,不能指望它解决复杂问题。
这里我踩过一个深刻的坑:降级必须带上上下文压缩。有一次主Agent超时后降级到兜底Agent,结果兜底Agent收到的上下文完整保留了原始会话——里面有十几轮和主Agent之间的工具调用历史,兜底Agent直接懵了,回答质量很差。后来我们给降级出口加了统一的上下文清洗函数,降级时只传递用户原始输入、必要的用户画像字段,不传递任何工具调用中间结果。降级链路质量因此上了一个台阶。
4. 实操过程:从零搭一套Agent-Reach
4.1 Agent注册中心与元信息管理
注册中心是整个系统最先要落地的基础模块。我们选型时对比过Etcd和Redis,最终用了Etcd,原因是它的watch机制能支持Agent列表变更的实时推送,路由层可以及时感知上下线。每个Agent启动时执行一次注册,注册成功后每30秒发送一次心跳,心跳连续3次丢失就将状态标记为offline,并从候选集合中摘除。
元信息管理的核心是把前面说的JSON Schema注册规范做成强制校验,不允许Agent想怎么传就怎么传。我们在CI流程里加了一个包了一层校验工具的SDK,Agent团队接入时直接调用sdk.register(config),SDK内部负责字段校验、心跳维护和探活信号对接。这样Agent团队不需要理解Agent-Reach的整体架构,只需要回答“我这个Agent能干什么、最多承受多少并发”这类业务问题即可。
还有一个细节:注册中心必须支持多版本并存。Agent做灰度发布的时候,新旧两个版本会同时在线,老版本负责存量流量,新版本接收灰度流量。我们在注册字段里加了version和traffic_group,路由器在读取候选Agent时会按流量分组过滤,否则灰度发布一启动就会导致新旧版本同时被路由命中,引发重复处理。
4.2 探活任务的编排和调度
探活模块的落地分为三块:探针任务库、调度器和结果聚合器。
探针任务库就是一组预置的测试请求模板,每个模板绑定一个业务域。比如“订单查询”域有一个探针任务是“查询单号为TEST-2024-0001的订单状态”,“库存查询”域是“查询SKU为TEST-SKU-A1的库存量”。开发探针模板时最花时间的不是写模板,而是跟各业务方确认哪些操作可以用不会产生副作用的测试数据。
调度器用的是一个简单的定时任务框架,每5分钟遍历一次所有在线Agent,对每个Agent的每个业务域跑一个探针任务。这里必须注意并发控制——如果同时有30个Agent在线,每个Agent探5个业务域,就是150个探针任务同时出去,很容易把下游服务打冒烟。我们加了全局信号量,探活并发控制在20以内,剩余任务排队执行。
结果聚合器把探针结果按“Agent × 业务域”维度累计,记录成功次数、失败原因、平均耗时。数据落在时序数据库里,触达矩阵计算模块直接查询这个时序库就能算出触达状态和触达质量。
4.3 触达路由器的实现要点
路由器的实现看着不难,但高并发下有几个隐藏问题。我们的入口QPS峰值在800左右,虽然不算夸张,但路由决策必须控制在10毫秒以内,否则对业务延迟影响明显。为此我们做了几个关键优化:
- 矩阵数据本地缓存:路由模块不直接查远程矩阵库,而是每秒钟从本地缓存读取一次最新矩阵。缓存更新通过注册中心推送和计算模块定时写入双通道保障。
- 候选集预计算:注册中心每次更新后,预先按“业务域 → 可用Agent列表”建好索引,路由时直接O(1)查索引,避免实时遍历全部Agent。
- 飞书/钉钉告警分离:路由模块只负责决策,不负责告警;决策结果通过异步消息队列发到监控服务,这样路由模块本身不会被告警逻辑拖慢。
路由器本身也做了降级保护:如果触达矩阵数据超过15分钟没更新,就自动切换到“只按注册信息路由”的模式。这个模式虽然会把请求发给一些实际已不可达的Agent,但至少不至于因为矩阵数据缺失而整个路由不可用。
4.4 指标监控与告警规则
上线初期我们只监控了通用的成功率,结果很多东西看不出来。后来梳理出一套Agent-Reach自己的监控指标体系:
- 触达率:某个Agent在全部业务域上可达的比例。触达率低于90%需要关注,低于80%会自动从路由候选集中摘除。
- 降级率:主Agent失败后走降级链路的比例。正常情况下降级率应该在5%以下,如果某Agent的降级率超过15%,说明它的注册信息和真实能力已经严重不匹配。
- 路由决策耗时P99:路由器的性能指标,超过20毫秒就要排查是不是缓存失效了。
- 探活利用率:探针任务本身占下游接口的比例,超过5%就要调大探活周期,不能为了监控把业务服务打挂。
告警采用分级策略:触达率下降触发P1告警,路由耗时升高触发P2告警,探活任务异常触发P3告警。P1告警直接走短信和电话,P3只发飞书群消息。这一套配好之后,整个系统才算真正具备生产环境可运维性。
5. 踩坑实录:这些问题不试一次真的想不到
5.1 Agent“假死”与上下文超限
我们遇到的最隐蔽的问题是Agent明明在线、心跳正常、探活也成功,但业务请求过去就是不行。排查了很久才发现,问题是Agent内部的会话上下文缓存一直在膨胀,累积到一定量之后每次请求都要把所有历史上下文重新灌入模型,导致推理延迟飙到十几秒,直接超过了路由器的超时阈值。
这个现象我们内部叫“假死”。后来在Agent SDK里加了三重防护:一是上下文窗口长度超过设定阈值就自动执行截断,把最早的会话轮次压缩成摘要;二是单请求上下文超过1MB时直接拒绝执行并返回明确错误码;三是在探活任务里增加了一项延迟探针,专门测量Agent在“上下文接近满载”时的响应延迟。有了这三重防护,“假死”的问题基本被根治了。
5.2 失败重试导致的重复请求风暴
最开始我们只做了简单的“失败就重试”,结果引发过一次线上事故。某个下游数据服务故障了30秒,在这30秒内所有路由到订单查询Agent的请求都失败,每个失败请求又自动重试3次,下游服务恢复后一下子涌入4倍的请求量,直接被冲垮。
这个教训让我们重新设计了重试策略:每个请求最多重试2次,重试之间采用指数退避,并且必须带上全局幂等键request_id,下游服务根据幂等键去重。另外增加了一个熔断机制,如果某个Agent的连续失败率达到50%,路由器会把它临时摘除5分钟,不再往上面扔任何请求。熔断期结束后才会恢复少量流量探测,确保不是一恢复就又被冲垮。
5.3 路由误判后如何快速定位
路由误判是这类系统里最难排查的问题之一,因为往往不是路由逻辑错了,而是训练集和业务实际的分布不一致。比如我们的语义分类模型在测试集上准确率95%,上线后“物流查询”这个业务域的请求经常被分到“订单查询”域,因为两者在话术上高度相似。
提供的解法是给路由决策日志加上了置信度采样机制——分类器输出概率低于0.9的请求,全部进入旁路日志,抽样人工复核。我们每周抽200条低置信度路由日志做标注,再增量微调分类模型。坚持了两个月,物流查询域的错误分布降了约60%。没有这份旁路日志,你根本不知道模型错在哪,只能等用户投诉。
5.4 探活数据“虚高”的教训
还有一个老实说有点羞愧的坑。我们一开始的探活任务用的是测试账号和测试数据,跟真实业务流量差异很大——测试账号的权限配置和普通用户不一样,测试数据也过于规整。结果就是触达矩阵显示的触达率非常高,但真实用户体验并没有变好。后来我们调整了探活策略,把探针任务里的数据源直接指向生产环境的只读副本,用脱敏后的真实订单号、真实SKU来做探活,同时严格保障探活请求只走只读接口。这才让矩阵数据跟真实情况对齐。
这个教训让我意识到,Agent-Reach这类治理平台的设计一定要记住一个原则:度量手段和真实业务越贴近,度量结果才越有价值。完全独立于业务之外的“纯净探活”,表面上干净,实际上失真。
6. 写在最后:Agent-Reach给我带来的几点思考
Agent-Reach从立项到稳定运行,整个过程大概是三个多月。回头看,这套系统本身没有用特别高深的技术,最复杂的部分反而是数据库模型和几个调度策略的权衡,但它确实解决了多Agent系统里最实在的问题——让每个Agent的触达能力变得可见、可管、可调度。
我个人最大的体会是,多Agent系统的复杂度不会因为模型变强而自动消失,反而会因为Agent越来越多而指数上升。单靠某几个聪明的Agent,撑不起一个可靠的整体;你需要的是一层能让不同Agent按能力分工、按质量排序、按故障隔离的治理基础设施。Agent-Reach本质上就是这一层基础设施的初步形态。
最后分享一个小技巧:如果你的团队还没想好要不要做类似的项目,可以从最小闭环开始——先把Agent能力注册表建起来,只做一个Excel也好,先把“每个Agent到底能干什么”这件事搞清楚,后面的一切治理手段都建立在这份清单之上。很多时候系统的问题是表象,对自身能力缺乏认知才是真正的根源。