Przejdź do treści
18/30Rozdział 18 z 30

Tool Calling i ustrukturyzowane wyjścia: kontrakt, który się trzyma

24 wywołania, zero zepsutego JSON i dwie użyteczne daty. Potem ten sam endpoint z lepszym opisem — i czego schema nie naprawi.

Na tej stronie

Daj modelowi narzędzie do wyszukiwania lotów i poproś, żeby znalazł lot z Madrytu do Berlina. Oto, co wraca:

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

JSON jest poprawny. Nazwa narzędzia się zgadza. Każde wymagane pole jest obecne. A wywołanie jest bezużyteczne: żadne API lotów nie przyjmie "Madrid" tam, gdzie oczekuje kodu lotniska, ani "3rd October 2026" tam, gdzie oczekuje daty.

Ta luka — składniowo perfekcyjne, semantycznie bezużyteczne — jest tematem tego rozdziału, a pierwsza rzecz do ustalenia brzmi: to nie jest problem JSON. W dwudziestu czterech żądaniach z tym narzędziem model wygenerował 24 poprawne wywołania narzędzia i zero zepsutego JSON. Ani razu nie zawiódł w tej części, którą wszyscy debugują.

Zanim przejdziemy do mechaniki, zdanie, które usuwa najwięcej zamieszania: wywołanie narzędzia to prośba, nie akcja.

Model emituje ustrukturyzowaną wiadomość, która mówi: chciałbym, żeby wywołać search_flights z tymi argumentami. Potem się zatrzymuje. Twój kod odbiera tę wiadomość, decyduje, czy ją honorować, wywołuje to, co ma wywołać, i odsyła wynik jako kolejną wiadomość. Model nigdy nie dotknął twojej bazy danych, nigdy nie wykonał żądania HTTP, nigdy nie miał poświadczeń.

Wszystko w bezpieczeństwie agent w rozdziale 30 wynika z tego podziału, tak samo jak wszystko w projektowaniu agent w rozdziale 23: model proponuje, a twój kod rozstrzyga, i to w kodzie mieszka każda gwarancja.

Narzędzie, odarte ze słownictwa, składa się więc z dwóch rzeczy:

Schema. JSON Schema opisująca funkcję: jej nazwę, co robi oraz jakie argumenty przyjmuje wraz z ich typami i ograniczeniami. To trafia do prompt i jest jedyną rzeczą, którą model kiedykolwiek widzi.

Endpoint. Funkcja w twoim kodzie, która przyjmuje te argumenty i coś zwraca. Model nigdy jej nie widzi, nigdy nie wie, w jakim jest języku, i nie potrafi odróżnić zapytania do bazy danych od zahardkodowanego ciągu znaków.

Wysyłasz schematy razem z żądaniem

Link do sekcji: Wysyłasz schematy razem z żądaniem

Definicje narzędzi trafiają do prompt, zserializowane do formatu, na którym trenowano model. Kosztują token przy każdym pojedynczym wywołaniu — fakt, który wróci z konkretną liczbą później w tym rozdziale.

Model odpowiada wywołaniem zamiast tekstem

Link do sekcji: Model odpowiada wywołaniem zamiast tekstem

Zamiast prozy odpowiedź zawiera ustrukturyzowaną prośbę, a API zgłasza powód zakończenia, który to sygnalizuje. Ten powód ma znaczenie: dzięki niemu twój kod wie, że ma uruchomić narzędzie, zamiast pokazać użytkownikowi odpowiedź.

Twój kod je uruchamia — albo odmawia

Link do sekcji: Twój kod je uruchamia — albo odmawia

To jest krok, w którym nie ma modelu. Zweryfikuj argumenty względem schema, zdecyduj, czy ten wywołujący ma prawo to zrobić, i wykonaj.

Wynik staje się kolejną turą w rozmowie, w roli zarezerwowanej właśnie dla niego. Model czyta go jak każdy inny context.

