Tool Calling con Spring AI: cómo conectar un LLM con tu backend Java

«¿Cuál es el estado del pedido 48392?». Sin una herramienta que consulte el ERP, el modelo no tiene forma fiable de saberlo: puede razonar sobre lo que hay en el contexto, pero no consultar un dato operativo que cambia en tiempo real.
Con Tool Calling, el modelo puede decidir solicitar una herramienta cuando necesita información o una acción externa: pide que se ejecute getOrderStatus con el identificador 48392, la aplicación Java consulta el ERP, devuelve SHIPPED y el modelo redacta la respuesta con un dato real.
Ahora cambia la frase por «cancela el pedido 48392». La mecánica es idéntica y el riesgo no tiene nada que ver. Consultar y modificar no son el mismo tipo de herramienta, y esa distinción recorre el artículo entero.
Este artículo explica cómo funciona Tool Calling en Spring AI 2.0 (la versión estable es la 2.0.1): cómo se define una herramienta, qué ve realmente el modelo, quién ejecuta el método Java y dónde siguen viviendo la autorización, los límites y la auditoría.
Tool Calling en Spring AI, en 30 segundos
- Defines un método Java como herramienta; Spring AI genera su nombre, su descripción y el JSON Schema de los argumentos.
- Esa definición viaja al modelo con el prompt; el modelo decide si quiere usarla y devuelve nombre y argumentos.
- Spring AI ejecuta el método y devuelve el resultado al modelo, que responde o pide otra herramienta.
- El modelo nunca recibe una referencia al método ni acceso a lo que hay detrás: solo el contrato.
- Autenticación, autorización, validación, límites y auditoría siguen siendo cosa del código Java.
Qué es Tool Calling en Spring AI
Tool Calling en Spring AI permite exponer funciones de una aplicación Java a un modelo de lenguaje mediante un nombre, una descripción y un JSON Schema de argumentos. El modelo puede solicitar la ejecución de una herramienta, pero la lógica de ejecución pertenece a la aplicación: el modelo nunca recibe acceso directo a las APIs que hay detrás.
Cubren dos propósitos que conviene no mezclar: recuperar información (consultar un pedido, buscar un cliente, llamar a una API) y ejecutar acciones (abrir un ticket, enviar una notificación, cancelar una operación). La segunda categoría necesita bastante más control, y casi todo lo que sigue trata de eso.
Tool Calling y function calling: ¿son lo mismo?
En la práctica describen el mismo patrón: el modelo devuelve el nombre de una capacidad y unos argumentos estructurados para que la aplicación la ejecute. «Function calling» es el término que popularizaron los proveedores de modelos; «tool calling» es el que usa hoy Spring AI.
La diferencia es de API, no de concepto. Spring AI unificó la terminología en torno a tools y a ToolCallback, una abstracción más general que la antigua FunctionCallback: da igual que la herramienta venga de un método anotado, de un callback programático o de un servidor externo, todas recorren el mismo bucle. Si buscas function calling en Java con Spring AI, la documentación actual lo trata como tool calling.
El bucle de tool calling: quién ejecuta realmente la herramienta
En Spring AI 2.0 el bucle de herramientas es un componente de la cadena de advisors del ChatClient. DefaultChatClient auto-registra un ToolCallingAdvisor salvo que se desactive o se aporte otro ToolAdvisor, y ese advisor dirige el ciclo apoyándose en un ToolCallingManager, que localiza el ToolCallback y lo ejecuta.
- Usuario
pregunta o instrucción
- Spring AI
envía el prompt y las definiciones de las herramientas
- LLM
decide si necesita una herramienta y devuelve nombre y argumentos
- ToolCallingAdvisor
recibe la solicitud y la pasa al ToolCallingManager
- Java
ejecuta el método con sus permisos y sus reglas
- LLM
recibe el resultado y responde o pide otra herramienta
Qué cambió en Tool Calling con Spring AI 2.0
En Spring AI 1.x el patrón estándar era declarar un @Bean de tipo java.util.function.Function<Request, Response> y referenciarlo por su nombre: el framework lo resolvía dinámicamente en el ApplicationContext —a través de SpringBeanToolCallbackResolver— y derivaba la definición del tipo de entrada. En 2.0 ese modelo desaparece: se eliminan tanto el resolutor por nombre como la API de FunctionCallback.
El sustituto es el registro explícito: una herramienta se declara con @Tool sobre un método de servicio o se construye como ToolCallback, la abstracción única bajo la que conviven los métodos anotados, MethodToolCallback, FunctionToolCallback y las herramientas remotas. La consecuencia práctica es que el modelo ve las herramientas que adjuntas a la petición, no las que el contenedor encuentre por nombre.
La otra mitad del cambio es el bucle. En 1.x cada implementación de ChatModel ejecutaba las tool calls internamente; en 2.0 esa responsabilidad está estrictamente en el ToolCallingAdvisor de la cadena del ChatClient. Llamar a ChatModel directamente con herramientas devuelve la solicitud sin ejecutarla, y el bucle lo conduces tú con ToolCallingManager.
| Spring AI 1.x | Spring AI 2.0 | Qué implica |
|---|---|---|
| FunctionCallback (deprecado) | ToolCallback | La sustitución venía de antes; 2.0 elimina los tipos heredados |
| Bucle dentro de cada ChatModel | ToolCallingAdvisor | ChatModel ya no ejecuta herramientas por su cuenta |
| functions() y defaultFunctions() | tools() y defaultTools() | «Tools» en toda la API |
| toolNames() sobre beans Function | ToolCallback explícito | SpringBeanToolCallbackResolver eliminado |
| Sin límite de llamadas | Límites por defecto | 40 por herramienta y 150 en total por turno |
Un cambio relevante para las facturas: el advisor acumula el uso de tokens de todas las llamadas del bucle, así que el ChatResponse final refleja el coste completo. El detalle de cada cambio está en las notas de migración oficiales.
Primer ejemplo: una herramienta con @Tool
Una herramienta es un método normal de un servicio Spring: la anotación solo añade el contrato que verá el modelo.
@Component
class OrderTools {
private final OrderService orderService;
OrderTools(OrderService orderService) {
this.orderService = orderService;
}
@Tool(name = "getOrderStatus",
description = "Obtiene el estado actual de un pedido existente. Úsala cuando el usuario "
+ "pregunte por el estado, el seguimiento o la entrega de un pedido.")
OrderStatus getOrderStatus(
@ToolParam(description = "Identificador interno del pedido, por ejemplo 48392")
Long orderId) {
return orderService.getStatus(orderId);
}
}String respuesta = chatClient.prompt()
.user("¿Cuál es el estado del pedido 48392?")
.tools(orderTools)
.call()
.content();Entre ambas llamadas: Spring AI envía la definición al modelo, el modelo devuelve una solicitud con el argumento orderId, el advisor ejecuta el método y el resultado vuelve al modelo, que redacta la respuesta final.
@Tool no es magia: nombre, descripción y schema
El modelo no ve tu implementación: recibe nombre, descripción y JSON Schema de los parámetros, y con eso decide qué herramienta usar y con qué argumentos.
// Pobre: el modelo tiene que adivinar cuándo usarla y qué es "id"
@Tool
Order find(Long id) { ... }
// Mejor: nombre semántico, cuándo usarla y qué significa cada argumento
@Tool(name = "getOrderStatus",
description = "Obtiene el estado actual de un pedido existente. Úsala cuando el usuario "
+ "pregunte por el estado o el seguimiento de un pedido. No sirve para "
+ "buscar pedidos por cliente ni para modificarlos.")
OrderStatus getOrderStatus(
@ToolParam(description = "Identificador interno del pedido") Long orderId) { ... }@ToolParam: argumentos obligatorios y opcionales
Spring AI genera el JSON Schema con su JsonSchemaGenerator, el mismo que usa la salida estructurada. Por defecto todos los parámetros son obligatorios; se marcan como opcionales con @ToolParam(required = false), @JsonProperty(required = false), @Schema(required = false) o @Nullable, en ese orden de precedencia.
La documentación avisa: si marcas un parámetro como obligatorio y el modelo no puede conocer su valor, se lo inventará.
@Tool(description = "Busca los pedidos de un cliente en un rango de fechas")
List<OrderSummary> findCustomerOrders(
@ToolParam(description = "Identificador del cliente") Long customerId,
@ToolParam(description = "Fecha inicial en formato ISO-8601", required = false)
LocalDate desde,
@ToolParam(description = "Estado por el que filtrar", required = false)
OrderStatus estado) {
return orderService.search(customerId, desde, estado);
}Dos reglas: pocos argumentos bien descritos y tipos cerrados —enums, records, LocalDate— en lugar de String para todo. Un enum convierte una alucinación en un error detectable.
Tres formas de definir herramientas en Spring AI 2.0
Las tres producen instancias de ToolCallback y circulan por el mismo bucle, así que se pueden mezclar en la misma llamada.
@Bean
ToolCallback catalogSearch(CatalogService catalogService) {
return FunctionToolCallback.builder("searchCatalog", catalogService::search)
.description("Busca productos del catálogo por texto libre")
.inputType(CatalogQuery.class)
.build();
}| Opción | Cuándo usarla |
|---|---|
| @Tool sobre un método | El método es tuyo y quieres la mínima ceremonia |
| MethodToolCallback | Necesitas control programático sobre un método concreto |
| FunctionToolCallback | Quieres exponer una Function, Supplier, Consumer o una referencia a método |
Si vienes de 1.x: los beans Function referenciados con toolNames() ya no se resuelven; hay que declarar un ToolCallback explícito.
tools() frente a defaultTools(): una decisión de seguridad
.tools(...) añade herramientas a una petición concreta; .defaultTools(...) las deja disponibles en todas las peticiones de ese ChatClient. Ambos aceptan objetos con @Tool, instancias de ToolCallback y ToolCallbackProvider.
Un matiz importante: en ChatClient las herramientas por petición se suman a las de por defecto, no las sustituyen, así que lo que dejas en los defaults está siempre sobre la mesa. La documentación es explícita: los defaults pueden ser peligrosos si se usan sin cuidado, y las herramientas destructivas deberían añadirse por llamada.
| Herramienta | ¿Default razonable? | Motivo |
|---|---|---|
| Consultar catálogo o documentación | Sí | Lectura, sin efectos y con datos públicos internos |
| Consultar un pedido del tenant actual | Con cuidado | Lectura, pero necesita aislamiento por tenant |
| Abrir un ticket o guardar un borrador | Mejor por petición | Escritura reversible, pero genera ruido |
| Cancelar un pedido o emitir un reembolso | No | Acción sensible: se habilita en el flujo que la necesita |
Garantía adicional: solo se ejecutan las herramientas adjuntas a la petición. La resolución por nombre está desactivada salvo que actives spring.ai.tools.resolution.fallback.enabled, que vuelve ejecutable cualquier herramienta conocida por el resolver en cuanto el modelo la nombre.
ToolContext: los datos que el modelo no debe controlar
ToolContext permite pasar a una herramienta datos que no son del modelo: tenant, usuario autenticado, ámbito de la petición o identificadores de traza. El punto que cambia el diseño: esos datos no se envían al modelo, llegan a la herramienta en el momento de la invocación.
Es la diferencia entre un agente multi-tenant seguro y uno que confía en que el modelo rellene bien el tenant.
@Tool(description = "Obtiene un pedido del cliente autenticado")
Order getOrder(
@ToolParam(description = "Identificador interno del pedido") Long orderId,
ToolContext toolContext) {
String tenantId = (String) toolContext.getContext().get("tenantId");
return orderService.getOrder(tenantId, orderId);
}chatClient.prompt()
.user(pregunta)
.tools(orderTools)
.toolContext(Map.of(
"tenantId", sesion.tenantId(),
"userId", sesion.userId()))
.call()
.content();Seguridad: el LLM no es tu capa de autorización
El modelo decide qué quiere solicitar; qué está permitido hacer lo decide el backend. Confundirlo es el fallo de diseño más caro de esta parte del stack.
El método anotado con @Tool se comporta como cualquier endpoint expuesto: permisos, validación, reglas de dominio, aislamiento por tenant y rastro. Una instrucción del tipo «llama a esta herramienta solo si el usuario tiene permiso» ayuda al modelo a elegir, pero no es un control de acceso.
@Tool(description = "Cancela un pedido que todavía admite cancelación")
CancellationResult cancelOrder(
@ToolParam(description = "Identificador interno del pedido") Long orderId,
ToolContext toolContext) {
String tenantId = (String) toolContext.getContext().get("tenantId");
String userId = (String) toolContext.getContext().get("userId");
if (!authorizationService.canCancel(userId, tenantId, orderId)) {
return CancellationResult.denied("NOT_ALLOWED",
"El usuario no tiene permiso para cancelar este pedido");
}
Order order = orderService.get(tenantId, orderId);
if (!order.isCancellable()) {
return CancellationResult.denied("ORDER_NOT_CANCELLABLE",
"El pedido ya ha salido del almacén");
}
return orderService.cancel(order, userId);
}La herramienta no implementa la cancelación: la delega en el servicio de dominio, con sus transacciones, su idempotencia y sus tests. Una tool con doscientas líneas de lógica es una tool que nadie puede probar sin un LLM delante.
Cuatro niveles de riesgo en las herramientas
No todas las herramientas necesitan el mismo control. Clasificarlas antes de escribirlas evita discusiones más tarde y ordena las revisiones de seguridad.
- Lectura
consultar un pedido o buscar documentación. Riesgo: exposición de datos. Control: filtrado por tenant y permisos de lectura.
- Escritura reversible
abrir un ticket o guardar un borrador. Riesgo: ruido operativo. Control: validación y trazabilidad.
- Acción sensible
cancelar una operación, enviar una comunicación, cambiar permisos. Riesgo: efectos visibles fuera. Control: autorización estricta y aprobación cuando proceda.
- Acción crítica
pagos, borrados, cambios en infraestructura. Riesgo: irreversible. Control: aprobación humana explícita, idempotencia y auditoría completa.
Que el modelo solicite una herramienta destructiva no significa que deba ejecutarse de inmediato. Spring AI 2.0 permite desactivar el auto-registro del advisor en una petición, con AdvisorParams.toolCallingAdvisorAutoRegister(false), y conducir el bucle a mano con ToolCallingManager: detectas las tool calls, decides si se ejecutan —aprobación humana, cola o regla de negocio— y devuelves el resultado al modelo. Para casos menos radicales suele bastar con un advisor propio dentro del bucle, que conserva observabilidad y composición.
returnDirect: cuándo evitar otra llamada al modelo
Por defecto el resultado de una herramienta vuelve al modelo para que redacte la respuesta final. Con returnDirect = true se devuelve directamente a quien hizo la llamada: útil cuando la salida ya es la respuesta y reformularla solo añade latencia y tokens.
Un matiz antes de usarlo: si el modelo solicita varias herramientas en la misma ronda, returnDirect solo se respeta si todas lo tienen activado; si no, los resultados vuelven al modelo.
Errores en las herramientas: el happy path no basta
Cuando una herramienta lanza una excepción, Spring AI la envuelve en ToolExecutionException y se la pasa al ToolExecutionExceptionProcessor: por defecto devuelve al modelo el mensaje de las RuntimeException y relanza las comprobadas y los Error. Con spring.ai.tools.throw-exception-on-error en true, todos se lanzan para que los gestione el caller.
El riesgo es evidente: ese mensaje acaba en el contexto del modelo. Nada de stack traces, SQL ni rutas internas. Para los fallos previsibles —pedido inexistente, operación no permitida— suele ser mejor un resultado explícito que una excepción.
record CancellationResult(boolean success, String code, String message) {
static CancellationResult denied(String code, String message) {
return new CancellationResult(false, code, message);
}
}
// Lo que ve el modelo:
// {"success": false, "code": "ORDER_NOT_CANCELLABLE",
// "message": "El pedido ya ha salido del almacén"}Con un código estable y un mensaje apto para el usuario, el modelo explica lo ocurrido sin que la aplicación filtre nada de su interior.
Límites: evitar bucles y abuso de herramientas
Spring AI 2.0 pone límites por defecto donde antes no había ninguno. DefaultToolCallingManager cuenta las llamadas del turno actual —solo desde el último mensaje del usuario— y corta en 40 por herramienta y 150 en total. Al superarse lanza ToolCallLimitExceededException, que el advisor convierte en la respuesta final; con RETURN_ERROR_RESPONSE se informa al modelo y la conversación continúa.
Son la diferencia entre un fallo acotado y una factura sorpresa: más tool calls significan más llamadas externas, más latencia, más tokens, más coste y más superficie de fallo. Al estimar cuánto cuesta la IA en producción, el bucle de herramientas es la partida que más se subestima; los límites de tool calls son el freno que trae la propia librería.
| Propiedad | Qué controla | Por defecto |
|---|---|---|
| spring.ai.tools.limits.max-calls-per-tool-default | Llamadas máximas a una herramienta por turno | 40 |
| spring.ai.tools.limits.max-calls-per-tool.<nombre> | Límite específico para una herramienta | — |
| spring.ai.tools.limits.max-total-tool-calls | Llamadas totales por turno | 150 |
| spring.ai.tools.limits.on-limit-exceeded | THROW o RETURN_ERROR_RESPONSE | THROW |
Observabilidad: qué herramientas usa el agente y con qué resultado
Spring AI emite observaciones de Micrometer por cada llamada bajo spring.ai.tool: nombre y tipo de la herramienta, duración, identificador de la tool call y contexto de trazas cuando hay un Tracer.
Los argumentos y el resultado no se exportan por defecto, porque pueden contener datos sensibles; se activan con spring.ai.tools.observations.include-content, y conviene decidir antes qué se registra y con qué retención.
Sin esas señales no sabes qué herramientas se usan, cuáles fallan ni cuántas vueltas da el bucle. Es el criterio de observabilidad de agentes de IA en producción.
¿Funciona Tool Calling con streaming?
Sí. El bucle funciona con .call() y con .stream(): ToolCallingAdvisor implementa ambos caminos. Streaming no significa que cada paso intermedio llegue al usuario: el advisor filtra los fragmentos de las tool calls, así que se emite la respuesta del modelo mientras la orquestación ocurre por debajo.
Para emitir progreso a una interfaz, la vía recomendada es un advisor propio dentro del bucle. Es una diferencia con Structured Output, donde entity() solo existe en el camino bloqueante.
Qué ocurre cuando tienes cincuenta herramientas
El advisor por defecto envía todas las definiciones en cada petición. Con cinco herramientas da igual; con treinta o más —o varios servidores MCP agregando catálogos— aparecen tres problemas: contexto inflado, peor selección y coste por token en cada llamada.
Para eso existe ToolSearchToolCallingAdvisor, que implementa divulgación progresiva: indexa el catálogo por sesión, envía solo una herramienta de búsqueda y añade las definiciones que el modelo descubre en lenguaje natural. Se activa con spring.ai.chat.client.tool-search-advisor.enabled.
- Catálogo completo
decenas o cientos de herramientas indexadas por sesión
- Primera petición
el modelo solo ve la herramienta de búsqueda
- Descubrimiento
el modelo consulta en lenguaje natural qué necesita
- Ejecución
solo las herramientas encontradas viajan en las siguientes peticiones
La documentación sitúa el punto de interés a partir de diez herramientas o cuando las definiciones superan los 10K tokens por petición, y cita reducciones del 34–64 % en sus propios benchmarks con OpenAI, Anthropic y Gemini. Son sus mediciones, no una garantía universal.
Tool Calling y Structured Output no resuelven el mismo problema
Los dos usan JSON Schema y por eso se confunden. Structured Output responde a «cómo convierto la salida del modelo en un contrato Java»; Tool Calling, a «cómo permito que el modelo solicite información o acciones».
La documentación deja clara la frontera: el StructuredOutputConverter no se usa en tool calling, porque esa función ya produce argumentos estructurados. En una aplicación real conviven.
| Dimensión | Structured Output | Tool Calling |
|---|---|---|
| Objetivo | Dar forma a la respuesta | Solicitar información o una acción |
| El schema describe | La salida del modelo | Los argumentos de la herramienta |
| Resultado | Un objeto Java | La ejecución de un método Java |
| Papel del modelo | Genera datos | Elige herramienta y argumentos |
| Papel del backend | Consume el resultado | Autoriza y ejecuta la acción |
La otra mitad, en Structured Output con Spring AI.
¿Tool Calling convierte un chatbot en un agente?
No. Tool Calling es un mecanismo: permite invocar herramientas y recibir su resultado. Un agente es un patrón de ejecución orientado a objetivos, en el que el modelo decide iterativamente qué pasos dar, normalmente con memoria, planificación y criterios de parada.
Las herramientas aportan capacidad de actuar; el comportamiento agéntico aparece cuando esa capacidad se organiza con memoria, control de flujo y evaluación. La arquitectura completa la desarrollamos en Spring AI, RAG y agentes, y cómo comprobar que funciona, en evals para agentes de IA.
| Tool Calling | Agente |
|---|---|
| Mecanismo para invocar herramientas | Patrón de ejecución orientado a objetivos |
| Puede resolverse en una sola llamada | Encadena varios pasos y decisiones |
| No implica autonomía | Implica cierto grado de autonomía |
| Es una capacidad | Es una arquitectura y un comportamiento |
Tool Calling y MCP: mecanismo frente a protocolo
Tool Calling es el mecanismo por el que un modelo solicita usar una capacidad y aporta los argumentos para ejecutarla. MCP es un protocolo para descubrir, exponer y consumir capacidades entre aplicaciones y servidores compatibles. No se diferencian por dónde vive el sistema al que se llama: una herramienta local de Spring AI puede consultar perfectamente un ERP, un CRM o una API remota.
Detalle de diseño: los proveedores MCP no se registran solos en el ChatClient —listar herramientas al arrancar obligaría a una llamada de red a cada servidor—, se inyectan y se pasan de forma explícita.
| Tool Calling | MCP |
|---|---|
| Mecanismo de invocación | Protocolo de integración |
| Define cómo el modelo solicita una herramienta | Define cómo se exponen y descubren capacidades entre sistemas |
| Usa herramientas implementadas en la aplicación | Permite consumir capacidades publicadas por servidores MCP |
| No establece un protocolo de interoperabilidad | Estandariza esa interoperabilidad |
La duda previa —recuperar conocimiento o ejecutar acciones— la tratamos en MCP vs RAG.
Caso práctico: un asistente de pedidos con herramientas de distinto riesgo
El asistente responde preguntas sobre pedidos y a veces cancela. Cuatro herramientas: getOrderStatus y getCustomerOrders leen; prepareCancellation comprueba si el pedido es cancelable; cancelOrder ejecuta la cancelación.
La decisión de arquitectura no está en el prompt, sino en qué herramientas recibe cada conversación: las de lectura viajan siempre; la cancelación, solo cuando el flujo lo requiere y el usuario tiene permiso.
@Service
class OrderAssistant {
private final ChatClient chatClient;
private final OrderReadTools readTools; // getOrderStatus, getCustomerOrders
private final OrderCancellationTools cancellationTools; // prepareCancellation, cancelOrder
OrderAssistant(ChatClient.Builder builder,
OrderReadTools readTools,
OrderCancellationTools cancellationTools) {
this.chatClient = builder
.defaultSystem("""
Eres el asistente de pedidos de una tienda B2B.
Consulta siempre el estado real antes de responder.
No prometas cancelaciones: propón y confirma con la herramienta.
""")
.defaultTools(readTools) // lectura: siempre disponible
.build();
this.readTools = readTools;
this.cancellationTools = cancellationTools;
}
String responder(Sesion sesion, String mensaje) {
var peticion = chatClient.prompt()
.user(mensaje)
.toolContext(Map.of(
"tenantId", sesion.tenantId(),
"userId", sesion.userId()));
// La capacidad de cancelar se habilita por petición, no como default
if (sesion.puedeCancelar()) {
peticion = peticion.tools(cancellationTools);
}
return peticion.call().content();
}
}- Usuario
«cancela el pedido 48392»
- LLM
solicita prepareCancellation
- Java
comprueba permisos y reglas y devuelve el resultado
- LLM
confirma con el usuario y solicita cancelOrder
- Java
ejecuta la cancelación con idempotencia y auditoría
Arquitectura recomendada: una frontera de ejecución
Tool Calling es una frontera entre un componente probabilístico que decide qué quiere hacer y un código determinista que decide qué puede hacerse. Lo que la cruza es siempre lo mismo: un nombre de herramienta y unos argumentos que cumplen un schema.
Por debajo no cambia nada: autenticación, autorización, validación, reglas de dominio, idempotencia, auditoría y límites. Lo que cambia es que quien propone la acción es un modelo, y eso obliga a que esos controles sean explícitos en el método.
Si la empresa ya trabaja con Java y Spring Boot, esa frontera encaja en la arquitectura existente sin montar un stack paralelo. Es el enfoque con el que trabajamos en desarrollo de soluciones con Spring AI.

