资讯详情

Agent 触达半径全复盘:模型推理只是下限,工具与协作才是上限

📅 2026/10/9 9:00:02 | 华诺云谱 👁 阅读
Agent 触达半径全复盘:模型推理只是下限,工具与协作才是上限
做 Agent 应用时间越长越发现一个容易被忽略的事实模型推理能力只是 Agent 的下限真正的体验上限取决于它到底能触达多少外部世界。我们内部有个代号叫 Agent-Reach 的项目起因非常朴素——同一个大模型接上不同的工具外壳任务完成率能从三成跳到九成。这个项目做了大半年踩了不少坑今天把整个思考链路、工程取舍和排障过程完整复盘一遍。这篇文章适合正在把 Agent 从 demo 往生产环境推的工程师也适合那些刚接触 Function Calling、MCP、多 Agent 协作想知道为什么我的 Agent 总是差一步的团队。1. 先聊清楚Agent-Reach 到底在解决什么问题1.1 从名字拆解看核心矛盾Agent-Reach 拆开是两个词Agent 和 Reach。Reach 在增长分析里常被翻译成触达覆盖率但在 Agent 工程语境下它的含义要具体得多——一个 Agent 在多大范围内能真正影响外部环境能拿到多少信息能调用多少工具能以多高的成功率完成一次完整的闭环任务。我习惯用触达半径来概括这个能力。一个 Agent 的触达半径由三层构成工具层它能调用哪些 API、函数、脚本数据层它能访问哪些数据库、文档、记忆、实时信息协作层它能跟哪些其他 Agent 通信能把手头的任务转交给谁。模型推理能力只是半径内部的一个点。你把这个点放在一个只会读单个文件的外壳里它的能力再强也做不了复杂任务但如果你给它接上数据库读写 CRM 操作 邮件发送 报表生成这一整套触达链路同一个模型的表现会完全不同。这不是模型变聪明了而是它的触达半径变大了。我见过很多团队的 Agent 项目死在模型很强但手很短这个坎上。demo 阶段接四五个工具跑得飞起一上生产就崩原因几乎都出在触达层工具数量超过二十个之后模型开始选错工具工具返回结果太长把上下文撑爆两个 Agent 协作时结果互相拿不到。这些问题没有一个是模型推理能力的问题全部是触达链路设计的问题。1.2 为什么触达失败比推理失败更难排查推理失败很直观模型答错了你知道是模型的问题。触达失败则很隐蔽——模型可能给出了一个看起来合理的回答但那个回答基于的是它脑子里编出来的数据而不是系统中真实存在的数据。比如我们第一批测试用户里有人问我这个月的电费账单有没有重复扣款Agent 没有去调账单查询接口而是直接根据对话历史推测看起来没有重复这个回答在格式上无可挑剔内容上完全不可信。这就是 Agent-Reach 这个项目要解决的核心问题怎么保证 Agent 每一次触达外部世界的行为是准确、及时、可审计的。落地上有三条线我会在后面的章节展开——工具触达、信息触达、多 Agent 触达外加一条贯穿始终的安全边界。2. 工具触达层的骨架工具注册、Function Calling 与 MCP 选型2.1 Agent 的手到底怎么长出来的工具触达的第一步是把外部能力描述成模型能理解的形式。当前主流做法是 Function Calling也就是让模型在推理时输出一个结构化的调用意图真正的执行动作由运行时来处理。这个过程中工具的描述质量直接决定模型能不能正确选择工具。工具描述有三个层次名称、用途说明、参数定义。很多团队把这三个层次写得非常敷衍比如一个查流水的工具就写query_transactions查询交易流水然后期待模型自己理解。实际效果很差。模型不是人它不会猜你的工具能做隐含的事情它只根据你给的 description 做路由。我在 Agent-Reach 里总结出一套描述规范名称用动词开头语义尽量唯一比如fetch_current_weather比weather_tool好用得多description 里必须写清楚什么时候用和什么时候不用参数描述要带具体格式说明比如城市名使用中文全名例如北京市、上海市。这里放一个我们实际用过的天气工具 JSON Schema 片段你直接能抄{ name: fetch_current_weather, description: 获取指定城市当前天气状况。仅在用户明确询问天气、温度、降雨等气象信息时使用。不要用于历史天气查询。, parameters: { type: object, properties: { city: { type: string, description: 城市名使用中文全名例如北京市、上海市。 } }, required: [city] } }什么时候不用这句话看着多余实测下来收益非常大。模型对排除性描述的敏感度很高写上不要用于历史天气查询之后模型在查昨天北京天气这种问题上的误路由率明显下降。2.2 Function Calling 的边界和标准答案陷阱裸 Function Calling 在小规模工具集少于 10 个时表现很好结构简单、延迟低、调试方便。但是工具数量一旦上到 20 个以上误路由率会肉眼可见地升高。根本原因是工具描述之间的语义空间开始重叠。比如查询交易流水和生成月度报表两个工具在语义上高度接近模型很容易选错。我在项目里用的一个有效手段是分组前缀。给工具名加命名空间finance__query_balance finance__list_transactions finance__generate_report crm__create_contact crm__list_leads实测下来分组后的误路由率比裸命名下降了 6 到 8 个百分点。原因是模型能借助前缀的整体语义压缩工具选择的搜索空间。另一个屡踩不止的坑是标准答案陷阱。很多工程师喜欢设计一个search_everything或者execute_any_task这类万能工具觉得对模型友好实际上是大忌。模型有路径依赖它一旦发现有个工具什么都能干就会形成偏见把所有请求都往这个工具上引。结果就是工具的 promise 很宽真正执行时却是所有业务逻辑堆在一个函数里出错之后你根本分不清是模型判断错还是工具逻辑错。正确的做法是拆细工具用编排层承担组合逻辑。让模型做决策让代码做执行这是触达可控性的核心。2.3 MCP 协议什么时候值得从 Function Calling 迁移MCPModel Context Protocol这两年讨论度很高它本质上是一个标准化的工具触达协议用 client-server 架构把工具发现、工具调用、资源读取统一起来。MCP 对 Agent-Reach 的价值在于Agent 运行时可以动态发现远端服务器上的工具不需要把几百个工具定义全部塞进上下文里。这个能力很关键。因为上下文塞不下 500 个工具定义但 MCP 服务器可以按需暴露工具让 Agent 像访问本地资源一样访问远程能力。我给你们一组选型对比维度裸 Function CallingMCP工具数量适合 20 个适合大规模、动态变更开发耦合工具定义写死在 Agent 代码里工具由独立 server 维护可跨团队复用延迟低多一跳网络调用延迟略高调试复杂度简单直接需要排查 client/server 两侧适用场景固定工具集、对延迟敏感企业级、多团队、工具共享我在 Agent-Reach 里的结论是3 到 5 个固定工具直接用 Function Calling别上 MCP工具超过 20 个或者工具由不同团队维护上 MCP 是值得的。一个用 Python 写的最小 MCP server 核心片段长这样# mcp_demo_server.py 关键片段 from mcp.server.fastmcp import FastMCP mcp FastMCP(agent-reach-demo) mcp.tool() def list_recent_files(project: str) - list[str]: 列出指定项目最近修改的文件按修改时间倒序。 # 真实环境里这里会去查对象存储或文件系统 return [f{project}/2024-10-01_report.md, f{project}/2024-10-02_logs.txt] mcp.resource(config://app/limits) def get_limits() - dict: 读取当前应用的运行阈值配置。 return {max_concurrent: 5, timeout_seconds: 30}注意 MCP 的工具发现机制有个隐含成本每次会话开始客户端需要和服务端做一次能力协商握手。如果你只有两三个工具这个握手是纯开销。3. 让 Agent 找得到信息记忆与检索的触达设计3.1 上下文不是记忆别让它背所有事第二个触达维度是信息。常见的错误做法是把所有背景资料、历史对话一股脑塞进系统提示里。大模型上下文窗口越来越大于是很多人觉得既然能装下就全部装进去。这个想法有两个隐患。第一是成本随长度快速上涨尤其是每次请求都重复发送一遍背景文档第二是模型对长上下文的关注度是衰减的。研究里有个著名的 lost in the middle 现象——模型对开头和结尾的内容记得牢对中间段落的记忆明显弱。你把一份 5000 字的业务手册塞在提示词中段大概率会被模型忽略。所以信息触达的正确姿势是能检索的不全量携带。这跟人脑一样你不会把整本书抄进脑子里再回答而是知道该翻哪一页。Agent 需要的是一个知道去哪找信息的能力而不是一台移动硬盘。3.2 RAG 检索召回触达信息的上限RAG检索增强生成是信息触达的基本实现。流程很简单把文档切成 chunk、向量化、存进向量库、用户提问时检索 top-k 拼接进上下文。但这个流水线里有几个容易被低估的细节。chunk 大小我实测最稳的是 300 到 800 个 token中文场景取偏 500 左右。切太大检索召回的 chunk 里噪声多模型反而找不关键信息切太小语义不完整一条知识被切成两半检索召回后各自孤立。检索策略上强烈推荐混合检索也就是关键词检索BM25和向量检索并行再用 RRFReciprocal Rank Fusion合并排序。纯向量检索对专有名词、代码片段、报错信息这类文本表现很差因为这些内容在向量空间里的分布很稀疏相似度常常不如关键词命中准确。一个混合检索的伪代码长这样def hybrid_retrieve(question: str, k: int 6) - list[Document]: bm25_docs bm25_index.search(question, kk) # 关键词召回 vec_docs vector_store.similarity_search(question, kk) # 向量召回 merged merge_and_dedup(bm25_docs, vec_docs) return merged[:k] # RRF合并按顺序加权召回数量 top-k 也不是越大越好。我实测最佳区间在 4 到 8 个有效结果再往上塞上下文会被无关片段灌满模型在多个片段之间来回摇摆反而降低回答准确率。3.3 记忆分层把对话历史也做成可检索的Agent 的对话记忆不能简单用滑动窗口处理。窗口一滑早期信息就没了用户要反复重复自己说过的话这种体验非常糟糕。我在 Agent-Reach 里把记忆拆成三层短期工作记忆最近几轮对话原样保留全量可见长期事实记忆用户偏好、已确定的决策、关键实体用 KV 或图结构存储情景记忆历史任务的最终结果摘要按时间和主题索引。每一层在系统提示中的可见性不同。短期全量可见长期和情景按需检索。效果很直接——Agent 在三十轮对话之后还能记得用户第一天设置的偏好它的有效触达半径就大了一圈。我们有一个做内容助理的实验 Agent接上分层记忆后任务完成率从 61% 提升到 78%主要提升点在于它不再重复向用户索要已经提供过的信息。4. 多 Agent 协作时的触达链路消息路由与共享状态4.1 为什么单 Agent 改成多 Agent 后反而更差多 Agent 协作是 ReAct 之外另一种常见的触达半径扩展方式一个规划 Agent 把任务拆给多个执行 Agent。但很多团队一上来就被带偏了节奏搞完发现失败率反而更高。原因在触达链路变长了。每个协作过程都是一次新的触达链路每加一环失败概率就叠加一次。多 Agent 不是银弹它只适用于三种情况子任务边界清晰、任务可以并行、不同子任务需要隔离权限。如果你的任务实际上是一个强顺序流程比如先下单再付款再开发票硬拆成多 Agent 只会增加消息传递开销没有任何收益。4.2 路由策略广播、语义路由还是能力注册表多 Agent 之间触达的核心是路由一个请求来了谁接收。我按规模从小到大排序分享三种策略。广播最简单把请求发给所有 Agent 等响应。但 token 消耗成倍增长而且多个 Agent 可能同时执行冲突任务生产环境几乎不可用。语义路由是把请求向量化和各 Agent 的能力描述算相似度取 top-1。这个方案在 Agent 数量小于 30 时很稳定前提是每个 Agent 的能力描述必须写得准确否则两个相似的 Agent 之间会出现大量误路由。能力注册表是更工程化的做法。每个 Agent 启动时上报自身能力元数据路由中心按规则匹配{ agent_id: billing-agent-v3, capabilities: [ { name: create_invoice, params: { customer_id: string, amount: number } }, { name: query_payment_status, params: { order_id: string } } ] }企业级多 Agent 场景我会推荐能力注册表因为它自带元数据和管理入口后续做负载均衡、审计、灰度都方便。4.3 共享状态是最大的隐性坑多 Agent 协作里最容易出问题的不是消息传不到而是共享状态被改坏。两个 Agent 同时操作同一个订单字段后写覆盖先写这种问题在真实系统里非常常见。我的解决方案是字段级写权限隔离 版本号检查组合。具体来说每个 Agent 只能写入自己负责的字段域其他字段即使它在上下文中看到了也不能改所有写操作走统一状态服务服务端用版本号做乐观锁冲突检测。这套方案在一个工单系统上把并发写冲突率从 12% 降到了 0.3%。代价是多了一个状态服务但对一致性要求高的业务完全值得。4.4 异步触达超时与补偿机制最后一个多 Agent 触达问题是同步等待。Agent A 把任务发给 Agent B如果 B 迟迟不返回A 只能干等整个任务超时。生产环境里必须给协作加异步化和超时补偿。我们现在的做法是任务先入队用 Redis Stream 或 Kafka 这类消息中间件B 处理完成之后回调结果A 发起任务后就去处理其他事情或者直接进入等待状态但会同时挂一个超时计时器。超时之后A 走降级逻辑——要么换成另一个 Agent要么把状态上报给人工介入。为了追踪每个子任务我们会维护一张任务状态表记录每个子任务从 pending、running、succeeded、failed、timeout 的完整状态机。没有这张表任何多 Agent 系统出问题之后都是一团乱麻。5. 触达的安全边界不是所有能力都该暴露给 Agent5.1 最小权限给 Agent 的手不能比人还长触达能力变强之后安全边界必须同步变严。原则很简单Agent 只获得完成当前任务所需的最小权限。举一个常见的例子客服 Agent 可以查订单但不应该能直接退款如果用户真的提出退款诉求Agent 应该生成退款申请单并升级到人工审批而不是自己执行。这个原则看似简单落地时很多团队会犯一个错误图省事给所有 Agent 配同一套宽权限。结果是最弱的一个 Agent 被攻破整个系统的敏感操作都暴露了。我强烈建议每个 Agent 独立配置权限矩阵细到能调用哪个工具、能访问哪个数据源、能写入哪个字段。5.2 最危险的万能工具execute_sql 和 run_shell很多工程师喜欢给 Agent 加一个execute_sql或run_shell工具方便它能自由发挥。这就像把一个刚毕业的实习生直接放在生产数据库的 psql 命令行前面不出事只是运气好。如果你无法避免暴露这类工具至少要加三层保护查询类 SQL 强制走只读副本用正则或 SQL 解析器拦截DELETE、UPDATE、DROP、ALTER、TRUNCATE等关键字Shell 类工具一律在容器沙箱里执行挂载只读文件系统禁止访问宿主机网络所有高影响操作必须触发用户二次确认。一个只读 SQL 工具的安全校验片段import re DANGEROUS_SQL re.compile( r\b(delete|update|drop|alter|truncate|insert|create|replace)\b, re.IGNORECASE, ) def safe_query(sql: str): if DANGEROUS_SQL.search(sql): raise PermissionDenied(高危操作已被拦截已拒绝执行) # 强制走只读副本 return execute_on_readonly_replica(sql)这段代码很基础但很好用。真正的防线不是让模型更听话而是从执行链路上物理切断危险的触达路径。5.3 审计与回滚触达之后要有痕迹安全触达的最后一环是审计。我们会在每条工具调用记录里标记四件事trace_id一次任务的全局 ID、tool_call_log工具名称、入参、出参摘要、user_approval_flag是否经过人工审批、结果如何、timestamp。没有这套审计链多 Agent 系统出问题时你根本没法回答这个订单为什么被改了是谁改的用了什么工具。遇到客户投诉没有 trace 的历史会直接把团队拖进旋涡。审计日志同时是调优触达率的重要数据源。我们做 Agent-Reach 期间很多误路由问题就是靠翻历史 tool_call_log 发现规律的。6. 一次真实排障工具明明注册了Agent 却一直不用它6.1 现象与初步排查前面讲了这么多理论最后分享一个具体排障案例。Agent-Reach 里有一个财务问答 Agent注册了 11 个工具包括查余额、查交易流水、生成报表。集成测试时发现典型问题用户问我这个月电费账单有没有重复扣款Agent 每次都直接凭对话内容生成回答而不是调用交易流水查询工具。人眼一看就知道该查数据Agent 就像完全忘了自己还有这个工具。初步排查方向定在四个点工具描述和用户问题的语义距离系统提示里工具注册顺序few-shot 示例的引导方向模型输出的置信度。先用向量相似度快速测了一下工具描述和用户问题的距离。把 11 个工具的描述向量化后跟电费重复扣款做相似度计算发现查询交易流水排在第三而排第一的居然是生成财务报表。原因很直观用户问题里包含账单扣款跟报表这类词在语义向量空间里更接近。接着检查系统提示里的工具注册顺序发现生成财务报表排在最前面。又翻了 few-shot 示例凡是涉及金额的提问示例都演示了调用报表工具。三条线索指向同一个问题工具描述跟业务场景的语义错位加上位置偏置和示例先验把模型带偏了。6.2 根因与修复根因整理下来有三层第一工具描述没写清楚触发条件模型无法判断什么时候该用我第二工具注册顺序存在位置偏置易混淆工具排在前面会被优先选第三few-shot 示例给模型灌输了错误先验。修复动作也分三步第一步重写查询交易流水工具的 description从干巴巴的查询交易流水信息改成带触发条件的完整描述当用户询问具体金额变动、扣款、扣除、入账、重复扣款、退款到账等细节时必须先调用本工具获取原始流水数据禁止直接根据对话内容生成回答。第二步调整工具注册顺序把具体事实查询类排在前面汇总分析类排在后面。第三步新增一个 few-shot 示例完整演示用户问扣款细节 - 调用流水工具 - 基于流水给出回答的过程。6.3 修复结果与经验迁移同一批 50 条测试用例问题命中率从 54% 提到了 86%误路由率从 22% 降到 8%。数据说明得很清楚触达失败的核心不是模型听不懂用户而是工具侧的描述、排序、先验没有跟真实业务场景对齐。这个案例的经验可以迁移到几乎所有 Agent 工程里。怀疑 Agent能力缺失时先别急着换模型、加参数回顾一下三件事工具描述有没有写清触发条件和排除条件工具注册顺序有没有造成位置偏置few-shot 示例有没有给模型传递正确先验。这三件事在 Agent-Reach 后期几乎成了我们团队每次做新 Agent 时的固定 review checklist。事后复盘我也记了一条教训不要迷信模型自己会联想。模型很依赖 prompt 里显式写出来的东西你觉得它应该知道去查流水它其实完全没有这个概念。所有触达预期都要显式声明在工具描述和示例里这一点在 Agent-Reach 里被反复验证是我们整套方法论里最实在的一条。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