上周我把 Agent-Reach 的触达层代码重构到 2.0 版本总算把两个核心 Agent 之间的调用链路压到了 300 毫秒以内。这个项目名听起来像个抽象概念实际上是我在多智能体协作项目里沉淀出来的一套触达中间层设计思路。如果你也在搞多智能体系统大概率遇到过这种情况模型能力选得挺好提示词也调得不错可两个 Agent 一对接就卡壳要么接口协议对不上要么根本找不到对方要么调用超时后雪崩。这就是我口中常说的触达问题。这篇文章不打算讲 Agent 怎么写提示词也不聊模型微调只聚焦一件事Agent 怎么找到目标、怎么沟通、怎么把一次跨系统调用稳定地跑完。我会把 Agent-Reach 的架构拆解开讲清楚注册中心、消息信封、路由策略和治理逻辑再给出一套可以直接落地的实操记录包括配置示例和三个真实踩过的坑。无论你是后端开发者、AI 应用工程师还是刚接触多智能体协作的小白这套思路都能直接用上。1. Agent-Reach 到底要解决什么问题1.1 一次让人抓狂的联调经历先说个真实场景。我接手过一个订单自动化项目里面有两个 Agent一个负责解析用户订单意图另一个负责查库存。前者的接口是 gRPC返回 protobuf后者是 HTTP REST返回 JSON。订单 Agent 调用库存 Agent 时协议对不上字段名对不上鉴权方式也不同一个要求 OAuth2一个要求 API Key。光是让这两个 Agent 正常说上话我们就花了一周。后来我意识到这类问题的根源不在于模型笨而在于 Agent 之间的触达没有任何统一治理。每个 Agent 都知道自己能做什么但不知道怎么被别人找到、用什么样的协议说话、失败了该由谁来兜底。这是典型的集成问题却被很多人误当成 AI 问题去解决。Agent-Reach 这个名字就是我在痛定思痛之后给这套方案起的代号。它要回答的只有三个问题第一调用方如何发现并路由到正确的目标 Agent第二异构协议之间如何统一转换第三跨 Agent 调用如何做到可观测、可重试、可熔断。1.2 我把触达拆成了三个维度在设计 Agent-Reach 时我把触达拆成了三个维度分别对应不同的技术挑战。第一个维度是寻址。也就是调用方怎么知道该找谁。找谁包括两层意思找哪个 Agent以及找这个 Agent 的哪个能力。我见过太多项目把 Agent 的地址硬编码在代码里Agent 一扩容就抓瞎。正确做法应该是引入注册中心让 Agent 启动时上报自己的地址、能力列表、协议类型和健康状态调用方只按能力名寻址不关心具体节点。第二个维度是沟通。两个 Agent 即使找到了彼此如果协议不一样、数据格式不一样也没法对话。最简单的做法是规定一种统一的消息格式让所有 Agent 对外暴露时都包一层信封把业务数据和通信元数据分开。业务数据爱用什么格式就用什么格式信封负责统一。第三个维度是保障。分布式系统里的调用不可能永远成功超时、重试、熔断、幂等这些老生常谈的东西在 Agent 场景里会被放大。因为 Agent 通常是有状态的同一个请求重发两次可能产生重复订单、重复退款这类严重后果。触达层必须内置治理能力而不是让每个 Agent 自己实现一套。这三个维度对应到架构上就是注册中心、消息信封和治理策略。后面几个章节我会逐个展开。2. 为什么触达才是多智能体协作的隐形瓶颈2.1 模型负责想触达负责做很多人评估一个多智能体系统好不好第一反应是看模型强不强、提示词精不精。这当然重要但业务跑不通的时候问题往往不出在想上而出在做上。我打个比方。你把一个公司里所有员工都换成了顶尖顾问每个人都聪明绝顶但公司没有内线电话、没有邮件系统、没有会议室预订机制顾问之间想协调一件事只能靠喊。这个公司能高效运转吗肯定不能。模型就是顾问的脑子Agent-Reach 就是那套内线电话和会议室系统。实际项目里Agent 要触达的对象不只是其他 Agent还有数据库、工单系统、企业微信、短信网关、第三方 SaaS。这些系统的协议五花八门有 HTTP、gRPC、WebSocket有 XML、JSON、二进制流。如果每次新增一个下游系统都要改一遍 Agent 的代码这个系统根本维护不下去。触达层的价值就在于把这些差异收敛到一个统一入口Agent 只跟触达层说话剩下的由触达层去适配。所以我常说一句话模型能力决定这个系统可能有多聪明触达能力决定这个系统实际有多可靠。后者才是最容易拖垮项目的隐形瓶颈。2.2 三类典型触达场景的差异我按触达对象不同把场景分成三类设计策略时需要区别对待。第一类是 Agent 到 Agent也就是 A2A。这是最常见的场景一个 Agent 的输出作为另一个 Agent 的输入。这类触达的核心痛点是寻址和协议统一。A2A 的调用通常是高频的、结构化的适合走内网 RPC对时延和稳定性要求最高。第二类是 Agent 到系统也就是 A2S。比如 Agent 要查询数据库、调用支付接口、写入 CRM。这类触达的核心痛点是适配和鉴权。下游系统千差万别有的连批量接口都没有有的限流很狠触达层需要做协议适配、流量整形和错误码归一。第三类是 Agent 到人也就是 A2H。比如 Agent 判断订单异常需要人工审核确认后再执行。这类触达的核心痛点是渠道管理和反馈闭环。你要保证消息确实送达了某个人还要等人确认之后再把结果回传给 Agent。很多团队把 A2H 做成了发一条通知就完事结果 Agent 那边傻等人工这边根本没看见。正确的做法是给每条 A2H 消息分配一个确认令牌人工处理完通过回执接口通知 AgentAgent 收到回执才继续执行。这三类场景的差异决定了触达层不能只做一种通用接口。我在 Agent-Reach 里给这三种触达分别定义了不同的 channel 类型后面实操部分会展示配置写法。2.3 硬编码触达的四个致命伤不引入触达层直接让 Agent 自己调别人会怎么样根据我的经验前期 Demo 跑得飞快后期维护痛不欲生。硬编码触达的四个致命伤我一个一个说。第一是点对点耦合。Agent A 的代码里写死了 Agent B 的地址和接口哪天 B 换了地址或者拆成两个服务A 必须发版。在 Agent 数量少的时候还能忍Agent 一多就是蜘蛛网改一个动全身。第二是错误处理各自为政。A 调 B 超时了怎么办、B 返回了一个不认识的错误码怎么办、要不要重试、重试会不会造成重复数据每个 Agent 都自己写一遍。最后你会发现十个 Agent 有十套错误处理逻辑线上出问题时根本没法统一排查。第三是链路断裂。没有统一的 traceId一个请求从 Agent A 到 Agent B 再到下游系统日志分散在好几个服务里。出了问题要跨日志系统去拼一个完整链路手工查好几个小时很正常。第四是能力无法复用。某个 Agent 本来已经接好了短信网关另一个 Agent 也想发短信但因为前者的接口是私有的、耦合的第二个 Agent 只能重新接一遍。重复建设这个事在缺少触达层的系统里是日常。这四个致命伤发生一个就够折腾了四个叠在一起这个多智能体系统基本就是个运行期的定时炸弹。3. Agent-Reach 的架构设计与核心实现3.1 总体分层触达层保持无业务状态Agent-Reach 的整体架构分为四层接入层、路由层、协议转换层和治理层。接入层负责接收 Agent 的注册信息和触达请求路由层根据能力标签和服务状态决定把请求发给谁协议转换层处理各种数据格式和协议的互相翻译治理层做超时、重试、熔断、限流和审计。这四层里面最核心的一条设计原则是触达层不保存业务状态。什么意思比如支付 Agent 处理一半触达层不会帮它记住订单数据库存 Agent 查完了触达层也不缓存库存结果。业务数据只在信封的 payload 里流过触达层只关心信封上的元数据。这么做有实实在在的好处触达层随时可以无损重启、扩容、发布不会因为持有状态而成为新的单点业务 Agent 可以保持无状态方便横向扩展。有人会问那 Agent 之间的会话状态怎么办我在 Agent-Reach 里的做法是把会话状态放在会话 Agent里专门有个 Agent 负责管理上下文触达层只负责把这个 Agent 的请求路由过去。这样职责清楚各管一段不会出触达层偷偷存了业务数据这种脏东西。3.2 注册中心让 Agent 能被找得到注册中心在 Agent-Reach 里扮演的角色相当于公司里的通讯录。每个 Agent 启动时向注册中心上报一系列元信息包括服务名、实例地址、能力列表、协议类型、权重和健康检查接口。我用的注册模型是服务名 能力标签双维度。服务名解决找哪个 Agent的问题比如 order-service能力标签解决找这个 Agent 的哪个能力的问题比如 parse-intent、check-inventory。一个 Agent 可以上报多个能力标签一个能力也可以由多个实例承载。这里有个容易踩坑的地方能力标签的命名必须全局统一。我见过有人把查库存写成 check-stock另一个人写成 inventory-query结果路由表里两个标签指向同一个 Agent调用方用错标签就查不到数据。我的建议是维护一份能力字典每个能力有唯一的 ID 和描述Agent 上报时引用字典里的 ID而不是自由发挥。注册中心推荐用 etcd 或者 Consul 这类天生支持服务注册和健康检查的组件。我自己用的是 etcd原因很简单watch 机制成熟路由表变更可以实时推送给调用方租约机制能自动清理不健康的 Agent 节点。注册信息里一定要带一个 schema_version 字段防止新旧 Agent 上报的元信息格式不一致导致解析失败。3.3 消息信封与协议转换触达层统一的消息格式我称之为 Envelope也就是信封。信封分三个部分Header、Payload 和 Meta。Header 放路由信息包括消息 ID、来源 Agent、目标 Agent、目标能力、消息类型、优先级。Payload 放业务数据可以是 JSON、XML、protobuf 字节流甚至是一段文本。Meta 放链路上下文包括 traceId、调用链路径、幂等键、鉴权凭证、超时策略引用。举个例子订单 Agent 调用库存 Agent信封大概是这样的{ header: { msg_id: msg_8f3a2c9e1b4d, source_agent: order-service, target_agent: inventory-service, target_capability: check-inventory, priority: high }, meta: { trace_id: trace_7e1a5c9d0f22, idempotency_key: idem_20240511_order_99813, auth_token_ref: token_ref_prod_001, timeout_policy: fast }, payload: { sku: SKU-10086, warehouse: WH-NORTH } }业务 Agent 之间只感知 PayloadHeader 和 Meta 由触达层统一管理并填充。这样即使两个 Agent 的业务数据结构完全不同触达层也能通过转换器把一边的 Payload 翻译成另一边的结构。协议转换层我建议用插件化设计。每一种传输协议 数据格式组合对应一个 adapter比如 http-json-adapter、grpc-protobuf-adapter、mq-xml-adapter。新接入一种下游系统时只需新增一个 adapter不用改动路由层和治理层。有一个细节值得注意adapter 里不要写业务逻辑只做数据搬移和格式翻译。一旦 adapter 里混入了业务判断后面排查问题时会非常痛苦。3.4 路由与治理策略路由层拿到信封后按 target_capability 查注册表得到候选实例列表再通过策略选出一个实例。我常用的路由策略有三种轮询、加权随机和一致性哈希。轮询适合无状态 Agent加权随机适合实例性能不一的场景一致性哈希适合需要把同一个用户的请求固定到同一个 Agent 实例的场景比如会话 Agent。除了基本策略还要支持灰度路由。比如新版本库存 Agent 只上线了 10% 的流量可以在路由表里配置版本标签和流量比例。配置中心里加一条规则就行versionv2 且 grain10%。这个灰度切换不影响调用方是触达层比较实用的能力之一。治理策略这块重点说三个超时、重试和熔断。超时我分成三档。快速档适合查询类能力比如查库存、查订单状态超时设为 2 秒。慢速档适合需要调第三方 AI 模型的能力比如意图识别、文本生成超时设为 15 秒。后台档适合异步任务比如批量打标签、数据同步超时放宽到 60 秒并配一个回调接口通知结果。这样分档的好处是查询类请求因超时堆积导致的雪崩能被及时兜住。重试策略必须配合幂等键使用。每次触达请求的 Meta 里带上幂等键接收方根据幂等键去重。没有幂等键的重试就是耍流氓轻则重复下单重则重复退款。重试次数我一般设 2 次指数退避间隔 200ms 起步、400ms、800ms最多到 3 秒。重试只在目标 Agent 返回可重试错误比如 503、超时时触发业务校验错误比如库存不足坚决不重试。熔断我用的是滑动窗口算法。每个目标 Agent 维护一个最近 60 秒的调用结果窗口失败率超过 50% 且调用量超过 20 次时触发熔断。熔断状态下新请求直接返回目标不可用的降级响应不再往目标 Agent 发。熔断 10 秒后半开放一个探测请求进去成功则恢复失败则继续熔断。这套逻辑和微服务里的熔断器一样但应用在 Agent 场景时有一个关键差异熔断后要给调用方返回一个对 Agent 友好的降级响应而不是一个冰冷的错误码。比如订单 Agent 调库存 Agent 熔断了触达层返回库存未知请转入人工审核这个响应本身就是 Agent 可以处理的语义。4. 从 0 到 1 接入实操实录4.1 先画触达清单我把 Agent-Reach 落地到具体项目时第一步不是写代码而是画一张触达清单。你把项目里所有 Agent、业务系统、人工处理节点都列出来然后逐对标注谁要调用谁、调用的什么能力、走什么协议、失败的影响是什么。这个清单长什么样我拿订单自动化项目举例。源目标能力协议失败影响订单解析 Agent库存 Agentcheck-inventoryHTTPJSON无法确认可售库存订单可能超卖订单解析 Agent风控 Agentrisk-checkgRPCprotobuf无法判断订单风险拦截失效风控 Agent人工审核台manual-reviewWebhookJSON高风险订单无人处理履约 AgentERP 系统create-delivery-orderHTTPXML订单无法出库客服 Agent知识库检索 Agentsearch-knowledgeHTTPJSON客服回答无依据这张表的作用是让所有触达需求一目了然也方便排优先级。我习惯用 Excel 或者在线文档维护每周过一遍新增一个 Agent 或系统时先更新清单再动代码。这看起来笨但确实能避免漏接一个调用线上才炸的情况。清单里还要额外标注一行触发条件。比如风险 Agent 调用人工审核台触发条件是风控评分超过 70 分。有了触发条件后面配置路由和编排时才知道什么时候该走哪条触达链路。4.2 配置与注册一份能跑的配置Agent-Reach 的配置我建议用 YAML结构清晰、注释友好。下面这份配置是一个真实的接入示例包含注册、路由和治理三部分。agent: name: order-service protocols: grpc: port: 9001 health_check: /healthz capabilities: - id: cap_parse_intent name: 解析订单意图 schema_version: 1.0 - id: cap_check_inventory_wrapper name: 查询库存包装库存Agent target: capability: check-inventory routing: strategy: weighted_random candidates: - agent: inventory-service version: v2 weight: 90 - agent: inventory-service version: v1 weight: 10 fallback: type: degraded_response message: 库存服务暂时不可用请稍后重试或转人工 timeout: fast: 2000 slow: 15000 background: 60000 retry: max_attempts: 2 backoff: [200, 400, 800] retry_on: [503, timeout, 429] circuit_breaker: window_seconds: 60 failure_threshold: 0.5 min_calls: 20 open_seconds: 10这段配置里有几个细节值得展开说。cap_check_inventory_wrapper 这个能力不是库存 Agent 自己上报的而是订单 Agent 的触达层里声明的一个转发能力。它让订单 Agent 像一个轻量代理把能力的请求经过路由层转发给库存 Agent。好处是订单智能体自身的逻辑不用关心库存 Agent 的实例细节路由策略由触达层统一维护。fallback 配置在很多人看起来小事一桩实际很关键。我见过没有 fallback 的项目库存一熔断订单 Agent 直接抛异常用户看到一条系统繁忙的报错。加上 lin回退消息后订单 Agent 拿到的是库存暂不可用请转人工它可以自己走人工确认流程体验完全不一样。这就是我前面说的Agent 友好的降级响应。4.3 联调过程中踩过的三个坑第一个坑是超时配置不匹配导致的连锁超时。我把库存 Agent 的超时设为 2 秒但库存接口本身要调一个第三方物流服务平均耗时 3.5 秒。结果触达层全部超时重试两次大量请求堆积在库存 Agent 手里雪崩。排查下来是触达层超时时间设置得比业务实际耗时还短。教训是超时时间要以完整业务链路耗时为准不能拍脑袋。先跑一次线记录真实耗时再加 30% 的余量。第二个坑是幂等键设计失误。最开始我用Agent 名加时间戳做幂等键结果同一毫秒内两次重试生成了不同键重复请求漏到下游导致一张订单被创建了两次。后来改成业务唯一 ID 加动作类型的组合键比如 order_99813_refund_request接收方按这个键做去重才真正解决问题。幂等键一定不能用时间戳要用业务里本来就唯一的 ID。第三个坑是人工审核触达的确认回执没接好。客服 Agent 把异常订单推给了人工审核台人工在系统里点了确认放行但确认消息没回到客服 AgentAgent 一直卡在等待人工确认状态。原因很典型A2H 触达只做了发出去没做收回来。我们在触达层为每一条 A2H 消息生成一个 confirmation_token人工点击确认后带着这个 token 调回执接口触达层再把回执转成普通消息发给源 AgentAgent 才继续执行。这个机制现在是我做 A2H 触达的标配。5. 触达链路观测与日常运维5.1 触达事件埋点规范触达层跑起来之后你要是不做观测基本等于盲飞。我给 Agent-Reach 定义了一套触达事件埋点规范每个触达事件记录五个核心字段来源 Agent、目标 Agent、动作、状态、耗时。再加上消息 ID 和 traceId组成事件主体。{ event_type: agent_reach, source_agent: order-service, target_agent: inventory-service, action: check-inventory, status: success, cost_ms: 287, msg_id: msg_8f3a2c9e1b4d, trace_id: trace_7e1a5c9d0f22 }埋点数据统一写到 ClickHouse 或者 Elasticsearch。我推荐 ClickHouse因为触达事件是典型的时序写入场景量大了以后 ClickHouse 的压缩比和查询性能明显好于 ES。每条触达事件最好都带一个 action_name方便后面按动作聚合统计。埋点规范里最容易忽略的是消息在途状态。一条触达消息从发出到收到回执中间可能经历多个阶段比如已路由、已发送、已到达、已处理。我建议每个阶段都打一个事件点而不是只记录最终结果。否则你只能知道失败了不知道失败在哪一跳。5.2 全链路 Trace 设计多智能体系统里一个用户请求往往要经过好几个 Agent链路特别长。比如用户问订单状态 - 客服 Agent - 订单查询 Agent - 数据库 Agent - 通知 Agent。要排查这类问题没有全链路追踪几乎不可能。我在 Agent-Reach 里用的是 W3C Trace Context 标准在信封的 Meta 里塞 traceparent 字段格式是版本号-trace-id-parent-id-flags。当触达层路由一个请求时它会生成或透传 traceId如果目标 Agent 自身也有子调用就往下传递同一个 traceId并追加新的 parentId。这样从用户入口到最终的数据库查询整条链路可以被串联起来。实践上我依赖 Jaeger 做 trace 可视化埋点里上报的触达事件会和 Jaeger 的 span 做关联。排查问题时只需要拿着用户反馈里带的 traceId去 Jaeger 上一查就能看到这个请求经过了哪些 Agent、每段耗时多少、哪一段失败或超时。没有这套东西的时候我排查一次订单 Agent 为什么不回复用户至少要半个小时现在基本五分钟内定位。新手容易犯的错误是只在触达层打 trace没有要求业务 Agent 的日志也带上 traceId。这会导致你看到触达链路是完整的但进了 Agent 内部就断了。最好在 Agent 的日志框架里统一配置 MDC把 traceId 自动注入让所有日志都带这个 ID。这一步能让你在排查时省下大量时间。5.3 健康巡检与告警服务上线的日常运维里我最依赖的是一个触达巡检 Agent。它本质是个定时任务每隔 30 秒向注册中心里的每个 Agent 发一次 ping 类型的触达请求就是查个心跳确认目标 Agent 能正常响应。巡检的响应时间、成功率直接接到告警系统。我配了三条告警规则触达成功率低于 99% 且持续 5 分钟告警 P1需要立即处理平均响应时间比基线慢 3 倍告警 P2可能是下游系统变慢或者资源不足注册中心路由表发生非预期变更告警 P2比如某个 Agent 实例下线没通知。告警渠道我接的是钉钉和企业微信机器人。这里有个实用心得告警消息里一定要带触达链路的 trace 查询链接值班的人收到告警可以直接点开看链路不用再自己去找日志入口。这个细节看着小却能显著拉低 MTTR。巡检 Agent 本身也要做成无状态的最好跑在独立的容器里不要跟业务 Agent 混部。否则业务 Agent 一崩巡检 Agent 也跟着挂你连谁挂了都看不清楚。6. 避坑速查表与后续思路6.1 高频踩坑速查表我把实际操作里遇到的高频问题整理成一张速查表方便你接入 Agent-Reach 时逐项自查。问题现象根因解决方案超时设置不准调用大面积超时触达层超时短于业务链路耗时按完整链路实际耗时加 30% 余量幂等键无效重复下单、重复退款幂等键用了时间戳用业务唯一 ID 加动作类型做幂等键熔断误触发目标 Agent 正常但调用被降级失败阈值设置过低调高 min_calls结合失败率判断能力标签冲突路由到错误的 Agent标签命名不统一统一维护能力字典用唯一 ID链路日志断裂无法定位故障点只有触达层有 traceIdAgent 日志 MDC 注入 traceIdA2H 无回执Agent 卡死等待只发送消息无确认机制增加 confirmation_token 回执闭环配置不热更新路由变更需重启使用静态配置文件接入配置中心支持实时推送重试风暴失败时下游被打爆重试无退避或次数过多指数退避重试上限 2 次巡检与业务混部故障时监控也挂巡检 Agent 与业务部署在一起独立部署巡检任务注册信息不清理路由表堆积死节点缺少健康检查机制配置租约过期自动注销协议适配器内聚业务排查问题困难adapter 里混入业务逻辑adapter 只做格式翻译降级响应是错误码Agent 无法处理降级返回 boom 而非语义响应返回库存未知请转人工类语义这张表不是一次性列完的是我在多个项目里迭代出来的。每次线上出问题我都会往表里补一行定期对照排查。你接自己的场景时也建议建一张类似的历史坑表后面会越用越顺手。6.2 Agent-Reach 还能用在哪些地方这套触达层设计的适用范围不止于多智能体系统。我用同样的思路改造过几个存量项目效果都不错。第一个是内部系统服务编排。公司里有订单系统、客服系统、仓库系统彼此之间接口混乱。我把它们统一注册进 Agent-Reach能力标签对应各自的业务接口路由和熔断由触达层接管。系统之间不再互相硬调而是通过触达层按能力名调用改造后接口变更的影响面明显收窄。第二个是 RPA 任务调度。RPA 机器人上一堆自动化脚本哪个脚本负责什么流程、跑在哪个机器上以前全靠人肉记录。用上注册机制后每个 RPA 机器启动时上报自己的脚本清单和状态调度器按能力标签精准下发任务失败自动重试和切换机器。第三个是客服工单智能分发。把工单系统的不同队列抽象成不同能力一个工单分发 Agent根据上下文路由到正确队列。这在本质上就是把路由策略泛化——不路由到 Agent而是路由到服务、到队列、到人。我自己的体会是Agent-Reach 的核心价值不在于它用了多少高大上的技术而在于它把Agent 之间怎么协作这个常被忽略的问题变成了一个有注册、有路由、有治理、有观测的工程问题。先把触达这条地基打牢后面换模型、加 Agent、接新系统都会轻松很多。如果你也在搞多智能体建议别急着堆功能先从一个真实场景的触达闭环跑起来再逐步完善规范和治理。这一步走稳了整个系统才能真正立住。