首页 / Spring AI 入门教程 / 动态工具发现与组合

Spring AI 入门教程

动态工具发现与组合

本教程共 45 篇 · 第 43 篇 · 更新于 2026-08-16 · 约 9 分钟阅读

Spring AI动态工具发现Tool SearchToolIndexToolSearchToolCallingAdvisorLuceneVectorStoreMCP

本节目标:理解工具搜索(Tool Search)模式解决的问题与原理,学完你能给 ChatClient 接上动态工具发现,让几十上百个工具按需加载。

43.1 工具太多也是问题

前几章的工具调用都是静态注册:启动时把工具定义全部发给模型。工具少时没问题,多了就出事了。

假设你的智能体接了 Slack、GitHub、Jira 和几个 MCP 服务器。工具轻松超过 50 个,每个定义都要占 token。对话还没开始,几万 token 就烧掉了。

更麻烦的是准确率。模型面对 30 多个名字相近的工具时,选错工具的概率明显上升。工具越多,选择越难。

问题的根源是”全量加载”。有没有办法让模型按需加载工具?这就是动态工具发现。

43.2 核心思路:给模型一个搜索工具

Anthropic 在《Advanced Tool Use》里提出了一种模式,Spring AI 实现了它,叫 Tool Search Tool。

思路很简单:不把所有工具定义发给模型,只发一个”搜索工具”的定义。模型需要能力时,调用搜索工具查一下,系统把命中的工具定义展开到上下文里。

整个过程模型感知不到区别。它只是先搜了一下,然后像平常一样调用工具。但每次请求只带少量工具定义,token 消耗大幅下降。

Spring AI 的实现基于第 6 章讲的 Recursive Advisor,扩展了 ToolCallingAdvisor。它对所有模型厂商通用:OpenAI、Anthropic、Gemini、Ollama 都能用。

Note

官方基准测试:28 个工具的场景下,Gemini 节省 60% token,OpenAI 节省 34%,Anthropic 节省 64%。模型不同,收益不同,但都显著。

43.3 快速开始

官方提供 Spring Boot starter,包含 Lucene 索引和自动配置。加依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-tool-search-advisor</artifactId>
</dependency>

然后在 application.properties 里开一个开关:

spring.ai.chat.client.tool-search-advisor.enabled=true

就这两步。工具照常用 @Tool 注册,唯一区别是工具定义不会一开始就发给模型。

索引类型默认是 regex,不需要额外依赖。lucene 需要 lucene-core,vector 需要一个 VectorStore Bean。

# 可选:regex(默认)/ lucene / vector
spring.ai.chat.client.tool-search-advisor.tool-index-type=regex
Tip

2.0 里 ToolSearchToolCallingAdvisor 有独立模块 spring-ai-tool-search-advisor,包名是 org.springframework.ai.chat.client.advisor.toolsearch。老版本的 spring-ai-tool-search-tool 模块已废弃。

43.4 工作原理

官方文档把完整流程拆成七步:

  1. 对话开始时,所有注册的工具被索引进 ToolIndex,但不发给模型
  2. 首次请求只带搜索工具的定义。
  3. 模型需要某个能力,调用搜索工具,传查询词。
  4. ToolIndex 找出匹配的工具,把定义加入下一次请求。
  5. 模型同时看到搜索工具和展开的工具定义。
  6. 命中的工具被执行,结果返回。
  7. 模型基于结果生成最终回答。

关键在第 4 步:工具定义是”展开”进上下文的,不是一次性全给。模型用多少,就展开多少。

代码层面,你只需要一个 ToolIndex 和 Advisor。下面是一个完整的示例:

@SpringBootApplication
public class Application {

