Skip to content
Spring AI 文档版本 2.1.0-SNAPSHOT 源快照 70fc62fe 更新于 2026-10-16

工具搜索工具

随着 AI 智能体连接到更多服务——Slack、GitHub、Jira、MCP 服务端——工具库会迅速增长。一个典型的多服务端部署很容易聚合 50+ 工具,在任何对话开始前就消耗 55,000+ 令牌。当模型面对 30+ 个名字相似的工具时,工具选择的准确性也会下降。

ToolSearchToolCallingAdvisor 通过把默认的 ToolCallingAdvisor 替换为渐进式工具披露模式的一个实现来解决这一点:工具定义是按需、增量地暴露给模型,而非预先全部发出。跨 OpenAI、Anthropic 和 Gemini 的基准测试显示 34–64% 的令牌缩减,同时保持对大型工具目录的访问——度量与方法参见 Smart Tool Selection 博客文章。

本页是该顾问、其 ToolIndex 策略、配置和 Spring Boot 自动配置的参考。关于该顾问在更广泛工具调用架构中的概念定位,参见扩展到数百个工具。

工作原理

ToolSearchToolCallingAdvisor 继承 ToolCallingAdvisor 并覆盖该循环的初始化与逐迭代钩子。运行时流程:

  1. 索引 —— 在会话开始时,所有已注册的工具被索引进所配置的 ToolIndex。没有任何工具定义被发给模型。
  2. 初始请求 —— 发给 LLM 的首个请求只包含内建的 toolSearchTool 定义。
  3. 发现调用 —— 当模型需要某项能力时,它用一个自然语言查询调用 toolSearchTool。
  4. 搜索与展开 —— ToolIndex 找到匹配的工具;它们的定义被追加进对话以供下一次迭代。
  5. 工具调用 —— 此刻装备了相关定义的模型发出一个普通的工具调用。
  6. 工具执行 —— ToolCallingManager 执行被发现的工具并返回其结果。
  7. 响应 —— 模型用工具结果产出最终答案。

被索引的工具集以会话为作用域(参见会话作用域);并发的对话有相互隔离的索引。

何时使用

契合的场景:

  • 有 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.Builder bean。多亏默认构建器上的 @ConditionalOnMissingBean 守卫,它透明地替换了默认的 ToolCallingAdvisor——你的 ChatClient 无需任何代码改动。底层机制参见自定义 ToolAdvisor:自动配置集成。
  • 除非你的应用显式声明一个,否则自动注册一个 ToolIndex bean。

ToolIndex 自动选择

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

值实现要求
regex(默认)RegexToolIndex无额外依赖
luceneLuceneToolIndexclasspath 上有 org.apache.lucene:lucene-core(starter 中已捆绑)
vectorVectorToolIndex应用上下文中有一个 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-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 的顾问上下文键。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-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:

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

另见

BytesRouter 是社区翻译站点,所有译文仅供参考,一切以官方英文原文为准。