Zum Inhalt springen
18/30Kapitel 18 von 30

Tool Calling und strukturierte Outputs: Der Vertrag, der hält

24 Calls, kein kaputtes JSON und zwei brauchbare Datumswerte. Dann derselbe Endpoint mit besserer Beschreibung – und was ein Schema nicht löst.

Auf dieser Seite

Gib einem Modell ein Flug-Such-Tool und bitte es, einen Flug von Madrid nach Berlin zu finden. Das kommt zurück:

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

Das JSON ist gültig. Der Tool-Name stimmt. Jedes Pflichtfeld ist vorhanden. Und der Call ist nutzlos: Keine Flug-API akzeptiert "Madrid", wenn sie einen Flughafencode erwartet, oder "3rd October 2026", wenn sie ein Datum erwartet.

Diese Lücke — syntaktisch perfekt, semantisch unbrauchbar — darum geht es in diesem Kapitel. Und als Erstes müssen wir festhalten: Das ist kein JSON-Problem. Über vierundzwanzig Requests mit diesem Tool hinweg erzeugte das Modell 24 gültige tool calls und null kaputtes JSON. Es scheiterte kein einziges Mal an dem Teil, den alle debuggen.

Vor der Mechanik der Satz, der die meiste Verwirrung verhindert: Ein tool call ist eine Anfrage, keine Aktion.

Das Modell gibt eine strukturierte Nachricht aus, die sagt: Ich möchte, dass search_flights mit diesen Argumenten aufgerufen wird. Dann stoppt es. Dein Code empfängt diese Nachricht, entscheidet, ob er sie beachtet, ruft auf, was er aufruft, und sendet das Ergebnis als weitere Nachricht zurück. Das Modell hat nie deine Datenbank berührt, nie einen HTTP-Request gestellt, nie Credentials gehabt.

Alles zur agent-Sicherheit in Kapitel 30 folgt aus dieser Trennung, und genauso alles zum agent-Design in Kapitel 23: Das Modell schlägt vor, dein Code entscheidet, und im Code liegen alle Garantien.

Ein Tool ist also, vom Vokabular befreit, zwei Dinge:

Ein Schema. Ein JSON Schema, das eine Funktion beschreibt: ihren Namen, was sie tut und welche Argumente sie mit welchen Typen und Constraints annimmt. Das landet im prompt, und es ist das Einzige, was das Modell jemals sieht.

Ein Endpoint. Eine Funktion in deinem Code, die diese Argumente nimmt und etwas zurückgibt. Das Modell sieht sie nie, weiß nie, in welcher Sprache sie geschrieben ist, und kann eine Datenbankabfrage nicht von einem hardcodierten String unterscheiden.

Die Tool-Definitionen gehen in den prompt, serialisiert in das Format, auf das das Modell trainiert wurde. Sie kosten bei jedem einzelnen Call token — eine Tatsache, die später in diesem Kapitel mit einer Zahl zurückkommt.

Das Modell antwortet mit einem Call statt mit Text

Link zum Abschnitt: Das Modell antwortet mit einem Call statt mit Text

Statt Prosa enthält die Response eine strukturierte Anfrage, und die API meldet einen entsprechenden finish reason. Dieser Grund ist wichtig: Daran erkennt dein Code, dass er ein Tool ausführen soll, statt dem Nutzer eine Antwort anzuzeigen.

Dein Code führt ihn aus — oder lehnt ab

Link zum Abschnitt: Dein Code führt ihn aus — oder lehnt ab

Das ist der Schritt, in dem kein Modell steckt. Validiere die Argumente gegen das Schema, entscheide, ob dieser Caller das tun darf, und führe aus.

Du sendest das Ergebnis als Nachricht zurück

Link zum Abschnitt: Du sendest das Ergebnis als Nachricht zurück

Das Ergebnis wird zu einem weiteren Turn in der Unterhaltung, in einer dafür reservierten Rolle. Das Modell liest es wie jeden anderen context.

Das Modell antwortet oder fragt nach einem weiteren Tool

