Tool Calling e saídas estruturadas: o contrato que aguanta
Vinte e catro chamadas, cero JSON roto e dúas datas útiles. Logo o mesmo endpoint cunha mellor descrición e o que un esquema non arranxa.
Nesta páxina
Dálle a un modelo unha ferramenta de busca de voos e pídelle que atope un voo de Madrid a Berlín. Isto é o que devolve:
<tool_call>
{"name": "search_flights",
"arguments": {"from": "Madrid", "to": "Berlin", "date": "3rd October 2026"}}
</tool_call>O JSON é válido. O nome da ferramenta é correcto. Todos os campos obrigatorios están presentes. E a chamada non serve para nada: ningunha API de voos acepta "Madrid" onde espera un código de aeroporto, nin "3rd October 2026" onde espera unha data.
Esa fenda — sintacticamente perfecta, semanticamente inútil — é do que trata este capítulo, e o primeiro que hai que deixar claro é que non é un problema de JSON. En vinte e catro peticións con esta ferramenta, o modelo produciu 24 chamadas válidas a ferramentas e cero JSON roto. Nin unha soa vez fallou na parte que todo o mundo depura.
O modelo non executa nada
Ligazón á sección: O modelo non executa nadaAntes da mecánica, a frase que evita a maior parte da confusión: unha chamada a ferramenta é unha petición, non unha acción.
O modelo emite unha mensaxe estruturada que di quero que se chame a search_flights con estes argumentos. Despois detense. O teu código recibe esa mensaxe, decide se a atende, chama o que teña que chamar e devolve o resultado como outra mensaxe. O modelo nunca tocou a túa base de datos, nunca fixo unha petición HTTP, nunca tivo credenciais.
Todo sobre a seguridade de agent en Capítulo 30 vén desa división, e tamén todo sobre o deseño de agent en Capítulo 23: o modelo propón e o teu código dispón, e no código é onde viven todas as garantías.
Así que unha ferramenta, sen vocabulario de máis, son dúas cousas:
Un esquema. Un JSON Schema que describe unha función: o seu nome, que fai e que argumentos acepta, cos seus tipos e restricións. Isto é o que entra no prompt, e é o único que o modelo chega a ver.
Un endpoint. Unha función no teu código que recibe eses argumentos e devolve algo. O modelo nunca a ve, nunca sabe en que linguaxe está e non pode distinguir unha consulta á base de datos dunha cadea hardcoded.
Envías os esquemas coa petición
Ligazón á sección: Envías os esquemas coa peticiónAs definicións das ferramentas van no prompt, serializadas no formato no que se adestrase o modelo. Custan tokens en cada chamada sen excepción — un feito que volve máis adiante neste capítulo cun número.
O modelo responde cunha chamada en vez de texto
Ligazón á sección: O modelo responde cunha chamada en vez de textoEn lugar de prosa, a resposta contén unha petición estruturada, e a API informa dun motivo de remate que o indica. O motivo importa: é como o teu código sabe que debe executar unha ferramenta en vez de mostrarlle unha resposta ao usuario.
O teu código execútaa — ou négase
Ligazón á sección: O teu código execútaa — ou négaseEste é o paso no que non hai modelo. Valida os argumentos contra o esquema, decide se este chamador ten permiso para facelo e executa.
Devolves o resultado como unha mensaxe
Ligazón á sección: Devolves o resultado como unha mensaxeO resultado convértese noutra quenda da conversa, nun rol reservado para iso. O modelo léao coma calquera outro context.
O modelo responde ou pide outra ferramenta
Ligazón á sección: O modelo responde ou pide outra ferramentaEse é o bucle do Capítulo 23, e a razón pola que unha soa petición pode converterse nunha ducia de idas e voltas.
Nada disto é emerxente. Como estableceu o Capítulo 11, tool calling é un comportamento adestrado:1 durante o post-training o modelo viu milleiros de conversas con exactamente esta forma. Por iso o formato é específico de cada modelo, por iso a fiabilidade varía tanto entre modelos de tamaño parecido e por iso un modelo pode chamar unha ferramenta que nunca vira: a forma estaba adestrada, a ferramenta concreta vén do teu prompt.
O custo dun mal esquema, medido
Ligazón á sección: O custo dun mal esquema, medidoAquí está a ferramenta tal como a escribe ao principio a maioría da xente. Fíxate en que non ten nada mal; simplemente é fina:
{
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"],
},
}Vinte e catro peticións, seis pares de cidades cruzados con catro maneiras de expresar unha data ("o día 3 do mes que vén", "o vindeiro venres", "15 de decembro", "mañá"), greedy decoding para que os resultados se reproduzan:
| ferramenta chamada | JSON roto | data en ISO | aeroportos como IATA | todo correcto | |
|---|---|---|---|---|---|
| o esquema anterior | 24/24 | 0 | 2/24 | 4/24 | 1/24 |
Le as dúas primeiras columnas antes das tres últimas. O modelo chama a ferramenta correcta sempre e produce JSON ben formado sempre. O fallo está enteiramente nos valores, e os valores non se poden usar: "Madrid" no canto de MAD, "3rd October 2026" no canto de 2026-10-03.
Paga a pena insistir nisto porque determina onde miras cando algo rompe. O instinto é engadir un parser de JSON cun reintento, ou pedirlle ao modelo con máis firmeza que produza JSON válido. Ningunha das dúas cousas aborda nada do que ocorreu aquí.
Agora cambia só a descrición
Ligazón á sección: Agora cambia só a descriciónO mesmo endpoint. O mesmo código detrás. O mesmo modelo, os mesmos prompts, a mesma decodificación. O único que cambia é o texto no 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 da data | VALOR da data | FORMATO do aeroporto | VALOR do aeroporto | |
|---|---|---|---|---|
| esquema fino | 2/24 | 1/24 | 4/24 | 4/24 |
| esquema descrito | 24/24 | 12/24 | 16/24 | 8/24 |
O formato da data pasa de 2 de 24 a 24 de 24. Perfecto, cun cambio de texto, sen tocar código e sen lóxica de reintentos. Se levas un hábito operativo deste capítulo, que sexa este: cando unha ferramenta se chama mal, a corrección case sempre está na descrición, e é a corrección máis barata do sistema.
Agora le a segunda columna, que é a metade máis importante.
Un esquema restrinxe a forma. Non pode achegar coñecemento.
Ligazón á sección: Un esquema restrinxe a forma. Non pode achegar coñecemento.A data está en formato ISO 24 veces de 24. É o día correcto 12 veces de 24.
Así que agora a metade das chamadas levan unha data perfectamente formatada que é a data incorrecta. A descrición díxolle ao modelo que forma debía producir, e o modelo produciuna de maneira impecable — pero converter "o vindeiro venres" en 2026-09-11 require saber a data de hoxe e facer aritmética de calendario, e ningunha descrición achega iso. O mesmo cos aeroportos: o formato pasou de 4 a 16, pero o valor só de 4 a 8, porque escribir MAD require saber que o aeroporto de Madrid é MAD.
Esa distinción é a idea que sostén o capítulo:
Un esquema é un contrato sobre a forma. Pode facer que a saída do modelo sexa parseable, tipada e consistente. Non pode facela certa, e todo modo de fallo que sobrevive a un bo esquema é un fallo de coñecemento, non un fallo de formato.
As dúas cousas necesitan arranxos distintos, e confundilas fai perder semanas. Os fallos de formato arránxanse na descrición ou con decodificación restrinxida, máis abaixo. Os fallos de coñecemento arránxanse poñendo o coñecemento no prompt — a data actual na mensaxe de sistema, unha busca de aeroportos como segunda ferramenta que o modelo chama primeiro, un enum no esquema cando o conxunto é pequeno dabondo para enumeralo. Fíxate no que teñen en común as tres opcións: sacan o problema da memoria do modelo e méteno na súa entrada, que é todo o Capítulo 24.
Saídas estruturadas, e que é realmente a "decodificación restrinxida"
Ligazón á sección: Saídas estruturadas, e que é realmente a "decodificación restrinxida"Todo o anterior segue dependendo de que o modelo escolla producir a forma correcta. Hai unha garantía máis forte dispoñible, e é a mellor recompensa do Capítulo 17.
Lembra como funciona a xeración: en cada paso o modelo produce un logit para cada token do vocabulario, e o sampler escolle un. A decodificación restrinxida insire un paso polo medio. Dada unha gramática — derivada do teu JSON Schema — calcula que tokens poderían vir legalmente a continuación, pon os logits de todos os demais en infinito negativo e deixa que o sampler escolla entre o que queda.
Se o esquema di que o seguinte debe ser un {, entón todo token que non sexa { ten probabilidade cero. Non "improbable": cero. O modelo non pode emitir JSON inválido porque os tokens inválidos foron retirados da distribución antes da mostraxe.
Iso é o que hai por baixo de "structured outputs", "JSON mode" e "guided generation", e explica as súas dúas propiedades. A garantía é total para calquera cousa que a gramática poida expresar — tipos, campos obrigatorios, enums, aniñamento — porque se aplica mecanicamente en vez de pedirse con educación. E non di nada sobre o contido: unha gramática pode forzar que "date" sexa unha cadea que encaixa cun patrón de data, e non pode forzar que sexa o día correcto. É a mesma parede da sección anterior, alcanzada desde o outro lado.
Dúas notas prácticas. Non é gratis: a máscara ten que calcularse en cada paso, e as gramáticas complexas custan unha latencia medible. E cambia o que fai o modelo: un modelo apartado do seu token preferido pode producir peor contido mentres produce unha estrutura perfecta, por iso "pedir ben e validar" segue sendo un valor por defecto razoable para formas simples, e a decodificación restrinxida xustifica o seu custo cando a forma é complexa ou o consumidor é estrito.
Efectos secundarios, e a única propiedade que importa
Ligazón á sección: Efectos secundarios, e a única propiedade que importaO Capítulo 14 mediu un timeout seguido dun reintento que facturaba dúas xeracións por unha soa resposta. Con ferramentas o mesmo fallo empeora, porque unha ferramenta pode facer algo.
Se o teu código chama charge_card, dá timeout e reintenta, tes dous cargos. O modelo non ten nin idea de que pasou nada diso; ve un único resultado de ferramenta. A corrección é a mesma ca en calquera sistema distribuído e non é problema do modelo: fai que a operación sexa idempotente dándolle unha clave á chamada, para que a segunda execución recoñeza a primeira e devolva o seu resultado en vez de facer o traballo outra vez.
A regra de deseño que segue paga a pena dicila claramente. Separa as lecturas das escrituras no teu catálogo de ferramentas. Unha lectura pódese reintentar libremente, executar en paralelo e gardar en cache. Unha escritura non, e debe levar unha clave, unha comprobación de permisos e — para calquera cousa que un usuario quixese coñecer antes de que ocorra — un paso de aprobación que poña unha persoa entre a petición e a acción. Ese paso de aprobación non é unha cortesía: é unha das poucas cousas que se interpoñen entre unha prompt injection e unha consecuencia real — e, como mide o Capítulo 30, a máis débil delas.
Cantas ferramentas antes de que empeore?
Ligazón á sección: Cantas ferramentas antes de que empeore?O folclore di que cargar moitas ferramentas fai que o modelo escolla mal. Paga a pena medilo en vez de repetilo, así que: as mesmas vinte e catro peticións, coa ferramenta de voos máis un conxunto crecente doutras — incluídas tres deliberadamente confundibles (horarios de tren, travesías en ferry, rutas de autobús).
| ferramentas cargadas | prompt tokens | escolleu search_flights | data 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 |
A selección non empeorou. Con vinte ferramentas, tres delas plausiblemente confundibles, un modelo de medio billón de parámetros escolleu a correcta vinte e catro veces de vinte e catro. A caída en dez son tres chamadas que nomearon unha ferramenta diferente, e non se mantén ao pasar a vinte.
É un resultado negativo e debe comunicarse como tal: nesta tarefa, con estas ferramentas, "demasiadas ferramentas" non foi o problema. O que si medrou, de maneira monótona e por un factor de seis, é o prompt: de 353 tokens a 2,119, pagados en cada petición da conversa, para sempre, se se usa algunha ferramenta ou non.
Así que a versión honesta do folclore fala de custo e context, non de precisión. Vinte ferramentas son un imposto permanente en cada mensaxe, e o Capítulo 16 xa mostrou que fai un prefixo permanente nunha factura ao longo de corenta quendas. Cando a xente conta que moitas ferramentas prexudican a calidade, o mecanismo adoita ser que as definicións expulsaron o context que importaba — que é un problema do Capítulo 24 disfrazado de Capítulo 18. As ferramentas que son realmente case duplicados tamén son un problema real, e a corrección para esas non é ter menos ferramentas senón mellores descricións e espazos de nomes: prefixalas por sistema (crm.search_customer, billing.search_customer) para que dous catálogos fusionados de dous equipos non colidan, e para que o modelo teña algo co que discriminar.
Tres tipos de ferramenta, e a que abre a seguinte parte
Ligazón á sección: Tres tipos de ferramenta, e a que abre a seguinte parteAxuda ordenar as ferramentas polo que lle fan ao mundo, porque a enxeñaría é distinta para cada unha.
Ferramentas de datos len: buscan, traen, consultan. Reintentables, paralelizables, cacheables. Fallan devolvendo nada útil, e o seu risco principal é que traen texto non fiable ao context — que é toda a superficie de ataque do Capítulo 30.
Ferramentas de acción escriben: envían, crean, cobran, eliminan. Non son reintentables sen unha clave, non son paralelizables con seguridade, e son a razón pola que existen os fluxos de aprobación.
Ferramentas de orquestración chaman outros modelos. Unha ferramenta cuxa implementación é outro agent, co seu propio prompt, as súas propias ferramentas e o seu propio bucle — e para o modelo que chama ten exactamente o mesmo aspecto ca as outras dúas, porque un esquema e un endpoint é todo o que chega a ver.
Ese terceiro tipo non é unha curiosidade. É o mecanismo detrás da metade agent-as-a-tool do Capítulo 25 — a outra topoloxía, o handoff, entrega a conversa e nunca a recupera — e funciona precisamente porque a interface deste capítulo é estreita dabondo para que un agent enteiro caiba detrás dela.
Cara a onde vai isto agora
Ligazón á sección: Cara a onde vai isto agoraAgora tes un modelo que pode pedir cousas, e un contrato que fai que o pedido sexa parseable. O que non tes é nada sobre o que poida pedir máis alá do que cabe no seu prompt.
A ferramenta máis común en produción, con moita diferenza, é unha busca nun corpo de texto que o modelo nunca viu durante o adestramento: a túa documentación, os teus tickets, os teus contratos. Iso soa a problema resolto — facerlle embedding, atopar os veciños máis próximos, pegalos — e as partes que non están resoltas son as que deciden se a resposta é fiable: como se corta o texto antes de facerlle embedding, que limiar de similitude é baixo dabondo para significar non o sei, e como se liga unha cita a unha afirmación para que unha persoa lectora poida comprobala.
O Capítulo 19 é recuperación, e é o capítulo no que unha resposta incorrecta deixa de ser unha curiosidade e empeza a ser unha responsabilidade.
Fontes e método
Ligazón á sección: Fontes e métodoAs medicións deste capítulo veñen de Qwen/Qwen2.5-0.5B-Instruct con greedy decoding, sobre 24 peticións xeradas que cruzan seis pares de cidades con catro formulacións de data, usando o propio modelo de chat do modelo para as definicións de ferramentas. Reprodúcense exactamente, e son un modelo pequeno: le a división formato/valor como unha demostración do mecanismo máis que como un benchmark do que fan os modelos actuais. Un modelo de fronteira resolve "o vindeiro venres" correctamente moito máis a miúdo — e aínda así un esquema non pode obrigalo a facelo, que é a parte que xeneraliza.
O vocabulario de JSON Schema usado arriba (type, properties, required, pattern, format, enum) está especificado no draft de JSON Schema que nomea a documentación do teu provedor; o subconxunto útil é pequeno e o mesmo entre provedores, e as diferenzas que existen — que palabras clave se aplican mediante decodificación restrinxida en vez de simplemente pasárense ao modelo — paga a pena lelas na guía de structured-output do provedor en vez de dalas por supostas.
Para a decodificación restrinxida como técnica, as bibliotecas de estilo guidance e o proxecto outlines documentan a construción de gramática a máscara de logit dun xeito que encaixa directamente co sampler do Capítulo 17. E para a ida e volta en si, a especificación máis clara non é un tutorial senón un protocolo: o Capítulo 26 leo liña por liña.
Referencias
Ligazón á sección: Referencias-
Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). O artigo que fixo estándar a receita de post-training; a forma dunha chamada a ferramenta apréndese aí, a partir de demostracións, exactamente igual que a forma dunha resposta. ↩