Model odpowiada albo prosi o kolejne narzędzie

Link do sekcji: Model odpowiada albo prosi o kolejne narzędzie

To pętla z rozdziału 23 i powód, dla którego jedno żądanie może zamienić się w tuzin rund tam i z powrotem.

Nic z tego nie jest emergentne. Jak ustalił rozdział 11, tool calling to wytrenowane zachowanie:1 podczas post-training model zobaczył tysiące rozmów ukształtowanych dokładnie w ten sposób. Dlatego format jest specyficzny dla modelu, dlatego niezawodność tak bardzo różni się między modelami o podobnym rozmiarze i dlatego model może wywołać narzędzie, którego nigdy wcześniej nie widział — kształt został wytrenowany, konkretne narzędzie pochodzi z twojego prompt.

Ile kosztuje zły schema, zmierzone

Link do sekcji: Ile kosztuje zły schema, zmierzone

Oto narzędzie w wersji, w której większość osób pisze je na początku. Zauważ, że nic w nim nie jest błędne; jest po prostu cienkie:

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"],
  },
}

Dwadzieścia cztery żądania, sześć par miast skrzyżowanych z czterema sposobami wyrażenia daty („trzeci dzień przyszłego miesiąca”, „następny piątek”, „15 grudnia”, „jutro”), greedy decoding, żeby wyniki dało się odtworzyć:

narzędzie wywołanezepsuty JSONdata w ISOlotniska jako IATAwszystko poprawne
schema powyżej24/2402/244/241/24

Przeczytaj pierwsze dwie kolumny przed ostatnimi trzema. Model za każdym razem wywołuje właściwe narzędzie i za każdym razem generuje poprawnie uformowany JSON. Porażka leży całkowicie w wartościach, a wartości są bezużyteczne: "Madrid" zamiast MAD, "3rd October 2026" zamiast 2026-10-03.

Warto to podkreślić, bo decyduje o tym, gdzie patrzysz, gdy coś się psuje. Odruch każe dodać parser JSON z ponowieniem albo poprosić model bardziej stanowczo o poprawny JSON. Żadne z tych rozwiązań nie dotyka tego, co wydarzyło się tutaj.

Ten sam endpoint. Ten sam kod za nim. Ten sam model, te same prompts, to samo decoding. Zmienia się tylko tekst w schema:

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 datyWARTOŚĆ datyFORMAT lotniskaWARTOŚĆ lotniska
cienki schema2/241/244/244/24
opisany schema24/2412/2416/248/24

Format daty rośnie z 2 na 24 do 24 na 24. Perfekcyjnie, po zmianie tekstu, bez dotykania kodu i bez logiki ponowień. Jeśli masz wynieść z tego rozdziału jeden nawyk operacyjny, niech będzie to: gdy narzędzie jest wywoływane źle, poprawka prawie zawsze jest w opisie, a to najtańsza poprawka w systemie.

Teraz przeczytaj drugą kolumnę, bo to ważniejsza połowa.

Schema ogranicza kształt. Nie potrafi dostarczyć wiedzy.

Link do sekcji: Schema ogranicza kształt. Nie potrafi dostarczyć wiedzy.

Data jest w formacie ISO 24 razy na 24. Jest właściwym dniem 12 razy na 24.

Połowa wywołań niesie więc perfekcyjnie sformatowaną datę, która jest złą datą. Opis powiedział modelowi, jaki kształt ma wygenerować, a model zrobił to bezbłędnie — ale zamiana „następnego piątku” na 2026-09-11 wymaga znajomości dzisiejszej daty i arytmetyki kalendarzowej, a żadna ilość opisu tego nie dostarczy. Z lotniskami jest tak samo: format wzrósł z 4 do 16, ale wartość tylko z 4 do 8, bo napisanie MAD wymaga wiedzy, że lotnisko Madrytu to MAD.

To rozróżnienie jest ideą nośną rozdziału:

Schema to kontrakt dotyczący formy. Może sprawić, że wyjście modelu będzie parsowalne, typowane i spójne. Nie może sprawić, że będzie prawdziwe, a każdy tryb awarii, który przetrwa dobry schema, jest awarią wiedzy, nie formatu.

Te dwie rzeczy wymagają różnych napraw, a mylenie ich marnuje tygodnie. Awarie formatu naprawia się w opisie albo przez constrained decoding, poniżej. Awarie wiedzy naprawia się przez włożenie wiedzy do prompt — bieżącej daty w wiadomości systemowej, wyszukiwarki lotnisk jako drugiego narzędzia, które model wywołuje najpierw, albo enum w schema, gdy zbiór jest na tyle mały, że da się go wyliczyć. Zauważ, co łączy wszystkie trzy: przenoszą problem z pamięci modelu do jego wejścia, czyli dokładnie do tematu rozdziału 24.

Ustrukturyzowane wyjścia i czym naprawdę jest „constrained decoding”

Link do sekcji: Ustrukturyzowane wyjścia i czym naprawdę jest „constrained decoding”

Wszystko powyżej nadal polega na tym, że model wybiera wygenerowanie właściwego kształtu. Dostępna jest mocniejsza gwarancja i to najlepszy zwrot z rozdziału 17.

Przypomnij sobie, jak działa generowanie: na każdym kroku model produkuje logit dla każdego token w słowniku, a sampler wybiera jeden. Constrained decoding wstawia krok pomiędzy. Mając gramatykę — wyprowadzoną z twojej JSON Schema — oblicza, które tokens mogłyby legalnie pojawić się jako następne, ustawia logits wszystkich pozostałych na minus nieskończoność i pozwala samplerowi wybrać z tego, co zostało.