Errores habituales al poner herramientas en producción
Casi ninguno es un problema de la API: son decisiones de diseño que se pagan después.
- Dar todas las herramientas siempre: más contexto, peor selección y capacidades activas sin motivo.
- Descripciones pobres: el modelo elige por nombre y descripción; si son ambiguos, elige mal.
- Herramientas genéricas: un executeAction(String accion, Map args) devuelve el problema al prompt.
- String para todo: los enums y tipos propios hacen detectable el error del modelo.
- Exponer permisos como argumento: cancelOrder(Long id, boolean isAdmin) hace del modelo la capa de autorización.
- No distinguir lectura de escritura: un lookup y un borrado no merecen los mismos controles.
- Herramientas destructivas en los defaults: la comodidad de configuración se vuelve capacidad permanente.
- Devolver excepciones internas al modelo: stack traces y SQL acaban en el contexto.
- No observar nada: sin métricas por herramienta, el agente es opaco.
Cuándo usar Tool Calling y cuándo no
Regla práctica: si hace falta un dato que solo existe en tus sistemas o hay que ejecutar algo, Tool Calling encaja; si el modelo ya tiene el material en el contexto, sobra.
| Caso | ¿Tool Calling? | Motivo |
|---|---|---|
| Consultar datos actuales de un sistema propio | Sí | El modelo no tiene ese dato y lo inventaría |
| Operar sobre CRM, ERP o un workflow | Sí, con control | Hay efectos reales: permisos, validación y auditoría |
| Clasificar o extraer campos de un texto | No suele hacer falta | Structured Output resuelve el contrato de salida |
| Responder con documentación propia | No | RAG cubre el caso con menos superficie de riesgo |
| Ejecutar una acción crítica sin supervisión | No directamente | Necesita aprobación explícita y auditoría |
Conclusión
Tool Calling no consiste en dejar que un LLM ejecute código: consiste en ofrecerle un conjunto explícito de capacidades cuyo contrato, ejecución y seguridad siguen perteneciendo a la aplicación. Spring AI 2.0 pone el bucle en un advisor, fija límites por defecto y deja a mano lo necesario para producción: ToolContext, control manual del bucle y observabilidad.
El modelo puede decidir qué herramienta quiere utilizar; el backend sigue decidiendo qué está permitido hacer. Esa frase resume casi todas las decisiones de diseño de este artículo.
Structured Output hace que Java reciba una salida estructurada y tipada bajo un contrato verificable —que la forma sea correcta no significa que el contenido sea cierto—; Tool Calling le da al modelo una vía controlada para pedir información y solicitar acciones. Son las dos piezas básicas de una aplicación Java con IA que hace algo más que conversar.
Fuentes técnicas
Preguntas frecuentes
¿Qué es Tool Calling en Spring AI?
Es el mecanismo para exponer funciones de una aplicación Java a un modelo mediante un nombre, una descripción y un JSON Schema de argumentos. El modelo puede solicitar la ejecución de una herramienta; Spring AI y la aplicación son quienes la resuelven y la ejecutan.
¿El LLM ejecuta directamente el método Java?
No. El modelo devuelve el nombre de la herramienta y los argumentos que propone; ToolCallingAdvisor y ToolCallingManager localizan el ToolCallback y ejecutan el método dentro de la aplicación. El modelo nunca recibe acceso a la base de datos ni a las APIs.
¿Cómo funciona @Tool en Spring AI?
Anotas un método con @Tool y sus parámetros con @ToolParam. Spring AI genera la definición —nombre, descripción y JSON Schema— y la envía al modelo con el prompt. La anotación admite name, description, returnDirect y resultConverter; por defecto todos los parámetros son obligatorios.
¿Qué diferencia hay entre @Tool y ToolCallback?
ToolCallback es la abstracción que modela una herramienta: la definición que ve el modelo y la lógica que se ejecuta. @Tool es la vía declarativa para producir uno desde un método; MethodToolCallback y FunctionToolCallback son las programáticas. Las tres circulan por el mismo bucle.
¿Qué diferencia hay entre Tool Calling y Structured Output?
Structured Output da forma a la respuesta final y la convierte en un objeto Java. En Tool Calling el schema describe los argumentos de la herramienta que el modelo quiere ejecutar, no la salida. La documentación aclara que el StructuredOutputConverter no se usa en tool calling.
¿Tool Calling convierte un chatbot en un agente?
No necesariamente. Tool Calling es la capacidad de invocar herramientas; el comportamiento agéntico aparece cuando el modelo encadena pasos hacia un objetivo, con memoria, criterios de parada y control de flujo.
¿Qué diferencia hay entre Tool Calling y MCP?
Tool Calling define cómo el modelo usa herramientas dentro de una aplicación; MCP es un protocolo para exponerlas y consumirlas entre sistemas. Spring AI puede consumir herramientas de servidores MCP y exponer las suyas, y el bucle que las ejecuta es el mismo.
¿Qué es ToolContext y para qué sirve?
Es el mecanismo para pasar a una herramienta datos que no debe controlar el modelo: tenant, usuario autenticado, ámbito de la petición o identificadores de traza. No se envían al modelo: llegan al método en el momento de la invocación, así que la identidad no depende de un argumento que el LLM pueda inventar.
¿Funciona Tool Calling con streaming en Spring AI?
Sí. El bucle funciona con .call() y con .stream(), porque ToolCallingAdvisor conduce ambos modos. Streaming no significa que cada resultado intermedio llegue al usuario: se emite la respuesta del modelo mientras la orquestación sigue por debajo.
¿Cómo se limita el número de llamadas a herramientas?
Spring AI 2.0 aplica límites por turno: 40 llamadas por herramienta y 150 en total. Se ajustan con las propiedades spring.ai.tools.limits.* o con el builder de ToolCallingManager, y al superarlos puede lanzarse una excepción o devolverse un error al modelo para que continúe.
Sigue leyendo
Ver todos los artículos- Leer artículo
Desarrollo con IA · JavaDesarrollo con IA12 minStructured Output con Spring AI: de texto a objetos Java fiables
- Leer artículo
Desarrollo con IA · JavaDesarrollo con IA18 minSpring AI vs LangChain4j: qué framework elegir para IA en Java
- Leer artículo
Spring AI · RAG · AgentesDesarrollo con IA13 minSpring AI, RAG y agentes: cómo construir aplicaciones Java con inteligencia artificial real
