
最近跟几个做智能体平台的朋友聊天大家不约而同提到一个现象单个智能体跑起来很容易两个以上的智能体放到一起反而经常掉链子。不是模型不给力而是它们之间“找不到对方、接不上话、任务送不到”的问题比想象中麻烦得多。Agent-Reach就是我在这个背景下整理出来的一个轻量级触达中间层核心解决三件事让智能体像服务一样被注册和发现、让任务请求按规则送达正确的智能体、让整个送达过程可观测可重试。Agent-Reach算是一个介于业务系统与智能体之间的“调度台”。你可以把天气查询Agent、差旅审批Agent、内容生成Agent全部登记进来然后统一从一个入口发任务。它不负责写业务逻辑也不负责调模型参数只负责确保“任务送到该干活的那个人手里”。如果你正在做多智能体协同、企业内部自动化编排或者想把一堆散落的AI能力统一暴露成服务这篇实操笔记应该对你有用。1. 项目概述与整体设计思路1.1 Agent-Reach的核心定位先把名字拆开看“Agent”指智能体“Reach”是触达、可达。连起来就是“把智能体触达起来”。很多团队做的其实不是应用而是一堆智能体能力的堆叠有人写了工单分类Agent有人写了舆情监控Agent还有人写了报表生成Agent。能力都很强但彼此是信息孤岛。业务方要想用其中某个能力得先知道它部署在哪台机器、接口长什么样、参数怎么传。这跟早期微服务没有注册中心时是一模一样的困境。Agent-Reach的定位就是补齐这个缺口。它向每一个接入的智能体提供“注册、发现、路由、重试”四件事。注册是让智能体主动上报能力和地址发现是让调用方不需要知道具体机器路由是把请求送到标签匹配的智能体重试是保证任务最终被执行。整体实现下来业务侧只需面对一个入口不再关心背后的智能体网络结构。1.2 多智能体协同时的“触达困局”我在实际项目里观察到一个很现实的问题团队把智能体做出来之后最消耗精力的往往不是模型效果调优而是“任务怎么交到正确的人手里”。比如一个内容审核Agent它需要人工审核员介入时得把请求发给另一个辅助Agent两个Agent各自有不同参数、不同超时时间、不同返回格式。最开始大家直接用HTTP互相调很快接口就爆炸了。每个Agent都维护一套私有协议改动一个字段下游五六个模块都要跟着改。这就像一个团队平时靠喊话沟通人少时还行人一多就全乱套。Agent-Reach本质上给它们配了一张组织架构图和分机号码表。每个智能体不用关心对方是谁只需要把任务说明写清楚交给触达层去转发。这套思路在微服务里早就成熟了只不过很少有人把它专门用来连接智能体。1.3 设计上的四个关键决策第一统一任务协议。所有发给智能体的请求都封装成结构化任务对象包含任务类型、输入数据、期望返回格式、超时时间。这样做的收益是接入成本低新智能体只要会解析这一种协议就能上线。第二注册驱动而非配置驱动。智能体启动后主动向Reach Server发起注册携带描述文件。不使用静态配置文件的原因是智能体场景变更频繁动态注册能避免每次改完功能还要人工改一遍路由表。第三标签路由优先语义路由兜底。硬规则保证90%的请求能在毫秒级内定位到目标只有规则覆盖不到的新场景才交给向量匹配。务实可预测不会让全网请求都跑去问大模型。第四所有状态可观测。每个任务从进入触达层到最终完成状态流转全部打点。这一点在初期不是必须但智能体数量超过十个以后没有状态追踪会让问题排查变成灾难。2. 核心机制与技术选型2.1 统一任务协议AgentDescAgent-Reach引入了一个轻量级的智能体描述文件我习惯叫它AgentDesc。它本质上是一张“岗位说明书”告诉触达层这个智能体擅长什么、怎么联系、希望收到什么样的活。字段不多但每个都有讲究下面这几个是我用了很久之后保留的核心字段字段名作用填写说明name智能体唯一名全局唯一服务发现的索引version功能版本号变更能力时递增用于灰度与管理description职责描述给语义路由看的用自然语言写清楚边界labels功能标签硬路由用的建议3到5个别堆endpoint接收地址触达层实际转发任务的URLtimeout建议超时调用方不需要知道触达层按此值兜底auth鉴权信息简单场景用Token复杂场景留空走网关description字段是最容易写崩的。很多人把它写成技术文档比如“接收JSON格式参数并返回字符串”这对路由没有任何帮助。正确写法是像跟同事交接工作一样写清楚“我是干什么的、擅长处理什么类型的问题、遇到什么情况不该找我”。语义路由就是靠这段话来理解智能体职责边界的。2.2 路由策略标签硬匹配加动态评分我见过一些多智能体项目一上来就打算用LLM做主路由把所有任务描述丢给大模型让它选。这种方案做Demo很酷上生产就痛苦延迟不可控、token成本高、偶尔抽风选错。Agent-Reach的默认路线是“先用规则把所有能确定的事情确定掉”动态评分做微调。具体逻辑分三层。第一层是标签全集匹配请求任务里带的labels必须完整命中智能体注册的labels这是最严格也是最高优先级的匹配。第二层是最小匹配请求要求有“天气、预警”而某个智能体注册了“天气、预警、环比分析”也能命中因为它的能力覆盖了请求所需。第三层是语义兜底前两层都找不到合适目标时把请求的description和所有AgentDesc里的description做向量相似度排序取最高分确认。评分并不复杂我用的公式是 score 标签命中数 * 3 语义相似度 * 1。标签是确定性的权重要高语义相似度只作为补充权重低一点不会造成全局偏离。2.3 触达保证状态机、重试与超时任务一旦进入Reach Server就进入了一个明确的状态机pending等待、routed已选好目标、accepted智能体已接收、processing处理中、completed完成、retrying重试、failed最终失败。这个流转过程可以类比快递物流面单打印好包裹进入分拣线到了中转站签收派送失败就重新安排实在送不到进异常件处理区。状态机里最值得讲的是重试策略。智能体返回5xx、连接超时或者处理超时都触发重试。重试次数我默认设2次间隔是“300ms起步每次乘以3加随机抖动”。抖动必须加否则大量任务同时失败时会形成同步重试波峰直接把下游打挂。幂等性这块放在后面常见问题里细说。关键参数里有几个经验值任务级别的超时时间建议设置为下游智能体依赖外部服务的P95响应时间的三倍比如你的Agent查天气时第三方接口最长需要8秒那这个任务的超时至少设24秒。太短会造成大量误伤重试太长会拖住整个队列。3. 从0到1搭建一个Agent-Reach3.1 最小实现注册与路由服务我自己手写过一个最小可用的Reach Server用FastAPI实现不到两百行就能跑起来。选择FastAPI没有特别高深的原因异步支持好、自带参数校验、写路由方便。下面是一个极简版先把核心脉络摸清楚from fastapi import FastAPI from pydantic import BaseModel import uuid app FastAPI() registry {} class AgentDesc(BaseModel): name: str description: str labels: list[str] endpoint: str timeout: int 24 class Task(BaseModel): task_id: str type: str labels: list[str] description: str payload: dict {} app.post(/register) async def register(desc: AgentDesc): registry[desc.name] desc return {status: registered, name: desc.name} app.post(/route) async def route_task(task: Task): if not task.task_id: task.task_id str(uuid.uuid4()) # 第一层标签全集匹配 for name, desc in registry.items(): if set(task.labels).issubset(set(desc.labels)): return { task_id: task.task_id, target: name, endpoint: desc.endpoint, status: routed, } return {task_id: task.task_id, target: None, status: unmatched}这个版本没有把转发的逻辑写出来只做了“路由判决”。实际使用时还会加一个httpx异步转发把task整体POST到目标endpoint再把结果原样返回给调用方。先把注册和判决这两个核心动作跑通再去补转发、重试和日志是整个项目投入产出比最高的路径。3.2 接入两个真实智能体光有路由网关不行得先有智能体“入驻”。我拿两个典型的例子来演示一个天气查询Agent一个差旅审批Agent。前者能力类型是信息查询标签我定义为“天气”“查询”“实时数据”后者是业务流程执行标签是“审批”“差旅”“流程处理”。它们的endpoint可以是本地接口也可以是远程部署的地址注册时填进去就行。# 模拟天气查询智能体 def weather_agent_handler(task_payload): city task_payload.get(city, 杭州) return {temp: 22, condition: 多云, city: city} # 模拟差旅审批智能体 def travel_approval_handler(task_payload): applicant task_payload.get(applicant, 张三) amount task_payload.get(amount, 0) return {decision: approved, applicant: applicant, amount: amount}注册动作通过Reach Server的/register接口完成携带各自的AgentDesc。注册完成后业务方发任务时不需要知道这两个Agent的地址只需要在labels里告诉触达层“这事跟天气有关”或者“这是审批流程”。这一下就把耦合打掉了以后Agent服务地址变了、端口换了业务方完全无感。3.3 几个关键参数怎么调参数调优是整个Agent-Reach前期运行中最容易出现“看起来能用、实际一压就坏”的部分。我总结了三个最值得花时间调的维度。注册心跳默认30秒一次连续3次无心跳就从注册表中标记为不可用。智能体进程崩溃后如果还留在注册表里路由会把任务打到黑洞里。有了心跳过期机制至少能保证只往活着的智能体上送。队列并发数。Reach Server同时发起的转发请求数要有上限。我最初没设并发控制上游业务高峰期一口气打进来几百个任务Reach Server同时占用几百个连接请求转发直接把自己搞到假死。后来加了信号量限制并发20后面的任务排队等待服务反而稳得很。重试窗口。重试的总时长不能超过调用方的心理承受时间。某个任务正常执行就要30秒如果重试2次每次都要等满超时调用方早就超时放弃了。实际做法是给每个任务设置一个“全局最迟完成时间”比如45秒超过这个值任务直接转failed不再重试。3.4 验证整个触达链路接入完成后我用一个简单的请求验证链路是否通畅。任务内容是查一下上海当前天气labels带上“天气、查询”。整个请求长这样curl -X POST http://localhost:8000/route \ -H Content-Type: application/json \ -d {type:query,labels:[天气,查询],payload:{city:上海}}Reach Server返回的目标是weather_agent同时给出endpoint。我把返回结果转发到weather_agent后拿到{temp: 19, condition: 小雨, city: 上海}。链路通了。接着我试了一个没有注册标签的任务比如labels是“航班、订票”这时候没有Agent能匹配返回unmatched在真实项目里会进入待确认队列等待人工分配或走语义兜底。3.5 接上语义兜底为了让“路由打偏”这个问题的出现概率降低我在完整版里加了语义路由。它不复杂用现成的embedding模型把每个AgentDesc里的description转成向量存到内存或者向量库里新任务进来时也把任务里的description转成向量做余弦相似度排序取Top1但相似度低于0.7就不承认这个结果。这个兜底层给整个系统带来的好处是新智能体上线时就算运维忘了配置标签只要description写得够清楚仍然有较大概率被正确触达。我见过一个案例团队加了新的“排班表生成Agent”标签只写了一个“排班”但请求里表达的是“帮我看一下这周谁的班更合适”标签匹配完全没命中语义兜底却准确找到了它。这就是兜底层的价值。4. 常见问题与排查技巧实录4.1 路由打偏明明有合适的智能体却选错这个问题出现得比我想象中频繁而且有规律可循。大部分情况是标签设计出现了交叉。比如两个Agent都注册了“查询”标签一个是查天气一个是查库存。任务只带“查询”时Agent-Reach按遍历顺序选到第一个注册的结果就错了。排查的时候我习惯直接看Reach Server打出的路由日志里面记录了每个候选Agent的标签命中情况。定位到交叉标签后改法有两种要么细分标签把“查询”降级为“天气查询”和“库存查询”要么调整匹配策略让同标签并发下按最近热度分发而不是只选第一个。第一种最优雅因为标签本身就是人的表达习惯划分细了反而更好理解。4.2 任务堆积触达层正常但响应越来越慢Reach Server本身没挂但任务排队时间一路飙升。我用监控面板看到pending状态数量持续增加但每个智能体的processing时间没有明显变化问题就出在Reach Server的并发控制太保守。信号量设置太小大量任务阻塞在等待许可证释放。另一个容易被忽视的原因是智能体accepted之后迟迟不返回结果任务卡死在processing状态。排查时一定要给每个已转发的任务加上“开始处理时间”标记Reach Server定期扫描处理超过全局阈值的任务该重试重试该转失败转失败不能让它永远占着并发名额。4.3 重试造成重复执行重试机制有个天然副作用如果智能体第一次其实已经执行成功只是响应在网络上丢了那么重试就会造成同一任务被执行两次。这在查询类Agent上问题不大但在审批、支付、工单创建这些写操作上就是事故。我的解决方案是强制要求每个任务携带幂等键。Agent-Reach在生成task_id时直接把它作为幂等键传给智能体智能体侧用一个去重表记录已处理的task_id发现重复就直接返回上一次的结果不重新执行业务逻辑。这套方案在交易类智能体上我至今没出过事故强烈建议所有写场景的智能体接入。4.4 问题速查表现象定位方向建议解法请求返回unmatched标签是否完整Agent是否心跳过期检查注册表存活状态细化标签返回目标A但预期是B标签交叉注册顺序影响遍历结果细分标签按热度或权重打分任务长时间处于routed智能体未响应accepted检查endpoint可达性确认回调链路重试后业务重复执行无幂等键用task_id做全局去重高峰期触达层线程打满信号量太小或下游响应慢扩大并发限制优化下游超时挂了但路由依然派单心跳机制未配置开心跳三次失联自动摘除5. 落地场景与扩展方向5.1 哪些场景真正需要Agent-Reach判断要不要引入一套这样的触达层我一般看两个条件智能体数量是否超过三个以及彼此之间是否经常要互相转发任务。满足任意一条Agent-Reach就值得考虑。企业内部自动化是最大的受益场景。我见过一家公司把报销审批Agent、发票识别Agent、预算查询Agent全部接进来业务人员发一句自然语言描述触达层负责路由到发票识别Agent提取数据再把结构化结果转发给报销审批Agent最后调用预算查询Agent核验余额。整个链路没有一个人去关心接口地址全部由触达层串联。内容生产团队也好用。写作Agent、素材检索Agent、排版Agent、敏感信息检查Agent以流水线方式组织起来一篇文章从选题到成品减少了一堆人工搬运。5.2 后续可以扩展的功能第一个优先级是鉴权。加了Auth之后每个注册的智能体可以限定只接收哪些业务方发来的任务天然做成多租户隔离。第二个是可视化拓扑把AgentDesc注册信息画成一张图谁依赖谁能一眼看清。第三个是跨集群支持Reach Server本身可以部署成多节点共用一套注册表避免单点故障。还有个有意思的扩展是支持Agent下线前的排空。智能体在更新版本时先把自己标记为draining触达层不再派新任务等存量任务跑完再真正下线。这个机制虽然简单但对生产环境的平滑升级非常关键。我实际遇到过旧版本Agent还在处理任务时就对新版本做流量切换结果两边都出了问题后来补了这个排空逻辑才消停。5.3 设计的边界意识Agent-Reach解决的是触达和调度但它不是万能的。它不解决智能体内部的推理质量问题也不负责任务拆分和规划编排。那些属于上层规划器或者工作流引擎的职责。我见过团队把这个触达层越做越重开始往里塞复杂的DAG编排逻辑最后整成了四不像反而失去了轻量灵活的优势。保持“只做触达不做编排”的边界这个角色才能长期稳定。我个人在实际操作中的体会是Agent-Reach这类决策点的关键不在于用多先进的算法而在于把“谁来处理这个任务”这件事做得足够确定、足够可靠。后续你接入的智能体会越来越多最先撑不住的一定是那些靠人肉记忆和维护的连接关系。早些把触达层独立出来后面每个新智能体接入的成本都会稳定在一个低成本区间。再分享一个小技巧写AgentDesc时把它当成岗位说明书来写别当成接口文档你会发现路由准确率会有一个明显提升。