Tool Search Tool
As AI agents connect to more services — Slack, GitHub, Jira, MCP servers — tool libraries grow rapidly. A typical multi-server setup can easily aggregate 50+ tools consuming 55,000+ tokens before any conversation starts. Tool selection accuracy also degrades when models face 30+ similarly-named tools.
ToolSearchToolCallingAdvisor solves this by replacing the default ToolCallingAdvisor with an implementation of the progressive tool disclosure pattern: tool definitions are exposed to the model incrementally, on demand, rather than sent upfront. Benchmarks across OpenAI, Anthropic, and Gemini show 34–64% token reduction while maintaining access to large tool catalogs — see the Smart Tool Selection blog post for measurements and methodology.
This page is the reference for the advisor, its ToolIndex strategies, configuration, and Spring Boot auto-configuration. For the conceptual placement of this advisor in the broader tool calling architecture, see Scaling to Hundreds of Tools.
How It Works
ToolSearchToolCallingAdvisor extends ToolCallingAdvisor and overrides the loop’s initialization and per-iteration hooks. The runtime flow:
-
Indexing — at session start, all registered tools are indexed in the configured
ToolIndex. No tool definitions are sent to the model. -
Initial request — the first request to the LLM contains only the built-in
toolSearchTooldefinition. -
Discovery call — when the model needs a capability, it calls
toolSearchToolwith a natural-language query. -
Search & expand — the
ToolIndexfinds matching tools; their definitions are appended to the conversation for the next iteration. -
Tool invocation — the model, now equipped with the relevant definition, issues a normal tool call.
-
Tool execution —
ToolCallingManagerexecutes the discovered tool and returns its result. -
Response — the model produces the final answer using the tool result.
The indexed tool set is scoped per session (see Session Scoping); concurrent conversations have isolated indexes.
When to Use
Good fit:
-
10+ tools registered with the
ChatClient. -
Tool definitions consuming more than 10K tokens per request.
-
Multi-server MCP setups where the aggregated tool catalog is large.
-
Symptoms of tool-selection accuracy issues with large tool sets.
Stick with the default ToolCallingAdvisor when:
-
Your tool library is small (under 10 tools).
-
All tools are frequently used in every session.
-
Tool definitions are very compact (the search round-trips would outweigh the token savings).
Installation
Use the Spring Boot starter for the simplest setup (includes Lucene and auto-configuration):
-
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'
}
Or use the library directly for manual configuration:
-
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'
}
Quick Start
The fastest path is the auto-configuration — see Spring Boot Auto-Configuration below. For manual wiring:
// 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();
Session Scoping
ToolSearchToolCallingAdvisor indexes tools per session. The session ID determines which tool index a request sees — this enables multi-tenant and multi-conversation isolation.
The caller must supply a session ID with every request. By default the advisor reads the session ID from the advisor context under the ChatMemory.CONVERSATION_ID key:
chatClient.prompt()
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "user-42-session"))
.user("...")
.call()
.content();
If your application already passes a session identifier under a different key — for example tenantId or userId — change the lookup key via sessionIdKeyName(…) (or the corresponding property):
var advisor = ToolSearchToolCallingAdvisor.builder()
.toolIndex(toolIndex)
.sessionIdKeyName("tenantId")
.build();
If memory advisors are configured with conversation IDs (the standard pattern with MessageChatMemoryAdvisor), the same key is already in the context — you get session scoping "for free" by virtue of the memory setup.
|
Search Strategies
The ToolIndex interface abstracts the search implementation. Three strategies are provided out of the box:
| Strategy | Implementation | Best for |
|---|---|---|
Semantic |
|
Natural-language queries, fuzzy matching, novel phrasings — when callers describe what they need rather than naming the tool |
Keyword |
|
Exact-term matching, fast retrieval, known vocabulary |
Regex |
|
Tool name patterns (e.g. |
VectorToolIndex (Semantic)
Uses embedding-based similarity search. Best when callers describe what they need in natural language.
@Bean
ToolIndex vectorToolIndex(VectorStore vectorStore) {
return new VectorToolIndex(vectorStore);
}
Requires a VectorStore bean (e.g. via spring-ai-starter-vector-store-pgvector). Tool name and description are embedded on indexing; queries from toolSearchTool are embedded and the top-K matches are returned.
LuceneToolIndex (Keyword)
Uses Apache Lucene for keyword-based search. Fast, no embedding model required.
@Bean
ToolIndex luceneToolIndex() {
return new LuceneToolIndex(); // default minimum score 0.25
// return new LuceneToolIndex(0.4f); // custom minimum score threshold
}
Hits below the minimum score threshold are silently dropped. Raise the threshold to be more selective; lower it to be more permissive.
RegexToolIndex (Pattern)
Uses regex pattern matching against tool names. Useful when tool names follow a strict naming convention (e.g. get_*, database). Zero additional dependencies.
@Bean
ToolIndex regexToolIndex() {
return new RegexToolIndex();
}
The default index when no explicit tool-index-type is configured.
Configuration
ToolSearchToolCallingAdvisor.Builder extends ToolCallingAdvisor.Builder and adds search-specific options. See ToolCallingAdvisor Builder Options for inherited settings.
| Option | Description | Default |
|---|---|---|
|
The search implementation to use. |
Required |
|
Maximum tool references returned per |
|
|
Custom prompt suffix appended to the system message to instruct the model on how to use |
Built-in template (see |
|
When |
|
|
Advisor context key used to look up the conversation/session ID. |
|
|
Determines when session tool indexes are freed. See Index Eviction. |
|
ToolIndex API
The ToolIndex interface and its companion types (ToolSearchRequest, ToolSearchResponse, ToolReference) live in the spring-ai-tool-search-tool module under org.springframework.ai.tool.toolsearch. The built-in implementations (LuceneToolIndex, VectorToolIndex, RegexToolIndex) are also in this module.
<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);
}
Every operation is scoped by sessionId. Implement ToolIndex directly when you need a custom search strategy — for example, a database-backed catalog with role-based filtering, or a cached remote tool registry.
Index Eviction
Per-session tool indexes consume memory. The ToolIndexEvictionStrategy decides when to free them.
By default (LruEvictionStrategy(1000)), up to 1,000 active sessions are retained and the least-recently-used session is evicted once the cap is exceeded. Call advisor.evictSession(sessionId) to release a session eagerly (e.g. on logout).
Eviction is evaluated lazily on each request — no background thread is required.
Five built-in strategies are provided:
| Strategy | Behavior |
|---|---|
|
Evicts the least-recently-used session once the number of active sessions exceeds |
|
Never evicts automatically; indexes persist until |
|
Clears a session’s index before every request, forcing a full re-index each turn. Useful for testing or when tool sets change every request. |
|
Evicts sessions whose last-access time exceeds the given TTL. |
|
Delegates to multiple strategies; evicts a session if any delegate requests it. |
// 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 Auto-Configuration
The spring-ai-starter-tool-search-advisor starter provides zero-boilerplate setup. Enable it with a single property:
spring.ai.chat.client.tool-search-advisor.enabled=true
When enabled, the auto-configuration:
-
Registers a
ToolSearchToolCallingAdvisor.Builderbean typed asToolCallingAdvisor.Builder<?>. This transparently replaces the defaultToolCallingAdvisorthanks to the@ConditionalOnMissingBeanguard on the default builder — no code changes to yourChatClientare needed. See Custom ToolAdvisor: Auto-Configuration Integration for the underlying mechanism. -
Auto-registers a
ToolIndexbean unless your application declares one explicitly.
ToolIndex Auto-Selection
Set spring.ai.chat.client.tool-search-advisor.tool-index-type to select the implementation:
| Value | Implementation | Requirements |
|---|---|---|
|
|
No additional dependencies |
|
|
|
|
|
A |
A custom ToolIndex bean declared by the application always takes precedence — @ConditionalOnMissingBean skips the auto-configured one.
Configuration Properties Reference
| Property | Description | Default |
|---|---|---|
|
Enable the advisor. Replaces the default |
|
|
|
|
|
Maximum tool references returned per search call. |
|
|
Custom prompt suffix appended to the system message. |
|
|
When |
|
|
Advisor context key that carries the conversation/session ID. |
|
|
Position of this advisor in the advisor chain. |
|
|
Maximum active sessions retained by the LRU eviction strategy. |
|
|
TTL for idle sessions. When set, a composite LRU+TTL strategy is used. Accepts a |
|
|
Minimum Lucene score for a hit to be included. Applies when |
|
Example Configurations
Lucene with custom threshold and TTL eviction:
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
Vector search (requires a VectorStore bean):
spring.ai.chat.client.tool-search-advisor.enabled=true
spring.ai.chat.client.tool-search-advisor.tool-index-type=vector
Custom session-ID key for a multi-tenant deployment:
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
See Also
-
Tool Calling: Scaling to Hundreds of Tools — conceptual overview
-
ToolCallingAdvisor — the base class and inherited builder options
-
Smart Tool Selection: 34–64% Token Savings (Dec 2025) — benchmarks and methodology
-
Dynamic Tool Discovery guide — worked example