Tool Calling og strukturerede outputs: Kontrakten der holder
24 kald, intet ødelagt JSON og to brugbare datoer. Så samme endpoint med bedre beskrivelse – og hvad et schema ikke kan fikse.
På denne side
Giv en model et værktøj til flysøgning, og bed den finde en flyrejse fra Madrid til Berlin. Her er, hvad der kommer tilbage:
<tool_call>
{"name": "search_flights",
"arguments": {"from": "Madrid", "to": "Berlin", "date": "3rd October 2026"}}
</tool_call>JSON'en er gyldig. Værktøjets navn er rigtigt. Alle påkrævede felter er til stede. Og kaldet er ubrugeligt: Ingen fly-API accepterer "Madrid", hvor den forventer en lufthavnskode, eller "3rd October 2026", hvor den forventer en dato.
Det hul — syntaktisk perfekt, semantisk ubrugeligt — er, hvad dette kapitel handler om, og det første, vi skal slå fast, er, at det ikke er et JSON-problem. På tværs af 24 forespørgsler med dette værktøj producerede modellen 24 gyldige tool calls og nul ødelagt JSON. Den fejlede ikke én eneste gang på den del, alle debugger.
Modellen udfører ikke noget
Link til afsnittet: Modellen udfører ikke nogetFør mekanikken: Sætningen, der forebygger mest forvirring: et tool call er en anmodning, ikke en handling.
Modellen udsender en struktureret besked, der siger jeg vil gerne have search_flights kaldt med disse argumenter. Så stopper den. Din kode modtager beskeden, beslutter om den vil efterkomme den, kalder hvad end den kalder, og sender resultatet tilbage som endnu en besked. Modellen rørte aldrig din database, lavede aldrig en HTTP-forespørgsel og havde aldrig legitimationsoplysninger.
Alt om agent-sikkerhed i kapitel 30 følger af den opdeling, og det samme gør alt om agent-design i kapitel 23: modellen foreslår, og din kode afgør, og det er i koden, alle garantierne ligger.
Så et værktøj, renset for fagord, er to ting:
Et schema. Et JSON Schema, der beskriver en funktion: dens navn, hvad den gør, og hvilke argumenter den tager med deres typer og begrænsninger. Det er det, der går ind i prompt, og det er det eneste, modellen nogensinde ser.
Et endpoint. En funktion i din kode, der tager de argumenter og returnerer noget. Modellen ser den aldrig, ved aldrig hvilket sprog den er skrevet i, og kan ikke skelne en databaseforespørgsel fra en hardcoded streng.
Du sender schemaerne med forespørgslen
Link til afsnittet: Du sender schemaerne med forespørgslenVærktøjsdefinitionerne ligger i prompt, serialiseret til det format, modellen blev trænet på. De koster token ved hvert eneste kald — en kendsgerning, der vender tilbage med et tal senere i kapitlet.
Modellen svarer med et kald i stedet for tekst
Link til afsnittet: Modellen svarer med et kald i stedet for tekstI stedet for prosa indeholder svaret en struktureret anmodning, og API'en rapporterer en finish reason, der siger det. Årsagen er vigtig: Det er sådan din kode ved, at den skal køre et værktøj i stedet for at vise brugeren et svar.
Din kode kører det — eller nægter
Link til afsnittet: Din kode kører det — eller nægterDette er trinnet uden nogen model i det. Valider argumenterne mod schemaet, beslut om denne afsender må gøre dette, og eksekvér.
Du sender resultatet tilbage som en besked
Link til afsnittet: Du sender resultatet tilbage som en beskedResultatet bliver en ny tur i samtalen, i en rolle reserveret til det. Modellen læser det som enhver anden context.
Modellen svarer eller beder om et andet værktøj
Link til afsnittet: Modellen svarer eller beder om et andet værktøjDet er loopet fra kapitel 23, og grunden til at en enkelt forespørgsel kan blive til et dusin rundture.
Intet af dette er emergent. Som kapitel 11 fastslog, er tool calling en trænet adfærd:1 under eftertræningen så modellen tusindvis af samtaler formet præcis sådan. Derfor er formatet modelspecifikt, derfor varierer pålideligheden så meget mellem modeller af lignende størrelse, og derfor kan en model kalde et værktøj, den aldrig har set — formen blev trænet, det konkrete værktøj kommer fra din prompt.
Hvad et dårligt schema koster, målt
Link til afsnittet: Hvad et dårligt schema koster, måltHer er værktøjet, som de fleste først skriver det. Bemærk, at der ikke er noget forkert ved det; det er bare tyndt:
{
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"],
},
}24 forespørgsler, seks bypar krydset med fire måder at udtrykke en dato på ("den 3. i næste måned", "næste fredag", "15. december", "i morgen"), greedy decoding så resultaterne kan reproduceres:
| værktøj kaldt | ødelagt JSON | dato i ISO | lufthavne som IATA | alt korrekt | |
|---|---|---|---|---|---|
| schemaet ovenfor | 24/24 | 0 | 2/24 | 4/24 | 1/24 |
Læs de første to kolonner før de sidste tre. Modellen kalder det rigtige værktøj hver gang og producerer velformet JSON hver gang. Fejlen ligger udelukkende i værdierne, og værdierne er ubrugelige: "Madrid" i stedet for MAD, "3rd October 2026" i stedet for 2026-10-03.
Det er værd at understrege, fordi det afgør, hvor du leder, når noget går i stykker. Instinktet er at tilføje en JSON-parser med et retry eller at bede modellen mere bestemt om gyldig JSON. Ingen af delene adresserer noget af det, der skete her.
Skift nu kun beskrivelsen
Link til afsnittet: Skift nu kun beskrivelsenSamme endpoint. Samme kode bag det. Samme model, samme prompts, samme decoding. Det eneste, der ændrer sig, er teksten i schemaet:
{
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"],
},
}| dato FORMAT | dato VÆRDI | lufthavn FORMAT | lufthavn VÆRDI | |
|---|---|---|---|---|
| tyndt schema | 2/24 | 1/24 | 4/24 | 4/24 |
| beskrevet schema | 24/24 | 12/24 | 16/24 | 8/24 |
Datoformatet går fra 2 ud af 24 til 24 ud af 24. Perfekt, fra en tekstændring, uden at røre kode og uden retry-logik. Hvis du tager én driftsvane med fra dette kapitel, er det denne: når et værktøj kaldes forkert, ligger rettelsen næsten altid i beskrivelsen, og det er den billigste rettelse i systemet.
Læs nu den anden kolonne, som er den vigtigere halvdel.
Et schema begrænser formen. Det kan ikke levere viden.
Link til afsnittet: Et schema begrænser formen. Det kan ikke levere viden.Datoen er i ISO-format 24 ud af 24 gange. Det er den rigtige dag 12 ud af 24 gange.
Så halvdelen af kaldene bærer nu en perfekt formateret dato, der er den forkerte dato. Beskrivelsen fortalte modellen, hvilken form den skulle producere, og modellen producerede den fejlfrit — men at omsætte "næste fredag" til 2026-09-11 kræver, at man kender dagens dato og laver kalenderaritmetik, og ingen mængde beskrivelse leverer det. Samme historie for lufthavne: formatet gik fra 4 til 16, men værdien kun fra 4 til 8, fordi det at skrive MAD kræver viden om, at Madrids lufthavn er MAD.
Den skelnen er kapitlets bærende idé:
Et schema er en kontrakt om form. Det kan gøre modellens output parsebart, typet og konsistent. Det kan ikke gøre det sandt, og enhver fejltype, der overlever et godt schema, er en vidensfejl, ikke en formatfejl.
De to kræver forskellige rettelser, og at forveksle dem spilder uger. Formatfejl rettes i beskrivelsen eller med constrained decoding nedenfor. Vidensfejl rettes ved at lægge viden ind i prompt — den aktuelle dato i systembeskeden, et lufthavnsopslag som et andet værktøj, modellen kalder først, en enum i schemaet, når mængden er lille nok til at opremse. Læg mærke til, hvad alle tre har til fælles: De flytter problemet ud af modellens hukommelse og ind i dens input, hvilket er hele kapitel 24.
Strukturerede outputs, og hvad "constrained decoding" faktisk er
Link til afsnittet: Strukturerede outputs, og hvad "constrained decoding" faktisk erAlt ovenfor afhænger stadig af, at modellen vælger at producere den rigtige form. Der findes en stærkere garanti, og det er den bedste gevinst fra kapitel 17.
Husk, hvordan generering fungerer: Ved hvert trin producerer modellen en logit for hver token i ordforrådet, og sampleren vælger én. Constrained decoding indsætter et trin imellem. Givet en grammatik — afledt af dit JSON Schema — beregner den, hvilke tokens der lovligt kan komme som det næste, sætter logits for alle de andre til negativ uendelig og lader sampleren vælge blandt det, der er tilbage.
Hvis schemaet siger, at det næste skal være en {, så har hver token, der ikke er {, sandsynlighed nul. Ikke "usandsynlig": nul. Modellen kan ikke udsende ugyldig JSON, fordi de ugyldige tokens blev fjernet fra distributionen før sampling.
Det er, hvad "structured outputs", "JSON mode" og "guided generation" er under overfladen, og det forklarer deres to egenskaber. Garantien er total for alt, hvad grammatikken kan udtrykke — typer, påkrævede felter, enums, nesting — fordi den håndhæves mekanisk i stedet for at blive bedt pænt om. Og den siger intet om indhold: En grammatik kan tvinge "date" til at være en streng, der matcher et datomønster, og kan ikke tvinge den til at være den rigtige dag. Det er den samme mur som i forrige afsnit, nået fra den anden side.
To praktiske noter. Det er ikke gratis: Masken skal beregnes ved hvert trin, og komplekse grammatikker koster målbar latency. Og det ændrer, hvad modellen gør — en model, der styres væk fra sin foretrukne token, kan producere dårligere indhold, mens den producerer perfekt struktur, og derfor er "bed pænt og valider" stadig en rimelig standard for simple former, mens constrained decoding tjener sin pris ind, når formen er kompleks eller forbrugeren er streng.
Sideeffekter, og den ene egenskab der betyder noget
Link til afsnittet: Sideeffekter, og den ene egenskab der betyder nogetKapitel 14 målte en timeout efterfulgt af et retry, der fakturerede to genereringer for ét svar. Med værktøjer bliver den samme fejl værre, fordi et værktøj kan gøre noget.
Hvis din kode kalder charge_card, timer ud og prøver igen, har du to opkrævninger. Modellen aner ikke, at noget af dette skete; den ser ét værktøjsresultat. Rettelsen er den samme som i ethvert distribueret system, og det er ikke modellens problem: Gør operationen idempotent ved at give kaldet en nøgle, så den anden eksekvering genkender den første og returnerer dens resultat i stedet for at udføre arbejdet igen.
Designreglen, der følger, er værd at sige direkte. Adskil læsninger fra skrivninger i dit værktøjskatalog. En læsning kan prøves igen frit, køres parallelt og caches. En skrivning kan ikke, og bør have en nøgle, et tilladelsestjek og — for alt, en bruger ville ønske at vide om, før det sker — et approval-trin, der placerer et menneske mellem anmodningen og handlingen. Det approval-trin er ikke en høflighed: Det er en af de få ting, der står mellem en prompt injection og en reel konsekvens — og, som kapitel 30 måler, den svageste af dem.
Hvor mange værktøjer før det forringes?
Link til afsnittet: Hvor mange værktøjer før det forringes?Folketroen siger, at indlæsning af mange værktøjer får modellen til at vælge dårligt. Det er værd at måle i stedet for at gentage, så: De samme 24 forespørgsler, med flyværktøjet plus et voksende sæt andre — inklusive tre, der med vilje kan forveksles med det (togtider, færgeoverfarter, busruter).
| indlæste værktøjer | prompt tokens | valgte search_flights | dato i 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 |
Udvælgelsen blev ikke dårligere. Med tyve værktøjer, hvoraf tre plausibelt kunne forveksles, valgte en model med en halv milliard parametre det rigtige 24 ud af 24 gange. Dyket ved ti er tre kald, der navngav et andet værktøj, og det overlever ikke springet til tyve.
Det er et negativt resultat, og det bør rapporteres som et: På denne opgave, med disse værktøjer, var "for mange værktøjer" ikke problemet. Det, der voksede monotont og med en faktor seks, er prompt: 353 token til 2,119, betalt på hver forespørgsel i samtalen, for altid, uanset om noget værktøj bruges eller ej.
Så den ærlige version af folketroen handler om omkostning og context, ikke nøjagtighed. Tyve værktøjer er en permanent afgift på hver besked, og kapitel 16 viste allerede, hvad et permanent præfiks gør ved en regning over 40 ture. Når folk rapporterer, at mange værktøjer skader kvaliteten, er mekanismen som regel, at definitionerne fortrængte den context, der betød noget — hvilket er et kapitel 24-problem forklædt som kapitel 18. Værktøjer, der reelt næsten er dubletter af hinanden, er også et rigtigt problem, og rettelsen for dem er ikke færre værktøjer, men bedre beskrivelser og namespaces: Præfiks dem efter system (crm.search_customer, billing.search_customer), så to kataloger flettet fra to teams ikke kolliderer, og så modellen har noget at skelne ud fra.
Tre slags værktøjer, og den ene der åbner næste del
Link til afsnittet: Tre slags værktøjer, og den ene der åbner næste delDet hjælper at sortere værktøjer efter, hvad de gør ved verden, fordi engineering er forskellig for hver.
Dataværktøjer læser: søger, henter, forespørger. Kan retryes, paralleliseres og caches. De fejler ved ikke at returnere noget brugbart, og deres primære risiko er, at de bringer upålidelig tekst ind i context — hvilket er hele angrebsfladen i kapitel 30.
Handlingsværktøjer skriver: sender, opretter, opkræver, sletter. Kan ikke retryes uden en nøgle, kan ikke paralleliseres sikkert, og er grunden til at approval-flows findes.
Orkestreringsværktøjer kalder andre modeller. Et værktøj hvis implementering er en anden agent, med sin egen prompt, sine egne værktøjer og sit eget loop — og for den kaldende model ser det præcis ud som de to andre, fordi et schema og et endpoint er alt, den nogensinde ser.
Den tredje slags er ikke en kuriositet. Det er mekanismen bag agent-as-a-tool-halvdelen af kapitel 25 — den anden topologi, handoff, giver samtalen væk og får den aldrig tilbage — og den virker netop fordi interfacet i dette kapitel er smalt nok til, at en hel agent kan ligge bag det.
Hvor det går hen nu
Link til afsnittet: Hvor det går hen nuDu har nu en model, der kan bede om ting, og en kontrakt, der gør det at bede parsebart. Det, du ikke har, er noget den kan bede om, ud over det der passer i dens prompt.
Det mest almindelige værktøj i produktion, med stor afstand, er en søgning over en tekstmængde, modellen aldrig så under træning: din dokumentation, dine tickets, dine kontrakter. Det lyder som et løst problem — embed den, find de nærmeste naboer, indsæt dem — og de dele, der ikke er løst, er dem der afgør, om svaret er troværdigt: hvordan teksten skæres op, før den bliver embedded, hvilken lighedstærskel der er lav nok til at betyde jeg ved det ikke, og hvordan en kildehenvisning knyttes til en påstand, så en læser kan tjekke den.
Kapitel 19 er retrieval, og det er kapitlet hvor et forkert svar holder op med at være en kuriositet og begynder at være et ansvar.
Kilder og metode
Link til afsnittet: Kilder og metodeMålingerne i dette kapitel kommer fra Qwen/Qwen2.5-0.5B-Instruct med greedy decoding, over 24 genererede forespørgsler, der krydser seks bypar med fire datoformuleringer, ved brug af modellens egen chat template til værktøjsdefinitioner. De reproducerer nøjagtigt, og det er en lille model: Læs format-/værdiopdelingen som en demonstration af mekanismen snarere end som et benchmark for, hvad nuværende modeller gør. En frontier-model løser "næste fredag" korrekt langt oftere — og kan stadig ikke tvinges til det af et schema, hvilket er den del, der generaliserer.
JSON Schema-ordforrådet brugt ovenfor (type, properties, required, pattern, format, enum) er specificeret i det JSON Schema draft, som din udbyders dokumentation navngiver; den nyttige delmængde er lille og den samme på tværs af udbydere, og de forskelle, der findes — hvilke keywords der håndhæves af constrained decoding frem for blot at blive sendt videre til modellen — er værd at læse i udbyderens guide til structured-output i stedet for at antage.
For constrained decoding som teknik dokumenterer guidance-lignende biblioteker og outlines-projektet konstruktionen fra grammatik til logit-mask på en måde, der mapper direkte til kapitel 17's sampler. Og for selve rundturen er den klareste specifikation ikke en tutorial, men en protokol: kapitel 26 læser den linje for linje.
Referencer
Link til afsnittet: Referencer-
Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). Artiklen, der gjorde eftertræningsopskriften til standard; formen på et tool call læres dér, fra demonstrationer, præcis som formen på et svar. ↩