Link zum Abschnitt: Das Modell antwortet oder fragt nach einem weiteren Tool

Das ist die Schleife aus Kapitel 23 — und der Grund, warum ein einzelner Request zu einem Dutzend Roundtrips werden kann.

Nichts davon ist emergent. Wie Kapitel 11 gezeigt hat, ist tool calling ein trainiertes Verhalten:1 Während des Post-Trainings sah das Modell Tausende von Unterhaltungen, die genau so geformt waren. Deshalb ist das Format modellspezifisch, deshalb unterscheidet sich die Zuverlässigkeit zwischen ähnlich großen Modellen so stark, und deshalb kann ein Modell ein Tool aufrufen, das es noch nie gesehen hat — die Form wurde trainiert, das konkrete Tool kommt aus deinem prompt.

Was ein schlechtes Schema kostet, gemessen

Link zum Abschnitt: Was ein schlechtes Schema kostet, gemessen

Hier ist das Tool so, wie die meisten es zuerst schreiben. Beachte: Daran ist nichts falsch; es ist nur dünn:

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

Vierundzwanzig Requests, sechs Städtepaare gekreuzt mit vier Arten, ein Datum auszudrücken („der 3. des nächsten Monats“, „nächsten Freitag“, „15. Dezember“, „morgen“), greedy decoding, damit die Ergebnisse reproduzierbar sind:

Tool aufgerufenkaputtes JSONDatum in ISOFlughäfen als IATAalles korrekt
das Schema oben24/2402/244/241/24

Lies die ersten beiden Spalten vor den letzten drei. Das Modell ruft jedes Mal das richtige Tool auf und erzeugt jedes Mal wohlgeformtes JSON. Der Fehler liegt vollständig in den Werten, und die Werte sind unbrauchbar: "Madrid" statt MAD, "3rd October 2026" statt 2026-10-03.

Darauf zu bestehen lohnt sich, weil es bestimmt, wo du suchst, wenn etwas bricht. Der Instinkt ist, einen JSON-Parser mit Retry einzubauen oder das Modell nachdrücklicher um gültiges JSON zu bitten. Beides adressiert nichts von dem, was hier passiert ist.

Derselbe Endpoint. Derselbe Code dahinter. Dasselbe Modell, dieselben prompts, dasselbe decoding. Das Einzige, was sich ändert, ist der Text im 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"],
  },
}
Datums-FORMATDatums-WERTFlughafen-FORMATFlughafen-WERT
dünnes Schema2/241/244/244/24
beschriebenes Schema24/2412/2416/248/24

Das Datumsformat springt von 2 von 24 auf 24 von 24. Perfekt, nur durch eine Textänderung, ohne Code anzufassen und ohne Retry-Logik. Wenn du aus diesem Kapitel eine operative Gewohnheit mitnimmst, dann diese: Wenn ein Tool falsch aufgerufen wird, liegt der Fix fast immer in der Beschreibung, und es ist der billigste Fix im System.

Lies jetzt die zweite Spalte, die wichtigere Hälfte.

Ein Schema beschränkt die Form. Es kann kein Wissen liefern.

Link zum Abschnitt: Ein Schema beschränkt die Form. Es kann kein Wissen liefern.

Das Datum ist 24 von 24 Mal im ISO-Format. Es ist 12 von 24 Mal der richtige Tag.

Die Hälfte der Calls trägt jetzt also ein perfekt formatiertes Datum, das das falsche Datum ist. Die Beschreibung sagte dem Modell, welche Form es erzeugen soll, und das Modell erzeugte sie fehlerlos — aber „nächsten Freitag“ in 2026-09-11 zu verwandeln erfordert, das heutige Datum zu kennen und Kalenderarithmetik zu machen, und keine Beschreibung der Welt liefert das. Dasselbe bei Flughäfen: Das Format ging von 4 auf 16, der Wert aber nur von 4 auf 8, weil MAD zu schreiben erfordert zu wissen, dass Madrids Flughafen MAD ist.

Diese Unterscheidung ist die tragende Idee des Kapitels:

