Ves al contingut
18/30Capítol 18 de 30

Tool Calling i sortides estructurades: el contracte que aguanta

24 crides, zero JSON trencats i dues dates útils. El mateix endpoint amb una descripció millor, i què no pot arreglar un esquema.

En aquesta pàgina

Dona a un model una eina de cerca de vols i demana-li que trobi un vol de Madrid a Berlín. Això és el que torna:

TEXT
<tool_call>
{"name": "search_flights",
 "arguments": {"from": "Madrid", "to": "Berlin", "date": "3rd October 2026"}}
</tool_call>

El JSON és vàlid. El nom de l’eina és correcte. Tots els camps obligatoris hi són. I la crida és inútil: cap API de vols accepta "Madrid" quan espera un codi d’aeroport, o "3rd October 2026" quan espera una data.

Aquesta bretxa —sintàcticament perfecta, semànticament inutilitzable— és el tema d’aquest capítol, i el primer que cal deixar clar és que no és un problema de JSON. En vint-i-quatre peticions amb aquesta eina, el model va produir 24 crides d’eina vàlides i zero JSON trencats. Ni una sola vegada va fallar en la part que tothom depura.

Abans de la mecànica, la frase que evita més confusions: una crida d’eina és una petició, no una acció.

El model emet un missatge estructurat que diu m’agradaria que es cridés search_flights amb aquests arguments. Després s’atura. El teu codi rep aquest missatge, decideix si l’accepta, crida allò que hagi de cridar i torna el resultat com un altre missatge. El model no ha tocat mai la teva base de dades, no ha fet cap petició HTTP, no ha tingut credencials.

Tot el que fa referència a la seguretat d’agent al capítol 30 deriva d’aquesta divisió, i també tot el que fa referència al disseny d’agent al capítol 23: el model proposa i el teu codi disposa, i el codi és on viu cada garantia.

Així que una eina, despullada de vocabulari, és dues coses:

Un esquema. Un JSON Schema que descriu una funció: el seu nom, què fa i quins arguments accepta, amb els seus tipus i restriccions. Això és el que entra al prompt, i és l’únic que veu mai el model.

Un endpoint. Una funció del teu codi que rep aquests arguments i retorna alguna cosa. El model no el veu mai, no sap en quin llenguatge està escrit i no pot distingir una consulta a una base de dades d’una cadena hardcoded.

Les definicions d’eina van al prompt, serialitzades en el format amb què s’hagi entrenat el model. Costen tokens en cada crida —un fet que tornarà més endavant en aquest capítol, amb una xifra.

En comptes de prosa, la resposta conté una petició estructurada, i l’API informa d’un motiu de finalització que ho indica. Aquest motiu importa: és com el teu codi sap que ha d’executar una eina en lloc de mostrar una resposta a l’usuari.

Aquest és el pas on no hi ha cap model. Valida els arguments contra l’esquema, decideix si aquest sol·licitant té permís per fer-ho i executa.

El resultat es converteix en un altre torn de la conversa, en un rol reservat per a això. El model el llegeix com qualsevol altre context.

Aquest és el bucle del capítol 23, i la raó per la qual una sola petició pot convertir-se en una dotzena d’anades i tornades.

Res d’això és emergent. Com va establir el capítol 11, el tool calling és un comportament entrenat:1 durant el post-training el model va veure milers de converses amb exactament aquesta forma. Per això el format és específic de cada model, per això la fiabilitat varia tant entre models de mida similar, i per això un model pot cridar una eina que no ha vist mai: la forma s’ha entrenat, l’eina concreta ve del teu prompt.

Aquesta és l’eina tal com la sol escriure la majoria al principi. Fixa’t que no hi ha res incorrecte; simplement és prima:

tools/badFlights.tsTS
{
  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"],
  },
}

Vint-i-quatre peticions, sis parelles de ciutats combinades amb quatre maneres d’expressar una data ("el dia 3 del mes que ve", "divendres vinent", "15 de desembre", "demà"), greedy decoding perquè els resultats siguin reproduïbles:

