+200 XP

Function Calling und Structured Outputs

# Function Calling und Structured Outputs

Der schnellste Weg, aus einem Chatbot ein System zu machen: Sie geben dem Modell eine Reihe von Funktionen, die es aufrufen kann, und verlangen Antworten als striktes JSON, dem Ihr Code vertrauen kann. Diese Lektion geht bei beiden Mechanismen der OpenAI API in die Tiefe: Tool Calling (die Schleife, in der das Modell Ihren Code bittet, etwas auszuführen) und Structured Outputs (die Garantie, dass die Antwort des Modells Ihrem Schema entspricht). Sie lösen unterschiedliche Probleme und lassen sich hervorragend kombinieren.

Wir verwenden die Responses API, die aktuelle Hauptschnittstelle von OpenAI. Chat Completions funktioniert weiterhin und nutzt nahezu identische Konzepte, aber das Ökosystem bewegt sich in Richtung Responses.

Zwei Mechanismen, zwei Aufgaben

Halten Sie diese beiden Dinge gedanklich getrennt:

  • Function (Tool) Calling: Das Modell entscheidet, dass es externe Daten oder eine Aktion braucht, und gibt eine strukturierte Anfrage aus, um eine *Ihrer* Funktionen aufzurufen. Ihr Code führt sie aus, gibt das Ergebnis zurück, und das Modell macht weiter. So greift das Modell über seinen Kontext hinaus: Datenbanken, Ihre API, ein Rechner, das Wetter.
  • Structured Outputs: Sie erzwingen, dass die *endgültige Antwort* des Modells exakt einem JSON Schema entspricht. Kein Prosatext, keine Markdown-Fences, kein „Klar, hier ist Ihr JSON“. Nur gültige, parsebare Daten, jedes Mal.

Oft nutzt man beides: Tools, um Fakten zu sammeln, Structured Outputs, um das Ergebnis zu verpacken.

Ein Tool definieren

Ein Tool ist eine JSON-Beschreibung einer Funktion: ein Name, eine Beschreibung und ein Parameter-Schema. Die Beschreibung ist keine Dekoration. Das Modell liest sie, um zu entscheiden, *wann* und *wie* es die Funktion aufruft. Schreiben Sie sie also wie eine Dokumentation für einen Junior-Entwickler.

python
from openai import OpenAI

client = OpenAI()

tools = [{
    "type": "function",
    "name": "get_weather",
    "description": "Get the current temperature for a city in Celsius.",
    "parameters": {
        "type": "object",
        "properties": {
            "city": {"type": "string", "description": "City name, e.g. 'Lisbon'"}
        },
        "required": ["city"],
        "additionalProperties": False
    }
}]

Zwei Details sind wichtig. required listet die Argumente auf, die das Modell liefern muss, und additionalProperties: False verhindert, dass das Modell zusätzliche Felder erfindet. Beides führt zu vorhersehbaren Aufrufen.

Die Tool-Calling-Schleife

Hier machen die meisten einen Fehler: Das Modell führt Ihre Funktion nicht aus. Es gibt eine *Anfrage* zurück, sie aufzurufen. Sie führen die Funktion aus, geben das Ergebnis zurück und rufen das Modell erneut auf. Dieser Round Trip ist die Schleife, und sie gehört Ihnen.

Der Ablauf:

1. Senden Sie die User-Nachricht plus Ihre Tool-Definitionen.

2. Das Modell antwortet mit einem oder mehreren function_call-Items (oder mit einer normalen Antwort, wenn kein Tool nötig ist).

3. Ihr Code führt jeden Aufruf aus und hängt ein function_call_output mit dem Ergebnis an.

4. Sie rufen das Modell erneut mit dem aktualisierten Input auf. Jetzt schreibt es die endgültige Antwort.

python
import json

def get_weather(city):
    # Tun wir so, als würde das eine echte API ansprechen.
    return {"city": city, "temp_c": 19}

