跳到主要内容
版本:2.0.1

工具搜索

QWen 3.8 中英对照 Tool Search Tool

随着 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。

工作原理​

Tool Search Tool Calling Flow

ToolSearchToolCallingAdvisor 继承自 ToolCallingAdvisor,并重写了循环的初始化钩子和每次迭代的钩子。运行时流程如下:

  1. 索引(Indexing) — 会话开始时,所有已注册的工具都会在配置的 ToolIndex 中进行索引。不会向模型发送任何工具定义。

  2. 初始请求(Initial request) — 发给 LLM 的第一个请求仅包含内置的 toolSearchTool 定义。

  3. 发现调用(Discovery call) — 当模型需要某种能力时,它会使用自然语言查询调用 toolSearchTool。

  4. 搜索与扩展(Search & expand) — ToolIndex 找到匹配的工具;它们的定义会被追加到对话中,用于下一次迭代。

  5. 工具调用(Tool invocation) — 此时,模型已具备相关的工具定义,随即发起常规的工具调用。

  6. 工具执行(Tool execution) — ToolCallingManager 执行发现到的工具并返回其结果。

  7. 响应(Response) — 模型利用工具的结果生成最终答案。

索引工具集的作用域限定在每个会话内(参见会话作用域);并发的对话具有相互隔离的索引。

何时使用​

适配良好:

  • 与 ChatClient 注册了 10 个以上的工具。

  • 每个请求中工具定义消耗超过 10K tokens。

  • 多服务器 MCP 配置,其中聚合的工具目录较大。

  • 使用大型工具集时出现工具选择准确性问题的症状。

在以下情况下,请继续使用默认的 ToolCallingAdvisor:

  • 您的工具库较小(少于 10 个工具)。

  • 所有工具在每次会话中均被频繁使用。

  • 工具定义非常紧凑(搜索的往返开销会超过 Token 节省量)。

安装​

使用 Spring Boot 启动器可以实现最简单的配置(包含 Lucene 和自动配置):

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

或者直接使用该库进行手动配置:

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

快速开始​

最快的路径是自动配置——参见下文中的 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.Builder Bean。由于默认 Builder 上的 @ConditionalOnMissingBean 守卫,这将透明地替换默认的 ToolCallingAdvisor——无需对您的 ChatClient 进行任何代码更改。有关底层机制,请参阅自定义 ToolAdvisor:自动配置集成。

  • 除非您的应用程序显式声明了一个 ToolIndex Bean,否则会自动注册一个 ToolIndex Bean。

ToolIndex 自动选择​

将 spring.ai.chat.client.tool-search-advisor.tool-index-type 设置为以选择实现:

ValueImplementationRequirements
regex (默认)RegexToolIndex无额外依赖关系
luceneLuceneToolIndex类路径中需包含 org.apache.lucene:lucene-core(已包含在 starter 中)
vectorVectorToolIndex应用上下文中需存在 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-typeToolIndex 实现类型: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-sessionsLRU 驱逐策略保留的最大活跃会话数。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

参见​