1. Visión general
Cuando construimos sistemas integrados con IA, a menudo proporcionamos a nuestros clientes de IA una gran cantidad de herramientas. En cada solicitud, enviamos las definiciones de todas las herramientas disponibles al LLM para que pueda decidir cuáles usar. Como resultado, desperdiciamos una cantidad significativa de tokens antes de que el modelo procese la consulta del usuario. En este artículo, exploramos cómo resolvemos este problema utilizando la Herramienta de Búsqueda de Herramientas.
2. Cómo funciona la Herramienta de Búsqueda de Herramientas
Utilizando la Herramienta de Búsqueda de Herramientas, no enviamos todas las definiciones de herramientas con el contexto. Sólo exponemos herramientas cuando el modelo realmente las necesita. Primero, indexamos todas las herramientas registradas al iniciar. Las almacenamos dentro del ToolSearcher, pero NO las enviamos al LLM. Luego, enviamos únicamente la Herramienta de Búsqueda de Herramientas en la solicitud inicial. Esto mantiene el prompt pequeño y enfocado. Cuando el modelo necesita una capacidad, llama a la Herramienta de Búsqueda de Herramientas usando una consulta en lenguaje natural.
Tratamos esto como una señal de descubrimiento y desencadenamos una búsqueda entre las herramientas indexadas usando la estrategia configurada. A continuación, devolvemos sólo las coincidencias más relevantes del ToolSearcher e inyectamos sus definiciones en la siguiente solicitud al LLM, de modo que el modelo vea un conjunto enfocado de herramientas en lugar del registro completo.
Una vez que las herramientas relevantes están disponibles, el modelo selecciona y llama a la herramienta real. La ejecutamos y enviamos el resultado de vuelta al LLM, que luego lo usa para generar la respuesta final.
3. Construyendo un Ejemplo de Asistente de Viajes
Construyamos un asistente de viajes que ayude a los usuarios a planificar sus viajes. Conectamos múltiples herramientas como vuelos, hoteles, clima y atracciones. Usamos el enfoque de la Herramienta de Búsqueda de Herramientas para evitar enviar todas las herramientas al LLM al principio. En su lugar, descubrimos herramientas dinámicamente en tiempo de ejecución.
3.1. Dependencias
Comenzamos añadiendo soporte de dependencia de búsqueda de herramientas:
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>tool-search-tool</artifactId>
<version>${tool-search-tool.version}</version>
</dependency>
Además, agreguemos la dependencia de buscador regex:
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>tool-searcher-regex</artifactId>
<version>${tool-search-tool.version}</version>
</dependency>
Al usarla, tendremos una estrategia de búsqueda de herramientas regex. Las otras estrategias disponibles se encuentran en el repositorio del proyecto.
3.2. Herramientas de Vuelos
Creemos una simple FlightTools. Usaremos esta herramienta para recuperar opciones de vuelo disponibles. Además, crearemos un conjunto de herramientas artificiales para simular la sobrecarga de contexto*:*
public class FlightTools {
@Tool(description = "Searches available flights between two cities")
public List<FlightOption> searchFlights(String from, String to, String departureDate) {
return List.of(
new FlightOption(
"Romania Airlines",
from,
to,
departureDate,
249.99
)
);
}
}
Aquí devolvemos una única opción de vuelo.
3.3. TokenCounterAdvisor
Ahora creemos un sencillo TokenCounterAdvisor que cuenta el número de tokens usados para producir el resultado final. Lo usaremos para comparar el uso de tokens entre diferentes configuraciones, con y sin búsqueda de herramientas habilitada:
public class TokenCounterAdvisor implements BaseAdvisor {
private static final Logger log = LoggerFactory.getLogger(TokenCounterAdvisor.class);
private final AtomicInteger totalTokenCounter = new AtomicInteger(0);
@Override
public String getName() {
return "TokenCounterAdvisor";
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1;
}
@Override
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) {
return chatClientRequest;
}
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
var usage = chatClientResponse.chatResponse().getMetadata().getUsage();
totalTokenCounter.addAndGet(usage.getTotalTokens());
log.info("Total tokens spent: {}", totalTokenCounter.get());
return chatClientResponse;
}
}
Aquí almacenamos el número de tokens en un campo AtomicInteger y registramos esta información durante la ejecución. Adjuntamos este asesor con el orden máximo, de modo que se ejecute al final de la canalización de procesamiento. Como resultado, captura el uso total de tokens después de que todos los demás asesores hayan finalizado.
3.4. Configuración
A continuación, añadimos la implementación TravelAssistantConfig:
@Configuration
public class TravelAssistantConfig {
@Bean
ToolSearcher toolSearcher() {
return new RegexToolSearcher();
}
@Bean
ToolSearchToolCallAdvisor toolSearchToolCallAdvisor(ToolSearcher toolSearcher) {
return ToolSearchToolCallAdvisor.builder()
.toolSearcher(toolSearcher)
.maxResults(5)
.build();
}
@Bean
ChatClient chatClient(ToolSearchToolCallAdvisor toolSearchToolCallAdvisor, OpenAiChatModel model) {
return ChatClient.builder(model)
.defaultTools(
new FlightTools(),
new RandomTools()
)
.defaultAdvisors(toolSearchToolCallAdvisor, new TokenCounterAdvisor())
.build();
}
@Bean
ChatClient chatClientWithoutToolsSearch(OpenAiChatModel model) {
return ChatClient.builder(model)
.defaultTools(
new FlightTools(),
new RandomTools()
)
.defaultAdvisors(new TokenCounterAdvisor())
.build();
}
}
Configuramos un asistente de viajes que utiliza descubrimiento dinámico de herramientas en lugar de cargar todas las herramientas en el LLM. Luego configuramos un ToolSearcher con una implementación RegexToolSearcher. Esto nos permite hacer coincidir herramientas basadas en patrones de nombres y consultas rápidas similares a palabras clave. Luego, creamos un ToolSearchToolCallAdvisor y lo conectamos al buscador. Después, construimos el ChatClient con las herramientas de vuelo registradas.
Por diseño, añadimos RandomTools, que incluye muchas definiciones de herramientas no relacionadas. Sin embargo, no enviamos estas definiciones de herramientas al LLM inicialmente. En su lugar, solo las indexamos en el sistema. Finalmente, exponemos solo la Herramienta de Búsqueda de Herramientas al modelo al principio. El modelo luego la usa para descubrir qué herramientas necesita realmente para una solicitud dada. Además, hemos configurado un bean ChatClient separado que no usa el ToolSearchToolCallAdvisor.
3.5. Llamar al Asistente de Viajes
Finalmente, creemos un ToolsSearchToolLiveTest con casos de prueba similares para ambos clientes:
@SpringBootTest
@ActiveProfiles("toolsearchtool")
class ToolsSearchToolLiveTest {
@Autowired
private ChatClient chatClient;
@Autowired
private ChatClient chatClientWithoutToolsSearch;
@Test
void shouldFindFlightsBetweenRomaniaAndCroatiaUsingToolsSearch() {
String response = getClientResponseString(chatClient);
assetClientResponse(response);
}
@Test
void shouldFindFlightsBetweenRomaniaAndCroatiaWithoutToolsSearch() {
String response = getClientResponseString(chatClientWithoutToolsSearch);
assetClientResponse(response);
}
private static void assetClientResponse(String response) {
assertThat(response).isNotBlank();
assertThat(response).containsIgnoringCase("Croatia");
assertThat(response).containsIgnoringCase("flight");
}
private String getClientResponseString(ChatClient chatClientWithoutToolsSearch) {
return chatClientWithoutToolsSearch.prompt()
.user("""
Find available flights from Romania to Croatia next week.
""")
.call()
.content();
}
}
Llamamos a nuestros clientes asesores de viajes con el mismo prompt y obtenemos los mismos resultados verificados. Ahora, comparemos el uso de tokens en ambos:
[2026-05-24 11:39:07] [INFO] [c.b.s.t.TokenCounterAdvisor] - Total tokens spent: 974 //Con herramienta de búsqueda de herramientas
[2026-05-24 11:39:10] [INFO] [c.b.s.t.TokenCounterAdvisor] - Total tokens spent: 3685 //Sin herramienta de búsqueda de herramientas
Como podemos ver, la diferencia en el uso de tokens es crucial. Cuantas más herramientas tengamos en nuestro sistema, mayor será el ahorro de tokens que la Herramienta de Búsqueda de Herramientas proporcionará.
4. Conclusión
En este artículo revisamos la Herramienta de Búsqueda de Herramientas y demostramos cómo ayuda a reducir el uso de tokens en escenarios reales. Al usarla, podemos construir sistemas integrados con IA con cientos de herramientas adjuntas y usarlas eficientemente, sin desperdiciar tokens. Además, podemos explorar otras estrategias de búsqueda de herramientas, como búsqueda vectorial, o incluso crear nuestra propia estrategia personalizada para hacer que el descubrimiento de herramientas sea aún más eficiente.
Como siempre, el código está disponible en GitHub.
Newsletter Semanal de Java
Cada viernes recibe lo más nuevo del ecosistema Java: frameworks, herramientas y mejores prácticas.
Sin spam. Cancela cuando quieras.