Tool Calling y salidas estructuradas: el contrato que aguanta
Veinticuatro llamadas, cero JSON roto y dos fechas útiles. Luego el mismo endpoint con una mejor descripción, y lo que un esquema no arregla.
En esta página
Dale a un modelo una herramienta de búsqueda de vuelos y pídele que encuentre un vuelo de Madrid a Berlín. Esto es lo que devuelve:
<tool_call>
{"name": "search_flights",
"arguments": {"from": "Madrid", "to": "Berlin", "date": "3rd October 2026"}}
</tool_call>El JSON es válido. El nombre de la herramienta es correcto. Todos los campos obligatorios están presentes. Y la llamada no sirve: ninguna API de vuelos acepta "Madrid" cuando espera un código de aeropuerto, ni "3rd October 2026" cuando espera una fecha.
Esa brecha —sintácticamente perfecta, semánticamente inútil— es de lo que trata este capítulo, y lo primero que hay que dejar claro es que no es un problema de JSON. En veinticuatro solicitudes con esta herramienta, el modelo produjo 24 llamadas a herramientas válidas y cero JSON roto. No falló ni una sola vez en la parte que todo el mundo depura.
El modelo no ejecuta nada
Enlace a la sección: El modelo no ejecuta nadaAntes de la mecánica, la frase que evita la mayoría de las confusiones: una llamada a herramienta es una solicitud, no una acción.
El modelo emite un mensaje estructurado que dice me gustaría que se llamara a search_flights con estos argumentos. Luego se detiene. Tu código recibe ese mensaje, decide si lo atiende, llama a lo que tenga que llamar y devuelve el resultado como otro mensaje. El modelo nunca ha tocado tu base de datos, nunca ha hecho una solicitud HTTP, nunca ha tenido credenciales.
Todo lo relativo a la seguridad de agent en el capítulo 30 se deriva de esa división, y también todo lo relativo al diseño de agent en el capítulo 23: el modelo propone y tu código dispone, y el código es donde vive cada garantía.
Así que una herramienta, despojada de vocabulario, son dos cosas:
Un esquema. Un JSON Schema que describe una función: su nombre, qué hace y qué argumentos acepta, con sus tipos y restricciones. Esto es lo que entra en el prompt, y es lo único que el modelo ve jamás.
Un endpoint. Una función en tu código que toma esos argumentos y devuelve algo. El modelo nunca la ve, nunca sabe en qué lenguaje está escrita y no puede distinguir una consulta a base de datos de una cadena hardcodeada.
Envías los esquemas con la solicitud
Enlace a la sección: Envías los esquemas con la solicitudLas definiciones de herramientas van en el prompt, serializadas en el formato para el que se entrenó el modelo. Cuestan tokens en cada llamada, un hecho que vuelve con un número más adelante en este capítulo.
El modelo responde con una llamada en vez de texto
Enlace a la sección: El modelo responde con una llamada en vez de textoEn lugar de prosa, la respuesta contiene una solicitud estructurada, y la API informa de un motivo de finalización que lo indica. Ese motivo importa: es como tu código sabe que debe ejecutar una herramienta en lugar de mostrarle una respuesta al usuario.
Tu código la ejecuta — o se niega
Enlace a la sección: Tu código la ejecuta — o se niegaEste es el paso en el que no hay modelo. Valida los argumentos contra el esquema, decide si a quien llama se le permite hacer esto y ejecuta.
Envías el resultado de vuelta como un mensaje
Enlace a la sección: Envías el resultado de vuelta como un mensajeEl resultado se convierte en otro turno de la conversación, en un rol reservado para ello. El modelo lo lee como cualquier otro context.
El modelo responde, o pide otra herramienta
Enlace a la sección: El modelo responde, o pide otra herramientaEse es el bucle del capítulo 23, y la razón por la que una sola solicitud puede convertirse en una docena de idas y vueltas.
Nada de esto es emergente. Como estableció el capítulo 11, tool calling es un comportamiento entrenado:1 durante el post-training, el modelo vio miles de conversaciones con exactamente esta forma. Por eso el formato es específico de cada modelo, por eso la fiabilidad varía tanto entre modelos de tamaño similar, y por eso un modelo puede llamar a una herramienta que nunca ha visto: la forma se entrenó; la herramienta concreta viene de tu prompt.
Lo que cuesta un mal esquema, medido
Enlace a la sección: Lo que cuesta un mal esquema, medidoEsta es la herramienta tal como la escribe casi todo el mundo al principio. Fíjate en que no hay nada incorrecto en ella; simplemente es escueta:
{
name: "search_flights",
description: "Search for flights.",
parameters: {
type: "object",
properties: {
from: { type: "string", description: "Airport." },
to: { type: "string", description: "Airport." },
date: { type: "string", description: "The date." },
},
required: ["from", "to", "date"],
},
}Veinticuatro solicitudes, seis pares de ciudades cruzados con cuatro formas de expresar una fecha ("el día 3 del mes que viene", "el próximo viernes", "15 de diciembre", "mañana"), greedy decoding para que los resultados se reproduzcan:
| herramienta llamada | JSON roto | fecha en ISO | aeropuertos como IATA | todo correcto | |
|---|---|---|---|---|---|
| el esquema anterior | 24/24 | 0 | 2/24 | 4/24 | 1/24 |
Lee las dos primeras columnas antes de las tres últimas. El modelo llama siempre a la herramienta correcta y produce siempre JSON bien formado. El fallo está por completo en los valores, y los valores no sirven: "Madrid" en vez de MAD, "3rd October 2026" en vez de 2026-10-03.
Conviene insistir en esto porque determina dónde miras cuando algo se rompe. El instinto es añadir un parser de JSON con reintento, o pedirle al modelo con más firmeza que genere JSON válido. Ninguna de las dos cosas aborda nada de lo que ha pasado aquí.
Ahora cambia solo la descripción
Enlace a la sección: Ahora cambia solo la descripciónMismo endpoint. Mismo código detrás. Mismo modelo, mismos prompts, mismo decoding. Lo único que cambia es el texto del esquema:
{
name: "search_flights",
description: "Search scheduled flights between two airports on a given day.",
parameters: {
type: "object",
properties: {
from: {
type: "string",
description: "Departure airport as a three-letter IATA code, e.g. MAD for Madrid. Never a city name.",
pattern: "^[A-Z]{3}$",
},
to: { /* same */ },
date: {
type: "string",
description: "Departure date as an ISO 8601 calendar date, YYYY-MM-DD. Resolve relative dates against today before calling.",
format: "date",
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
},
},
required: ["from", "to", "date"],
},
}| FORMATO de fecha | VALOR de fecha | FORMATO de aeropuerto | VALOR de aeropuerto | |
|---|---|---|---|---|
| esquema escueto | 2/24 | 1/24 | 4/24 | 4/24 |
| esquema descrito | 24/24 | 12/24 | 16/24 | 8/24 |
El formato de fecha pasa de 2 de 24 a 24 de 24. Perfecto, a partir de un cambio de texto, sin tocar código y sin lógica de reintento. Si te llevas un hábito operativo de este capítulo, que sea este: cuando una herramienta se llama mal, la solución casi siempre está en la descripción, y es la solución más barata del sistema.
Ahora lee la segunda columna, que es la mitad más importante.
Un esquema restringe la forma. No puede aportar conocimiento.
Enlace a la sección: Un esquema restringe la forma. No puede aportar conocimiento.La fecha está en formato ISO 24 veces de 24. Es el día correcto 12 veces de 24.
Así que la mitad de las llamadas llevan ahora una fecha perfectamente formateada que es la fecha equivocada. La descripción le dijo al modelo qué forma producir, y el modelo la produjo impecablemente, pero convertir "el próximo viernes" en 2026-09-11 exige saber la fecha de hoy y hacer aritmética de calendario, y ninguna cantidad de descripción aporta eso. Lo mismo ocurre con los aeropuertos: el formato pasó de 4 a 16, pero el valor solo de 4 a 8, porque escribir MAD exige saber que el aeropuerto de Madrid es MAD.
Esa distinción es la idea que sostiene el capítulo:
Un esquema es un contrato sobre la forma. Puede hacer que la salida del modelo sea parseable, tipada y coherente. No puede hacer que sea verdadera, y todo modo de fallo que sobreviva a un buen esquema es un fallo de conocimiento, no un fallo de formato.
Las dos cosas necesitan soluciones distintas, y confundirlas hace perder semanas. Los fallos de formato se arreglan en la descripción o con constrained decoding, más abajo. Los fallos de conocimiento se arreglan metiendo el conocimiento en el prompt: la fecha actual en el mensaje de sistema, una búsqueda de aeropuerto como una segunda herramienta que el modelo llama primero, un enum en el esquema cuando el conjunto es lo bastante pequeño como para enumerarlo. Fíjate en lo que tienen en común las tres opciones: sacan el problema de la memoria del modelo y lo ponen en su entrada, que es todo el capítulo 24.
Salidas estructuradas, y qué es realmente "constrained decoding"
Enlace a la sección: Salidas estructuradas, y qué es realmente "constrained decoding"Todo lo anterior sigue dependiendo de que el modelo elija producir la forma correcta. Hay una garantía más fuerte disponible, y es la mejor recompensa del capítulo 17.
Recuerda cómo funciona la generación: en cada paso el modelo produce un logit para cada token del vocabulario, y el muestreador elige uno. Constrained decoding inserta un paso entre medias. Dada una gramática —derivada de tu JSON Schema— calcula qué tokens podrían venir legalmente a continuación, pone los logits de todos los demás a infinito negativo y deja que el muestreador elija entre lo que queda.
Si el esquema dice que lo siguiente debe ser un {, entonces todo token que no sea { tiene probabilidad cero. No "improbable": cero. El modelo no puede emitir JSON inválido porque los tokens inválidos se eliminaron de la distribución antes del muestreo.
Eso es lo que hay debajo de "salidas estructuradas", "modo JSON" y "generación guiada", y explica sus dos propiedades. La garantía es total para cualquier cosa que la gramática pueda expresar —tipos, campos obligatorios, enums, anidamiento— porque se aplica mecánicamente en lugar de pedirse con educación. Y no dice nada sobre el contenido: una gramática puede obligar a que "date" sea una cadena que coincide con un patrón de fecha, y no puede obligar a que sea el día correcto. Es el mismo muro que en la sección anterior, alcanzado desde el otro lado.
Dos notas prácticas. No es gratis: la máscara tiene que calcularse en cada paso, y las gramáticas complejas cuestan una latencia medible. Y cambia lo que está haciendo el modelo: un modelo apartado de su token preferido puede producir peor contenido mientras produce una estructura perfecta, por eso "pedirlo bien y validar" sigue siendo un valor por defecto razonable para formas simples, y constrained decoding se gana su coste cuando la forma es compleja o el consumidor es estricto.
Efectos secundarios, y la única propiedad que importa
Enlace a la sección: Efectos secundarios, y la única propiedad que importaEl capítulo 14 midió un timeout seguido de un reintento que facturaba dos generaciones por una respuesta. Con herramientas, el mismo fallo empeora, porque una herramienta puede hacer algo.
Si tu código llama a charge_card, hace timeout y reintenta, tienes dos cargos. El modelo no tiene ni idea de que nada de esto ha pasado; ve un resultado de herramienta. La solución es la misma que en cualquier sistema distribuido y no es problema del modelo: hacer que la operación sea idempotente dándole una clave a la llamada, para que la segunda ejecución reconozca la primera y devuelva su resultado en lugar de volver a hacer el trabajo.
La regla de diseño que se deriva de esto merece decirse claramente. Separa las lecturas de las escrituras en tu catálogo de herramientas. Una lectura puede reintentarse libremente, ejecutarse en paralelo y cachearse. Una escritura no, y debería llevar una clave, una comprobación de permisos y —para cualquier cosa de la que un usuario querría enterarse antes de que ocurra— un paso de aprobación que ponga a una persona entre la solicitud y la acción. Ese paso de aprobación no es una cortesía: es una de las pocas cosas que se interponen entre un prompt injection y una consecuencia real y, como mide el capítulo 30, la más débil de ellas.
¿Cuántas herramientas antes de que degrade?
Enlace a la sección: ¿Cuántas herramientas antes de que degrade?El folclore dice que cargar muchas herramientas hace que el modelo elija mal. Merece la pena medirlo en lugar de repetirlo, así que: las mismas veinticuatro solicitudes, con la herramienta de vuelos más un conjunto creciente de otras, incluidas tres deliberadamente confundibles (horarios de tren, travesías en ferry, rutas de autobús).
| herramientas cargadas | prompt tokens | eligió search_flights | fecha en ISO |
|---|---|---|---|
| 1 | 353 | 24/24 | 24/24 |
| 5 | 730 | 24/24 | 24/24 |
| 10 | 1,193 | 21/24 | 21/24 |
| 20 | 2,119 | 24/24 | 24/24 |
La selección no degradó. Con veinte herramientas, tres de ellas plausiblemente confundibles, un modelo de quinientos millones de parámetros eligió la correcta veinticuatro veces de veinticuatro. La bajada en diez son tres llamadas que nombraron otra herramienta, y no sobrevive al pasar a veinte.
Es un resultado negativo y debería contarse como tal: en esta tarea, con estas herramientas, "demasiadas herramientas" no fue el problema. Lo que sí creció, de forma monótona y por un factor de seis, es el prompt: de 353 tokens a 2,119, pagados en cada solicitud de la conversación, para siempre, se use o no alguna herramienta.
Así que la versión honesta del folclore trata de coste y context, no de precisión. Veinte herramientas son un impuesto permanente sobre cada mensaje, y el capítulo 16 ya mostró lo que hace un prefijo permanente a una factura a lo largo de cuarenta turnos. Cuando la gente informa de que muchas herramientas perjudican la calidad, el mecanismo suele ser que las definiciones desplazaron el context que importaba, que es un problema del capítulo 24 disfrazado de capítulo 18. Las herramientas que son casi duplicados reales entre sí también son un problema, y la solución para ellas no es menos herramientas, sino mejores descripciones y namespaces: prefíjalas por sistema (crm.search_customer, billing.search_customer) para que dos catálogos fusionados de dos equipos no colisionen y para que el modelo tenga algo con lo que discriminar.
Tres tipos de herramienta, y la que abre la siguiente parte
Enlace a la sección: Tres tipos de herramienta, y la que abre la siguiente parteAyuda ordenar las herramientas por lo que hacen al mundo, porque la ingeniería difiere en cada caso.
Herramientas de datos leen: buscan, recuperan, consultan. Reintentables, paralelizables, cacheables. Fallan devolviendo nada útil, y su principal riesgo es que traen texto no fiable al context, que es toda la superficie de ataque del capítulo 30.
Herramientas de acción escriben: envían, crean, cobran, eliminan. No son reintentables sin una clave, no son paralelizables de forma segura, y son la razón de que existan los flujos de aprobación.
Herramientas de orquestación llaman a otros modelos. Una herramienta cuya implementación es otro agent, con su propio prompt, sus propias herramientas y su propio bucle; y para el modelo que la llama tiene exactamente el mismo aspecto que las otras dos, porque un esquema y un endpoint es todo lo que llega a ver.
Ese tercer tipo no es una curiosidad. Es el mecanismo detrás de la mitad de agent-como-herramienta del capítulo 25: la otra topología, el handoff, entrega la conversación y nunca la recupera. Y funciona precisamente porque la interfaz de este capítulo es lo bastante estrecha como para que quepa un agent entero detrás.
Adónde va esto ahora
Enlace a la sección: Adónde va esto ahoraAhora tienes un modelo que puede pedir cosas, y un contrato que hace que la petición sea parseable. Lo que no tienes es nada sobre lo que pueda preguntar más allá de lo que cabe en su prompt.
La herramienta más común en producción, con mucha diferencia, es una búsqueda sobre un cuerpo de texto que el modelo nunca vio durante el entrenamiento: tu documentación, tus tickets, tus contratos. Suena a problema resuelto —embed it, encuentra los vecinos más cercanos, pégalos dentro— y las partes que no están resueltas son las que deciden si la respuesta es fiable: cómo se corta el texto antes de hacer embedding, qué umbral de similitud es lo bastante bajo como para significar no lo sé, y cómo se adjunta una cita a una afirmación para que una persona pueda comprobarla.
El capítulo 19 es retrieval, y es el capítulo en el que una respuesta incorrecta deja de ser una curiosidad y empieza a ser una responsabilidad.
Fuentes y método
Enlace a la sección: Fuentes y métodoLas mediciones de este capítulo proceden de Qwen/Qwen2.5-0.5B-Instruct con greedy decoding, sobre 24 solicitudes generadas que cruzan seis pares de ciudades con cuatro formulaciones de fecha, usando la propia plantilla de chat del modelo para las definiciones de herramientas. Se reproducen exactamente, y son un modelo pequeño: lee la división formato/valor como una demostración del mecanismo, no como un benchmark de lo que hacen los modelos actuales. Un modelo frontier resuelve "el próximo viernes" correctamente con mucha más frecuencia, y aun así un esquema no puede obligarle a hacerlo, que es la parte que generaliza.
El vocabulario de JSON Schema usado arriba (type, properties, required, pattern, format, enum) está especificado en el borrador de JSON Schema que nombre la documentación de tu proveedor; el subconjunto útil es pequeño y el mismo entre proveedores, y las diferencias que sí existen —qué keywords se hacen cumplir mediante constrained decoding en lugar de simplemente pasarse al modelo— merece la pena leerlas en la guía de salidas estructuradas del proveedor en vez de darlas por supuestas.
Para constrained decoding como técnica, las bibliotecas de estilo guidance y el proyecto outlines documentan la construcción de gramática a máscara de logit de una manera que encaja directamente con el muestreador del capítulo 17. Y para la ida y vuelta en sí, la especificación más clara no es un tutorial, sino un protocolo: el capítulo 26 lo lee línea por línea.
Referencias
Enlace a la sección: Referencias-
Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). El paper que convirtió en estándar la receta de post-training; la forma de una llamada a herramienta se aprende ahí, a partir de demostraciones, exactamente igual que la forma de una respuesta. ↩