    @Bean
    CommandLineRunner demo(ChatClient.Builder builder, ToolIndex toolIndex) {
        return args -> {
            var advisor = ToolSearchToolCallingAdvisor.builder()
                    .toolIndex(toolIndex)
                    .build();

            ChatClient chatClient = builder
                    .defaultTools(new MyTools())  // 注册了但不发给模型
                    .defaultAdvisors(advisor)     // 激活工具搜索
                    .build();

            var answer = chatClient.prompt("""
                    帮我规划今天在阿姆斯特丹穿什么。
                    请推荐现在还在营业的服装店。
                    """).call().content();

            System.out.println(answer);
        };
    }

    static class MyTools {

        @Tool(description = "获取指定地点、指定时间的天气")
        public String weather(String location,
                @ToolParam(description = "YYYY-MM-DDTHH:mm") String atTime) {
            // 实现省略
            return "";
        }

        @Tool(description = "获取指定地点、指定时间营业的服装店名称")
        public List<String> clothing(String location,
                @ToolParam(description = "YYYY-MM-DDTHH:mm") String openAtTime) {
            // 实现省略
            return List.of();
        }

        @Tool(description = "获取指定地点的当前日期和时间")
        public String currentTime(String location) {
            // 实现省略
            return "";
        }

        // ... 可能还有几百个工具
    }
}

注意 defaultTools 的注释:工具注册了,但初始请求不会携带它们的定义。

43.5 三种检索策略

ToolIndex 是接口,官方提供三种实现,对应三种检索策略:

策略实现类适合场景
语义检索VectorToolIndex自然语言查询、模糊匹配
关键词检索LuceneToolIndex精确术语匹配、工具名已知
正则匹配RegexToolIndex工具名模式匹配(如 get_*_data)

语义检索需要 VectorStore,把工具描述向量化后按相似度匹配。关键词检索用 Lucene,适合工具名规范的情况。正则最轻量,零依赖。

选型建议:工具少且名字规范,用 regex 就够。工具多、名字乱、查询偏自然语言,上 lucene 或 vector。

43.6 手动配置

不想用 starter 也可以只加核心库,自己声明 Bean:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-tool-search-advisor</artifactId>
</dependency>

然后手动装配 Advisor 和索引。灵活的地方在于可以换 ToolIndex 实现,比如接入自己的向量库做语义检索。

43.7 官方性能数据

官方用 28 个工具做了基准测试,下面是节选的 token 消耗对比:

模型启用工具搜索未启用节省
Gemini2,165 tokens5,375 tokens60%
OpenAI4,706 tokens7,175 tokens34%
Anthropic6,273 tokens17,342 tokens64%

数字是初步基准,但趋势明确:工具定义越大、越多,收益越大。Anthropic 节省最多,因为它的工具定义格式更占 token。

43.8 什么时候用

官方文档给了一张对比表,直接抄来:

用 Tool Search用传统方式
工具超过 20 个工具库很小(少于 20 个)
工具定义超过 5K token所有工具每个会话都会用到
多 MCP 服务器场景工具定义非常紧凑
出现工具选择准确率问题

两个判断维度:工具数量和工具定义体积。都小,静态注册更快更简单;任何一个变大,动态发现更划算。

Tip

如果所有工具每个会话都用一遍,动态发现的收益很小。它优化的是”工具很多但每次只用少数”的场景。

43.9 与 MCP 组合

第 27-31 章讲过 MCP:多个服务器各自暴露工具。这正是动态发现的典型场景。

一个 MCP 客户端可能挂 5 个服务器,每个服务器 10 个工具,总共 50+。静态注册要把 50 个定义全发给模型,动态发现只发一个搜索工具。

两者定位不同:MCP 解决”工具从哪来”,动态发现解决”工具怎么按需给模型”。组合使用,才能支撑上百个工具的智能体。

43.10 小结

动态工具发现的核心是一个思想:让模型自己搜工具。Token 节省 34%-64%,工具选择准确率也更高。

上手三步:加 starter 依赖、开 enabled 开关、照常注册 @Tool。检索策略按工具规模选,regex 起步,lucene 或 vector 进阶。

下一章是升级指南。2.0 改动很大,老项目迁移要过一遍清单。