
Agent-Reach 这名字是我去年年底折腾多智能体系统时定下来的。当时团队内部在做一批自动化任务编排发现一个特别尴尬的现象单个 Agent 单聊模型表现挺好一旦让它们协作干一件稍微复杂点的事比如“查资料 → 整理数据 → 生成报告 → 推送到指定渠道”就开始连环翻车。不是模型能力不够而是任务传到第二个 Agent 手里时它根本拿不到自己该拿的上下文、工具和数据。说白了每个 Agent 都挺能干但互相之间“够不着”。我后来把这类问题统一归纳为Agent 协作中的可达性问题并据此写了一个轻量级的编排框架代号就叫 Agent-Reach。这篇文章不聊大道理就聊这个框架到底解决什么问题、怎么设计的、实操中踩过哪些坑以及你如何把一个“够不着”的 Agent 协作场景改造得顺顺畅畅。1. 为什么需要 Agent-Reach一次典型的多Agent协作翻车现场1.1 问题的本质Agent 之间的“够不着”比“不会做”更致命我见过太多团队包括我们自己最开始把多 Agent 协作想得太简单。大家默认的逻辑是我给每个 Agent 配好模型、写好 Prompt、注册几个工具然后让它们自由对话任务就能自动完成。但实际上事情远没那么顺利。举个我们真实踩过的场景。你有一个研究型 Agent代码里叫 researcher、一个写作型 Agentwriter、一个发送型 Agentsender。任务是把一批行业数据整理成日报并推送出去。流程看着很清晰researcher 去查数据writer 生成日报sender 发送。真正跑起来之后会怎样researcher 查到了数据把结果写在一段很长的文本里返回writer 接收到的却是被系统截断的文本或者更惨——它拿到的是 researcher 的 Prompt 模板而不是数据本身。sender 想调用邮件接口但它所在的执行环境中根本没有注册这个工具。三个 Agent 都在努力干活但每一步都在“够不着”的状态下挣扎最后的结果就是任务失败或者更气人的是它失败得悄无声息——系统告诉你“任务已完成”实际上发送环节压根没执行。这个问题的本质不是任何单个 Agent 的能力缺陷而是整个协作链路里缺少一种机制去保证“每个 Agent 在需要的时候都能触达它应该触达的资源”。这个资源包括上下文数据、工具 API、执行权限以及其他 Agent 的输出结果。我把这类问题统称为可达性缺失。它有三个典型表现数据不可达、工具不可达、上下文不可达。Agent-Reach 这个项目核心就是围绕这三个表现来做文章的。1.2 从痛点出发Agent-Reach 的设计目标与边界既然要做一个解决“够不着”问题的框架第一步就是明确它的职责边界。我不想做一个“大而全”的 Agent 运行时那需要解决模型调度、记忆管理、人机交互等一堆问题。Agent-Reach 只专注一件事让消息、工具、数据和执行权限以可预期的方式到达正确的 Agent。所以它给自己定了四条设计准则显式优于隐式每个 Agent 能触达什么必须在协议层明确定义不能靠模型“猜”。可观测性优先任何一次资源传递失败都要有清晰的日志和错误码不能默默失败。轻量可嵌入不重新发明轮子它可以作为中间层嵌进已有的 LangChain、LlamaIndex 或自研 Agent 代码里。权限最小化Agent 只能触达其任务所需的资源避免权限泛化导致越权行为。打个生活化的比方传统多 Agent 协作像一群人在一个大办公室干活大家啥都能看到但没人明确谁该拿什么文件、谁能用哪台打印机于是互相干扰、拿错文件。Agent-Reach 干的活就是给这个办公室装上清晰的工位分区的权限牌、文件流转的传送带以及每一步操作的可视化记录。当然它不解决模型本身蠢不蠢的问题。如果你把两个什么都干不好的模型硬凑在一起Agent-Reach 只能让它们协作得“非常有条理地失败”而不会让失败变成成功。这一点非常关键想用框架解决模型能力问题的同学可以省省了。2. Agent-Reach 核心架构与设计拆解2.1 三层路由模型意图层、资源层、执行层Agent-Reach 的架构核心是一个我称之为“三层次可达模型”的设计。它把 Agent 协作中所有“资源流动”的行为拆分到三个独立的层面去管理。第一层是意图层。这一层只负责解析任务意图不涉及任何实际资源操作。它接收用户的自然语言目标比如“查一下最近一周的行业数据并生成日报”然后把它拆解成若干个意图节点检索意图、分析意图、生成意图、投递意图。每个意图节点对应一个期望输出但不绑定具体的 Agent 或工具。第二层是资源层。这一层维护了一张“资源注册表”里面登记了当前系统里所有可用的数据源、工具函数、Agent 能力标签、上下文片段。每个资源都有标准化的元信息包括类型、输入输出schema、权限级别、调用限制。这一层是 Agent-Reach 真正区别于普通消息队列的地方——它管的不是消息的长途运输而是资源的“可见性”。第三层是执行层。执行层根据意图层的任务拆解结果结合资源层的能力匹配选定具体的 Agent 实例和工具路径并最终执行。执行层还负责记录每次调用的轨迹生成可审计的日志。为什么非得分三层因为如果只做一层比如直接在代码里写死“A 调 B、B 调 C”的链路那你得到的是一个僵硬的工作流。任务一旦出现偏离比如某个数据源临时不可用、某个 Agent 被别的任务占用整个链路就断了。而三层模型下意图层只关心“要什么”资源层关心“有什么”执行层关心“怎么调”每一层都可以独立重试、替换、降级。我在最初设计时也想过直接用现成的消息队列比如 Redis Stream 或者 RabbitMQ 不就行了后来发现远远不够。消息队列解决了“消息从 A 传到 B”的通道问题但解决不了“B 凭什么是接收方”“消息里的数据是否符合 B 的输入格式”“B 有没有权限调用下一步的工具”这三个致命问题。Agent 之间的协作不是简单的消息接力而是带上下文的、带约束的、带权限的资源交换。所以 Agent-Reach 在消息队列之上加了一层语义控制的中间件。2.2 可达性评分怎么判断一个 Agent 真的“够得着”有了三层模型接下来的关键问题是系统怎么知道资源“可达”靠硬编码几个 URL 和函数名显然不行。我的做法是引入了一个可达性评分机制。每一次执行层准备调用某个 Agent 或工具时Agent-Reach 会先做一轮可达性评估。评估包含四个维度维度含义评估方式数据匹配度传入上下文是否符合目标的输入 schema用 JSON Schema 校验 类型推断工具可用性目标工具是否存在且处于健康状态注册表心跳检测 定时探活权限合规性本次调用是否符合资源访问策略策略引擎匹配请求者身份与操作类型上下文新鲜度数据是否过期、是否来自可信来源时间戳校验 来源标签比对每个维度都会打出一个 0 到 1 的分值最后做加权求和。不同场景的权重不一样比如数据敏感度高的场景“权限合规性”权重拉高到 0.5“追求执行速度的时候“工具可用性”权重可以更高。系统默认情况下总分低于 0.6 的调用会被直接拦截并返回带错误码的失败信息而不是硬着头皮执行。这套机制看起来简单实际效果却出乎意料地好。最直观的感受是系统从“失败后报错”变成了“执行前预拦截”。多 Agent 协作中最让人头疼的“执行到一半才发现上下文传错”的问题从根上被规避了。我记得有一次一个数据分析 Agent 需要读取数据库里最新的销售表但传入的是前一天的缓存文件。按以前的做法它会照样跑完出报告甚至报告里标注了“数据截至昨日”——但下游的决策 Agent 不认这个直接把它当最新数据处理最后产出错误判断。在 Agent-Reach 体系下“上下文新鲜度”维度的评分会直接拉低总分系统在预测到数据不一致时就会触发告警并尝试从数据源重新拉取。这就是“可达性评分”真正发挥作用的地方。2.3 为什么不做成集中式调度中心到这里可能有人会提一个问题为什么不把所有 Agent 都接到一个中央调度中心由中心统一控制谁去执行什么、谁能访问什么这听起来不更简单吗我也试过。集中式调度在中小规模场景下确实简单明了但有几个绕不开的问题。集中调度意味着所有 Agent 之间的交互都要经过一个中心节点这个节点一旦负载过高整个系统的吞吐量直接塌方。你可能会说那就横向扩容中心节点呗。但问题在于Agent 之间大量的交互是短频快的——可能就是两三个函数调用之间的结果传递。为了这些短频交互每次都走中心延迟和序列化开销非常不划算。更重要的是集中式调度会让 Agent 变成“提线木偶”。每个 Agent 本来应该有自己的决策能力如果你把每一步调度都收归中心Agent 就丧失了根据上下文自主推进的能力。与其叫多智能体系统不如叫一个巨大的状态机。Agent-Reach 的路线是去中心化编排 集中式注册。注册表是集中的但执行决策是分布式的。每个 Agent 知道自己能触达什么资源知道自己下一步该调用谁只有在模糊不定的情况下才会向注册表发起查询。这很像真实团队运作大家共享一个通讯录但具体跟谁对接不需要每次都问老板。3. 实操从零搭建一个 Agent-Reach 工作流3.1 环境准备与最小依赖Agent-Reach 整体用 Python 实现核心依赖刻意保持精简。我当时的选用逻辑是能不进重框架就不进尽量让这套东西可以嵌入任何已有项目。以下是基础依赖列表# 核心依赖 pip install pydantic2.0 pip install redis5.0 pip install jsonschema4.18 # 可选用于Agent编排 pip install langchain-core0.2pydantic用于定义资源 schema 和工具参数校验。Agent 之间传递的数据如果格式不对直接拦截不让脏数据进入执行层。redis既当消息中转也当注册表的存储后端。Agent-Reach 没有另起一个存储服务Redis 的 Hash 结构足够支撑资源索引。jsonschema是校验层的底层依赖。langchain-core只是可选项如果你已经在用 LangChain可以直接用它的事件回调接口对接 Agent-Reach避免重复引入。安装完依赖后第一步是初始化 Agent-Reach 的运行时环境。注意Agent-Reach 本身不是一个独立后台服务而是一个 Python 库 一套运行时约定。你可以把它集成到 FastAPI 服务里也可以作为任务队列的 worker 存在甚至跑在 Jupyter 环境里做实验。我实际部署时是把它包在一个 FastAPI 服务里对外暴露 HTTP 接口。3.2 注册资源与工具一切可达性的起点Agent-Reach 的一切都是从注册开始的。资源注册表是唯一集中式组件日常操作主要在注册表里维护三类实体Agent、工具、数据集。先看工具注册的代码示例。给一个“发送邮件”工具做注册from agent_reach import Registry, ToolSpec, SchemaField registry Registry(redis_urlredis://localhost:6379/0) send_email_tool ToolSpec( namesend_email, version1.0.0, description发送邮件到指定收件人支持HTML正文, input_schema{ recipient: SchemaField(typestring, requiredTrue, description收件人邮箱), subject: SchemaField(typestring, requiredTrue, description邮件标题), body: SchemaField(typestring, requiredTrue, description邮件正文), }, output_schema{ message_id: SchemaField(typestring, description发送成功后的消息ID), status: SchemaField(typestring, descriptionsuccess 或 failed), }, access_policyeditor, # 只有editor角色的Agent才有权调用 health_check_endpointhttp://localhost:9101/health, ) registry.register_tool(send_email_tool)注册工具时最容易犯的错误是把 schema 写得过于宽泛。我见过有人把 input_schema 直接写成body: {type: object}这等于告诉系统随便传什么我都收。后果就是下游 Agent 拿到一个结构完全不可预期的数据解析失败整个链路崩掉。所以 schema 一定要精确到字段级别尤其是在跨 Agent 传递数据时宁可多花点时间定义 schema也不要在部署后为解析错误加班。Agent 注册的逻辑类似但更强调“能力标签”。Agent 注册时声明自己擅长的任务类型、可访问的工具白名单和上下文输入偏好email_agent AgentSpec( nameemail_agent, description负责邮件编写与发送, capabilities[email_compose, email_send], allowed_tools[send_email, template_render], input_context_preference[draft:email, recipient:resolved], max_context_length4096, ) registry.register_agent(email_agent)“能力标签”是个很重要的设计。它不是为了给人看的而是为了给资源层做匹配用的。比如意图层解析出一个“投递邮件”的意图时资源层会遍历所有注册过的 Agent看谁的 capabilities 列表里含email_send只有匹配到的 Agent 才会进入候选列表。3.3 搭建多 Agent 协作流程以“日报生成与推送”为例注册完成后就到了核心环节定义一次多 Agent 协作的完整流程。我们继续回到日报场景。假设有三个 Agentdata_agent查数据、report_agent写日报、deliver_agent推送日报。在 Agent-Reach 里我使用的是声明式流程定义 动态路由的组合。先看看最简单的声明式写法from agent_reach import Flow, Step, Intent daily_report_flow Flow( namedaily_report, intents[ Intent( idfetch_data, description获取过去24小时的核心业务数据, required_capabilitydata_fetch, output_keyraw_data, ), Intent( idcompose_report, description基于数据生成日报文本, required_capabilityreport_generation, input_keys[raw_data], output_keyreport_text, ), Intent( iddeliver_report, description将日报发送到目标渠道, required_capabilitydelivery, input_keys[report_text, recipient], output_keydelivery_result, ), ], )这段代码定义的不是“让谁干”而是“要什么结果”。实际执行时Agent-Reach 会根据每个 Intent 的required_capability去资源层匹配可用的 Agent。如果当前有多个 Agent 都具备data_fetch能力系统会将候选 Agent 列表连同上下文一并返回给执行层选择一个健康状态最好、历史成功率最高的来执行。流程启动的入口非常简单result await daily_report_flow.execute( start_intentfetch_data, initial_context{time_range: 24h, recipient: teamexample.com}, ) print(result.delivery_result)整个执行过程中每个 Intent 的输出都会存到一张上下文工作表里。下一步 Intent 通过显式的input_keys列表来索引前序输出。如果你在定义 Intent 时漏掉了input_keys执行层会直接报错并提示“上下文不可达”。这个约束很重要它逼迫你将 Agent 之间的数据依赖显式化而不是隐含在对话里。3.4 动态路由与上下文校验让流程在运行中自主调整上面例子里的流程是声明式的但 Agent-Reach 真正让我觉得值回时间投入的是它的动态路由能力。有一次我在跑一个更复杂的三方协作场景一个 Agent 负责从微信公众号后台拉取数据另一个负责做数据可视化。公众号后台偶尔会出现接口限流数据拉取超时。在传统流程里整个链路会卡死或者报错重试。但在 Agent-Reach 里我在fetch_data这个 Intent 上配置了降级策略fetch_intent Intent( idfetch_data, required_capabilitydata_fetch, output_keyraw_data, fallback_capabilitydata_fetch_cached, # 降级能力 max_retries3, retry_backoff2.0, )当data_fetch能力的 Agent 连续调用失败后执行层会自动尝试匹配data_fetch_cached能力的 Agent这个 Agent 会去读缓存数据并在返回的数据集上打一个stale: true的标记。下游的compose_report看到这个标记后会在日报里主动生成一行“数据为缓存版本”的提示。这个细节虽然小但避免了“假装有数据”的错误。这里关键的一点是Agent 的下游不是傻等结果而是会校验结果的新鲜度和完整性。上下文校验规则写在流程级别也可以写在 Agent 级别。如果校验不通过执行层不会直接把结果塞给下游而是先触发一次补偿动作——重试、换源、或者升级给人工。3.5 关键参数调优经验Agent-Reach 的默认参数能跑通 demo但离“生产可用”还有一段距离。调优过程中我总结了一些核心参数的经验值供你参考参数默认值建议值说明min_reach_score0.60.7~0.8生产环境建议调高减少脏数据进入执行链max_retries32多 Agent 协作中重试次数太多会滚雪球context_ttl300s60~120s上下文过期时间太长会拿旧数据parallel_executionfalse按场景开启无依赖的 Intent 建议并行执行rate_limit_per_agent无10~20 req/min防止单个 Agent 被打爆特别提醒一点min_reach_score并不是越高越好。我一开始把它拉到 0.95结果很多正常的调用也被拦截了因为“上下文新鲜度”稍微低一点就扣分。后来发现合理的做法是区分场景内部系统调用可以把分设到 0.8面向外部 API 的调用保持 0.7涉及敏感数据操作的调用强制 0.9 以上。4. 常见问题与排查实录4.1 Agent 陷入循环调用任务永远结束不了这是我在用 Agent-Reach 时遇到的最典型问题。两个 Agent 互相调用对方的能力形成 A 调 B、B 调 A 的死循环最后把 token 烧光任务超时。排查思路其实不复杂Agent-Reach 每次调用都会写入审计日志里面有调用链的完整拓扑。只要把日志按request_id拉出来画出调用关系就能定位。我后来干脆在框架层面加了一个调用深度限制默认最深 10 层。当调用深度超过阈值时执行层会强制中断并返回一个“loop_detected”错误。另外我还养成了一个习惯在定义 Intent 时给每个 Intent 声明max_execution_time和timeout_policy。这样即使出现罕见的长尾调用也会被及时熔断。不要指望模型“意识到自己在循环”——模型在上下文耗尽前通常毫无察觉熔断必须靠框架而不是靠自觉。4.2 上下文漂移下游看到的是“变形”的数据第二个高频问题是上下文漂移。听起来很高大上实际现象很简单上游 Agent 输出的是一个嵌套 JSON下游 Agent 收到的却是被某个中转环节转成了字符串或者部分字段被截断。系统没有报错因为模型“很聪明”地适应了变形后的数据。但这种“适应”恰恰是最危险的——它可能从错误的字段里推断出错误结论然后面不改色地继续往下走。这个问题靠 schema 校验不能完全堵住。我的经验是在 Agent-Reach 里开了上下文指纹校验。上游输出时计算一次 JSON 结构的哈希值下游接收时再算一次不一致就触发警告。指纹校验非常便宜几乎不影响性能却能从机制上防止“数据悄悄变形”。4.3 权限越界一个只该读数据的 Agent偷偷调用了写操作权限问题是多 Agent 协作里比较敏感也比较容易被忽视的。我曾经碰到过一次事故一个本应只读数据的 Agent因为注册表里的access_policy配置成了*结果在某个特殊路径下竟然触发了数据删除操作。当时人还在度假看到告警电话打过来差点当场去世。事后我做了两件事。第一是全面审计所有 Agent 的权限配置把所有宽泛的*权限收敛到最小集合。第二是在 Agent-Reach 里增加了权限沙箱模式对所有写操作做二次确认。哪怕 Agent 的代码已经发起了删除指令只要操作目标是敏感资源执行层就会拦截并向控制台发起确认请求。这次教训也让我总结出一个结论Agent 的权限设置必须“按能力收敛”不能按信任收敛。你不能因为某个 Agent 是内部开发的就给它全开权限。模型的行为置信度没有 100%你必须假设“它可能会出错”然后在权限层面把出错的代价降到最低。4.4 性能瓶颈Agent 多了之后调度延迟暴增Agent-Reach 初始版本有一个性能问题每当有新的资源注册或更新时所有 Agent 的本地缓存都会失效蜂拥去注册表拉取新数据。Agent 数量少的时候没感觉当注册的 Agent、工具总数超过 200 时注册表所在的 Redis 开始频繁响应缓存重建请求调度延迟从 30ms 涨到了 300ms。排查之后发现瓶颈不在 Redis 本身而在缓存失效策略。我把全局缓存改成了基于版本号的增量缓存——每次注册表变更都会生成一个新版本号Agent 缓存里存有自己上次同步的版本号只有版本号不一致时才去拉取。这个改动把无效拉取请求降低了 80% 以上。如果你也在自研类似的中间件记住一个原则不要让下游频繁问“你有没有变化”而是让上游主动说“我变了”。版本号同步是最廉价的实现方式效果远好于定时轮询。4.5 问题速查表现象可能原因处理方式Agent 调用链无限循环交互意图互相依赖、无终止条件启用调用深度限制检查日志中调用拓扑下游拿到变形的数据传递过程格式转换、字段丢失开启上下文指纹校验严格执行 schema 校验权限越界事件access_policy 配置过宽收敛权限敏感操作启用沙箱二次确认调度延迟飙升全局缓存失效风暴改为基于版本号的增量缓存同步任务“成功”但结果缺失某个 Intent 未被真正执行打开审计日志检查每个 Intent 的 reach_score 和实际调用记录模型结果反复不确定上游上下文新鲜度低、来源标签缺失调高 min_reach_score增加数据新鲜度校验5. 实测效果与经验沉淀Agent-Reach 在我这边的几个项目里跑了将近三个月最直观的变化是多 Agent 协作的“死因”从五花八门的边界问题收敛到了“模型本身能力不够”这一个维度。这不是说框架解决了所有问题而是说它把那些可控的、机制性的问题全部通过显式化设计提前拦截掉了。我个人在实际操作中最有感触的一点是做 Agent 协作框架跟做团队管理非常像。你得给每个人明确职责给每份资料打上标签给每个操作设置权限然后在它们干活的时候默默在后台记录一切。指望大家心有灵犀、无边界协作在真实世界里是灾难在 Agent 系统里同样是灾难。另外还有个小技巧一定不要为了追求“智能”而省略“显式化”。Agent-Reach 里很多看起来很笨重的设计——比如每个 Intent 必须声明 input_keys每个工具必须有完整 schema每次调用必须有审计日志——恰恰是这些笨重的部分保证了整个系统不会在某个深夜毫无征兆地崩掉。如果你也在做多 Agent 项目我建议你不一定非要引用 Agent-Reach但请务必在自己的系统里回答三个问题数据是怎么传给下游的工具是怎么被发现的权限是怎么被约束的想清楚这三个问题你离“Agent 协作不掉链子”就不远了。