Tool Search原理实战:海量AI Agent工具轻量化上下文最佳实践
文章目录
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
前言
大家好,我是那个在 AI 圈混了 22 年的老油条。
今天咱们聊一个特别接地气的话题:当你的 AI Agent 手里攥着 400 多个工具,上下文窗口都快被挤爆了,怎么办?
💡 这就好比你请了一个全能管家,结果管家每次出门都要把家里所有钥匙串在腰上——从大门钥匙到鱼缸钥匙,从保险柜钥匙到猫砂盆钥匙。走两步叮叮当当响,不知道的还以为你雇了个移动五金店。
别笑,这场景在 AI Agent 开发里每天都在上演。
一、LLM 是个"嘴炮王者",工具才是它的手脚
先说个冷知识:大语言模型本身啥也干不了。
它就是个高级复读机,你喂它文本,它吐文本。它不能读文件,不能跑命令,不能查数据库,连给自己倒杯水都做不到。
那 AI Agent 为啥能干这么多事?
因为模型会"指挥"工具。
模型说:“我要调用 readFile,参数是 package.json”。客户端收到指令,真的去读文件,再把内容塞回给模型。模型一看:“哦,原来依赖是这样的”,然后继续下一步。
这套"模型发号施令,客户端跑腿执行"的循环,就是 Agent 的核心玩法。
💡 你可以把 LLM 想象成一个坐在轮椅上的战略家——脑子绝顶聪明,但身体动不了。工具就是它的轮椅、拐杖和电动滑板车。没有工具,它只能对着空气发表高见。
每个工具长啥样?三个零件:
- name:工具名,比如
readFile - description:自然语言描述,告诉模型这玩意儿能干啥
- inputSchema:JSON Schema,定义参数格式
这三个东西拼在一起,每次请求都要塞进 tools 数组发给模型。模型靠 description 判断用哪个,靠 schema 生成合法参数。
二、工具数量爆炸:从"小钥匙串"到"移动五金店"
内置工具在膨胀
Agent 产品刚上线时,内置工具可能就十几个:读文件、写文件、执行命令、搜索代码。
但产品一迭代,工具就像野草一样疯长。联网搜索、网页抓取、子 Agent 委派、待办管理、后台任务…… Claude Code、Cursor、Codex 这些主流产品,哪个不是几十个内置工具打底?
MCP 工具更是重量级
如果说内置工具是"自家菜园",那 MCP(Model Context Protocol)就是"农贸市场"。
接个 GitHub MCP Server,Agent 就能创建 Issue、合并 PR。接个数据库 MCP Server,Agent 就能跑 SQL 查询。接个交易所 MCP Server……
等等,交易所?
对,真有这玩意儿。一个加密货币交易所的 MCP Server,覆盖现货交易、合约交易、资产划转、行情查询、余额查询…… 一口气给你 400 个工具。
💡 400 个工具什么概念?你让模型每次请求都背 400 个工具的完整定义,相当于让一个人每次开会前先把整本《新华字典》背一遍。不是,哥们,咱们是来开会的,不是来参加最强大脑的。
一个中等复杂度的工具定义,大概 150-300 token。400 个工具 × 平均 160 token = 64,000 token。
用户还没开口问问题,光工具定义就占了 64k。如果模型上下文窗口是 128k,一半没了。
这还没算 system prompt 和对话历史。
三、Tool Search:给工具列表"瘦身"的骚操作
既然全量注入太费 token,那能不能只告诉模型"有哪些工具",等它真要用的时候再加载详细定义?
能。这就是 Tool Search(按需加载)。
核心思路:名字先行,Schema 后置
传统做法:每次请求把 400 个工具的完整定义(name + description + schema)全塞进去。
Tool Search 做法:
- 系统提示里只放工具名字清单,不放 schema
- 给模型一个内置的
toolSearch工具 - 模型需要某个工具时,先调
toolSearch加载它的 schema - 下一轮请求里,这个工具的完整定义就出现了
上下文占用从 64k 降到 3-5k(名字清单)+ 按需加载的几个工具 schema。
💡 这就好比你去图书馆借书。传统做法是每次进图书馆把全部藏书搬到桌上,Tool Search 是只给你一份目录,想看哪本再让管理员拿过来。管理员累是累点,但你桌子不会塌啊。
Deferred 工具 vs 直接加载工具
不是所有工具都要延迟加载。有些"劳模"工具,每轮都可能用到,必须直接加载:
| 工具 | 功能 | 为什么直接加载 |
|---|---|---|
readFile |
读文件 | 几乎所有编码任务都要先读文件 |
edit |
编辑文件 | 写代码的核心操作 |
shell |
执行命令 | 跑测试、装依赖、git 操作 |
grep |
搜索代码 | 定位代码位置 |
toolSearch |
工具搜索 | 加载 deferred 工具的唯一入口,它自己必须先在场 |
剩下的工具,比如 webSearch、webFetch、todoWrite,以及所有 MCP 工具,全部 defer。
💡 你可以把直接加载的工具理解为"随身工具包"——螺丝刀、扳手、胶带,走哪带哪。Deferred 工具就是"仓库里的设备"——电焊机、龙门吊、3D 打印机,用的时候再去搬。
四、模型只看到名字,怎么找到对的工具?
这是个好问题。系统提示里只列了名字,模型连参数都不知道,怎么判断该用哪个?
第一层保障:工具名本身就是语义索引
MCP 工具名一般长这样:
mcp__github__create_issue—— GitHub 创建 Issuemcp__gate__cex_spot_get_ticker—— Gate 交易所现货行情
名字遵循 server__resource__verb 的命名规范,本身就是一层语义索引。模型看到 get_ticker 就知道是查行情,看到 create_order 就知道是下单。
💡 这就好比你看到"老干妈"三个字,不需要看配料表也知道是辣椒酱。要是工具名叫
tool_001、tool_002,那这套方案直接崩盘——模型会一脸懵逼:“tool_001 是啥?是开门的还是炸厨房的?”
第二层保障:toolSearch 的两种搜索模式
toolSearch 支持两种调用方式:
方式一:关键词搜索(不确定名字时用)
模型传 toolSearch({query: "spot ticker"}),客户端拿 query 和每个工具的预处理文本做关键词匹配,按得分排序,返回前几个。
举个例子。假设 deferred 目录里有这些工具:
| 工具名 | 搜索文本 |
|---|---|
mcp__gate__cex_spot_get_ticker |
gate cex spot get ticker 获取现货行情 currency_pair |
mcp__gate__cex_spot_create_order |
gate cex spot create order 创建现货订单 currency_pair side amount price |
mcp__github__create_issue |
github create issue 创建 issue title body labels |
模型搜 "spot ticker":
cex_spot_get_ticker:“spot” 命中 +10,“ticker” 命中 +10,总分 20 ✓cex_spot_create_order:“spot” 命中 +10,“ticker” 没命中,总分 10github_create_issue:都没命中,总分 0
返回得分最高的几个,模型自己挑。
Codex 用的是 BM25 算法(搜索引擎同款),核心思路一样:拿 query 关键词匹配工具搜索文本,按相关度排序。
💡 注意,这里不需要 embedding,也不需要再调一次模型。就是纯字符串匹配——同样的 query、同样的工具集,结果永远一致。不像某些方案,每次搜索结果都带点"随机性",跟开盲盒似的。
方式二:select: 精确匹配(推荐方式)
模型直接传 toolSearch({query: "select:mcp__gate__cex_spot_get_ticker"}),客户端按名字精确查找,不打分,直接返回。
模型怎么知道确切名字?因为系统提示的 ## Deferred Tools 段里列出了所有 deferred 工具的完整名字。模型每轮都能看到这份名单。
toolSearch 的 description 里直接告诉模型:如果已经知道名字,优先用 select:;不确定名字时,再用关键词描述能力。
💡 实际使用中,模型大多数情况下会走
select:,因为它能直接从名单里看到工具名。关键词搜索是"备胎"——比如工具名太长、有多个相似工具时,模型可能用"spot order"这种模糊查询让客户端帮它筛。
激活是持久的
一旦某个 deferred 工具被 toolSearch 激活,它就会一直留在 tools 数组里,后续轮次不用重复搜索。
代价是:首次使用某个 deferred 工具时,至少需要 2 轮 API 调用——先搜索,下一轮才能调用。
💡 这就好比你去餐厅点菜。第一次来,你得先看菜单(toolSearch),然后才能点菜(调用工具)。但下次再来,你直接喊"老样子",菜单都不用看了。当然,如果你记性不好(对话轮次太多,早期的 tool_result 被压缩掉了),可能又得重新看菜单——这时候客户端会提醒:“这道菜你点过了,直接上菜就行。”
五、完整流程走一遍:查 BTC 价格
咱们用"查 BTC 现货价格"这个场景,完整走一遍 Tool Search 的流程。
启动阶段
客户端从 MCP Server 全量拿到 400 个工具的完整定义,存在本地内存里。但只把名字写进系统提示的 ## Deferred Tools 段。
第 1 轮请求
用户说:“帮我查一下 BTC 的现货价格。”
发给模型的内容:
- System Prompt(包含 deferred 名字清单)
- 核心工具 schema(readFile、edit、shell、grep、toolSearch 等)
- 用户消息
模型看到名单里有 cex_spot_get_ticker,但没有它的 schema,无法直接调用。
模型决定:先调 toolSearch。
toolSearch({query: "select:mcp__gate__cex_spot_get_ticker"})
第 1 轮响应
客户端精确匹配命中,把 cex_spot_get_ticker 加入 activated 集合。
返回给模型:
Loaded 1 tool(s) — now callable directly on your next step: mcp__gate__cex_spot_get_ticker: 获取现货行情
第 2 轮请求
客户端合成新的 tools 数组:核心工具 + toolSearch + cex_spot_get_ticker 的完整 schema。
模型现在能看到 cex_spot_get_ticker 的参数定义了,直接调用:
cex_spot_get_ticker({currency_pair: "BTC_USDT"})
第 2 轮响应
客户端执行 MCP 工具调用,拿到价格,塞回给模型。
模型回复:“BTC 现货价格是 63,521.30 USDT。”
💡 整个过程就像:你去药店买药,第一次来不知道药在哪,问导购(toolSearch)。导购告诉你"在第三排货架"。下次再来,你直奔第三排,导购都懒得理你。但如果药店搬家了(activated 变了),你可能又得问一次。
六、工程实现里的那些坑
阈值判断:工具少的时候别瞎 defer
工具少的时候,全量注入更简单,也不需要多一轮往返。
x-code-cli 的做法:估算所有候选 deferred 工具的 schema token 量,如果不超过模型上下文窗口的 10%,就跳过 defer,退回全量加载。
毕竟,为了省几百个 token 搞一套 deferred 机制,纯属杀鸡用牛刀。
模型兼容性:不是所有模型都配合
Tool Search 依赖模型主动去调 toolSearch。如果模型不支持工具调用,或者模型太笨理解不了这套机制,deferred 工具就永远加载不出来。
各家处理策略不同:
| 产品 | 策略 |
|---|---|
| Codex | 在 models.json 里按具体模型版本配置 supports_search_tool 布尔字段。明确支持的才启用,未知模型默认关闭。 |
| Claude Code | 检查 API 能力而非模型智力:用 modelSupportsToolReference() 检查是否支持 tool_reference 协议块。默认排除 haiku,可远程动态更新。 |
| x-code-cli | 粗粒度字符串匹配:WEAK_MODEL_PATTERNS = ['haiku', 'nano', 'glm-4v'],模型名包含这些子串就退回全量注入。问题是区分不了同一系列的不同版本——Claude 3 Haiku 和 Claude 3.5 Haiku 能力差距很大,但都会被一刀切。 |
💡 x-code-cli 这个方案,相当于看到"张"姓就默认对方不会武功。张三丰:“???”
系统提示的字节稳定性
LLM API 的前缀缓存(prefix caching)依赖请求前缀不变。system + tools 组成的前缀如果每轮都一样,就能命中缓存,按折扣价计费。
这就要求系统提示里的 deferred 名单不能在运行中动态修改。x-code-cli 的做法:启动时生成 systemPromptCache 并冻结,整个 session 不再改动。
副作用来了:工具激活后,系统提示里的名单仍然写着这个工具是"未加载"状态,但 tools 数组里已经有了它的完整 schema。
模型看到的系统提示和实际的工具列表之间存在不一致。
💡 这就好比你家墙上挂着"此处无狗"的牌子,但地上明明趴着一只金毛。访客会疑惑:“这狗是量子态的吗?”
不过在实际使用中,这个不一致很少造成问题。强模型(Sonnet、GPT-4o 等级)在收到第一次 toolSearch 的 tool_result 后,会记住工具已经加载,后续直接调用而不会重复搜索。
重复搜索只在极端情况下出现——对话轮次非常多、早期的 tool_result 被压缩掉了、模型又看到系统提示里写着"未加载"时,才可能再搜一次。
x-code-cli 加了一层防御:如果搜索的工具已经在 activated 集合里,返回 "Already loaded — call xxx directly now.",避免模型陷入搜索循环。
Claude Code 和 Codex 从架构上避免了这种不一致——它们靠各自 API 的服务端能力处理工具激活,客户端不需要改 tools 数组。x-code-cli 因为要跨多个 provider,没法依赖单一 API 的服务端能力,只能走客户端重注入。
子 Agent 不开 defer
子 Agent 的工具集由白名单控制,大多数子 Agent 只有几个内置工具。比如 explore 子 Agent 只有 readFile、glob、grep、listDir、shell 五个工具。
工具这么少,加 tool search 纯属脱裤子放屁。
💡 这就好比你出门买瓶酱油,非要先写个采购计划书。便利店老板会以为你在拍《职场风云》。
七、主流方案对比
| 产品 | 全量注入? | 动态机制 | 匹配算法 |
|---|---|---|---|
| Claude Code | 否 | ToolSearch 工具 |
关键词打分 + select: |
| Codex | 否 | tool_search 工具 |
BM25 全文检索 |
| Gemini CLI | 是 | 无 | — |
| x-code-cli | 否 | toolSearch 工具 |
关键词打分 + select: |
Claude Code、Codex、x-code-cli 三家思路几乎一样,区别只在激活后的 schema 怎么对模型可用——Claude Code 和 Codex 靠各自 API 的服务端能力,x-code-cli 靠客户端重注入。
Gemini CLI 目前没有内置 tool search,属于"硬核全量注入派"。
💡 这就好比四个人去爬山。Claude Code 和 Codex 坐缆车,x-code-cli 自己爬(但带了登山杖),Gemini CLI 光着脚往上冲。respect。
八、总结一下
Tool Search 的核心逻辑就一句话:名字先行,Schema 后置,按需激活,持久缓存。
- 启动时从 MCP Server 全量拿到工具定义,存在本地内存
- 核心工具直接加载,非核心工具和所有 MCP 工具 defer
- 系统提示里只列 deferred 工具的名字清单
- 模型通过
toolSearch加载需要的工具 schema - 激活后的工具持久保留,后续轮次可直接调用
代价是首次使用某个 deferred 工具时多一轮 API 调用(先搜索后调用)。收益是上下文占用从 64k 降到 3-5k,对话历史和用户消息有了更多空间。
💡 最后说句掏心窝子的话:做 AI Agent 开发,工具管理就像收拾房间。东西少的时候随便扔,东西多了就得分类、贴标签、按需取用。不然你每次找东西都要翻箱倒柜,效率低到怀疑人生。Tool Search 就是给你的工具箱装了个智能索引——不是让工具变少,而是让找工具变快。
好了,今天就聊到这。如果你觉得有用,点个赞,咱们下期见!
本文基于 x-code-cli、Claude Code、Codex 等开源项目的源码分析整理而成。
P.S. 目前国内还是很缺AI人才的,希望更多人能真正加入到AI行业,共同促进行业进步,增强我国的AI竞争力。想要系统学习AI知识的朋友可以看看我精心打磨的教程 http://blog.csdn.net/jiangjunshow,教程通俗易懂,高中生都能看懂,还有各种段子风趣幽默,从深度学习基础原理到各领域实战应用都有讲解,我22年的AI积累全在里面了。注意,教程仅限真正想入门AI的朋友,否则看看零散的博文就够了。
更多推荐

所有评论(0)