主题
工具搜索工具
随着 AI 智能体连接到更多服务——Slack、GitHub、Jira、MCP 服务端——工具库会迅速增长。一个典型的多服务端部署很容易聚合 50+ 工具,在任何对话开始前就消耗 55,000+ 令牌。当模型面对 30+ 个名字相似的工具时,工具选择的准确性也会下降。
ToolSearchToolCallingAdvisor 通过把默认的 ToolCallingAdvisor 替换为渐进式工具披露模式的一个实现来解决这一点:工具定义是按需、增量地暴露给模型,而非预先全部发出。跨 OpenAI、Anthropic 和 Gemini 的基准测试显示 34–64% 的令牌缩减,同时保持对大型工具目录的访问——度量与方法参见 Smart Tool Selection 博客文章。
本页是该顾问、其 ToolIndex 策略、配置和 Spring Boot 自动配置的参考。关于该顾问在更广泛工具调用架构中的概念定位,参见扩展到数百个工具。
工作原理
ToolSearchToolCallingAdvisor 继承 ToolCallingAdvisor 并覆盖该循环的初始化与逐迭代钩子。运行时流程:
- 索引 —— 在会话开始时,所有已注册的工具被索引进所配置的
ToolIndex。没有任何工具定义被发给模型。 - 初始请求 —— 发给 LLM 的首个请求只包含内建的
toolSearchTool定义。 - 发现调用 —— 当模型需要某项能力时,它用一个自然语言查询调用
toolSearchTool。 - 搜索与展开 ——
ToolIndex找到匹配的工具;它们的定义被追加进对话以供下一次迭代。 - 工具调用 —— 此刻装备了相关定义的模型发出一个普通的工具调用。
- 工具执行 ——
ToolCallingManager执行被发现的工具并返回其结果。 - 响应 —— 模型用工具结果产出最终答案。
被索引的工具集以会话为作用域(参见会话作用域);并发的对话有相互隔离的索引。
何时使用
契合的场景:
- 有 10+ 工具注册到
ChatClient。 - 每个请求中工具定义消耗超过 10K 令牌。
- 聚合工具目录庞大的多服务端 MCP 部署。
- 大工具集下出现工具选择准确性问题的征兆。
在以下情形请沿用默认的 ToolCallingAdvisor:
- 你的工具库很小(少于 10 个工具)。
- 每个会话都频繁使用所有工具。
- 工具定义非常紧凑(搜索往返会抵消令牌节省)。
安装
使用 Spring Boot starter 获得最简单的装配(含 Lucene 与自动配置):
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-tool-search-advisor</artifactId>
</dependency>或直接使用该库进行手动配置:
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tool-search-advisor</artifactId>
</dependency>快速开始
最快的路径是自动配置——见下方 Spring Boot 自动配置。对于手动装配:
java
// 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。默认情况下,顾问在 ChatMemory.CONVERSATION_ID 键下从顾问上下文读取会话 ID:
java
chatClient.prompt()
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "user-42-session"))
.user("...")
.call()
.content();如果你的应用已在一个不同的键下传入会话标识符——例如 tenantId 或 userId——通过 sessionIdKeyName(...)(或相应属性)改变查找键:
java
var advisor = ToolSearchToolCallingAdvisor.builder()
.toolIndex(toolIndex)
.sessionIdKeyName("tenantId")
.build();NOTE
如果用对话 ID 配置了记忆顾问(配合 MessageChatMemoryAdvisor 的标准模式),同一个键已在上下文中——你借助记忆装配就“免费”获得会话作用域。
搜索策略
ToolIndex 接口抽象了搜索实现。开箱提供三种策略:
| 策略 | 实现 | 最适于 |
|---|---|---|
| 语义 | VectorToolIndex | 自然语言查询、模糊匹配、新颖措辞——当调用方描述他们需要什么而非点名工具时 |
| 关键词 | LuceneToolIndex | 精确词项匹配、快速检索、已知词汇 |
| 正则 | RegexToolIndex | 工具名模式(例如 get_*_data);无依赖的轻量默认 |
VectorToolIndex(语义)
使用基于嵌入的相似度搜索。当调用方用自然语言描述他们需要什么时最佳。
java
@Bean
ToolIndex vectorToolIndex(VectorStore vectorStore) {
return new VectorToolIndex(vectorStore);
}需要一个 VectorStore bean(例如通过 spring-ai-starter-vector-store-pgvector)。工具名和描述在索引时被嵌入;来自 toolSearchTool 的查询被嵌入,返回 top-K 匹配。
LuceneToolIndex(关键词)
使用 Apache Lucene 做基于关键词的搜索。快速,无需嵌入模型。
java
@Bean
ToolIndex luceneToolIndex() {
return new LuceneToolIndex(); // default minimum score 0.25
// return new LuceneToolIndex(0.4f); // custom minimum score threshold
}低于最低分数阈值的命中会被静默丢弃。调高阈值以更挑剔;调低以更宽松。
RegexToolIndex(模式)
对工具名使用正则模式匹配。当工具名遵循严格的命名约定(例如 get_*、*_database_*)时很有用。零额外依赖。
java
@Bean
ToolIndex regexToolIndex() {
return new RegexToolIndex();
}当未配置显式 tool-index-type 时,这是默认索引。
配置
ToolSearchToolCallingAdvisor.Builder 继承 ToolCallingAdvisor.Builder 并添加搜索特定的选项。继承的设置参见 ToolCallingAdvisor 构建器选项。
| 选项 | 描述 | 默认值 |
|---|---|---|
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 的顾问上下文键。 | 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)也在此模块。
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tool-search-tool</artifactId>
</dependency>java
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...) | 委托给多个策略;只要任一委托请求即驱逐一个会话。 |
java
// 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 starter 提供零样板装配。用一个属性即可启用:
properties
spring.ai.chat.client.tool-search-advisor.enabled=true启用后,自动配置会:
- 注册一个类型为
ToolCallingAdvisor.Builder<?>的ToolSearchToolCallingAdvisor.Builderbean。多亏默认构建器上的@ConditionalOnMissingBean守卫,它透明地替换了默认的ToolCallingAdvisor——你的ChatClient无需任何代码改动。底层机制参见自定义 ToolAdvisor:自动配置集成。 - 除非你的应用显式声明一个,否则自动注册一个
ToolIndexbean。
ToolIndex 自动选择
设置 spring.ai.chat.client.tool-search-advisor.tool-index-type 来选择实现:
| 值 | 实现 | 要求 |
|---|---|---|
regex(默认) | RegexToolIndex | 无额外依赖 |
lucene | LuceneToolIndex | classpath 上有 org.apache.lucene:lucene-core(starter 中已捆绑) |
vector | VectorToolIndex | 应用上下文中有一个 VectorStore bean |
应用声明的自定义 ToolIndex bean 总是优先——@ConditionalOnMissingBean 会跳过自动配置的那个。
配置属性参考
| 属性 | 描述 | 默认值 |
|---|---|---|
spring.ai.chat.client.tool-search-advisor.enabled | 启用该顾问。为 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 的顾问上下文键。 | chat_memory_conversation_id |
spring.ai.chat.client.tool-search-advisor.advisor-order | 该顾问在顾问链中的位置。 | 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:
properties
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):
properties
spring.ai.chat.client.tool-search-advisor.enabled=true
spring.ai.chat.client.tool-search-advisor.tool-index-type=vector为多租户部署使用自定义会话-ID 键:
properties
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 —— 基类与继承的构建器选项
- Smart Tool Selection:34–64% 令牌节省(2025 年 12 月)—— 基准与方法论
- 动态工具发现指南 —— 成示例