input_list = [{"role": "user", "content": "What's the weather in Lisbon?"}]

response = client.responses.create(
    model="gpt-4.1",
    tools=tools,
    input=input_list,
)

# Den Output des Modells als Teil der Konversation weitertragen.
input_list += response.output

for item in response.output:
    if item.type == "function_call":
        args = json.loads(item.arguments)
        result = get_weather(**args)
        input_list.append({
            "type": "function_call_output",
            "call_id": item.call_id,
            "output": json.dumps(result),
        })

final = client.responses.create(
    model="gpt-4.1",
    tools=tools,
    input=input_list,
)
print(final.output_text)

Beachten Sie call_id. Das Modell kann in einem Zug mehrere Tool Calls anfragen (parallel Tool Calling), und jeder Output muss per ID seinem Aufruf zugeordnet werden. Hängen Sie jedes Ergebnis an, bevor Sie die Folge-Anfrage stellen.

Praktische Regeln für die Schleife

  • Schleife bauen, nicht von einer Runde ausgehen. Nachdem Sie Tool-Outputs zurückgegeben haben, ruft das Modell möglicherweise ein weiteres Tool auf. Packen Sie den Schritt aus Anfrage und Ausführung in eine while-Schleife, die läuft, bis keine function_call-Items mehr zurückkommen. Begrenzen Sie sie (etwa auf 8 Iterationen), damit ein verwirrtes Modell sich nicht endlos dreht.
  • Argumente vor der Ausführung validieren. Das Schema beschränkt das Modell, behandeln Sie Tool-Argumente aber wie jeden nicht vertrauenswürdigen Input. Geben Sie sie niemals direkt in eine Shell, einen SQL-String oder einen Dateipfad.
  • Funktionen eng halten. get_order_status(order_id) ist besser als eine Mega-Funktion mit mode-Flag. Enge Tools kann das Modell leichter korrekt auswählen, und Sie können sie leichter absichern.
  • Bei Bedarf die Auswahl steuern. Nutzen Sie tool_choice="auto" (Standard), "required", um *irgendein* Tool zu erzwingen, oder nennen Sie ein bestimmtes Tool, um genau dieses zu erzwingen. Setzen Sie parallel_tool_calls=False, wenn Ihre Tools sequenziell laufen müssen.

Structured Outputs: Schluss mit dem Parsen von Prosa

Tool Calling deckt *Aktionen* ab. Structured Outputs deckt die *Form der Antwort* ab. Wenn Sie strict: true setzen und ein Schema mitgeben, beschränkt die API die Generierung so, dass der Output nachweislich Ihrem Schema entspricht. Das ist stärker als der alte Prompt-Trick „antworte in JSON“, der meistens gültiges JSON produzierte und um 2 Uhr nachts kaputtging.

Der saubere Weg in Python: Definieren Sie Ihr Schema als Pydantic-Modell und lassen Sie das SDK es für Sie parsen.

python
from pydantic import BaseModel

class CalendarEvent(BaseModel):
    name: str
    date: str
    participants: list[str]

response = client.responses.parse(
    model="gpt-4.1",
    input=[
        {"role": "system", "content": "Extract the event details."},
        {"role": "user", "content": "Standup with Ana and Rui on Friday."},
    ],
    text_format=CalendarEvent,
)

event = response.output_parsed
print(event.participants)  # ['Ana', 'Rui']

output_parsed liefert Ihnen ein typisiertes Objekt, keinen String, den Sie durch json.loads schicken und dabei beten müssen. Wenn Sie nicht in Python arbeiten, übergeben Sie ein rohes JSON Schema in text.format mit "type": "json_schema" und "strict": true und erhalten garantiert konformen JSON-Text zurück.