Ein Schema ist ein Vertrag über Form. Es kann den Output des Modells parsebar, typisiert und konsistent machen. Es kann ihn nicht wahr machen, und jeder Fehlermodus, der ein gutes Schema überlebt, ist ein Wissensfehler, kein Formatfehler.

Beides braucht unterschiedliche Fixes, und sie zu verwechseln verschwendet Wochen. Formatfehler behebst du in der Beschreibung oder mit constrained decoding, siehe unten. Wissensfehler behebst du, indem du das Wissen in den prompt legst — das aktuelle Datum in die Systemnachricht, eine Flughafensuche als zweites Tool, das das Modell zuerst aufruft, ein enum im Schema, wenn die Menge klein genug ist, um sie aufzuzählen. Beachte, was alle drei gemeinsam haben: Sie verschieben das Problem aus dem Gedächtnis des Modells in seinen Input, und genau darum geht es in Kapitel 24.

Structured outputs und was „constrained decoding“ wirklich ist

Link zum Abschnitt: Structured outputs und was „constrained decoding“ wirklich ist

Alles oben hängt immer noch davon ab, dass das Modell entscheidet, die richtige Form zu erzeugen. Es gibt eine stärkere Garantie, und sie ist der beste Ertrag aus Kapitel 17.

Erinnere dich, wie Generierung funktioniert: Bei jedem Schritt erzeugt das Modell ein logit für jedes token im Vokabular, und der Sampler wählt eines aus. Constrained decoding fügt dazwischen einen Schritt ein. Aus einer Grammatik — abgeleitet von deinem JSON Schema — berechnet es, welche tokens legal als Nächstes kommen könnten, setzt die logits aller anderen auf minus unendlich und lässt den Sampler aus dem Rest wählen.

