--- name: tool-discovery description: Agent 工具数量变多、上下文被工具定义占满时使用——设计工具发现与渐进式披露机制、实现 discover_tools 元工具与两层语义路由、选择索引式/检索式/主动发现/Skills 方案、处理动态加载破坏 KV Cache 的问题、评估 token 成本与工具选择准确率时查阅。含渐进式加载五步流程、工具数量阈值与关键实验数据。 --- # 工具发现与渐进式加载 ## 何时使用 - 接入的 MCP 服务器越来越多,system prompt 被工具 schema 占掉几千到几万 token - 工具库增长到上百个,模型开始选错工具、漏掉工具 - 设计"只暴露索引、按需加载定义"的工具披露层 - 实现主动工具发现:Agent 声明能力缺口 → 系统语义匹配 → 动态注入 schema - 需要在工具数量、token 成本、准确率之间做架构取舍 - 动态加载工具后 Prompt Cache 命中率下降,需要定位原因 - 为小参数量模型(4B 级)设计可用的上百工具接入方案 ## 核心原则 - **规模本身伤害正确性。** 工具数超过 100 个时,即使最先进模型也容易在工具选择上出错;全部平铺还占大量 token,且每次工具集变动都击穿 KV Cache。 - **三层答案,一层比一层"按需"**:① 层次化组织 + 按需加载(定义事先备好,不全量注入);② 主动工具发现(Agent 意识到缺口,主动声明需求,系统匹配注入);③ Skills(不把工具当正式定义,当可随手翻阅的参考资料)。 - **披露策略与能力形态是两个独立决策。** 挂着几百个 MCP 工具的服务器可以只暴露一份索引;二十来个 skill 也可能需要分层检索。别把"全量常驻上下文"当默认。 - **只暴露索引是第一件事。** Cursor 的做法:把工具描述同步到文件夹,Agent 默认只看工具名索引,需要时再查具体定义。A/B 测试显示 MCP 相关任务总 token **减少 46.9%**。 - **约 200 token 的代理工具**(pi-mcp-adapter)就能支撑"搜索 → 查看定义 → 调用"的完整按需发现流程,MCP 服务器还可延迟到首次使用才启动。是否用 MCP 作协议与会话开始时是否暴露全部工具定义,是两个独立决策。 - **一次性检索有内在局限。** 检索式预筛选按用户的初始查询做一次匹配,而 "Debug the file" 这类请求实际牵出文件访问、代码分析、命令执行的多步骤跨领域工具链,任务开始时无法预见全部需求。 - **匹配必须层次化。** 工具按服务器(类似 App)分组,先定位服务器、再在服务器内匹配工具,把搜索空间从"数千个工具"缩到"数十个服务器 × 每个数十个工具",既省算力也减少跨领域语义混淆。 - **schema 固定原位,静态前缀只增不改。** 新工具的完整 schema 追加到上下文末尾并固定在首次注入的位置,作为普通历史消息继续命中缓存——"工具定义必须在上下文最前面"不再是铁律。 ## 实践模式 ### 1. 方案选型(按工具数量与场景) | 工具规模 | 推荐方案 | |---|---| | 十几个 | 全量注入,无需额外机制 | | 几十个 | 只暴露索引,按需查定义 | | 上百个 | 检索式预筛选(top-k 后注入) | | 上百至上千 | 主动工具发现(元工具 + 两层语义路由)或 Skills 渐进式披露 | | 上千 | Skills / Skill Hub;专用工具须另建索引层 | 层次化组织按信息源性质分类,并在系统提示词中显式说明,帮助 LLM 快速定位工具组:**搜索工具**(主动查找:网络/知识库/文件搜索)、**读取工具**(从已知位置提取:网页阅读、文档读取、DB 查询)、**解析工具**(处理非结构化数据:OCR、视频分析、音频转录)、**查询工具**(访问结构化数据源:天气、股票、公开数据库 API)。 ### 2. 渐进式工具加载五步流程(核心机制) 1. **索引**:启动时只注入一份薄索引——工具/服务器的 `name` + `description`(数百 token),不注入完整 schema。 2. **声明需求**:Agent 在思考中意识到能力缺口,生成结构化请求块声明"我需要什么能力",例如 `server: GitHub for repository operations; tool: search repositories by keyword`。系统提示词中只保留少数基础工具(`web_search`、`code_interpreter`)加一个 `discover_tools` 元工具。 3. **语义匹配**:系统用离线构建、支持增量更新的嵌入索引做**服务器级 → 工具级**两层路由,返回 3-5 个候选工具及其完整 schema。 4. **注入 schema**:新工具 schema **追加到上下文末尾**(作为普通 user/assistant 消息),此后固定在轨迹原位置;状态栏只维护一份简短的工具名列表。后续轮次作为普通历史命中缓存。 5. **调用**:Agent 用注入的工具执行;再次遇到同类需求时直接复用已加载工具,无需重复加载。 两层匹配候选相似度都低于阈值时,**明确返回"未找到"**,让 Agent 改写需求重试、用基础工具手工实现,或创造新工具。 ### 3. 三种策略的可度量对比 | 策略 | 注入内容 | 适用 | |---|---|---| | 全量注入(all-tools) | 全部 N 个 schema,token 随目录规模线性增长 | 工具少的基线 | | 检索预筛选(retrieval) | 按初始查询语义 top-k | 工具上百、需求可预估 | | 主动发现(active) | Agent 迭代声明需求,上下文按需增长 | 工具上千、多步骤跨领域任务 | 实测(top-k=5):全量注入 35 个工具 3,857 token,且 400 个工具时涨到 40,258 token;retrieval 恒为约 540-550 token 且 recall 保持 100%——按需选择把"选哪个工具"变成"查哪条资料",且成本不随生态规模膨胀。 ### 4. 动态加载与 KV Cache - **根因**:若把全部工具定义放进静态前缀,每加载一个新工具就使整段缓存失效。 - **解法**:把新工具 schema 追加到上下文末尾,静态前缀保持稳定;schema 固定在轨迹原位置,后续轮次作为普通历史消息命中缓存。 - **API 原生支持**:OpenAI `tool_search` + `defer_loading`(要求后续请求保持 `tool_search_output` 项的原位置,同一工具无需重复加载);Anthropic `tool_reference`(在会话历史原位置内联展开 block,官方文档明确后续每轮保持缓存命中);Codex CLI 默认开启 `tool_search`。 - **真正导致重算的只有两种情况**:Prompt Cache 的 TTL 过期(整段前缀一起重算,并非工具定义特有代价),以及修改、移除或重排已加载工具集(缓存从变动点起失效)。 - **代价**:模型必须在后训练中学会理解散落在上下文各处的工具定义。 ### 5. Skills:把工具发现变成"按需查阅" 不需要嵌入索引、检索元工具、`tool_search`/`tool_reference` 这类基础设施。Agent 启动时只看到一份薄目录(每个 skill 的 `name` + `description`),当前上下文真的需要某种能力时,才读取对应 sub-skill,并顺着引用再往下读具体脚本或子文档。类比:没人会把工具书从第一页读到最后一页,而是顺着索引按需查词条。专用工具要达到同样的渐进式披露,必须在工具之外另建一层基础设施——这正是那些机制存在的理由。 ### 6. 小模型场景的落地配置 实验 4-1 的做法(Qwen3-4B 面对 120+ 工具): - 对照组:全部 schema 一次性注入 system prompt(超 50K tokens)→ 指令遵循严重退化,会把"查股价"错选成 Web Search 而非 Yahoo Finance 工具,或"忘记"工具导致任务失败。 - 实验组:system prompt 只保留 `web_search`、`code_interpreter`、`discover_tools`;`discover_tools` 收自然语言需求,经嵌入相似度返回 3-5 个候选及完整 schema;新定义追加到对话历史,状态栏更新工具名列表;提示词引导模型在遇到能力缺口时主动调用 `discover_tools`。 ## 常见陷阱 - 工具数破百还全量平铺,模型选错、漏选,上下文和 token 双重浪费。 - 只做索引不做检索,也不做层次化组织,索引本身长得和全量列表一样。 - 检索预筛选按初始查询一次性匹配,任务中途出现的新能力缺口无人接管。 - 把新加载的 schema 插到上下文前部或重排已加载工具集,导致缓存从变动点起失效。 - 两层匹配都低于阈值时返回一个低置信度的"最佳猜测",而不是明确返回未找到。 - 需要嵌入索引、增量更新、KV Cache 处理和弱模型专门训练的整套基础设施,却没评估过 Skills 这条更轻量的路。 ## 配套代码 - `chapter4/active-tool-discovery/` — 在 126 个跨领域工具上对比全量注入 / 检索预筛选 / 主动发现;`python demo.py --offline` 跑通机制,`run_exact_experiment.py` 是正式实验入口。 - `chapter4/active-tool-selection/` — MCP-Zero 风格教学实现:`` 结构化请求、服务器级→工具级两层语义路由、`demo_comparison.py --offline` 输出 recall/token/规模曲线。 ## 深度阅读 - `book/chapter4.md`「工具太多怎么办:层次化组织与主动工具发现」