Schema-Design, das funktioniert

  • Jede Property ist faktisch required. Der Strict Mode behandelt alle Keys als erforderlich. Um ein Feld „optional“ zu machen, geben Sie ihm eine Union mit null, zum Beispiel Optional[str] in Pydantic, und prüfen dann in Ihrem Code auf null.
  • Enums zur Beschränkung von Auswahlmöglichkeiten nutzen. Wenn status nur open, pending oder closed sein kann, definieren Sie es als Enum. Das Modell kann dann nicht in-progress zurückgeben und Ihnen weiter unten Überraschungen bereiten.
  • Beschreibungen steuern Werte. Feldbeschreibungen im Schema steuern, *was* in jedes Feld kommt, nicht nur die Typen. Nutzen Sie sie.
  • Auf die unterstützte Teilmenge achten. Structured Outputs unterstützt einen definierten Teil von JSON Schema. Muster wie minimum, maximum und manche format-Constraints werden möglicherweise nicht erzwungen. Prüfen Sie den Structured Outputs Guide, bevor Sie sich auf ein Keyword verlassen.

OpenAI Function Calling and Structured Outputs Explained

Watch on YouTube

Beides kombinieren: das realistische Muster

Die meisten Produktivfeatures nutzen beides zusammen. Stellen Sie sich einen Support-Assistenten vor, der eine Frage zur Rückerstattung beantwortet:

1. Das Modell ruft lookup_order(order_id) auf (Tool Calling), um echte Daten zu holen.

2. Sie geben den Bestelldatensatz zurück.

3. Das Modell erzeugt eine endgültige Antwort, beschränkt auf ein RefundDecision-Schema (Structured Outputs): ein boolesches eligible, ein Enum reason und ein String customer_message.

Ihr Anwendungscode parst nie Freitext. Er liest decision.eligible und verzweigt. Genau darum geht es: Das Modell übernimmt Sprache und Urteil, Ihr Code übernimmt den Kontrollfluss, und die Grenze dazwischen ist ein typisierter Vertrag.

Eine Feinheit, die man kennen sollte: Tool-*Parameter* und Structured *Outputs* sind getrennte Schema-Slots. Der eine formt den eingehenden Aufruf, der andere die ausgehende Antwort. Sie können jeweils einen allein oder beide zusammen in derselben Anfrage verwenden.

Wissenscheck

1. Was ist der grundlegende Unterschied zwischen Function (Tool) Calling und Structured Outputs?

2. Was passiert in der Tool-Calling-Schleife tatsächlich, wenn das Modell eine Funktion „aufruft“?

3. Warum betont die Lektion, die „description“ eines Tools wie eine Dokumentation für einen Junior-Entwickler zu schreiben?

MEHRFACHAUSWAHL

4. Wählen Sie ALLE korrekten Aussagen zu den in der Lektion besprochenen Feldern des Tool-Parameter-Schemas.

Wählen Sie alle richtigen Antworten aus.

MEHRFACHAUSWAHL

5. Wählen Sie ALLE Szenarien, die die in der Lektion beschriebenen Einsatzzwecke korrekt widerspiegeln.

Wählen Sie alle richtigen Antworten aus.

Fehler, Kosten und Failure Modes