eina cridadaJSON trencatdata en ISOaeroports com a IATAtot correcte
l’esquema anterior24/2402/244/241/24

Llegeix les dues primeres columnes abans de les tres últimes. El model crida l’eina correcta cada vegada i produeix JSON ben format cada vegada. El fracàs és totalment en els valors, i els valors són inutilitzables: "Madrid" en lloc de MAD, "3rd October 2026" en lloc de 2026-10-03.

Val la pena insistir-hi perquè determina on mires quan alguna cosa es trenca. L’instint és afegir un parser de JSON amb un reintent, o demanar al model amb més fermesa un JSON vàlid. Cap de les dues coses aborda res del que ha passat aquí.

Mateix endpoint. Mateix codi al darrere. Mateix model, mateixos prompts, mateix decoding. L’únic que canvia és el text de l’esquema:

tools/goodFlights.tsTS
{
  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"],
  },
}
FORMAT de dataVALOR de dataFORMAT d’aeroportVALOR d’aeroport
esquema prim2/241/244/244/24
esquema descrit24/2412/2416/248/24

El format de la data passa de 2 de 24 a 24 de 24. Perfecte, a partir d’un canvi de text, sense tocar codi i sense lògica de reintent. Si t’emportes un hàbit operatiu d’aquest capítol, que sigui aquest: quan una eina es crida malament, la solució gairebé sempre és a la descripció, i és la solució més barata del sistema.

Ara llegeix la segona columna, que és la meitat més important.

Un esquema constreny la forma. No pot aportar coneixement.

Enllaç a la secció: Un esquema constreny la forma. No pot aportar coneixement.

La data és en format ISO 24 vegades de 24. És el dia correcte 12 vegades de 24.

Així que la meitat de les crides ara porten una data perfectament formatada que és la data equivocada. La descripció ha dit al model quina forma havia de produir, i el model l’ha produïda impecablement —però convertir "divendres vinent" en 2026-09-11 requereix saber la data d’avui i fer aritmètica de calendari, i cap quantitat de descripció aporta això. Passa el mateix amb els aeroports: el format passa de 4 a 16, però el valor només de 4 a 8, perquè escriure MAD requereix saber que l’aeroport de Madrid és MAD.

Aquesta distinció és la idea que sosté el capítol:

Un esquema és un contracte sobre la forma. Pot fer que la sortida del model sigui parsejable, tipada i coherent. No pot fer que sigui certa, i cada mode de fallada que sobreviu a un bon esquema és una fallada de coneixement, no una fallada de format.

Les dues coses necessiten solucions diferents, i confondre-les fa perdre setmanes. Les fallades de format es corregeixen a la descripció o amb constrained decoding, més avall. Les fallades de coneixement es corregeixen posant el coneixement al prompt: la data actual al missatge de sistema, una cerca d’aeroport com a segona eina que el model crida primer, un enum a l’esquema quan el conjunt és prou petit per enumerar-lo. Fixa’t què tenen en comú totes tres: treuen el problema de la memòria del model i el posen a la seva entrada, que és tot el capítol 24.

Sortides estructurades, i què és realment el "constrained decoding"

Enllaç a la secció: Sortides estructurades, i què és realment el "constrained decoding"

Tot el que hi ha a sobre encara depèn que el model triï produir la forma correcta. Hi ha una garantia més forta disponible, i és el millor retorn del capítol 17.

Recorda com funciona la generació: a cada pas el model produeix un logit per a cada token del vocabulari, i el sampler en tria un. Constrained decoding insereix un pas entremig. Donada una gramàtica —derivada del teu JSON Schema— calcula quins tokens podrien venir legalment a continuació, posa els logits de tots els altres a infinit negatiu i deixa que el sampler triï entre el que queda.