Jeśli schema mówi, że następną rzeczą musi być {, wtedy każdy token, który nie jest {, ma prawdopodobieństwo zero. Nie „mało prawdopodobne”: zero. Model nie może wyemitować niepoprawnego JSON, bo niepoprawne tokens zostały usunięte z rozkładu przed samplingiem.

To właśnie kryje się pod „ustrukturyzowanymi wyjściami”, „trybem JSON” i „generowaniem sterowanym”, i wyjaśnia ich dwie właściwości. Gwarancja jest pełna dla wszystkiego, co gramatyka potrafi wyrazić — typów, wymaganych pól, enum, zagnieżdżeń — bo jest egzekwowana mechanicznie, a nie grzecznie wyproszona. I nie mówi nic o treści: gramatyka może wymusić, żeby "date" było ciągiem znaków pasującym do wzorca daty, ale nie może wymusić, żeby był to właściwy dzień. To ta sama ściana co w poprzedniej sekcji, tylko osiągnięta od drugiej strony.

Dwie praktyczne uwagi. To nie jest darmowe: maska musi być liczona na każdym kroku, a złożone gramatyki kosztują mierzalne opóźnienie. I zmienia to, co robi model — model odciągnięty od preferowanego token może generować gorszą treść, jednocześnie generując perfekcyjną strukturę, dlatego „poproś ładnie i waliduj” nadal jest rozsądną domyślną opcją dla prostych kształtów, a constrained decoding zarabia na swój koszt wtedy, gdy kształt jest złożony albo konsument jest rygorystyczny.

Efekty uboczne i jedyna właściwość, która ma znaczenie

Link do sekcji: Efekty uboczne i jedyna właściwość, która ma znaczenie

Rozdział 14 zmierzył timeout, po którym ponowienie rozliczyło dwie generacje za jedną odpowiedź. Z narzędziami ta sama awaria robi się gorsza, bo narzędzie może coś zrobić.

Jeśli twój kod wywołuje charge_card, dostaje timeout i ponawia, masz dwa obciążenia. Model nie ma pojęcia, że cokolwiek z tego się wydarzyło; widzi jeden wynik narzędzia. Poprawka jest taka sama jak w każdym systemie rozproszonym i nie jest problemem modelu: spraw, żeby operacja była idempotentna, nadając wywołaniu klucz, tak aby drugie wykonanie rozpoznało pierwsze i zwróciło jego wynik zamiast wykonać pracę ponownie.

Regułę projektową, która z tego wynika, warto powiedzieć wprost. Oddziel odczyty od zapisów w swoim katalogu narzędzi. Odczyt można swobodnie ponawiać, uruchamiać równolegle i cachować. Zapisu nie można robić bezpiecznie w ten sposób; powinien mieć klucz, kontrolę uprawnień oraz — przy wszystkim, o czym użytkownik chciałby wiedzieć, zanim się wydarzy — krok zatwierdzenia, który stawia człowieka między prośbą a akcją. Ten krok zatwierdzenia nie jest uprzejmością: to jedna z niewielu rzeczy stojących między prompt injection a realną konsekwencją — i, jak mierzy rozdział 30, najsłabsza z nich.

Ile narzędzi, zanim jakość spada?

Link do sekcji: Ile narzędzi, zanim jakość spada?

Folklor mówi, że załadowanie wielu narzędzi sprawia, że model źle wybiera. Warto to zmierzyć, zamiast powtarzać, więc: te same dwadzieścia cztery żądania, z narzędziem lotów plus rosnącym zestawem innych — w tym trzema celowo mylącymi się ze sobą (rozkłady pociągów, przeprawy promowe, trasy autobusowe).

załadowane narzędziaprompt tokenswybrał search_flightsdata w ISO
135324/2424/24
573024/2424/24
101,19321/2421/24
202,11924/2424/24

Wybór się nie pogorszył. Przy dwudziestu narzędziach, z których trzy mogły wiarygodnie mylić się ze sobą, model o pół miliarda parametrów wybrał właściwe dwadzieścia cztery razy na dwadzieścia cztery. Spadek przy dziesięciu to trzy wywołania, które nazwały inne narzędzie, i nie utrzymuje się przy przejściu do dwudziestu.

To wynik negatywny i należy go tak raportować: w tym zadaniu, z tymi narzędziami, „zbyt wiele narzędzi” nie było problemem. Tym, co rosło monotonicznie i sześciokrotnie, był prompt: z 353 tokens do 2,119, płacone przy każdym żądaniu w rozmowie, na zawsze, niezależnie od tego, czy jakiekolwiek narzędzie zostanie użyte.

Uczciwa wersja folkloru dotyczy więc kosztu i context, nie accuracy. Dwadzieścia narzędzi to stały podatek od każdej wiadomości, a rozdział 16 pokazał już, co stały prefiks robi z rachunkiem przez czterdzieści tur. Gdy ludzie raportują, że wiele narzędzi szkodzi jakości, mechanizm zwykle polega na tym, że definicje wypchnęły context, który miał znaczenie — czyli problem z rozdziału 24 w kostiumie rozdziału 18. Narzędzia, które są naprawdę bliskimi duplikatami, też są realnym problemem, a naprawą dla nich nie jest mniej narzędzi, tylko lepsze opisy i namespaces: prefiksuj je według systemu (crm.search_customer, billing.search_customer), żeby dwa katalogi scalone z dwóch zespołów nie kolidowały i żeby model miał po czym rozróżniać.

Trzy rodzaje narzędzi i ten jeden, który otwiera następną część

Link do sekcji: Trzy rodzaje narzędzi i ten jeden, który otwiera następną część

Pomaga posortować narzędzia według tego, co robią światu, bo inżynieria różni się w każdym przypadku.

Narzędzia danych czytają: search, fetch, query. Można je ponawiać, równoleglić i cachować. Zawodzą, zwracając nic użytecznego, a ich głównym ryzykiem jest to, że wnoszą niezaufany tekst do context — czyli całej powierzchni ataku z rozdziału 30.

Narzędzia akcji zapisują: wysyłają, tworzą, obciążają, usuwają. Nie da się ich ponawiać bez klucza, nie da się ich bezpiecznie równoleglić i są powodem istnienia przepływów zatwierdzania.

Narzędzia orkiestracji wywołują inne modele. Narzędzie, którego implementacją jest inny agent, z własnym prompt, własnymi narzędziami i własną pętlą — a dla modelu wywołującego wygląda dokładnie tak samo jak pozostałe dwa, bo schema i endpoint to wszystko, co kiedykolwiek widzi.

Ten trzeci rodzaj nie jest ciekawostką. To mechanizm stojący za połową „agent jako narzędzie” z rozdziału 25 — druga topologia, handoff, oddaje rozmowę i nigdy jej nie odzyskuje — i działa właśnie dlatego, że interfejs w tym rozdziale jest na tyle wąski, że cały agent mieści się za nim.

Masz teraz model, który może prosić o rzeczy, i kontrakt, który sprawia, że proszenie jest parsowalne. Nie masz natomiast niczego, o czym mógłby prosić, poza tym, co mieści się w jego prompt.

Zdecydowanie najczęstsze narzędzie w produkcji to wyszukiwanie po korpusie tekstu, którego model nigdy nie widział podczas treningu: twojej dokumentacji, twoich zgłoszeniach, twoich kontraktach. Brzmi jak rozwiązany problem — embed, znajdź najbliższych sąsiadów, wklej ich — a części nierozwiązane są tymi, które decydują, czy odpowiedź jest godna zaufania: jak tekst jest cięty, zanim zostanie embedded, jaki próg podobieństwa jest na tyle niski, że znaczy nie wiem, i jak cytowanie zostaje przypięte do twierdzenia, żeby czytelnik mógł je sprawdzić.

Rozdział 19 to retrieval i rozdział, w którym zła odpowiedź przestaje być ciekawostką, a zaczyna być odpowiedzialnością.


Pomiary w tym rozdziale pochodzą z Qwen/Qwen2.5-0.5B-Instruct z greedy decoding, na 24 wygenerowanych żądaniach krzyżujących sześć par miast z czterema sformułowaniami daty, przy użyciu własnego szablonu czatu modelu dla definicji narzędzi. Odtwarzają się dokładnie i dotyczą małego modelu: traktuj podział format/wartość jako demonstrację mechanizmu, a nie benchmark tego, co robią obecne modele. Frontier model rozwiązuje „następny piątek” poprawnie znacznie częściej — i nadal nie da się go do tego zmusić przez schema, co jest częścią, która się uogólnia.

Słownictwo JSON Schema użyte powyżej (type, properties, required, pattern, format, enum) jest określone w szkicu JSON Schema wskazanym w dokumentacji twojego dostawcy; użyteczny podzbiór jest mały i taki sam u dostawców, a istniejące różnice — które słowa kluczowe są egzekwowane przez constrained decoding, a nie tylko przekazywane modelowi — warto przeczytać w przewodniku dostawcy po ustrukturyzowanych wyjściach, zamiast zakładać.

W przypadku constrained decoding jako techniki biblioteki w stylu guidance oraz projekt outlines dokumentują konstrukcję gramatyka–maska logit w sposób, który mapuje się bezpośrednio na sampler z rozdziału 17. A dla samej rundy tam i z powrotem najjaśniejszą specyfikacją nie jest tutorial, lecz protokół: rozdział 26 czyta go linia po linii.

  1. Ouyang, L. i in. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). Artykuł, który uczynił przepis na post-training standardem; kształt wywołania narzędzia jest tam uczony z demonstracji, dokładnie tak jak kształt odpowiedzi.

Gotowy, żeby to LIA wybierała za Ciebie?

Twórz ze wszystkimi modelami AI w jednym miejscu — zacznij dziś za darmo.