Tool Calling och structured outputs: kontraktet som håller
Tjugofyra anrop, noll trasig JSON och två användbara datum. Samma endpoint med bättre beskrivning – och vad ett schema inte kan laga.
På den här sidan
Ge en modell ett flygsökningsverktyg och be den hitta ett flyg från Madrid till Berlin. Här är vad som kommer tillbaka:
<tool_call>
{"name": "search_flights",
"arguments": {"from": "Madrid", "to": "Berlin", "date": "3rd October 2026"}}
</tool_call>JSON:en är giltig. Verktygets namn är rätt. Alla obligatoriska fält finns med. Och anropet är värdelöst: inget flyg-API accepterar "Madrid" där det vill ha en flygplatskod, eller "3rd October 2026" där det vill ha ett datum.
Det gapet — syntaktiskt perfekt, semantiskt oanvändbart — är vad det här kapitlet handlar om, och det första att slå fast är att det inte är ett JSON-problem. Över tjugofyra förfrågningar med det här verktyget producerade modellen 24 giltiga verktygsanrop och noll trasig JSON. Den misslyckades inte en enda gång med den del som alla debuggar.
Modellen exekverar ingenting
Länk till avsnittet: Modellen exekverar ingentingFöre mekaniken, meningen som förhindrar mest förvirring: ett verktygsanrop är en begäran, inte en handling.
Modellen skickar ett strukturerat meddelande som säger jag vill att search_flights anropas med de här argumenten. Sedan stannar den. Din kod tar emot meddelandet, avgör om den ska godkänna det, anropar vad den nu anropar och skickar tillbaka resultatet som ett nytt meddelande. Modellen rörde aldrig din databas, gjorde aldrig en HTTP-förfrågan, hade aldrig några inloggningsuppgifter.
Allt om agent-säkerhet i kapitel 30 följer av den uppdelningen, och det gör även allt om agent-design i kapitel 23: modellen föreslår och din kod avgör, och koden är där varje garanti bor.
Så ett verktyg, avskalat från vokabulären, är två saker:
Ett schema. Ett JSON Schema som beskriver en funktion: dess namn, vad den gör och vilka argument den tar med deras typer och begränsningar. Det är detta som går in i prompt, och det är det enda modellen någonsin ser.
En endpoint. En funktion i din kod som tar de argumenten och returnerar något. Modellen ser den aldrig, vet aldrig vilket språk den är skriven i och kan inte skilja en databasfråga från en hårdkodad sträng.
Du skickar schemana med förfrågan
Länk till avsnittet: Du skickar schemana med förfråganVerktygsdefinitionerna går in i prompt, serialiserade i det format modellen tränades på. De kostar token vid varje enskilt anrop — ett faktum som återkommer med en siffra senare i kapitlet.
Modellen svarar med ett anrop i stället för text
Länk till avsnittet: Modellen svarar med ett anrop i stället för textI stället för prosa innehåller svaret en strukturerad begäran, och API:et rapporterar en finish reason som säger det. Orsaken spelar roll: det är så din kod vet att den ska köra ett verktyg i stället för att visa användaren ett svar.
Din kod kör det — eller vägrar
Länk till avsnittet: Din kod kör det — eller vägrarDet här är steget där ingen modell ingår. Validera argumenten mot schemat, avgör om den här anroparen får göra detta och exekvera.
Du skickar tillbaka resultatet som ett meddelande
Länk till avsnittet: Du skickar tillbaka resultatet som ett meddelandeResultatet blir en ny tur i konversationen, i en roll reserverad för det. Modellen läser det som vilken annan context som helst.
Modellen svarar, eller ber om ett annat verktyg
Länk till avsnittet: Modellen svarar, eller ber om ett annat verktygVilket är loopen från kapitel 23, och anledningen till att en enda förfrågan kan bli ett dussin rundresor.
Inget av detta är emergent. Som kapitel 11 slog fast är tool calling ett tränat beteende:1 under efterträningen såg modellen tusentals konversationer formade exakt så här. Det är därför formatet är modellspecifikt, varför tillförlitligheten varierar så mycket mellan modeller av liknande storlek och varför en modell kan anropa ett verktyg den aldrig har sett — formen tränades in, det specifika verktyget kommer från din prompt.
Vad ett dåligt schema kostar, mätt
Länk till avsnittet: Vad ett dåligt schema kostar, mättHär är verktyget som de flesta först skriver det. Observera att inget med det är fel; det är bara tunt:
{
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"],
},
}Tjugofyra förfrågningar, sex stadspar korsade med fyra sätt att uttrycka ett datum ("den 3:e nästa månad", "nästa fredag", "15 december", "i morgon"), greedy decoding så att resultaten går att reproducera:
| verktyg anropat | trasig JSON | datum i ISO | flygplatser som IATA | allt korrekt | |
|---|---|---|---|---|---|
| schemat ovan | 24/24 | 0 | 2/24 | 4/24 | 1/24 |
Läs de två första kolumnerna före de tre sista. Modellen anropar rätt verktyg varje gång och producerar välformad JSON varje gång. Misslyckandet ligger helt i värdena, och värdena är oanvändbara: "Madrid" i stället för MAD, "3rd October 2026" i stället för 2026-10-03.
Det är värt att insistera på, eftersom det avgör var du letar när något går sönder. Instinkten är att lägga till en JSON-parser med ett nytt försök, eller att be modellen mer bestämt om giltig JSON. Ingetdera adresserar något som hände här.
Ändra nu bara beskrivningen
Länk till avsnittet: Ändra nu bara beskrivningenSamma endpoint. Samma kod bakom den. Samma modell, samma prompts, samma decoding. Det enda som ändras är texten i schemat:
{
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"],
},
}| datum FORMAT | datum VÄRDE | flygplats FORMAT | flygplats VÄRDE | |
|---|---|---|---|---|
| tunt schema | 2/24 | 1/24 | 4/24 | 4/24 |
| beskrivet schema | 24/24 | 12/24 | 16/24 | 8/24 |
Datumformatet går från 2 av 24 till 24 av 24. Perfekt, från en textändring, utan att röra någon kod och utan retry-logik. Om du tar med dig en operativ vana från det här kapitlet är det den: när ett verktyg anropas fel ligger fixen nästan alltid i beskrivningen, och det är den billigaste fixen i systemet.
Läs nu den andra kolumnen, som är den viktigare halvan.
Ett schema begränsar form. Det kan inte tillföra kunskap.
Länk till avsnittet: Ett schema begränsar form. Det kan inte tillföra kunskap.Datumet är i ISO-format 24 gånger av 24. Det är rätt dag 12 gånger av 24.
Så hälften av anropen innehåller nu ett perfekt formaterat datum som är fel datum. Beskrivningen berättade för modellen vilken form den skulle producera, och modellen producerade den felfritt — men att göra om "nästa fredag" till 2026-09-11 kräver att man vet dagens datum och gör kalenderaritmetik, och ingen mängd beskrivning tillför det. Samma sak med flygplatser: formatet gick från 4 till 16, men värdet bara från 4 till 8, eftersom att skriva MAD kräver att man vet att Madrids flygplats är MAD.
Den distinktionen är kapitlets bärande idé:
Ett schema är ett kontrakt om form. Det kan göra modellens output parsebar, typad och konsekvent. Det kan inte göra den sann, och varje felläge som överlever ett bra schema är ett kunskapsfel, inte ett formatfel.
De två kräver olika fixar, och att blanda ihop dem slösar veckor. Formatfel fixas i beskrivningen eller med constrained decoding, nedan. Kunskapsfel fixas genom att lägga kunskapen i prompt — dagens datum i systemmeddelandet, en flygplatsuppslagning som ett andra verktyg modellen anropar först, en enum i schemat när mängden är liten nog att räkna upp. Notera vad alla tre har gemensamt: de flyttar problemet ur modellens minne och in i dess input, vilket är hela kapitel 24.
Structured outputs, och vad "constrained decoding" faktiskt är
Länk till avsnittet: Structured outputs, och vad "constrained decoding" faktiskt ärAllt ovan bygger fortfarande på att modellen väljer att producera rätt form. Det finns en starkare garanti, och den är den bästa utdelningen från kapitel 17.
Kom ihåg hur generering fungerar: vid varje steg producerar modellen en logit för varje token i vokabulären, och samplaren väljer en. Constrained decoding lägger in ett steg däremellan. Givet en grammatik — härledd från ditt JSON Schema — beräknar den vilka token som lagligen kan komma härnäst, sätter logits för alla andra till negativ oändlighet och låter samplaren välja bland det som återstår.
Om schemat säger att nästa sak måste vara ett {, då har varje token som inte är { sannolikhet noll. Inte "osannolikt": noll. Modellen kan inte producera ogiltig JSON eftersom de ogiltiga token togs bort från distributionen före sampling.
Det är vad "structured outputs", "JSON mode" och "guided generation" är under ytan, och det förklarar deras två egenskaper. Garantin är total för allt grammatiken kan uttrycka — typer, obligatoriska fält, enums, nästling — eftersom den verkställs mekaniskt snarare än begärs artigt. Och den säger ingenting om innehåll: en grammatik kan tvinga "date" att vara en sträng som matchar ett datummönster, och kan inte tvinga den att vara rätt dag. Det är samma vägg som i föregående avsnitt, fast nådd från andra hållet.
Två praktiska noteringar. Det är inte gratis: masken måste beräknas vid varje steg, och komplexa grammatiker kostar mätbar latens. Och det förändrar vad modellen gör — en modell som styrs bort från sin föredragna token kan producera sämre innehåll samtidigt som den producerar perfekt struktur, vilket är varför "be snällt och validera" fortfarande är en rimlig standard för enkla former och constrained decoding tjänar in sin kostnad när formen är komplex eller konsumenten är strikt.
Bieffekter, och den enda egenskapen som spelar roll
Länk till avsnittet: Bieffekter, och den enda egenskapen som spelar rollKapitel 14 mätte en timeout följd av ett nytt försök som debiterade två genereringar för ett svar. Med verktyg blir samma fel värre, eftersom ett verktyg kan göra något.
Om din kod anropar charge_card, får timeout och försöker igen har du två debiteringar. Modellen har ingen aning om att något av detta hände; den ser ett verktygsresultat. Fixen är densamma som i alla distribuerade system och är inte modellens problem: gör operationen idempotent genom att ge anropet en nyckel, så att den andra exekveringen känner igen den första och returnerar dess resultat i stället för att göra arbetet igen.
Designregeln som följer är värd att säga rakt ut. Separera läsningar från skrivningar i din verktygskatalog. En läsning kan försöka igen fritt, köras parallellt och cachas. En skrivning kan inte det, och bör bära en nyckel, en behörighetskontroll och — för allt en användare skulle vilja veta om innan det händer — ett godkännandesteg som placerar en människa mellan begäran och handlingen. Det godkännandesteget är inte en artighet: det är en av få saker som står mellan en prompt injection och en verklig konsekvens — och, mäter kapitel 30, den svagaste av dem.
Hur många verktyg innan det försämras?
Länk till avsnittet: Hur många verktyg innan det försämras?Folktron säger att många laddade verktyg gör att modellen väljer dåligt. Det är värt att mäta i stället för att upprepa, så: samma tjugofyra förfrågningar, med flygverktyget plus en växande uppsättning andra — inklusive tre avsiktligt förväxlingsbara (tågtidtabeller, färjeöverfarter, busslinjer).
| laddade verktyg | prompt token | valde search_flights | datum 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 |
Urvalet försämrades inte. Med tjugo verktyg, tre av dem rimligt förväxlingsbara, valde en modell med en halv miljard parameter rätt verktyg tjugofyra gånger av tjugofyra. Dippet vid tio är tre anrop som namngav ett annat verktyg, och det överlever inte övergången till tjugo.
Det är ett negativt resultat och bör rapporteras som ett sådant: i den här uppgiften, med de här verktygen, var "för många verktyg" inte problemet. Det som däremot växte, monotont och med en faktor sex, är prompt: 353 token till 2 119, betalda vid varje förfrågan i konversationen, för alltid, oavsett om något verktyg används eller inte.
Så den ärliga versionen av folktron handlar om kostnad och context, inte precision. Tjugo verktyg är en permanent skatt på varje meddelande, och kapitel 16 visade redan vad ett permanent prefix gör med en faktura över fyrtio turer. När folk rapporterar att många verktyg skadar kvalitet är mekanismen oftast att definitionerna trängde undan den context som spelade roll — vilket är ett kapitel 24-problem i kapitel 18-kostym. Verktyg som verkligen är nästintill dubletter av varandra är också ett verkligt problem, och fixen för dem är inte färre verktyg utan bättre beskrivningar och namespaces: prefixa dem efter system (crm.search_customer, billing.search_customer) så att två kataloger sammanslagna från två team inte krockar, och så att modellen har något att särskilja på.
Tre sorters verktyg, och den som öppnar nästa del
Länk till avsnittet: Tre sorters verktyg, och den som öppnar nästa delDet hjälper att sortera verktyg efter vad de gör med världen, eftersom engineering skiljer sig åt för varje sort.
Dataverktyg läser: söker, hämtar, frågar. Går att försöka igen, parallellisera och cacha. De misslyckas genom att inte returnera något användbart, och deras huvudrisk är att de tar in opålitlig text i context — vilket är hela attackytan i kapitel 30.
Handlingsverktyg skriver: skickar, skapar, debiterar, tar bort. Går inte att försöka igen utan en nyckel, går inte att parallellisera säkert, och är skälet till att godkännandeflöden finns.
Orkestreringsverktyg anropar andra modeller. Ett verktyg vars implementation är en annan agent, med sin egen prompt, sina egna verktyg och sin egen loop — och för den anropande modellen ser det ut precis som de andra två, eftersom ett schema och en endpoint är allt den någonsin ser.
Den tredje sorten är ingen kuriositet. Det är mekanismen bakom agent-som-verktyg-halvan av kapitel 25 — den andra topologin, handoff, ger bort konversationen och får den aldrig tillbaka — och den fungerar just för att gränssnittet i det här kapitlet är smalt nog för att en hel agent ska få plats bakom det.
Vart det här går härnäst
Länk till avsnittet: Vart det här går härnästDu har nu en modell som kan be om saker, och ett kontrakt som gör frågandet parsebart. Det du inte har är något för den att fråga om utöver det som får plats i dess prompt.
Det vanligaste verktyget i produktion, med bred marginal, är sökning över en textmassa modellen aldrig såg under träningen: din dokumentation, dina ärenden, dina avtal. Det låter som ett löst problem — embedda den, hitta de närmaste grannarna, klistra in dem — och de delar som inte är lösta är de som avgör om svaret är trovärdigt: hur texten skärs upp innan den embedding, vilken likhetströskel som är låg nog för att betyda jag vet inte, och hur en källhänvisning fästs vid ett påstående så att en läsare kan kontrollera det.
Kapitel 19 är retrieval, och det är kapitlet där ett felaktigt svar slutar vara en kuriositet och börjar bli en skuld.
Källor och metod
Länk till avsnittet: Källor och metodMätningarna i det här kapitlet kommer från Qwen/Qwen2.5-0.5B-Instruct med greedy decoding, över 24 genererade förfrågningar som korsar sex stadspar med fyra datumformuleringar, med modellens egen chattmall för verktygsdefinitioner. De reproduceras exakt, och de är en liten modell: läs format/värde-splitten som en demonstration av mekanismen snarare än som ett benchmark av vad dagens modeller gör. En frontier-modell löser "nästa fredag" korrekt mycket oftare — och kan fortfarande inte tvingas att göra det av ett schema, vilket är delen som generaliserar.
JSON Schema-vokabulären som används ovan (type, properties, required, pattern, format, enum) specificeras i det JSON Schema-utkast som din leverantörs dokumentation namnger; den användbara delmängden är liten och densamma mellan leverantörer, och de skillnader som faktiskt finns — vilka keywords som verkställs av constrained decoding snarare än bara skickas till modellen — är värda att läsa i leverantörens structured-output-guide i stället för att anta.
För constrained decoding som teknik dokumenterar guidance-liknande bibliotek och outlines-projektet konstruktionen från grammatik till logit-mask på ett sätt som mappar direkt till kapitel 17:s sampler. Och för själva rundresan är den tydligaste specifikationen inte en tutorial utan ett protokoll: kapitel 26 läser det rad för rad.
Referenser
Länk till avsnittet: Referenser-
Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). Artikeln som gjorde efterträningsreceptet till standard; formen på ett verktygsanrop lärs in där, från demonstrationer, precis som formen på ett svar. ↩