
1. 项目概述与核心思路1.1 Agent-Reach 到底解决什么问题我一直在做 AI Agent 落地项目前期踩过最大的坑不是模型能力不够而是 Agent 和外部工具之间那层最后一公里的连接。今天聊的这个项目叫 Agent-Reach它专门解决一个看似简单但越到后期越头疼的问题当你的 Agent 需要调用几十个 API、查询多个数据库、操作不同内部系统时怎么让它每次都能准确、稳定、可追踪地触达目标服务。先说一下项目背景。我们团队维护着几个不同角色的 Agent有客服助手、数据洞察助手、还有一些内部运维机器人。最开始大家习惯把工具函数直接写进 Agent 的 system prompt 里工具少的时候还好当工具数量超过十来个问题就来了。模型经常选错函数、漏传参数新增一个工具就要重新走一次 prompt 调优流程而且每个 Agent 里塞了重复的工具定义维护成本直接失控。Agent-Reach 不是又一个 Agent 框架它是一层位于 Agent 与工具之间的触达中间件。核心思路很简单Agent 只负责表达意图触达层负责把意图翻译成具体的工具调用再把执行结果整理回传给 Agent。英文里 reach 本身就有伸手够到的含义这层东西做的工作就是让 Agent 能稳定地够到它需要的外部资源所以叫 Agent-Reach 非常贴切。如果你也在做 Agent 应用现在工具数量开始变多、prompt 快塞不下、模型经常乱调用那么这篇文章里的思路和代码可以直接拿过去用。哪怕你暂时没有上生产环境的需求只看里面的设计拆解也能帮你少走不少弯路。1.2 为什么不能继续把工具定义堆给模型很多初期的 Agent 应用都是靠堆上下文来让模型了解工具这种方法在 5 个工具以内是有效的但再往上走就会遇到三堵墙。第一堵墙是上下文窗口被工具描述占满。每个 OpenAPI 工具描述加上参数定义随便就是几百个 token20 个工具就是上万 token留给对话历史和业务数据的空间被严重挤压。第二堵墙是选择准确率不可控。GPT 这类模型在面对大量相似工具时经常出现张冠李戴的情况比如把查询订单的请求路由到了查询物流的接口。第三堵墙是观测性为零。模型到底调了哪个工具、成功没有、耗时多少全都靠日志拍脑袋出了线上事故根本没法复盘。Agent-Reach 的设计初衷就是绕开这三堵墙。让模型不再直接面对几十个工具而是只面对一个经过压缩的工具路由策略。触达层内部先做一次轻量级的候选召回只把最相关的 3 到 5 个工具描述送给模型做最终裁决这样既保留了模型的语义理解能力又极大减少了干扰。这个思路听起来像是给 Agent 做了一道前置网关但实际落地时要比网关复杂得多。因为工具路由不仅要考虑名字匹配还要考虑参数抽取、权限边界、依赖关系、结果截断这些都在后面的模块里逐个解决。2. 核心模块设计与技术选型2.1 整体架构触达层拆成四块Agent-Reach 在设计上分成了四个子模块注册中心、路由引擎、调用编排器和观测审计。这四个模块各司其职组合起来构成了一个完整的触达链路。注册中心负责管理所有可被 Agent 触达的外部能力包括 HTTP API、gRPC 服务、本地 Python 函数、SQL 查询、文件读取等等。每个工具在被注册时都必须附带一份标准化的 Schema 描述包含入参、出参、鉴权方式、超时阈值、幂等键定义。路由引擎是核心决策模块它接收 Agent 传来的用户意图语句先通过 embedding 模型从注册中心召回候选工具再通过 LLM 进行二次精排最终决定调用哪个或哪几个工具。调用编排器则负责执行具体的工具调用处理同步和异步场景管理重试、降级、并发和超时。观测审计模块会为每一次触达请求生成一条全链路 trace记录路由决策、调用参数、返回摘要、运行耗时和 token 消耗。这四个模块之间的通信全部走内部事件总线模块之间没有强依赖任何一个模块都可以独立升级和替换。举个例子如果想换一个更便宜的 embedding 模型做路由召回只需要改路由引擎的配置注册中心和调用编排器都不需要动。2.2 注册中心给每个工具一张身份证在 Agent-Reach 里工具注册不是简单地把 URL 和 method 塞进去而是要把工具的语义信息和工程信息都结构化保存。我强烈建议使用 OpenAPI Schema 作为基础格式但要做一层扩展加上 Agent-Reach 特有的路由标签和权限标记。每个工具在注册时至少需要包含这几个字段工具唯一标识、功能描述、输入输出 Schema、路由标签、权限作用域、幂等键配置、超时级别。功能描述这个字段看起来不起眼但它直接影响路由命中率。写的时候要用动词对象场景的句式而不是形容词堆砌。比如根据订单号查询订单状态、金额和物流信息就比获取订单详情要好因为后者很容易在路由时和获取订单列表统计订单金额混淆。路由标签是我后来加的设计本质上是一组同义词和分类比如订单电商查询。当用户说我的包裹到哪了embedding 模型可能把包裹和物流关联起来但通过路由标签可以直接把包裹映射到物流查询这个工具上作为规则兜底。2.3 路由引擎先从全部工具里召回再做二次精排路由引擎是 Agent-Reach 最复杂的部分落地时我采用了召回精排的两段式策略。召回阶段使用 embedding 向量检索把用户意图和所有工具描述都向量化然后计算余弦相似度取 Top 5 作为候选集。这里有个细节召回数量不能太大也不能太小。选 3 个的话容易漏掉正确工具选 10 个的话交给 LLM 做精排时又会增加 token 开销和选择熵我实测下来 Top 5 是一个比较平衡的值。精排阶段把 Top 5 工具的 Schema 摘要拼接成一段 prompt让 LLM 从中间选出最终要调用的工具并抽取必要的参数。为了降低 token 消耗精排阶段不会把工具的完整 Schema 放进去而是只放精简摘要包含核心参数名、类型、必填性和一句参数说明。如果模型觉得候选工具都不够匹配它会返回一个无法匹配信号这样就不会强行调用一个错误的工具。这套两段式方案最大的好处是稳定。embedding 召回速度快、成本低LLM 精排的准确率高、灵活性好两者互补。实测下来在 30 个工具的注册中心上路由准确率从单一 LLM 直接全量选择的 82% 提升到了 96%同时单次路由消耗的 token 从 1800 降到了 300 左右。2.4 调用编排器不能只做中间人还得做交通调度路由选好工具只是第一步Agent-Reach 的价值还体现在执行过程中。调用编排器做的事情比较杂但每一项都直接关系到线上稳定性。第一是同步与异步的统一。有些工具接口响应很快可以同步等待有些工具是异步任务比如提交一个数据分析任务然后轮询结果。编排器为这两类场景提供了统一的接口同步调用直接返回结果异步调用先发起请求拿到 task ID再根据配置轮询或回调。第二是重试与幂等。网络抖动和接口报错是常态无脑重试会坑死下游系统。Agent-Reach 在注册中心里维护每个工具的幂等键字段重试时自动携带相同的幂等键保证同一操作不会被重复执行。如果工具没有幂等键编排器默认采用失败即降级策略直接返回错误结果给 Agent而不是盲目重试。第三是超时分级。内部工具和外部工具的超时阈值差别很大我用了一组经验值内部 RPC 服务超时 3 秒外部 HTTP API 超时 8 秒批处理任务超时 60 秒。粗粒度的分级比统一超时更实用不会因为一个外部接口的慢请求阻塞了整个 Agent 的卡顿感知。3. 实操过程从零搭建一个 Agent-Reach 实例3.1 环境准备与基础依赖Agent-Reach 的核心代码我用 Python 写的因为团队内部 Agent 框架也是 Python 技术栈。安装依赖可以简单点主要用到几个库FastAPI 提供 HTTP 服务sentence-transformers 做 embedding 召回OpenAI SDK 负责调用 LLMpydantic 做 schema 校验Redis 用来共享状态和锁。如果只是本地实验Redis 可以先用 fakeredis 顶替但生产环境我不建议省这一步因为异步任务的 task ID 和幂等标记都依赖 Redis 的原子性。下面是基础安装命令建议用 Python 3.11 以上的版本pip install fastapi uvicorn sentence-transformers openai pydantic redis fakeredis pyyaml启动 Agent-Reach 服务之前需要准备一个 embedding 模型路径和一个 LLM 的 API Key。Embedding 模型我推荐使用 text-embedding-ada-002也可以用本地跑的 bge-small-zh-v1.5后者在中文工具描述上的效果更稳而且没有调用成本。3.2 定义工具清单并注册为了演示我准备了三个非常典型的工具订单查询、物流查询、库存查询。每个工具都定义成一个 YAML 文件里面写清楚基本属性和调用信息。下面是订单查询工具的注册配置示例id: order_query name: 订单查询 description: 根据订单号查询订单状态、实付金额、商品列表和收货地址 tags: [订单, 电商, 查询] scope: order:read endpoint: http://internal-api/order/{order_id} method: GET timeout: 5s idempotency_key: order_id request_schema: order_id: type: string required: true description: 商户系统生成的订单编号形如 ORD20250101001 response_schema: status: type: string amount: type: number这个 YAML 通过 Agent-Reach 的注册接口写入后会自动拼接成向量化描述并建立索引。注意 description 字段我特意写得很具体甚至包含了 ID 的格式示例这样 embedding 在召回时就能更好地识别用户口语化表述。如果你有几十个工具需要注册建议写一个脚本批量导入不要一个个手动调 API。脚本并不复杂就是循环读取 YAML 文件并 POST 到 /v1/tools 就行。curl -X POST http://localhost:8000/v1/tools \ -H Content-Type: application/yaml \ --data-binary tools/order_query.yaml3.3 配置路由规则与系统提示工具注册好之后接下来要让 Agent 和 Agent-Reach 之间建立通信。这里有两种接入方式第一种最省事把 Agent-Reach 的 URL 作为唯一的工具告诉 AgentAgent 只负责产出用户意图剩下的路由交给 Agent-Reach。我建议在 Agent 侧配置一段系统提示词明确告诉模型不要自己猜测工具而是把原始请求转发给 Agent-Reach。下面是一个可以在 LangChain 里使用的最小配置from openai import OpenAI client OpenAI(api_keyyour-key, base_urlhttp://localhost:8000/v1) messages [ {role: system, content: 你是客服助手。请根据用户问题调用 /v1/reach 接口来触达后端工具。不要臆造参数。}, {role: user, content: 帮我查一下订单 ORD20250101001 的状态} ] response client.chat.completions.create( modelgpt-4o-mini, messagesmessages, tools[{ type: function, function: { name: reach_tool, description: 统一触达接口参数为意图字符串, parameters: { type: object, properties: { intent: {type: string, description: 完整用户意图} } } } }] )这里其实有个细节Agent 调用 reach_tool 时传的是原始意图Agent-Reach 会先做意图澄清如果发现参数缺失它会生成一条询问消息让 Agent 追问用户。这个交互回环对用户体验影响很大不能省。3.4 验证核心链路从对话到工具触达如果你已经完成了上面几步现在就可以启动 Agent-Reach 服务然后发起一条对话来验证链路。服务的启动入口非常简单uvicorn agent_reach.app:app --host 0.0.0.0 --port 8000我在本地测试时通常会构造这样一个模拟请求用户输入我的订单 ORD20250101001 到哪一步了中间件内部的处理流程大致如下。路由引擎会先把这句话向量化与三个工具描述做相似度计算。订单查询、物流查询、库存查询三个工具中物流查询的召回分数通常会最高因为到哪一步了和物流的语义关联更强。精排阶段 LLM 会输出 final_toollogistics_query同时抽取参数 order_idORD20250101001。接着调用编排器向物流服务发起请求拿到物流轨迹后把结果截断成不超过 800 字的一段摘要回传给 Agent。Agent 再根据摘要组织自然语言回答用户。如果反馈结果是未匹配到合适工具那就可以在 Agent 侧触发一个走查分支让模型主动告诉用户我暂时不能处理这个请求而不是把一条错误信息直接甩给用户。这个兜底逻辑对于维护产品体验很重要。3.5 可视化观测一条触达请求是怎么被追踪的Agent-Reach 的观测模块会把每个阶段的耗时和决策结果打点写入 Elasticsearch然后用 Grafana 展示。下面是我常用的一套核心指标指标名含义参考阈值route_recall_count候选召回数量5llm_rerank_latency精排耗时 1.5stool_call_success_rate工具调用成功率 95%tool_call_latency_p95工具调用耗时 5stoken_cost_per_reach单次触达 token 消耗 600我踩过的一个坑是一开始把观测重点放在最终结果上结果出了问题时只知道这次没调成功完全不知道是召回阶段就漏了还是精排阶段选错了。后来我在 trace 里同时记录召回 Top 5 候选及相似度分数这样每次路由错误都可以回溯到具体的决策节点修复起来非常有指向性。4. 常见问题与排查技巧实录4.1 路由命中率低总是选错工具这是 Agent-Reach 上线后最多人问的问题。路由不准的原因通常不是模型不行而是工具描述质量太差。我总结了一套排查清单按顺序排就行。首先打开一条失败 trace看召回阶段的 Top 5 里是否包含正确工具。如果不包含说明 embedding 索引有问题去检查工具描述是否有太多不相关的修饰词并补充路由标签里的同义词。如果召回阶段有正确工具但最终精排选了错误工具那就需要把精排 prompt 里的工具摘要再写清楚一点尤其是区分容易混淆的工具。我遇到过最典型的一个场景是业务方同时接了订单列表查询和订单详情查询两个工具描述高度相似embedding 召回分数几乎相同。解决方法很简单把 detail 工具的 description 改成根据订单号查询单个订单的完整状态、金额明细和商品条目注意是单条记录不是列表并在 tags 里加上详情、单笔。4.2 调用超时引发的重试风暴Agent-Reach 刚上线时我们给编排器开了一个重试三次的配置结果下游某个服务本来就慢重试反而把服务打挂了。后来我改成超时分级 幂等键 失败降级的组合策略才把稳定性拉回来。现在每个工具注册时都会声明自己的超时级别并指定重试次数。普通 API 层面超时只重试一次而且必须带幂等键。如果下游接口不支持幂等键第一次调用还不确定是否成功时编排器就直接把结果标记为 unknown让 Agent 回复用户系统处理超时请稍后查询而不是硬着头皮再打一次。关于幂等键有个经验幂等键不能用时间戳否则重试时等于新请求。对订单查询这类场景直接把订单号当幂等键就行。对创建类操作在请求体里加一个客户端生成的 uuid 最稳妥。4.3 token 开销太大路由比业务还贵有些工具描述写得特别详细把每个字段的枚举值都贴进去了结果每次路由都要把这些内容塞给 LLM成本直线上升。我给出的解法很简单注册中心里保存两套描述一套是完整 schema 用于文档和代码生成另一套是精排摘要只保留核心字段名、类型和一句话说明。精排摘要要控制在 50 到 80 个 token 以内对模型决策影响最大的其实是工具名称 一句话功能描述 必填参数名其它信息都可以省略。另外召回阶段直接用 embedding根本不需要花 token只要精排阶段控制好输入大小单次路由成本是可以降到两三厘以内的。如果你发现还是贵看看是不是注册了太多低质量工具。Agent-Reach 的注册中心支持配置路由白名单和黑名单把那些无人调用且召回率极低的工具临时下掉不仅省钱还能提升路由准确率。4.4 权限与安全边界怎么控制Agent 能触达的能力越多安全风险就越大。我们要求每个工具在注册时都声明 scopeAgent-Reach 在路由出结果后、实际调用前会检查当前 Agent 会话的权限令牌是否拥有该 scope。如果没有直接拒绝并返回无权触达该工具。这里最容易踩的坑是跨 Agent 的权限泄露。比如客服 Agent 查订单是合理的但如果路由引擎把客服 Agent 的触达请求定向到了财务工具就出事了。解决办法是在路由精排阶段就把 scope 条件过滤掉如果 Agent 无权限这个工具就不会进入候选集而不是候选出来之后再拒绝。这样既省了 LLM 的决策负担又从根本上避免模型打出我知道但不能告诉你这种尴尬答案。4.5 处理返回结果过长导致对话被截断外部接口经常返回一大坨 JSON直接塞回给 Agent 会把上下文撑爆。Agent-Reach 在编排器里内置了一个结果裁剪器支持三种策略截断前 N 个字符、只提取关键字段、PDF 转摘要。我强烈建议使用关键字段提取策略每个工具在 schema 里声明哪些字段是核心字段裁剪器只保留这些字段。如果核心字段本身也很多可以再加一步结果摘要。例如物流接口返回几十条轨迹裁剪器可以只保留最新三条和总状态Agent 需要完整轨迹时再发起一次额外触达。这样做还有一个好处就是减少了 Agent 的干扰信息回复质量明显提升。5. 后续扩展思路与个人体会Agent-Reach 目前已经在我们内部支撑了三个 Agent 应用稳定运行超过半年累计调用量 20 多万次。最让我满意的不是它能跑通主流程而是它把 Agent 工具调用的不确定性降到了一个可接受的范围。如果你准备在自己的 Agent 项目里引入这套思路我建议不要一上来就搞全量模块。可以先只接一个注册中心和路由引擎把工具的 schema 整理好让 Agent 通过统一接口触达工具。跑通之后再加调用编排和观测审计每一步都能独立验证排查问题也容易。最后分享一个小技巧Agent-Reach 的路由决策记录是攒下来调 prompt 的宝矿。每次精排选错的时候把当时用户的问题、候选工具、模型输出存下来定期回放分析。你会发现选错工具的类型非常集中基本都是某几个相似工具之间的混淆。针对这种问题只需要改工具描述的一两句话就能把准确率提上来远比调一个复杂的 prompt 模板有效。我现在的习惯已经变了遇到新的工具接入第一件事不是写接口代码而是先想清楚这个工具的描述怎么写、路由标签怎么打、权限边界怎么划。Agent-Reach 带给我的不仅仅是一个中间件更是一套关于让智能体有边界地触达真实世界的方法论。后续如果条件允许我打算把注册中心里的工具描述做进一个协作式维护流程让各业务方都能自己提交工具变更再由统一窗口审核上线这样触达生态才能真正长起来。