接了个挺奇怪的需求同事想让他负责资料总结的 AI Agent自动去调用另一个负责合同审核的 AI Agent 的结果中间还得经过一个本地部署的模型服务。我听完第一反应不是“这智能程度够不够”而是“这俩服务到底怎么触达对方”。后来我把这套解决 Agent 之间“触达”问题的机制做成了一整个小项目名字就叫 Agent-Reach。这篇文章不是产品发布会是我自己在做这个项目时的完整记录。适合那些本地部署了多个 AI 服务、想让它们互相协作却发现“怎么调起来”比“怎么生成”更头疼的人。我会把核心设计、部署过程、生产环境踩坑以及最后沉淀下来的取舍全部摊开讲。1. 做 Agent 串联时我先撞上的不是“智能”而是“触达”1.1 断头路场景每个 Agent 都是一座孤岛客户环境里跑着十几个 AI 服务有做文档解析的有做摘要的有做合同条款抽取的还有一个本地向量检索服务。每个服务单独用都挺正常可一旦要让它们协同问题就全涌出来了A 服务根本不知道 B 服务存在不知道 B 能干什么不知道该用哪个地址调它更不知道 B 返回的数据长什么样。我刚开始天真地以为这事情找个工程师写几段 HTTP 调用代码就完了。可真动手就发现这不是“写接口对接”的活而是“连接关系会一直变”的活。今天 A 要调 B明天 B 要调 C后天 A 又要调 C 的新版本。接口文档更新不及时参数对不上鉴权方式各搞一套一个联动需求能改两周。1.2 从“能生成”到“能触达”是一个大家下意识忽略的层次大多数 Agent 项目讨论的焦点都在模型选择、Prompt 编排、RAG 召回效果这些“智能”环节上。可真实跑起来之后你会意识到模型再聪明如果它连该调的另一个服务都调不到那一切智能都白搭。我把这层能力叫“触达”。触达不只是把 HTTP 请求发出去它至少包含四件事发现一个 Agent 怎么知道另一个 Agent 提供了什么能力选择多个 Agent 都能做同一件事时该选哪一个调用按对方要求的鉴权方式、参数格式把请求送进去并拿到结果回溯一次协作跨了三四个服务出了问题能查出在哪一环断的。这四个点合起来就是 Agent-Reach 项目要解决的问题。往大了说这是智能体之间的连接层往小了说这就是一张不断更新的服务地图。1.3 别把它当成 API 网关差挺多的有人可能会说这不就是 API 网关吗我开始也这么想直到画架构图时才意识到两者有本质区别。API 网关面对的是稳定的、由人定义的接口路由规则写死就行Agent 场景里调用方不是人是模型。模型会根据当前任务动态决定下一步调谁所以“能力列表”必须是可查询、可变化的而不是写死在墙上。这有点像你到了一个园区不是只看门口贴的店铺名单那是 API 文档而是随时能打开手机地图看哪个店开着、人多不多、要不要排队能力注册与路由状态。Agent-Reach 做的就是这张地图加一套导航机制。2. 给每个 Agent 一张“可达性地图”2.1 能力注册不是接口文档是能力清单Agent-Reach 的第一个核心组件是一个叫能力注册中心的东西。每个 Agent 启动时都会上报自己“能做什么”而不是“接口长什么样”。我用的能力描述格式大致是agent_id: contract-checker display_name: 合同审核助手 endpoint: http://contract-svc:8080/invoke auth: type: bearer token_env: CONTRACT_SVC_TOKEN capabilities: - name: check_contract_compliance description: 检查合同条款是否符合给定合规策略返回风险项列表 input_schema: contract_text: string policy: string output_schema: risk_items: array rate_limit: 10这个设计的思路是把“接口”抽象成“能力”。接口是给程序看的能力是给模型看的——模型不需要知道 HTTP 动词和 URL它只要知道“有一项能力叫 check_contract_compliance输入是什么输出是什么”。注册中心存储这些能力同时定期做健康检查挂了的能力自动从可用列表里摘掉。2.2 路由决策直连、中转、广播地图有了接下来是导航。Agent-Reach 里我实现了三种路由模式对应真实场景中的三种情况直连调用方明确知道要调哪个 Agent注册中心只负责返回目标地址和鉴权信息调用方直接发请求。这种模式延迟最低适合服务间已建立稳定信任的场景。中转调用方只知道要找某个能力不知道具体该调谁。请求先进 Reach 中心由中心根据能力名、负载、健康状态选一个目标再转发。这种方式对调用方最透明缺点是多一跳。广播有时候不是“找一个服务执行”而是“通知所有关注这件事的服务”。比如合同审核完成后需要同步给归档服务、通知服务、风控服务。Reach 支持按主题发布事件各 Agent 按订阅关系接收。我实际项目里 80% 的流量走的是“中转”因为它最适合模型主导的动态调用。只有高频且路径稳定的调用才切成直连。别一上来就全部直连会让调用方代码里写死一堆地址地图就失效了。2.3 触达链路的可观测性trace 贯穿全程多 Agent 协作最怕的是“黑盒”。以前排查问题靠人工翻各服务日志时间全靠猜。Agent-Reach 从设计初就强制所有请求带上一个 trace_id格式是随机 ID 加节点编号。每经过一个 Agent就在日志里追加一条[1a2b3c] summary-agent - router - contract-checker [1a2b3c] contract-checker 返回 2 个风险项 [1a2b3c] summary-agent 收到结果耗时 4.2s这套东西看着简单但后面排查“为什么合同审核结果没回到总结 Agent”这类问题时救命级别的好用。没有 trace你只能对着十几个服务一个个猜。3. 把 Agent-Reach 跑起来的全过程3.1 环境准备与初始化Agent-Reach 本身是一个 Python 服务我用的技术栈很常规FastAPI 提供 HTTP 入口Redis 做注册中心的临时存储SQLite 存能力描述的历史版本Docker Compose 统一拉起。你不需要多大的机器单机 2C4G 就能跑起来因为真正消耗资源的是下游 Agent不是连接层。初始化时有个容易忽略的细节所有 Agent 实例的时间必须同步。trace 排序、超时计算、健康检查都依赖时间戳如果各机器时间差超过 1 秒你会在日志里看到“离奇”的乱序调用记录。我直接用 NTP 协议让所有节点对时这个问题建议你提前处理别等出问题再排查。3.2 用能力描述文件注册第一个 Agent我把一个现成的“合同审核服务”接进来它原本是个独立的 Flask 应用我只改了少量代码。接入方式是给 Agent-Reach SDK 加一段初始化代码from agent_reach import Agent agent Agent( agent_idcontract-checker, config_filecapability.yaml, ) agent.register()SDK 启动后做三件事读取本地能力描述文件向注册中心上报 endpoint 与能力列表然后开启一个心跳协程每 15 秒上报一次存活状态。这里我最想提醒的一点是注册时一定要确认 endpoint 是“别人能访问到的地址”而不是“自己访问自己的地址”。我第一版配置就写成了 http://localhost:8080/invoke结果另一台机器上的总结 Agent 拿到这个地址后请求打到了它自己身上报错报了半小时。这种问题不是看代码能看出来的得想清楚网络拓扑。3.3 发起一次跨进程调用注册完成后我在另一个 Agent 里写了一段调用逻辑总结 Agent 拿到一份合同文档先生成摘要然后把摘要原文发给合同审核 Agent让对方检查合规性。代码大致是这样的from agent_reach.client import ReachClient client ReachClient() result client.invoke( capabilitycheck_contract_compliance, payload{ contract_text: summary_result[text], policy: 内部采购合规政策v3, }, timeout60, )这段代码里没有出现任何目标 IP、URL、端口。调用方只表达“我要什么能力给什么输入”至于这个能力目前由谁提供、地址变没变全部由 Reach 层处理。这跟我前面说的“地图导航”对上了开车的人不用背路名导航知道就行。3.4 实测中三个典型的失败场景第一次真正跑通之前我连续失败了好多次记录一下免得你重复踩输入字段不匹配合同审核 Agent 期望的字段名是 contract_text我传的是 content结果对方返回 schema 校验错误。这种情况 Reach 层检查不出来因为连接层不知道业务字段的语义必须靠调用方仔细看能力描述。鉴权头丢失Reach 层做中转时只转发业务 body没带目标服务要求的 Authorization 头目标服务直接回 401。后来我在能力描述里显式声明 auth 来源支持从调用方透传或由 Reach 统一注入才解决。同步等待过长合规模块内部还调了一个外部 OCR 服务最慢跑了 40 多秒而 SDK 默认超时 10 秒。第一次跑直接超时重试重试两次把下游打出来重复任务。这三个问题都不是“代码写错”而是“机制没想清楚”。我的结论是连接层一定要把超时、鉴权、数据契约当成一等公民来设计不能等用户报错再补救。4. 生产环境最容易翻车的三个连接问题4.1 重试风暴Agent 的固执会把下游打挂Agent 场景有个特点模型收到异常后往往会自己重试。这在用户体验上是好的但在系统负载上是灾难。有一个晚上我压测发现上游 Agent 连续报错后不停地重发请求下游合同审核服务直接被打到 CPU 满载。问题根源是两层重试叠加了Reach 层重试一次Agent 层又重试三次相当于一次失败产生四次流量。解决方式是我在 Reach 层做了三个措施指数退避每次重试间隔翻倍第一次 1 秒第二次 2 秒第三次 4 秒而不是立即重发最大重试限制默认 2 次特殊场景最多 5 次不让模型逻辑无限放大流量熔断连续失败超过 10 次暂时把该能力标记为不可用让它冷却 30 秒再恢复。这里我建议你把“哪些重试是连接层该做的、哪些是业务层该做的”用文档写清楚。连接层只负责解决瞬时抖动业务层才处理逻辑失败。两者混在一起全链路负载就没法算。4.2 回调地址你以为的 localhost 不是我的 localhost这个坑我在 3.2 里提过但它在生产环境反复出现值得单独说。Kubernetes 部署时Agent A 返回一个回调地址写的是 http://pod-ip:8080/callback。问题是这个 Pod IP 是集群内部的只有集群内能访问。如果消费者在集群外回调就打不回来。Agent-Reach 的解法是注册时支持两种 endpoint 声明internal 和 externalReach 层根据调用方所处网络位置返回对应地址。如果因为安全策略不能直接暴露地址就必须走中转模式由中心转发。你如果自己做类似项目一定要设计这个“地址可见范围”的概念不然就是一地 localhost。4.3 超时设置慢思考不等于连接失败AI Agent 的推理速度跟传统 API 完全不是一个量级。传统接口 200 毫秒没返回就是异常可一个 Agent 在逐步思考、调用工具、翻阅上下文时10 秒 30 秒都很正常。我见过太多团队拿默认的 HTTP 超时去调 Agent五分钟之内全在报错。我后来的策略是分开设置两组超时超时类型默认值说明连接超时5 秒只覆盖 TCP/TLS 握手阶段响应超时120 秒覆盖 Agent 完整推理时间空闲超时30 秒流式响应中相邻数据包最大间隔这个表看起来简单但它解决的问题很关键连接超时短是为了快速失败出来别让调用方傻等一个连不上的地址响应超时长是为了给 Agent 足够的思考时间。两个值分开设就不需要因为“怕超时”而把所有超时都调大。5. 扩展场景把它变成人机协作的业务通道5.1 人工审批节点的“触达人”Agent 之间的触达跑通后客户提了一个我没想到的需求能不能在链路里插入一个人工审批节点合同审核 Agent 发现了高风险条款不能直接通过得让合规专员看一眼。这就从“触达服务”变成了“触达人”。我在 Agent-Reach 里加了一个 person_target 类型把通知推送到协作平台的审批流。链路的 trace 和上下文全部保留人处理完结果再交给下一个 Agent。这个设计让我意识到连接层不需要区分对象是人还是 Agent只要统一“接收者”抽象就能把人和智能体放在同一条链路上。5.2 联合上下文少传点数据多传点线索多 Agent 协作时最原始的做法是把上下文全量塞给每个 Agent。文档是 40 页 PDF摘要 Agent 读完生成 2000 字摘要合同审核 Agent 又要重新把 40 页读一遍。浪费算力还慢。Agent-Reach 做了一个轻量的上下文索引每个 Agent 处理完后把产出的文档片段存到向量库再把索引进上下文。下一个 Agent 按需召回而不是全量接收。这一步优化把一次“总结经合同审核”链路的整体耗时从 3 分钟降到了 45 秒体感非常明显。5.3 与向量库、消息队列、模型网关并存项目后期Agent-Reach 已经不只是转发 HTTP 请求的连接层了它开始做更细的事把结果写进消息队列供异步消费、从向量库检索相似条款、调用统一模型网关做最终判断。这些都可以做成 Agent 能力注册进来。我给的建议是不要企图让连接层直接实现业务功能连接层只做“触达”具体业务逻辑继续留在 Agent 内部。否则这个项目会从“轻量连接器”膨胀成一个“业务中台”维护成本完全不可控。我的规则很简单触达的归触达业务的归业务。6. 落地之后我最后想说的三件事6.1 连接层要克制别让“触达”变复杂Agent-Reach 项目做到了中后期最容易犯的毛病是加功能加消息队列加分布式事务加各种自定义协议。我最后砍掉了一大半只保留了能力注册、路由、trace 三条主线。不是因为那些功能没用而是连接层的核心价值是“简化”如果连触达本身都变得复杂Agent 团队就会绕开你自己造轮子。6.2 从小规模起步别一上来全家桶如果你也想做类似的 Agent 连接层我的血泪建议是先用单体服务加 Redis 把 2 个 Agent 接起来跑通端到端再考虑高可用、分布式、权限分域。我一开始就按“微服务治理平台”的规格做设计结果光是部署配置就花了一周真正跟 Agent 相关的东西一行没写。这个项目真正的转折点是放弃宏大设计改用最小可用架构跑通第一个调用。6.3 留下一份检查清单最后分享一个我在项目结束前写下的落地清单现在每次接新的 Agent 我都会照着过一遍省了不少事该 Agent 有哪些能力是否都有 input_schema 和 output_schemaendpoint 对外部调用方是否真的可达还是又一个 localhost鉴权是透传还是注入是否在能力描述里写清楚超时配置是否区分了连接超时和响应超时重试次数和退避策略是否约束过会不会叠加成风暴每次协作是否携带 trace_id链路日志能不能完整拼出来人机协作节点是否预留了“人等机器”和“机器等人”两种状态。Agnt-Reach 这个项目谈不上多高深但它把一个长期被忽略的问题端到了台面上Agent 之间能不能可靠地触达彼此决定了 AI 协作的天花板。我自己的体会是别一上来就研究模型会不会“犯蠢”先问一句它能不能拿到该拿的数据、调到该调的服务。连接层打通了后面很多事情才会顺。