ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agent-Reach:为智能体构建安全可靠的触达底座

Agent-Reach:为智能体构建安全可靠的触达底座 1. 为什么会有 Agent-Reach从 Agent 的孤立无援说起1.1 大多数 Agent 死在够不着上我最早做智能体应用时踩过一个大坑模型选的是当时最强的Prompt 写得自认为天衣无缝但实际跑起来任务完成率惨不忍睹。用户问帮我查一下上周的销售数据Agent 要么说我无法访问您的数据库要么干脆编一个数字。问题不在推理而在触达。所谓 Agent 的触达能力就是它能否在真实环境中拿到数据、调用工具、操作系统、对接第三方服务。很多 Agent 演示视频看起来很惊艳是因为它们被关在一个有固定工具的沙盒里。一旦放到生产环境要面对的是十几个内部系统、几十种 API、不同的认证方式、严格的权限限制——这时候Agent 的手就短了。我复盘了十几个失败案例发现真正导致任务失败的往往不是模型不理解用户意图而是Agent 不知道该调用哪个工具Agent 知道该调用什么但没有权限访问工具超时了Agent 傻等工具返回了错误格式Agent 直接崩溃工具之间互相依赖调用链太长导致上下文爆炸。这些问题的本质都是触达没做好。我把这个方向叫做 Agent-Reach——集中解决智能体在真实业务场景中的可达性与可靠性问题。最开始它只是我项目里的一个工具层后来拆出来成了一个独立的方案。1.2 Agent-Reach 想解决什么问题如果你也做过 Agent 落地的尝试一定遇到过下面这些具体需求让 Agent 能连接 MySQL、PostgreSQL、ClickHouse 等数据库但只给只读权限让 Agent 能调用公司内部 HTTP API但需要经过网关认证让 Agent 能访问私有知识库但不同部门的数据隔离让 Agent 能处理长时间运行的异步任务如导出报表、触发流水线而不至于超时让多个 Agent 之间能互相调用对方的工具形成协作。Agent-Reach 的设计目标就是给 Agent 提供一套统一的触达底座对上屏蔽异构系统的差异对下提供安全可控的访问通道中间解决超时、重试、降级、审计这些工程问题。这套东西不是某个单一开源库能完美覆盖的LangChain 的工具加载、Function Calling 的 Schema 定义、微服务里的 API 网关都是它的零件。Agent-Reach 更像是把这些零件按 Agent 场景重组的实践总结。我接下来的内容会把这套设计拆开讲包括核心架构、关键实现、以及我在真实业务里踩过的坑。如果你正打算把一个 Agent 从 Demo 推向生产这篇文章大概率对你有用如果你只是想了解 Agent 怎么触达外部世界也能从里面摸到大致脉络。2. Agent-Reach 的核心设计连接、权限、可靠性的三角平衡2.1 连接层统一工具接入协议第一版 Agent-Reach 里我直接用 LangChain 的tool装饰器定义工具跑 Demo 挺爽。但业务一复杂就暴露问题每个工具都是孤立的没有统一的元数据管理也没有连接池和生命周期概念。后来我把连接层独立出来抽象成一个Connector接口。每个外部资源数据库、API、文件系统、消息队列对应一个 Connector它负责建立连接连接池复用执行操作查询、写入、调用返回标准化结果释放资源。举个例子对于 MySQL我写了一个MySQLConnector内部用连接池管理多个连接。Agent 调用它时只需要传入 SQL 语句Connector 负责取连接、执行、关闭、返回行数据。Schema 层面我给每个 Connector 配一个 JSON Schema 描述说明工具叫什么、参数是什么、返回什么。设计关键点在于Agent 看到的永远是工具的函数签名而不是底层连接细节。这样换数据库、换 API 版本Agent 无需感知。统一协议的核心是这样一段 schema 结构{ type: function, function: { name: query_sales_data, description: 查询销售数据库中的订单、客户、产品信息仅支持只读 SELECT 语句, parameters: { type: object, properties: { sql: { type: string, description: 完整的 SQL SELECT 查询语句禁止包含 INSERT/UPDATE/DELETE }, limit: { type: integer, description: 返回的最大行数默认 100最大 1000 } }, required: [sql] } }, connector: mysql_sales, timeout_ms: 5000 }注意最后的connector字段它把工具路由到具体的连接器实例。这样同一个类型的工具可以对应多个不同环境的连接器测试库、生产库而 Agent 只需要选择逻辑上的工具名。2.2 权限层细粒度的可达边界连接层解决能不能通权限层解决允许通到哪里。Agent-Reach 里我设计了一套轻量级的权限策略不是简单的 API Key而是基于资源 动作 条件的三元组。资源是一个字符串路径比如mysql/sales/read、http/internal/report/generate。动作在 Agent 场景下一般是read、write、call。条件可以加上 IP 白名单、时间窗口、数据量限制等。权限策略放在一个统一的 Policy Engine 里每次 Agent 发起工具调用前都会经过一次校验。这样做的好处是Agent 永远不会直接触碰它没权限的工具——连摸都摸不到。我的权限模型长这样PERMISSIONS { agent_crm: { mysql_sales: { actions: [read], databases: [sales_db], max_rows: 500 }, http_report: { actions: [call], paths: [/report/generate, /report/status] } } }这里agent_crm是 Agent 的身份标识。在更复杂的场景里身份可以是用户、部门、角色。策略校验发生在工具执行之前在重试之前也在日志记录之前——顺序很重要先鉴权再干活。2.3 可靠性层重试、降级与熔断真实业务里一个工具调用可能在网络抖动时失败也可能因为对方服务过载而超时。Agent 如果傻傻地等一次任务会卡很久如果失败就报错任务又完不成。我在可靠性层做了三件事第一超时分层。每个工具必须有明确的超时时间Connector 执行时采用快速失败策略。默认 HTTP 连接超时 3 秒读取超时 5 秒数据库查询超时 10 秒。超时后立刻返回一个结构化错误给 Agent告诉它这个操作太慢了你可以换一个方式或者告诉用户稍后再试。第二重试策略。对于幂等操作只读查询、状态查询允许自动重试一次但间隔随机化避免惊群。对于非幂等操作创建订单、删除记录绝不自动重试必须由 Agent 结合业务规则决定是否重试。第三熔断机制。当某个 Connector 在 10 秒内连续失败 5 次就把它熔断后续请求直接返回服务不可用。同时启动半开探测每 30 秒放一个请求过去试成功则恢复。这三层组合起来Agent 在外部系统不稳定时不会卡死也不会重复提交破坏性操作。实际效果是我这边接入的某个人力资源系统偶尔响应 10 秒以上熔断机制把整个 Agent 的平均响应时间从 25 秒降到了 6 秒。3. 关键实现拆解一个请求如何穿过整个链路3.1 从自然语言到工具调用意图路由Agent 收到用户问题后第一件事是决定要不要触达外部系统。这一步我把它叫意图路由。它不是一个独立的模型任务而是和 LLM 的 Function Calling 深度绑定。在调用 LLM 时我把所有工具 schema 一次性传给模型让模型自己判断该调用哪个或多个工具。但工具列表太多时会出现两类问题一是上下文窗口被工具描述占满二是模型选择工具的准确率下降工具多了容易选错。实测下来当工具数量超过 30 个准确率明显下滑。Agent-Reach 的思路是给工具打标签先用一个轻量级分类器或规则引擎缩小候选集。比如用户提到订单就只把订单相关的 5 个工具 schema 传给 LLM。这一步我实现了两种模式基于关键词的预过滤用正则或小模型识别实体映射到工具标签基于向量检索的工具召回把所有工具描述 embedding 化用户问题 embedding 后取 top-KK 通常在 510。召回后LLM 的输入里就只包含 K 个工具定义。这样既省 token又提高了选对概率。3.2 上下文组装把触达目标说清楚很多 Agent 失败是因为它根本不知道某个工具是干什么的。Schema 里的 description 写得模糊模型就会乱猜。我在 Agent-Reach 里设计了一套工具描述规范要求每个工具的 description 必须包含四要素这个工具做什么什么时候应该用、什么时候不该用它的输入参数含义返回结果的结构和可能错误。举个例子一个订单查询工具的 description 我这样写查询订单系统里的订单信息。当用户询问我的订单、订单状态、物流信息时使用。 如果用户只想修改订单备注请勿使用此工具修改备注需要调用 update_order_note。 返回的是一个订单列表每个订单包含订单号、创建时间、状态pending/paid/shipped/completed、金额、收件人地址。 如果订单不存在返回空列表不要臆造数据。这段描述在传统 API 文档里没必要但对 Agent 来说却至关重要。它相当于在教模型什么时候伸手、什么时候别伸手、伸手之后会拿到什么。上下文组装时我会把用户历史对话、当前问题、工具 schema 拼成一条消息发给模型并要求模型严格按函数调用的格式输出。3.3 执行与反馈让 Agent 知道自己够到了什么模型输出了tool_calls之后Agent-Reach 不会直接执行而是走一遍上面的鉴权和超时控制。执行完毕后结果会以统一格式反馈给模型{ status: success, tool_name: query_order, result: { order_count: 1, orders: [ { order_id: ORD20250101, status: shipped, amount: 399.0 } ] }, duration_ms: 312 }如果失败{ status: error, tool_name: query_order, error: { type: timeout, message: Connection to order-db timed out after 5s }, duration_ms: 5000 }模型拿到这些结构化反馈后可以决定下一步继续调用别的工具或者用已有信息生成答案。这里有个关键细节不要直接喂原始返回给模型而要保留status和error字段。因为模型对错误码的理解能力很弱但对成功/失败 原因短语的理解能力很强。4. 我在落地 Agent-Reach 时踩过的坑4.1 工具描述不一致导致误调用最早我把工具名写得很随意比如get_info、get_orderdescription 也写得很简略。结果模型经常把查订单误调用成查用户信息甚至把两个工具一起调用浪费 token 还容易出错。后来我意识到工具描述必须经过模型视角的测试。具体做法是写一组覆盖典型意图的测试问题每次改完工具描述就跑一遍测试集看模型是否选对了工具。这个测试集我已经整理成了一套 Prompt 回归用例任何工具改动都得过这关。另外我强烈建议工具名称遵循动词_对象_作用的格式比如query_order_status、list_user_address。不要让 Agent 去理解一个含义模糊的缩写。4.2 超时与阻塞Agent 也会卡死有一次线上事故Agent 调用一个报表生成接口这个接口本身要跑 3 分钟。Agent 等了一个超时周期后返回失败用户自然不满意。后来我针对这种长时间任务做了异步化改造工具层面提供submit_job和query_job_status两个函数。Agent 先提交任务拿到 job_id再轮询状态。但轮询很耗 token我又加了一个通知机制——让 Agent 在提交后直接告诉用户任务已开始请稍后问我结果然后系统在后台完成时通过回调把结果写入存储下次用户再问时直接从缓存读。这个模式几乎适用于所有耗时的外部操作。核心原则是Agent 绝不能同步等待一个超过 10 秒的操作异步化是唯一合理解。4.3 权限配置太宽或太窄的平衡权限太宽Agent 可能把生产库的表删了权限太窄Agent 又说没权限用户体验差。我的经验是在权限模型里加一个可解释拒绝机制。当 Agent 尝试调用没权限的工具时不要直接返回permission denied而是返回一条更友好的提示{ status: error, error: { type: permission_denied, message: 你没有权限访问这个数据源。如果你需要请找管理员申请申请路径http://internal/perms/apply } }模型会把这个提示转述给用户用户知道为什么不行也知道该怎么办。而不是一句冷冰冰的没有权限。同时权限配置要可审计。每次拒绝都要记录 agent 身份、工具名、拒绝原因、时间。这样出了问题可以追溯。4.4 可观测性看不见的触达没有可观测性Agent 出错了根本无从查起。我一开始只打了简单的日志后来发现根本不够。必须记录每个请求的完整链路用户输入意图路由结果选择了哪些工具候选LLM 输出的 tool_calls权限校验结果连接器执行耗时返回给模型的结果模型最终的回答。这些数据沉淀下来之后我建了一个简单的 dashboard每天看工具触达成功率这个指标。刚开始只有 62%——大部分失败是超时和工具描述不清。针对性地优化后触达成功率稳定在 94% 左右。这个指标比任务完成率更前置更容易定位问题。5. Agent-Reach 的实测效果与扩展思路5.1 一组实验数据触达成功率从 62% 到 94%我为 Agent-Reach 写了一个小的基准测试模拟了一个包含 15 个工具的真实场景覆盖订单查询、用户管理、报表生成、知识库检索、异常处理。每次测试跑 100 个任务统计三个指标指标第一版优化后工具触达成功率62%94%任务最终完成率71%89%平均交互轮数3.82.6触达成功率提升主要来自三件事统一工具描述规范、超时快速失败、异步化改造。任务完成率没有触达成功率那么高是因为还有一部分任务是业务规则本身就不完整Agent 无论如何都完成不了。但明显能看到触达能力的提升对最终效果有直接拉动力。另外我还测了工具数量对选择准确率的影响。用同样一套意图测试集工具数量从 5 个增加到 50 个LLM 选对工具的准确率从 98% 掉到 81%。而加入标签过滤和向量召回之后即使工具总量有 80 个候选集控制在 8 个以内准确率回到 96%。这说明工具路由这层在 Agent-Reach 里不是伪需求。5.2 下一步从单 Agent 到多 Agent 协同触达单 Agent 的触达能力受限于它的上下文和单一身份。现在我开始把 Agent-Reach 往多 Agent 方向扩展每个 Agent 有自己擅长的工具集和权限范围然后通过一个协调者来编排。比如一个数据分析 Agent负责查询数据库一个通知 Agent负责发消息用户只需跟最外层的助手对话助手内部决定调哪个子 Agent。在这个架构里Agent-Reach 的连接层和权限层正好可以复用。子 Agent 之间不再直接暴露工具而是通过协调者转发。这样权限边界更清晰也更容易控制调用链的深度。我目前还在测试阶段遇到的一个新问题是工具 schema 的调用链描述——协调者需要知道哪个 Agent 能做什么这比单 Agent 的工具路由复杂得多。5.3 适合谁用、怎么开始如果你正在做 Agent 类应用客服助手、数据分析助手、办公自动化机器人并且遇到了模型没问题但工具连不通的瓶颈Agent-Reach 这套思路值得借鉴。它不需要你一次性实现全部模块可以按这个顺序渐进式落地先给工具统一 schema 和描述规范马上能提升模型选对工具的概率加上超时控制和结构化错误返回避免 Agent 卡死再补上权限校验和审计日志保障安全最后再做异步化、熔断、工具路由这些高级特性。我自己最初的实现是在一个内部客服助手上跑的代码量不大核心部分不到两千行。它不是什么重量级框架而是一组可以按需拿走的模式。如果你在开发中也遇到了 Agent 触达的难题欢迎按照这几个模块去重构你自己的项目——你会发现当 Agent 的手真正够得着东西的时候它离生产可用就近了一大半。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进