工具搜索
随着 AI 智能体连接更多服务——Slack、GitHub、Jira、MCP 服务器——工具库迅速增长。一个典型的多服务器配置,在对话开始前就可能轻松聚合 50+ 个工具,消耗 55,000+ tokens。当模型面对 30+ 个名称相似的工具时,工具选择的准确率也会下降。
ToolSearchToolCallingAdvisor 通过用 渐进式工具披露 模式的实现来替换默认的 ToolCallingAdvisor,从而解决了这一问题:工具定义会按需、增量地暴露给模型,而不是预先全部发送。在 OpenAI、Anthropic 和 Gemini 上的基准测试显示,在保持对大型工具目录访问能力的同时,Token 消耗减少了 34–64% —— 有关测量数据和研究方法,请参阅 Smart Tool Selection 博客文章。
本页是有关 advisor、其 ToolIndex 策略、配置以及 Spring Boot 自动配置的参考文档。关于该 advisor 在更广泛的 tool calling 架构中的概念性定位,请参阅 Scaling to Hundreds of Tools。
工作原理

ToolSearchToolCallingAdvisor 继承自 ToolCallingAdvisor,并重写了循环的初始化钩子和每次迭代的钩子。运行时流程如下:
-
索引(Indexing) — 会话开始时,所有已注册的工具都会在配置的
ToolIndex中进行索引。不会向模型发送任何工具定义。 -
初始请求(Initial request) — 发给 LLM 的第一个请求仅包含内置的
toolSearchTool定义。 -
发现调用(Discovery call) — 当模型需要某种能力时,它会使用自然语言查询调用
toolSearchTool。 -
搜索与扩展(Search & expand) —
ToolIndex找到匹配的工具;它们的定义会被追加到对话中,用于下一次迭代。 -
工具调用(Tool invocation) — 此时,模型已具备相关的工具定义,随即发起常规的工具调用。
-
工具执行(Tool execution) —
ToolCallingManager执行发现到的工具并返回其结果。 -
响应(Response) — 模型利用工具的结果生成最终答案。
索引工具集的作用域限定在每个会话内(参见会话作用域);并发的对话具有相互隔离的索引。
何时使用
适配良好:
-
与
ChatClient注册了 10 个以上的工具。 -
每个请求中工具定义消耗超过 10K tokens。
-
多服务器 MCP 配置,其中聚合的工具目录较大。
-
使用大型工具集时出现工具选择准确性问题的症状。
在以下情况下,请继续使用默认的 ToolCallingAdvisor:
-
您的工具库较小(少于 10 个工具)。
-
所有工具在每次会话中均被频繁使用。
-
工具定义非常紧凑(搜索的往返开销会超过 Token 节省量)。
安装
使用 Spring Boot 启动器可以实现最简单的配置(包含 Lucene 和自动配置):
- Maven
- Gradle
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-tool-search-advisor</artifactId>
</dependency>
dependencies {
implementation 'org.springframework.ai:spring-ai-starter-tool-search-advisor'
}
或者直接使用该库进行手动配置:
- Maven
- Gradle
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tool-search-advisor</artifactId>
</dependency>
dependencies {
implementation 'org.springframework.ai:spring-ai-tool-search-advisor'
}
快速开始
最快的路径是自动配置——参见下文中的 Spring Boot 自动配置。如需手动装配:
// 1. Configure a ToolIndex (semantic, keyword, or regex)
@Bean
ToolIndex toolIndex(VectorStore vectorStore) {
return new VectorToolIndex(vectorStore);
}
// 2. Build the advisor
var toolSearchAdvisor = ToolSearchToolCallingAdvisor.builder()
.toolIndex(toolIndex)
.maxResults(5)
.build();
// 3. Register with ChatClient — tools are indexed but NOT sent to the LLM up front
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(new MyTools())
.defaultAdvisors(toolSearchAdvisor)
.build();
// 4. Make a request — supply a session ID via the advisor context
String answer = chatClient.prompt("Help me plan what to wear today in Amsterdam")
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "user-42-session"))
.call()
.content();
会话作用域
ToolSearchToolCallingAdvisor 按会话维度对工具进行索引。会话 ID 决定请求看到的工具索引——这实现了多租户和多会话的隔离。
调用方必须在每次请求中提供会话 ID。默认情况下,advisor 会从 advisor 上下文中 ChatMemory.CONVERSATION_ID 键下读取会话 ID:
chatClient.prompt()
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "user-42-session"))
.user("...")
.call()
.content();
如果你的应用程序已经通过其他键传递会话标识符——例如 tenantId 或 userId——请通过 sessionIdKeyName(…)(或相应的属性)更改查找键:
var advisor = ToolSearchToolCallingAdvisor.builder()
.toolIndex(toolIndex)
.sessionIdKeyName("tenantId")
.build();
如果内存顾问(memory advisors)配置了对话 ID(使用 MessageChatMemoryAdvisor 的标准模式),相同的键已经存在于上下文中——得益于内存配置,您可以“免费”获得会话级作用域(session scoping)。
搜索策略
ToolIndex 接口抽象了搜索实现。开箱即用提供了三种策略:
| 策略 | 实现 | 适用场景 |
|---|---|---|
| 语义 | VectorToolIndex | 自然语言查询、模糊匹配、新颖的表达方式 —— 当调用者描述其需求而非直接指定工具名称时 |
| 关键词 | LuceneToolIndex | 精确术语匹配、快速检索、已知词汇 |
| 正则表达式 | RegexToolIndex | 工具名称模式(例如 get_*_data);无依赖的轻量级默认选项 |
VectorToolIndex (Semantic)
采用基于嵌入的相似性搜索。当调用者使用自然语言描述需求时效果最佳。
@Bean
ToolIndex vectorToolIndex(VectorStore vectorStore) {
return new VectorToolIndex(vectorStore);
}
需要一个 VectorStore Bean(例如通过 spring-ai-starter-vector-store-pgvector 提供)。工具的名称和描述会在索引时进行嵌入;来自 toolSearchTool 的查询也会进行嵌入,并返回相似度排名前 K 的结果。
LuceneToolIndex(关键词)
使用 Apache Lucene 进行基于关键词的搜索。速度快,无需嵌入模型。
@Bean
ToolIndex luceneToolIndex() {
return new LuceneToolIndex(); // default minimum score 0.25
// return new LuceneToolIndex(0.4f); // custom minimum score threshold
}
低于最低分数阈值的命中项将被静默丢弃。提高阈值可提高选择性;降低阈值则更宽容。
RegexToolIndex(模式)
使用正则表达式对工具名称进行匹配。当工具名称遵循严格的命名约定时(例如 get_*、**_database_**)非常有用。无额外依赖项。
@Bean
ToolIndex regexToolIndex() {
return new RegexToolIndex();
}
当未明确配置 tool-index-type 时使用的默认索引。
配置
ToolSearchToolCallingAdvisor.Builder 继承自 ToolCallingAdvisor.Builder,并添加了搜索相关的选项。有关继承的配置,请参阅 ToolCallingAdvisor Builder Options。
| 选项 | 描述 | 默认值 |
|---|---|---|
toolIndex(ToolIndex) | 要使用的搜索实现。 | 必填 |
maxResults(Integer) | 每次 toolSearchTool 调用返回的最大工具引用数量。当值为 null 时,由 LLM 决定(内置的工具描述提示建议值为 5)。 | null |
systemMessageSuffix(String) | 追加到系统消息末尾的自定义提示后缀,用于指导模型如何使用 toolSearchTool。 | 内置模板(参见 DEFAULT_SYSTEM_PROMPT_SUFFIX.md) |
referenceToolNameAccumulation(boolean) | 当值为 true 时,在所有先前的 toolSearchTool 调用中发现的工具名称会被累积并注入。当值为 false 时,仅使用最近一次对话轮次的结果(包括该轮次内的所有并行 toolSearchTool 调用)。 | true |
sessionIdKeyName(String) | 用于查找对话/会话 ID 的 Advisor 上下文键。 | ChatMemory.CONVERSATION_ID |
evictionStrategy(ToolIndexEvictionStrategy) | 决定何时释放会话工具索引。参见 索引驱逐。 | LruEvictionStrategy(1000) |
ToolIndex API
ToolIndex 接口及其配套类型(ToolSearchRequest、ToolSearchResponse、ToolReference)位于 spring-ai-tool-search-tool 模块的 org.springframework.ai.tool.toolsearch 包中。内置的实现类(LuceneToolIndex、VectorToolIndex、RegexToolIndex)也位于该模块中。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tool-search-tool</artifactId>
</dependency>
public interface ToolIndex {
void indexTool(String sessionId, ToolReference toolReference);
/** Default implementation loops over indexTool. */
void indexTools(String sessionId, List<ToolReference> toolReferences);
ToolSearchResponse search(ToolSearchRequest request);
void clearIndex(String sessionId);
}
每个操作都由 sessionId 作用域限定。当你需要自定义搜索策略时,请直接实现 ToolIndex —— 例如,一个带有基于角色的过滤功能且由数据库支持的目录,或一个缓存的远程工具注册表。
索引淘汰
每个会话的工具索引会占用内存。ToolIndexEvictionStrategy 决定何时释放它们。
默认情况下(LruEvictionStrategy(1000)),最多保留 1,000 个活动会话,当超出上限时,会驱逐最近最少使用的会话。调用 advisor.evictSession(sessionId) 可主动释放会话(例如在注销时)。
淘汰操作在每次请求时惰性评估——无需后台线程。
提供五种内置策略:
| 策略 | 行为 |
|---|---|
LruEvictionStrategy(maxSessions) (默认) | 当活跃会话数量超过 maxSessions 时,淘汰最近最少使用的会话。 |
NeverEvictStrategy.INSTANCE | 从不自动淘汰;索引将持续存在,直到显式调用 evictSession()。 |
AlwaysEvictStrategy.INSTANCE | 在每次请求前清除会话的索引,强制每轮进行完整重新索引。适用于测试场景或工具集在每次请求中发生变化的情况。 |
TtlEvictionStrategy(duration) | 淘汰最后一次访问时间超过指定 TTL 的会话。 |
CompositeEvictionStrategy(strategies…) | 委托给多个策略;如果 任意 一个委托策略请求淘汰,则淘汰该会话。 |
// Default: LRU cap of 1000 sessions — no configuration needed
var advisor = ToolSearchToolCallingAdvisor.builder()
.toolIndex(toolIndex)
.build();
// Never evict — manage session lifetime yourself
var advisor = ToolSearchToolCallingAdvisor.builder()
.toolIndex(toolIndex)
.evictionStrategy(NeverEvictStrategy.INSTANCE)
.build();
// Always evict — re-index every request (useful for testing)
var advisor = ToolSearchToolCallingAdvisor.builder()
.toolIndex(toolIndex)
.evictionStrategy(AlwaysEvictStrategy.INSTANCE)
.build();
// LRU with custom cap
var advisor = ToolSearchToolCallingAdvisor.builder()
.toolIndex(toolIndex)
.evictionStrategy(new LruEvictionStrategy(200))
.build();
// Evict sessions idle for more than 30 minutes
var advisor = ToolSearchToolCallingAdvisor.builder()
.toolIndex(toolIndex)
.evictionStrategy(new TtlEvictionStrategy(Duration.ofMinutes(30)))
.build();
// Combine: TTL + LRU cap
var advisor = ToolSearchToolCallingAdvisor.builder()
.toolIndex(toolIndex)
.evictionStrategy(new CompositeEvictionStrategy(
new TtlEvictionStrategy(Duration.ofMinutes(30)),
new LruEvictionStrategy(200)))
.build();
Spring Boot 自动配置
spring-ai-starter-tool-search-advisor 启动器提供零样板代码配置。只需启用一个属性即可:
spring.ai.chat.client.tool-search-advisor.enabled=true
启用后,自动配置会:
-
注册一个类型为
ToolCallingAdvisor.Builder<?>的ToolSearchToolCallingAdvisor.BuilderBean。由于默认 Builder 上的@ConditionalOnMissingBean守卫,这将透明地替换默认的ToolCallingAdvisor——无需对您的ChatClient进行任何代码更改。有关底层机制,请参阅自定义 ToolAdvisor:自动配置集成。 -
除非您的应用程序显式声明了一个
ToolIndexBean,否则会自动注册一个ToolIndexBean。
ToolIndex 自动选择
将 spring.ai.chat.client.tool-search-advisor.tool-index-type 设置为以选择实现:
| Value | Implementation | Requirements |
|---|---|---|
regex (默认) | RegexToolIndex | 无额外依赖关系 |
lucene | LuceneToolIndex | 类路径中需包含 org.apache.lucene:lucene-core(已包含在 starter 中) |
vector | VectorToolIndex | 应用上下文中需存在 VectorStore Bean |
应用程序声明的自定义 ToolIndex Bean 始终具有优先级——@ConditionalOnMissingBean 会跳过自动配置的 Bean。
配置属性参考
| 属性 | 描述 | 默认值 |
|---|---|---|
spring.ai.chat.client.tool-search-advisor.enabled | 启用该 advisor。当值为 true 时,将替换默认的 ToolCallingAdvisor。 | false |
spring.ai.chat.client.tool-search-advisor.tool-index-type | ToolIndex 实现类型:regex、lucene 或 vector。 | regex |
spring.ai.chat.client.tool-search-advisor.max-results | 每次搜索调用返回的最大工具引用数量。null 表示使用内置默认值。 | null |
spring.ai.chat.client.tool-search-advisor.system-message-suffix | 追加到系统消息中的自定义提示词后缀。null 表示使用内置模板。 | null |
spring.ai.chat.client.tool-search-advisor.reference-tool-name-accumulation | 当为 true 时,在所有搜索轮次中累积工具名称;当为 false 时,仅保留最近一轮(包括其中所有并行调用)的工具名称。 | true |
spring.ai.chat.client.tool-search-advisor.session-id-key-name | 用于携带对话/会话 ID 的 Advisor 上下文键名。 | chat_memory_conversation_id |
spring.ai.chat.client.tool-search-advisor.advisor-order | 该 advisor 在 advisor 链中的位置。 | HIGHEST_PRECEDENCE + 300 |
spring.ai.chat.client.tool-search-advisor.eviction.lru-max-sessions | LRU 驱逐策略保留的最大活跃会话数。 | 1000 |
spring.ai.chat.client.tool-search-advisor.eviction.ttl | 空闲会话的 TTL(生存时间)。设置后,将采用 LRU+TTL 复合策略。接受 java.time.Duration 格式的字符串(例如 30m、1h)。 | null |
spring.ai.chat.client.tool-search-advisor.lucene.min-score-threshold | 命中结果被包含所需的最小 Lucene 分数。在 tool-index-type=lucene 时生效。 | 0.25 |
示例配置
使用自定义阈值和 TTL 驱逐的 Lucene:
spring.ai.chat.client.tool-search-advisor.enabled=true
spring.ai.chat.client.tool-search-advisor.tool-index-type=lucene
spring.ai.chat.client.tool-search-advisor.lucene.min-score-threshold=0.4
spring.ai.chat.client.tool-search-advisor.eviction.ttl=30m
向量搜索(需要 VectorStore Bean):
spring.ai.chat.client.tool-search-advisor.enabled=true
spring.ai.chat.client.tool-search-advisor.tool-index-type=vector
多租户部署中的自定义会话 ID 键:
spring.ai.chat.client.tool-search-advisor.enabled=true
spring.ai.chat.client.tool-search-advisor.tool-index-type=vector
spring.ai.chat.client.tool-search-advisor.session-id-key-name=tenantId
参见
-
工具调用:扩展至数百个工具 — 概念概述
-
ToolCallingAdvisor — 基类及继承的构建器选项
-
智能工具选择:34%–64% 的 Token 节省(2025 年 12 月)— 基准测试与方法论
-
动态工具发现指南 — 实战示例