Tool Calling ja rakenteiset tulosteet: sopimus, joka pitää
24 kutsua, ei rikkinäistä JSONia ja kaksi käyttökelpoista päivämäärää. Mitä parempi kuvaus korjaa — ja mitä schema ei voi korjata.
Tällä sivulla
Anna mallille lennonhakutyökalu ja pyydä sitä etsimään lento Madridista Berliiniin. Tämä palautuu:
<tool_call>
{"name": "search_flights",
"arguments": {"from": "Madrid", "to": "Berlin", "date": "3rd October 2026"}}
</tool_call>JSON on validi. Työkalun nimi on oikea. Jokainen pakollinen kenttä on mukana. Ja kutsu on hyödytön: mikään lento-API ei hyväksy arvoa "Madrid" silloin, kun se haluaa lentokenttäkoodin, tai arvoa "3rd October 2026" silloin, kun se haluaa päivämäärän.
Tuo kuilu — syntaktisesti täydellinen, semanttisesti käyttökelvoton — on tämän luvun aihe, ja ensimmäiseksi on todettava, ettei kyse ole JSON-ongelmasta. Kahdenkymmenenneljän tällä työkalulla tehdyn pyynnön aikana malli tuotti 24 validia tool callia eikä yhtään rikkinäistä JSONia. Se ei kertaakaan epäonnistunut siinä osassa, jota kaikki debuggaavat.
Malli ei suorita mitään
Linkki osioon: Malli ei suorita mitäänEnnen mekaniikkaa lause, joka ehkäisee eniten sekaannusta: tool call on pyyntö, ei toiminto.
Malli tuottaa rakenteisen viestin, joka sanoo haluaisin, että search_flights kutsutaan näillä argumenteilla. Sitten se pysähtyy. Koodisi vastaanottaa viestin, päättää kunnioittaako sitä, kutsuu mitä kutsuukin ja lähettää tuloksen takaisin uutena viestinä. Malli ei koskaan koskenut tietokantaasi, ei tehnyt HTTP-pyyntöä eikä sillä ollut tunnistetietoja.
Kaikki agent-turvallisuudessa luvussa 30 seuraa tästä jaosta, samoin kaikki agent-suunnittelussa luvussa 23: malli ehdottaa ja koodisi päättää, ja koodi on paikka, jossa jokainen takuu elää.
Sanastosta riisuttuna työkalu on siis kaksi asiaa:
Schema. JSON Schema, joka kuvaa funktion: sen nimen, mitä se tekee ja mitä argumentteja se ottaa sekä niiden tyypit ja rajoitteet. Tämä menee promptiin, ja se on ainoa asia, jonka malli koskaan näkee.
Endpoint. Koodissasi oleva funktio, joka ottaa nuo argumentit ja palauttaa jotakin. Malli ei koskaan näe sitä, ei koskaan tiedä millä kielellä se on kirjoitettu eikä pysty erottamaan tietokantakyselyä kovakoodatusta merkkijonosta.
Lähetät schemat pyynnön mukana
Linkki osioon: Lähetät schemat pyynnön mukanaTyökalumäärittelyt menevät promptiin sarjallistettuina siihen muotoon, jolla malli on koulutettu. Ne maksavat tokeneita jokaisella yksittäisellä kutsulla — asia, johon palaamme numeron kanssa myöhemmin tässä luvussa.
Malli vastaa tekstin sijaan kutsulla
Linkki osioon: Malli vastaa tekstin sijaan kutsullaProosan sijaan vastaus sisältää rakenteisen pyynnön, ja API raportoi sen kertovan lopetussyyn. Syyllä on väliä: siitä koodisi tietää, että sen pitää ajaa työkalu eikä näyttää käyttäjälle vastausta.
Koodisi ajaa sen — tai kieltäytyy
Linkki osioon: Koodisi ajaa sen — tai kieltäytyyTämä on vaihe, jossa mallia ei ole mukana. Validoi argumentit schemaa vasten, päätä saako tämä kutsuja tehdä tämän, ja suorita.
Lähetät tuloksen takaisin viestinä
Linkki osioon: Lähetät tuloksen takaisin viestinäTuloksesta tulee keskustelun uusi vuoro sille varatussa roolissa. Malli lukee sen kuten minkä tahansa muun contextin.
Malli vastaa tai pyytää toista työkalua
Linkki osioon: Malli vastaa tai pyytää toista työkaluaTämä on luvun 23 silmukka ja syy siihen, miksi yksi pyyntö voi muuttua tusinaksi edestakaiseksi kierrokseksi.
Mikään tästä ei ole emergenttiä. Kuten luvussa 11 todettiin, tool calling on opittua käyttäytymistä:1 post-training-vaiheessa malli näki tuhansia keskusteluja, jotka oli muotoiltu täsmälleen näin. Siksi muoto on mallikohtainen, siksi luotettavuus vaihtelee niin paljon samankokoisten mallien välillä ja siksi malli voi kutsua työkalua, jota se ei ole koskaan nähnyt — muoto on koulutettu, tietty työkalu tulee promptistasi.
Mitä huono schema maksaa, mitattuna
Linkki osioon: Mitä huono schema maksaa, mitattunaTässä on työkalu sellaisena kuin useimmat sen ensin kirjoittavat. Huomaa, ettei siinä mikään ole väärin; se on vain ohut:
{
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"],
},
}Kaksikymmentäneljä pyyntöä, kuusi kaupunkiparia ristiin neljän päivämäärän ilmaisutavan kanssa ("ensi kuun 3. päivä", "ensi perjantaina", "15. joulukuuta", "huomenna"), greedy decoding, jotta tulokset toistuvat:
| työkalua kutsuttiin | rikkinäinen JSON | päivämäärä ISO-muodossa | lentokentät IATA-koodeina | kaikki oikein | |
|---|---|---|---|---|---|
| yllä oleva schema | 24/24 | 0 | 2/24 | 4/24 | 1/24 |
Lue kaksi ensimmäistä saraketta ennen kolmea viimeistä. Malli kutsuu oikeaa työkalua joka kerta ja tuottaa hyvin muodostettua JSONia joka kerta. Epäonnistuminen on kokonaan arvoissa, ja arvot ovat käyttökelvottomia: "Madrid" arvon MAD sijaan, "3rd October 2026" arvon 2026-10-03 sijaan.
Tätä kannattaa painottaa, koska se määrittää mistä katsot, kun jokin hajoaa. Vaisto on lisätä JSON-parseri ja retry tai pyytää mallia tiukemmin tuottamaan validia JSONia. Kumpikaan ei korjaa mitään, mitä tässä tapahtui.
Muuta nyt vain kuvausta
Linkki osioon: Muuta nyt vain kuvaustaSama endpoint. Sama koodi sen takana. Sama malli, samat promptit, sama decoding. Ainoa muuttuva asia on scheman teksti:
{
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"],
},
}| päivämäärän MUOTO | päivämäärän ARVO | lentokentän MUOTO | lentokentän ARVO | |
|---|---|---|---|---|
| ohut schema | 2/24 | 1/24 | 4/24 | 4/24 |
| kuvattu schema | 24/24 | 12/24 | 16/24 | 8/24 |
Päivämäärän muoto nousee 2:sta 24:stä arvoon 24/24. Täydellinen tulos pelkällä tekstimuutoksella, koskematta koodiin ja ilman retry-logiikkaa. Jos otat tästä luvusta yhden toimintatavan, se on tämä: kun työkalua kutsutaan väärin, korjaus on lähes aina kuvauksessa, ja se on järjestelmän halvin korjaus.
Lue nyt toinen sarake, joka on tärkeämpi puolikas.
Schema rajoittaa muotoa. Se ei voi tuoda tietoa.
Linkki osioon: Schema rajoittaa muotoa. Se ei voi tuoda tietoa.Päivämäärä on ISO-muodossa 24 kertaa 24:stä. Se on oikea päivä 12 kertaa 24:stä.
Puolet kutsuista kantaa siis nyt täydellisesti muotoiltua päivämäärää, joka on väärä päivämäärä. Kuvaus kertoi mallille, minkä muodon tuottaa, ja malli tuotti sen virheettömästi — mutta ilmauksen "ensi perjantaina" muuttaminen arvoksi 2026-09-11 vaatii tämän päivän päivämäärän tietämistä ja kalenteriaritmetiikkaa, eikä mikään määrä kuvausta anna sitä. Sama pätee lentokenttiin: muoto nousi 4:stä 16:een, mutta arvo vain 4:stä 8:aan, koska arvon MAD kirjoittaminen edellyttää tietoa siitä, että Madridin lentokenttä on MAD.
Tämä erottelu on luvun kantava ajatus:
Schema on muotoa koskeva sopimus. Se voi tehdä mallin tulosteesta parsittavan, tyypitetyn ja johdonmukaisen. Se ei voi tehdä siitä totta, ja jokainen hyvän scheman jälkeen jäljelle jäävä epäonnistumistapa on tietoepäonnistuminen, ei muotoepäonnistuminen.
Nämä kaksi tarvitsevat eri korjaukset, ja niiden sekoittaminen tuhlaa viikkoja. Muotoepäonnistumiset korjataan kuvauksessa tai constrained decodingilla, alla. Tietoepäonnistumiset korjataan laittamalla tieto promptiin — nykyinen päivämäärä system-viestiin, lentokenttähaku toiseksi työkaluksi, jota malli kutsuu ensin, tai enum schemaan, kun joukko on tarpeeksi pieni lueteltavaksi. Huomaa, mitä kaikilla kolmella on yhteistä: ne siirtävät ongelman pois mallin muistista sen syötteeseen, mikä on luvun 24 koko ydin.
Rakenteiset tulosteet ja mitä "constrained decoding" oikeasti on
Linkki osioon: Rakenteiset tulosteet ja mitä "constrained decoding" oikeasti onKaikki yllä oleva nojaa yhä siihen, että malli valitsee tuottaa oikean muodon. Saatavilla on vahvempi takuu, ja se on luvun 17 paras tuotto.
Muista, miten generointi toimii: jokaisessa vaiheessa malli tuottaa logitin jokaiselle sanaston tokenille, ja sampler valitsee yhden. Constrained decoding lisää väliin yhden vaiheen. Kun sillä on kielioppi — johdettuna JSON Schemastasi — se laskee, mitkä tokenit voisivat laillisesti tulla seuraavaksi, asettaa kaikkien muiden logitit negatiiviseen äärettömyyteen ja antaa samplerin valita jäljelle jäävistä.
Jos schema sanoo, että seuraavan asian täytyy olla {, jokaisen tokenin, joka ei ole {, todennäköisyys on nolla. Ei "epätodennäköinen": nolla. Malli ei voi tuottaa epävalidia JSONia, koska epävalidit tokenit poistettiin jakaumasta ennen samplingia.
Tätä "structured outputs", "JSON mode" ja "guided generation" ovat pinnan alla, ja se selittää niiden kaksi ominaisuutta. Takuu on täydellinen kaikelle, mitä kielioppi voi ilmaista — tyypit, pakolliset kentät, enumit, sisäkkäisyys — koska se pakotetaan mekaanisesti eikä pyydetä kohteliaasti. Ja se ei sano mitään sisällöstä: kielioppi voi pakottaa arvon "date" olemaan merkkijono, joka vastaa päivämääräkuviota, mutta ei pakottaa sitä olemaan oikea päivä. Tämä on sama seinä kuin edellisessä osiossa, vain toiselta puolelta saavuttuna.
Kaksi käytännön huomiota. Se ei ole ilmaista: maski on laskettava jokaisessa vaiheessa, ja monimutkaiset kieliopit maksavat mitattavaa viivettä. Ja se muuttaa sitä, mitä malli tekee — malli, joka ohjataan pois sen suosimasta tokenista, voi tuottaa huonompaa sisältöä samalla kun rakenne on täydellinen. Siksi "pyydä nätisti ja validoi" on yhä järkevä oletus yksinkertaisille muodoille, ja constrained decoding ansaitsee kustannuksensa, kun muoto on monimutkainen tai kuluttaja on tiukka.
Sivuvaikutukset ja ainoa ominaisuus, jolla on väliä
Linkki osioon: Sivuvaikutukset ja ainoa ominaisuus, jolla on väliäLuvussa 14 mitattiin timeout, jota seurasi retry, ja joka laskutti kaksi generointia yhdestä vastauksesta. Työkalujen kanssa sama epäonnistuminen pahenee, koska työkalu voi tehdä jotakin.
Jos koodisi kutsuu charge_card, saa timeoutin ja yrittää uudelleen, sinulla on kaksi veloitusta. Mallilla ei ole aavistustakaan, että mitään tästä tapahtui; se näkee yhden työkalutuloksen. Korjaus on sama kuin missä tahansa hajautetussa järjestelmässä eikä se ole mallin ongelma: tee operaatiosta idempotentti antamalla kutsulle avain, jotta toinen suoritus tunnistaa ensimmäisen ja palauttaa sen tuloksen sen sijaan, että tekisi työn uudelleen.
Tästä seuraava suunnittelusääntö kannattaa sanoa suoraan. Erota lukuoperaatiot kirjoitusoperaatioista työkalukatalogissasi. Lukua voi yrittää vapaasti uudelleen, ajaa rinnakkain ja cachettaa. Kirjoitusta ei voi, ja sillä pitäisi olla avain, oikeustarkistus ja — kaikessa, mistä käyttäjä haluaisi tietää ennen kuin se tapahtuu — hyväksyntävaihe, joka asettaa ihmisen pyynnön ja toiminnon väliin. Tuo hyväksyntävaihe ei ole kohteliaisuus: se on yksi harvoista asioista, jotka seisovat prompt injectionin ja todellisen seurauksen välissä — ja, kuten luku 30 mittaa, niistä heikoin.
Kuinka monta työkalua ennen kuin laatu heikkenee?
Linkki osioon: Kuinka monta työkalua ennen kuin laatu heikkenee?Perimätieto sanoo, että monen työkalun lataaminen saa mallin valitsemaan huonosti. Se kannattaa mitata eikä toistaa, joten: samat kaksikymmentäneljä pyyntöä, lentotyökalu sekä kasvava joukko muita — mukana kolme tarkoituksella sekoitettavissa olevaa (junien aikataulut, lauttayhteydet, bussireitit).
| ladatut työkalut | prompt-tokenit | valitsi search_flights | päivämäärä ISO-muodossa |
|---|---|---|---|
| 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 |
Valinta ei heikentynyt. Kahdellakymmenellä työkalulla, joista kolme oli uskottavasti sekoitettavissa, puolen miljardin parametrin malli valitsi oikean työkalun kaksikymmentäneljä kertaa kahdestakymmenestäneljästä. Kymmenen kohdalla näkyvä pudotus on kolme kutsua, jotka nimesivät eri työkalun, eikä se säily, kun mennään kahteenkymmeneen.
Tämä on negatiivinen tulos ja se pitäisi raportoida sellaisena: tässä tehtävässä, näillä työkaluilla, "liian monta työkalua" ei ollut ongelma. Se mikä kasvoi, monotonisesti ja kuusinkertaiseksi, oli prompt: 353 tokenista 2,119 tokeniin, maksettuna jokaisesta keskustelun pyynnöstä, ikuisesti, käytettiin työkalua tai ei.
Rehellinen versio perimätiedosta koskee siis kustannusta ja contextia, ei tarkkuutta. Kaksikymmentä työkalua on pysyvä vero jokaiselle viestille, ja luku 16 näytti jo, mitä pysyvä prefiksi tekee laskulle neljänkymmenen vuoron aikana. Kun ihmiset raportoivat, että monet työkalut heikentävät laatua, mekanismi on yleensä se, että määrittelyt syrjäyttivät tärkeän contextin — mikä on luvun 24 ongelma luvun 18 puvussa. Aidosti lähes päällekkäiset työkalut ovat myös todellinen ongelma, ja niiden korjaus ei ole vähemmän työkaluja vaan paremmat kuvaukset ja namespaces: lisää etuliite järjestelmän mukaan (crm.search_customer, billing.search_customer), jotta kahden tiimin kahdesta katalogista yhdistetyt työkalut eivät törmää ja jotta mallilla on jotakin, jonka perusteella erotella.
Kolme työkalulajia ja se yksi, joka avaa seuraavan osan
Linkki osioon: Kolme työkalulajia ja se yksi, joka avaa seuraavan osanTyökaluja kannattaa lajitella sen mukaan, mitä ne tekevät maailmalle, koska kunkin engineering on erilainen.
Datatyökalut lukevat: hakevat, noutavat, kyselevät. Retry-kelpoisia, rinnakkaistettavia, cachetettavia. Ne epäonnistuvat palauttamalla ei-mitään-hyödyllistä, ja niiden suurin riski on, että ne tuovat epäluotettua tekstiä contextiin — mikä on luvun 30 koko hyökkäyspinta.
Toimintotyökalut kirjoittavat: lähettävät, luovat, veloittavat, poistavat. Ei turvallisesti retry-kelpoisia ilman avainta, ei turvallisesti rinnakkaistettavia, ja syy siihen, miksi hyväksyntävirrat ovat olemassa.
Orkestrointityökalut kutsuvat muita malleja. Työkalu, jonka toteutus on toinen agent, omalla promptilla, omilla työkaluilla ja omalla silmukalla — ja kutsuvalle mallille se näyttää täsmälleen samalta kuin kaksi muuta, koska schema ja endpoint ovat kaikki, mitä se koskaan näkee.
Tuo kolmas laji ei ole kuriositeetti. Se on mekanismi luvun 25 agent-as-a-tool-puoliskon takana — toinen topologia, handoff, luovuttaa keskustelun pois eikä koskaan saa sitä takaisin — ja se toimii täsmälleen siksi, että tämän luvun rajapinta on tarpeeksi kapea, jotta kokonainen agent mahtuu sen taakse.
Mihin tämä johtaa seuraavaksi
Linkki osioon: Mihin tämä johtaa seuraavaksiSinulla on nyt malli, joka voi pyytää asioita, ja sopimus, joka tekee pyytämisestä parsittavaa. Sinulla ei ole mitään, mistä se voisi kysyä, paitsi se mikä mahtuu sen promptiin.
Tuotannossa yleisin työkalu on ylivoimaisesti haku tekstimassasta, jota malli ei koskaan nähnyt koulutuksessa: dokumentaatiosi, tukipyyntösi, sopimuksesi. Se kuulostaa ratkaistulta ongelmalta — tee embedding, etsi lähimmät naapurit, liitä ne mukaan — ja ratkaisemattomat osat ovat juuri ne, jotka ratkaisevat, onko vastaus luotettava: miten teksti leikataan ennen embeddingiä, mikä samankaltaisuuskynnys on riittävän matala tarkoittamaan en tiedä, ja miten lähdeviite kiinnitetään väitteeseen, jotta lukija voi tarkistaa sen.
Luku 19 on retrieval, ja se on luku, jossa väärä vastaus lakkaa olemasta kuriositeetti ja alkaa olla vastuu.
Lähteet ja menetelmä
Linkki osioon: Lähteet ja menetelmäTämän luvun mittaukset tulevat kohteesta Qwen/Qwen2.5-0.5B-Instruct greedy decodingilla, 24 generoidun pyynnön yli, joissa risteytettiin kuusi kaupunkiparia neljän päivämäärämuotoilun kanssa, käyttäen mallin omaa chat-templatea työkalumäärittelyille. Ne toistuvat täsmälleen, ja kyseessä on pieni malli: lue muoto/arvo-jako mekanismin demonstraationa eikä benchmarkina siitä, mitä nykyiset mallit tekevät. Frontier-malli ratkaisee "ensi perjantain" oikein paljon useammin — eikä sitä silti voi pakottaa tekemään niin schemalla, mikä on yleistyvä osa.
Yllä käytetty JSON Schema -sanasto (type, properties, required, pattern, format, enum) on määritelty siinä JSON Schema -luonnoksessa, jonka palveluntarjoajasi dokumentaatio nimeää; hyödyllinen osajoukko on pieni ja sama palveluntarjoajasta toiseen, ja olemassa olevat erot — mitkä avainsanat enforced by constrained decoding eivätkä vain välitetty mallille — kannattaa lukea palveluntarjoajan structured-output-oppaasta eikä olettaa.
Constrained decoding -tekniikasta guidance-tyyliset kirjastot ja outlines-projekti dokumentoivat grammar-to-logit-mask-rakenteen tavalla, joka vastaa suoraan luvun 17 sampleria. Ja itse edestakaisesta kierroksesta selkein määrittely ei ole tutoriaali vaan protokolla: luku 26 lukee sen rivi riviltä.
Viitteet
Linkki osioon: Viitteet-
Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). Artikkeli, joka teki post-training-reseptistä standardin; tool callin muoto opitaan siinä demonstraatioista, täsmälleen kuten vastauksen muoto. ↩