Echte Systeme brechen auf bestimmte Weisen. Planen Sie dafür.

  • Das Modell halluziniert ein Tool, das nicht existiert, oder falsche Argumente. Mit strikten Tool-Schemata ist das selten, aber wenn eine Funktion wirklich fehlschlägt, geben Sie einen Fehler *als Tool-Output* zurück (zum Beispiel {"error": "order not found"}) statt eine Exception zu werfen. Das Modell kann das lesen und sich erholen, indem es den Nutzer nach einer korrekten Order-ID fragt.
  • Refusals. Structured Outputs kann aus Sicherheitsgründen dennoch verweigern. Das SDK macht das sichtbar, sodass Sie ein Refusal-Feld prüfen, bevor Sie output_parsed vertrauen. Behandeln Sie das explizit, statt eine Verweigerung als fehlerhafte Daten zu werten.
  • Truncation. Wenn das Modell mitten im JSON das Output-Token-Limit erreicht, erhalten Sie auch im Strict Mode unvollständige Daten, weil die Beschränkung *gültige Struktur* garantiert, nicht *Vollständigkeit*. Prüfen Sie den Response-Status auf incomplete und erhöhen Sie max_output_tokens für große Objekte.
  • Latenz bei der ersten Nutzung eines neuen Schemas. Die allererste Anfrage mit einem neuen strikten Schema kann langsamer sein, während die API die Beschränkung vorbereitet. Wiederholte Schemata sind schnell. Verwenden Sie Schemata wieder, statt sie pro Anfrage dynamisch zu generieren.
  • Tokens. Tool-Definitionen und Schemata liegen im Context Window und zählen bei jedem Aufruf als Input Tokens. Lange, wortreiche Schemata über viele Tools summieren sich. Halten Sie Beschreibungen knapp und hängen Sie nur Tools an, die das Modell für diese Anfrage plausibel brauchen könnte.

Wo das im Ökosystem steht

Sie haben jetzt die rohe Mechanik gesehen. Höherstufige OpenAI-Produkte sind auf genau diesen Primitiven gebaut:

  • GPT Actions in Custom GPTs sind Function Calling, gesteuert durch eine OpenAPI-Spezifikation. Sie beschreiben Ihre API einmal, und der GPT ruft sie über dasselbe Muster aus Anfrage und Ausführung auf, nur verwaltet von ChatGPT statt von Ihrem Code.
  • Das Agents SDK kapselt die Tool-Schleife, Retries und Handoffs, sodass Sie die while-Schleife nicht mehr selbst schreiben. Wenn Ihre Tool-Orchestrierung komplex wird (mehrere Agents, Guardrails, Tracing), wechseln Sie dorthin, statt eigenen Schleifencode zu pflegen. Siehe die Agents SDK Docs.
  • Built-in Tools wie Web Search und File Search sind Function Calls, die die API serverseitig ausführt. Sie aktivieren sie in tools, ohne den Executor zu schreiben.

Wer die nackte Schleife zuerst versteht, hält davon nichts für Magie. Es sind Bequemlichkeiten über dem Vertrag, den Sie gerade gelernt haben.

Key Takeaways

  • Tool Calling ist eine Schleife, die Ihnen gehört. Das Modell *fragt* eine Funktion an; Ihr Code führt sie aus, gibt den Output mit call_id zurück und ruft das Modell erneut auf. Schleife, bis keine Tool Calls mehr kommen, mit einer harten Iterationsgrenze.
  • Nutzen Sie Structured Outputs mit `strict: true` für jede Antwort, die Ihr Code parst. Das garantiert die Form und beendet fragiles JSON nach dem Prinzip Prompt-and-Pray. In Python: Pydantic-Modell definieren und output_parsed lesen.
  • Schemata defensiv entwerfen. Alle Felder als required behandeln (Null-Unions für optionale), Auswahlmöglichkeiten mit Enums beschränken, Feldbeschreibungen schreiben und prüfen, ob Ihre Keywords in der unterstützten Teilmenge liegen.
  • Tool-Fehler als Daten zurückgeben und auf Refusals und Truncation prüfen. Lassen Sie das Modell sich von fehlgeschlagenen Aufrufen erholen; prüfen Sie den Response-Status, bevor Sie der Payload vertrauen.
  • Greifen Sie zum Agents SDK oder zu GPT Actions, wenn die Schleife wächst. Sie sind auf genau diesen Primitiven gebaut, das Mentalmodell überträgt sich direkt.

Was Sie aus dieser Lektion umsetzen

Diese Maßnahmen sind im Playbook der Rolle zusammengefasst.

  • Neuen Code auf der Responses API starten und response.usage von Tag eins an auslesen
Vollständiges Action Playbook ansehen