你给 Agent 接了 50 个工具,本以为它变聪明了,结果每次问答都慢半拍、账单还贵了一截。你以为是模型不行,其实锅在"喂工具"的方式上。
问题出在哪?绝大多数人接工具,都是把全部工具定义一次性塞进每次请求的上下文——不管这次问题用不用得到。工具少的时候没事,工具上到二三十个,灾难就开始了。今天这篇讲 Spring AI 2.0 给出的正解:ToolSearch 渐进式工具发现,以及它怎么把"喂工具"的成本砍掉一大半。
为什么全量塞工具会炸
先算一笔账。一个工具定义一般包含三块:名字、自然语言描述、JSON Schema 参数结构。随便一个正经业务工具,描述加 schema 轻松跑到 200~400 token。50 个工具,光定义就 10K~21K token。
这 10K~21K 每个请求都重发一遍。后果有四个:
1. 上下文被工具占满,对话被挤没。 你本来就只有 8K 对话历史 + 系统提示,结果一半窗口被用不上的工具定义吃了。模型真正要处理的"用户刚才说了啥"反而被截断。
2. 干扰工具越多,选错率越高。 这是学界验证过的"distractor tools"现象:候选工具里混了几个八竿子打不着的,模型选对的概率明显下降。你接的工具越多,纯凑数的越多,它越容易张冠李戴。
3. 成本线性增长,且毫无必要。 每个请求都带全量工具定义,token 费用跟着工具数走。你攒了半年工具库,每次问"今天天气怎么样"都要为那 20K 工具定义买单。
4. 改一个工具,全链路抖动。 工具描述一调整,所有请求上下文都变,缓存命中率归零,延迟和成本双升。
所以真相是:工具不是接得越多越好,是"该出现的时候才出现"才好。
Spring AI 2.0 的解法:ToolSearch
2.0 之前,工具定义是平铺在 ChatClient 里的。2.0 抽出一个专门的 Advisor——`ToolSearchToolCallingAdvisor`,思路变了:
不再把全部工具塞进请求,而是先把所有工具建一个索引(vector 语义索引或 keyword 索引)。当模型要调用工具时,advisor 先用模型的"意图"去索引里检索出最相关的 top-k 个,只把这 k 个注入当次请求。
官方基准数据:在 300 个工具的场景下,ToolSearch 比全量注入省 token 34%~64%,而任务精度几乎无损。原因很简单——每次实际只需要几个工具,凭什么背一整本字典。
核心流程是这样的:
注意一个关键点:检索发生在每次请求时,不是一次性。所以模型永远只看到和当前问题最相关的那几个工具,干扰项被天然过滤。
实战一:最小配置(application.yml 自动开)
最省事的方式,是交给 Spring Boot 自动配置。只要你在 classpath 里有 vector store 和 embedding model,加几行配置就能开:
`tool-index-type` 有两个取值:
- `vector`:语义索引,适合工具多(几十到几百)、描述长、需要"理解意图"的场景。最常用。
- `simple`:基于关键词/精确匹配的轻量索引,工具少(个位数到十几个)时用它足够,还不依赖 embedding 模型,启动更轻。
`top-k` 是每次最多注入几个工具。太小会漏掉该用的,太大又回到老问题。经验值:工具总数 20 以内 k=8,50 左右 k=12,100+ 再往 15~20 调。
工具本身还是用 `@Tool` 注册成 `ToolCallback` bean,和 1.x→2.0 迁移后的写法一致:
描述写法是成败关键,后面踩坑一节专门讲。
实战二:Java 显式构建(不用自动配置时)
有些项目要把 ToolSearch 和自定义 Advisor 链精确编排(比如还要叠加记忆、校验),这时候用 `ToolSearchToolCallingAdvisor.builder(...)` 手动装配:
这里有个顺序坑(来自 2.0 官方文档):`MessageChatMemoryAdvisor` 放 `ToolSearchToolCallingAdvisor` 前面,记忆里只存紧凑历史;放后面,会连工具请求和响应一起存进记忆,适合审计但更占 token。一人公司做客服 Agent,放前面省成本;做需要复盘的工具调用链,放后面留痕。
实战三:vector 索引要什么依赖
`tool-index-type: vector` 需要一个可用的 `VectorStore` bean 和 `EmbeddingModel` bean。Spring AI 2.0 里随便接一个就行,比如简单用内存版做演示:
生产环境换成 PgVector、Redis 或 Chroma,把 bean 换掉即可,业务代码一行不用动。ToolSearch 会在启动时自动把所有 `@Tool` 的描述向量化写进这个 store,你不用手写灌库逻辑。
生产三坑(都是真踩过的)
坑一:工具描述写太短、太像,检索召回不准。
这是最高频的翻车点。很多人 `@Tool(description = "订单工具")` 一句话带过,结果检索时分不清"查订单"和"退订单",top-k 里排错。
解决模板——每个工具描述写齐三要素:什么时候用 + 输入是什么 + 输出是什么。
描述越像人话、越讲清楚触发条件,语义检索越准。这是 ToolSearch 能不能生效的命门,没有之一。
坑二:top-k 拍脑袋,要么漏要么胀。
k 太小,真正要用的工具没进 top-k,模型"想调但看不见",直接胡答;k 太大,又退化成全量注入,省 token 的收益没了。
建议从 k=12 起,压一批真实问题做 A/B:用全量跑一遍当基线,再用 ToolSearch 跑,对比准确率和 token。哪个 k 在准确率不掉的前提下 token 最低,就用哪个。别凭感觉。
坑三:vector store 没初始化 / embedding 维度不匹配,启动直接挂。
`tool-index-type: vector` 但没给 `VectorStore` bean,或 embedding 模型维度和 store 建表时不一致,启动就 `NoSuchBeanDefinition` 或写入时报维度错。
解决:显式声明 `VectorStore` bean(如上面实战三),并确认 embedding 模型与 store 配置一致。SimpleVectorStore 内存版能帮你快速验证链路,生产再换持久化。
坑四(边界):MCP 大服务器才是 ToolSearch 的主场。
单个 `@Tool` bean 几十个还好说,真恐怖的是接了一个 MCP 服务器,对方一次性暴露 200 个工具——单次全量注入直接 10K~21K token 打满。这种场景 ToolSearch 不是"优化项",是"必选项"。如果你用 2.0 的 `@McpTool` 接外部系统,强烈建议 tool-search-advisor 直接开。
什么时候该用,什么时候别折腾
一句话判断:
- 工具数 < 10:别用,全量注入简单直接,ToolSearch 反而多一层检索开销。
- 工具数 10~20:可选,看你对成本和精度的敏感度。
- 工具数 > 20,尤其接了 MCP 大服务器:无脑上 ToolSearch,省 token 34%~64% 是实打实的。
我再贴一个 50 工具场景的模拟对比,帮你建立体感:
数字因模型和计价而异,但量级不会骗人——工具库每膨胀一倍,全量方案成本翻倍,ToolSearch 几乎不动。
小结
ToolSearch 的本质,是把"工具发现"从一次性平铺,变成了按需检索。它解决的是 Agent 规模化后最现实的成本和准确率问题,不是花活。
接工具的姿势进化史:1.x 手写 for 循环 → 2.0 的 `ToolCallingAdvisor` 把循环收编 → 再往上叠 `ToolSearchToolCallingAdvisor` 做按需发现。一层一层,都是在把"重复且笨重"的事从你代码里挪走。
下篇聊 2.0 另一个扛把子:`AugmentedToolCallbackProvider`——给工具调用注入结构化"内心独白",让工具调用可记录、可路由、可审计,免得你事后去扒日志猜模型为什么调了它。
(100+ 个 AI 搞钱真实案例、可复制 SOP、Java 后端接 AI 的踩坑清单,都在知识星球「AI搞钱实验室」。68 元/年,扫码进。)

你现在的 Agent 接了多少个工具?是全量塞,还是已经做了按需发现?评论区报个数,我帮你看看该不该上 ToolSearch。
夜雨聆风