Wenn das Schema sagt, dass als Nächstes ein { kommen muss, dann hat jedes token, das nicht { ist, Wahrscheinlichkeit null. Nicht „unwahrscheinlich“: null. Das Modell kann kein ungültiges JSON ausgeben, weil die ungültigen tokens vor dem Sampling aus der Verteilung entfernt wurden.

Das steckt unter „structured outputs“, „JSON mode“ und „guided generation“, und es erklärt ihre zwei Eigenschaften. Die Garantie ist total für alles, was die Grammatik ausdrücken kann — Typen, Pflichtfelder, enums, Verschachtelung —, weil sie mechanisch erzwungen und nicht höflich erbeten wird. Und sie sagt nichts über den Inhalt: Eine Grammatik kann erzwingen, dass "date" ein String ist, der einem Datumsmuster entspricht, aber sie kann nicht erzwingen, dass es der richtige Tag ist. Das ist dieselbe Wand wie im vorherigen Abschnitt, nur von der anderen Seite erreicht.

Zwei praktische Hinweise. Es ist nicht kostenlos: Die Maske muss bei jedem Schritt berechnet werden, und komplexe Grammatiken kosten messbare Latenz. Und es verändert, was das Modell tut — ein Modell, das von seinem bevorzugten token weggesteuert wird, kann schlechtere Inhalte produzieren, während es perfekte Struktur erzeugt. Deshalb ist „nett fragen und validieren“ für einfache Formen weiterhin ein vernünftiger Default, und constrained decoding verdient seine Kosten, wenn die Form komplex ist oder der Consumer streng.

Side Effects und die eine Eigenschaft, die zählt

Link zum Abschnitt: Side Effects und die eine Eigenschaft, die zählt

Kapitel 14 hat gemessen, wie ein Timeout mit anschließendem Retry zwei Generierungen für eine Antwort abrechnet. Mit Tools wird derselbe Fehler schlimmer, weil ein Tool etwas tun kann.

Wenn dein Code charge_card aufruft, in einen Timeout läuft und erneut versucht, hast du zwei Abbuchungen. Das Modell hat keine Ahnung, dass irgendetwas davon passiert ist; es sieht ein Tool-Ergebnis. Der Fix ist derselbe wie in jedem verteilten System, und er ist nicht das Problem des Modells: Mach die Operation idempotent, indem du dem Call einen Schlüssel gibst, sodass die zweite Ausführung die erste erkennt und ihr Ergebnis zurückgibt, statt die Arbeit noch einmal zu erledigen.

Die daraus folgende Designregel sollte klar ausgesprochen werden. Trenne Reads von Writes in deinem Tool-Katalog. Ein Read kann frei erneut versucht, parallel ausgeführt und gecacht werden. Ein Write kann das nicht und sollte einen Schlüssel, eine Berechtigungsprüfung und — für alles, worüber ein Nutzer vorab Bescheid wissen wollen würde — einen Approval-Schritt tragen, der einen Menschen zwischen Anfrage und Aktion stellt. Dieser Approval-Schritt ist keine Höflichkeit: Er ist eines der wenigen Dinge zwischen einer prompt injection und einer realen Konsequenz — und, wie Kapitel 30 misst, das schwächste davon.

Ab wie vielen Tools verschlechtert es sich?

Link zum Abschnitt: Ab wie vielen Tools verschlechtert es sich?

Die Folklore sagt, dass viele geladene Tools das Modell schlechter wählen lassen. Es lohnt sich, das zu messen statt zu wiederholen. Also: dieselben vierundzwanzig Requests, mit dem Flug-Tool plus einer wachsenden Menge anderer Tools — darunter drei absichtlich verwechselbare (Zugfahrpläne, Fährverbindungen, Buslinien).

geladene Toolsprompt tokenswählte search_flightsDatum in ISO
135324/2424/24
573024/2424/24
101.19321/2421/24
202.11924/2424/24

Die Auswahl verschlechterte sich nicht. Mit zwanzig Tools, drei davon plausibel verwechselbar, wählte ein Modell mit einer halben Milliarde Parameter vierundzwanzig von vierundzwanzig Mal das richtige Tool. Der Dip bei zehn sind drei Calls, die ein anderes Tool nannten, und er überlebt den Sprung auf zwanzig nicht.

Das ist ein negatives Ergebnis und sollte auch so berichtet werden: Bei dieser Aufgabe, mit diesen Tools, war „zu viele Tools“ nicht das Problem. Was dagegen monoton und um den Faktor sechs wuchs, ist der prompt: von 353 token auf 2.119, bezahlt bei jedem Request in der Unterhaltung, für immer, egal ob irgendein Tool genutzt wird oder nicht.

Die ehrliche Version der Folklore handelt also von Kosten und context, nicht von Genauigkeit. Zwanzig Tools sind eine permanente Steuer auf jede Nachricht, und Kapitel 16 hat bereits gezeigt, was ein permanenter Präfix über vierzig Turns hinweg mit einer Rechnung macht. Wenn Leute berichten, dass viele Tools die Qualität schädigen, ist der Mechanismus meist, dass die Definitionen den wichtigen context verdrängt haben — ein Kapitel-24-Problem im Kapitel-18-Kostüm. Tools, die tatsächlich Beinahe-Duplikate voneinander sind, sind ebenfalls ein echtes Problem, und der Fix dafür sind nicht weniger Tools, sondern bessere Beschreibungen und Namespaces: Präfixe nach System (crm.search_customer, billing.search_customer), damit zwei Kataloge aus zwei Teams beim Zusammenführen nicht kollidieren und damit das Modell etwas hat, woran es unterscheiden kann.

Drei Arten von Tools und die eine, die den nächsten Teil öffnet

Link zum Abschnitt: Drei Arten von Tools und die eine, die den nächsten Teil öffnet

Es hilft, Tools danach zu sortieren, was sie mit der Welt machen, denn das Engineering unterscheidet sich jeweils.

Data Tools lesen: suchen, abrufen, abfragen. Retrybar, parallelisierbar, cachebar. Sie scheitern, indem sie nichts Nützliches zurückgeben, und ihr Hauptrisiko ist, dass sie nicht vertrauenswürdigen Text in den context bringen — was die gesamte Angriffsfläche von Kapitel 30 ist.

Action Tools schreiben: senden, erstellen, abrechnen, löschen. Ohne Schlüssel nicht retrybar, nicht sicher parallelisierbar und der Grund, warum Approval-Flows existieren.

Orchestration Tools rufen andere Modelle auf. Ein Tool, dessen Implementierung ein anderer agent ist, mit eigenem prompt, eigenen Tools und eigener Schleife — und für das aufrufende Modell sieht es exakt aus wie die anderen beiden, weil ein Schema und ein Endpoint alles sind, was es jemals sieht.

Diese dritte Art ist keine Kuriosität. Sie ist der Mechanismus hinter der agent-as-a-tool-Hälfte von Kapitel 25 — die andere Topologie, das handoff, gibt die Unterhaltung weg und bekommt sie nie zurück — und sie funktioniert genau deshalb, weil das Interface in diesem Kapitel schmal genug ist, dass ein ganzer agent dahinterpasst.

Du hast jetzt ein Modell, das nach Dingen fragen kann, und einen Vertrag, der dieses Fragen parsebar macht. Was du nicht hast, ist irgendetwas, worüber es fragen kann, jenseits dessen, was in seinen prompt passt.

Das mit großem Abstand häufigste Tool in Produktion ist eine Suche über einen Textbestand, den das Modell im Training nie gesehen hat: deine Dokumentation, deine Tickets, deine Verträge. Das klingt nach einem gelösten Problem — embedden, nächste Nachbarn finden, einfügen — und die nicht gelösten Teile sind die, die entscheiden, ob die Antwort vertrauenswürdig ist: wie der Text zerschnitten wird, bevor er embedded wird, welcher Ähnlichkeitsschwellenwert niedrig genug ist, um ich weiß es nicht zu bedeuten, und wie eine Citation an eine Behauptung gehängt wird, damit ein Leser sie prüfen kann.

Kapitel 19 ist Retrieval, und es ist das Kapitel, in dem eine falsche Antwort aufhört, eine Kuriosität zu sein, und anfängt, eine Haftung zu werden.


Die Messungen in diesem Kapitel stammen aus Qwen/Qwen2.5-0.5B-Instruct mit greedy decoding, über 24 generierte Requests hinweg, die sechs Städtepaare mit vier Datumsformulierungen kreuzen, unter Verwendung des modellspezifischen Chat-Templates für Tool-Definitionen. Sie reproduzieren exakt, und es ist ein kleines Modell: Lies die Format/Wert-Trennung als Demonstration des Mechanismus, nicht als Benchmark dessen, was heutige Modelle leisten. Ein Frontier-Modell löst „nächsten Freitag“ deutlich häufiger korrekt — und kann durch ein Schema trotzdem nicht dazu gezwungen werden, was der Teil ist, der generalisiert.

Das oben verwendete JSON Schema-Vokabular (type, properties, required, pattern, format, enum) ist in dem JSON-Schema-Draft spezifiziert, den die Dokumentation deines Providers nennt; die nützliche Teilmenge ist klein und providerübergreifend gleich, und die Unterschiede, die es gibt — welche Keywords durch constrained decoding erzwungen werden, statt nur an das Modell weitergereicht zu werden — sollte man im Structured-Output-Guide des Providers lesen, statt sie anzunehmen.

Für constrained decoding als Technik dokumentieren die Guidance-Style-Bibliotheken und das Projekt outlines die Konstruktion von Grammatik zu logit-Maske auf eine Weise, die direkt auf den Sampler aus Kapitel 17 abbildet. Und für den Roundtrip selbst ist die klarste Spezifikation kein Tutorial, sondern ein Protokoll: Kapitel 26 liest es Zeile für Zeile.

  1. Ouyang, L. et al. Training language models to follow instructions with human feedback. arXiv:2203.02155 (2022). Das Paper, das das Post-Training-Rezept zum Standard gemacht hat; die Form eines tool call wird dort aus Demonstrationen gelernt, genau wie die Form einer Antwort.

Bereit, LIA die Wahl zu überlassen?

Bau mit jedem KI-Modell an einem Ort — starte heute kostenlos.