Si l’esquema diu que el següent ha de ser un {, llavors cada token que no sigui { té probabilitat zero. No "improbable": zero. El model no pot emetre JSON invàlid perquè els tokens invàlids s’han eliminat de la distribució abans del mostreig.

Això és el que hi ha sota "sortides estructurades", "mode JSON" i "generació guiada", i explica les seves dues propietats. La garantia és total per a qualsevol cosa que la gramàtica pugui expressar —tipus, camps obligatoris, enums, niament— perquè s’imposa mecànicament en lloc de demanar-se educadament. I no diu res sobre el contingut: una gramàtica pot forçar que "date" sigui una cadena que coincideixi amb un patró de data, i no pot forçar que sigui el dia correcte. És el mateix mur que a la secció anterior, però arribat des de l’altra banda.

Dues notes pràctiques. No és gratis: la màscara s’ha de calcular a cada pas, i les gramàtiques complexes costen latència mesurable. I canvia el que fa el model: un model desviat del seu token preferit pot produir pitjor contingut mentre produeix una estructura perfecta, que és per què "demana-ho bé i valida" continua sent un valor per defecte raonable per a formes simples, i el constrained decoding es guanya el seu cost quan la forma és complexa o el consumidor és estricte.

Efectes secundaris, i l’única propietat que importa

Enllaç a la secció: Efectes secundaris, i l’única propietat que importa

El capítol 14 mesurava un timeout seguit d’un reintent que facturava dues generacions per una resposta. Amb eines, la mateixa fallada empitjora, perquè una eina pot fer alguna cosa.

Si el teu codi crida charge_card, fa timeout i ho torna a intentar, tens dos càrrecs. El model no té ni idea que hagi passat res d’això; veu un sol resultat d’eina. La solució és la mateixa que en qualsevol sistema distribuït i no és problema del model: fes que l’operació sigui idempotent donant una clau a la crida, de manera que la segona execució reconegui la primera i en retorni el resultat en lloc de tornar a fer la feina.

La regla de disseny que se’n deriva val la pena dir-la clarament. Separa les lectures de les escriptures al teu catàleg d’eines. Una lectura es pot reintentar lliurement, executar en paral·lel i desar a la cache. Una escriptura no, i hauria de portar una clau, una comprovació de permisos i —per a qualsevol cosa que un usuari voldria saber abans que passés— un pas d’aprovació que posi una persona entre la petició i l’acció. Aquest pas d’aprovació no és una cortesia: és una de les poques coses que s’interposen entre una prompt injection i una conseqüència real —i, com mesura el capítol 30, la més feble.

La saviesa popular diu que carregar moltes eines fa que el model triï malament. Val la pena mesurar-ho en lloc de repetir-ho, així que: les mateixes vint-i-quatre peticions, amb l’eina de vols més un conjunt creixent d’altres —incloses tres de deliberadament confusibles (horaris de tren, travessies en ferri, rutes d’autobús).

eines carregadesprompt tokensha triat search_flightsdata en ISO
135324/2424/24
573024/2424/24
101,19321/2421/24
202,11924/2424/24

La selecció no es va degradar. Amb vint eines, tres d’elles plausiblement confusibles, un model de mig bilió de paràmetres va triar la correcta vint-i-quatre vegades de vint-i-quatre. La caiguda a deu són tres crides que van anomenar una eina diferent, i no sobreviu en passar a vint.

És un resultat negatiu i s’hauria d’informar com a tal: en aquesta tasca, amb aquestes eines, "massa eines" no era el problema. El que sí que va créixer, monòtonament i per un factor de sis, és el prompt: de 353 tokens a 2,119, pagats en cada petició de la conversa, per sempre, tant si s’utilitza alguna eina com si no.

Així que la versió honesta de la saviesa popular va de cost i context, no de precisió. Vint eines són un impost permanent sobre cada missatge, i el capítol 16 ja va mostrar què fa un prefix permanent a una factura al llarg de quaranta torns. Quan la gent informa que moltes eines perjudiquen la qualitat, el mecanisme sol ser que les definicions han expulsat el context que importava —que és un problema del capítol 24 disfressat de capítol 18. Les eines que són autènticament gairebé duplicades entre si també són un problema real, i la solució per a aquestes no és tenir menys eines sinó millors descripcions i namespaces: prefixa-les per sistema (crm.search_customer, billing.search_customer) perquè dos catàlegs fusionats de dos equips no col·lideixin, i perquè el model tingui alguna cosa amb què discriminar.

Tres tipus d’eina, i la que obre la següent part

Enllaç a la secció: Tres tipus d’eina, i la que obre la següent part

Ajuda classificar les eines segons el que fan al món, perquè l’enginyeria difereix per a cadascuna.

Eines de dades llegeixen: cerquen, recuperen, consulten. Reintentables, paral·lelitzables, cachejables. Fallen retornant res útil, i el seu risc principal és que porten text no fiable al context —que és tota la superfície d’atac del capítol 30.

Eines d’acció escriuen: envien, creen, cobren, esborren. No són reintentables sense una clau, no són paral·lelitzables de manera segura, i són la raó per la qual existeixen els fluxos d’aprovació.

Eines d’orquestració criden altres models. Una eina la implementació de la qual és un altre agent, amb el seu propi prompt, les seves pròpies eines i el seu propi bucle —i per al model que la crida sembla exactament igual que les altres dues, perquè un esquema i un endpoint és tot el que veu mai.

Aquest tercer tipus no és cap curiositat. És el mecanisme darrere de la meitat agent-com-a-eina del capítol 25 —l’altra topologia, el handoff, cedeix la conversa i no la recupera mai— i funciona precisament perquè la interfície d’aquest capítol és prou estreta perquè hi càpiga tot un agent al darrere.

Ara tens un model que pot demanar coses, i un contracte que fa que la demanda sigui parsejable. El que no tens és res sobre què pugui demanar més enllà del que cap al seu prompt.

L’eina més habitual en producció, de llarg, és una cerca sobre un cos de text que el model no va veure mai durant l’entrenament: la teva documentació, els teus tiquets, els teus contractes. Sona com un problema resolt —fes-ne embedding, troba els veïns més propers, enganxa’ls— i les parts que no estan resoltes són les que decideixen si la resposta és fiable: com es talla el text abans de fer-ne embedding, quin llindar de similitud és prou baix per voler dir no ho sé, i com s’adjunta una citació a una afirmació perquè un lector la pugui comprovar.

El capítol 19 és retrieval, i és el capítol on una resposta equivocada deixa de ser una curiositat i comença a ser una responsabilitat.


Les mesures d’aquest capítol provenen de Qwen/Qwen2.5-0.5B-Instruct amb greedy decoding, sobre 24 peticions generades que creuen sis parelles de ciutats amb quatre formulacions de data, utilitzant la plantilla de xat pròpia del model per a les definicions d’eina. Es reprodueixen exactament, i són d’un model petit: llegeix la divisió format/valor com una demostració del mecanisme més que com un benchmark del que fan els models actuals. Un model frontier resol "divendres vinent" correctament molt més sovint —i tot i així un esquema no el pot obligar a fer-ho, que és la part que generalitza.

El vocabulari de JSON Schema utilitzat més amunt (type, properties, required, pattern, format, enum) s’especifica a l’esborrany de JSON Schema que anomena la documentació del teu proveïdor; el subconjunt útil és petit i és el mateix entre proveïdors, i les diferències que existeixen —quines keywords s’imposen amb constrained decoding en lloc de passar-se simplement al model— val la pena llegir-les a la guia de sortides estructurades del proveïdor en lloc d’assumir-les.

Per al constrained decoding com a tècnica, les biblioteques d’estil guidance i el projecte outlines documenten la construcció de gramàtica a màscara de logit d’una manera que encaixa directament amb el sampler del capítol 17. I per a l’anada i tornada en si, l’especificació més clara no és un tutorial sinó un protocol: el capítol 26 el llegeix línia per línia.

  1. Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). L’article que va convertir la recepta de post-training en estàndard; la forma d’una crida d’eina s’aprèn allà, a partir de demostracions, exactament igual que la forma d’una resposta.

A punt per deixar que triï LIA?

Crea amb tots els models d'IA en un sol lloc — comença gratis avui mateix.