+200 XP

ChatGPT · 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, dass seine Antworten als striktes JSON zurückkommen, dem Ihr Code vertrauen kann. Diese Lektion behandelt beide Mechanismen in der OpenAI API im Detail.

  • Tool Calling ist der Loop, in dem das Modell Ihren Code bittet, etwas auszuführen.
  • Structured Outputs ist die Garantie, dass die Antwort des Modells Ihrem Schema entspricht.

Sie lösen unterschiedliche Probleme und funktionieren gut zusammen.

Wir verwenden die Responses API, die primäre Schnittstelle von OpenAI. Chat Completions funktioniert weiterhin und nutzt nahezu identische Konzepte, neue Features erscheinen aber zuerst in Responses. Die Beispiele verwenden gpt-5. Setzen Sie das aktuelle Modell ein, das zu Ihrem Kosten- und Latenzbudget passt, denn die Mechanik ist in der gesamten Modellfamilie gleich.

Zwei Mechanismen, zwei Aufgaben

Halten Sie die beiden gedanklich getrennt:

  • Function (Tool) Calling: Das Modell entscheidet, dass es externe Daten oder eine Aktion braucht, und gibt eine strukturierte Anfrage aus, 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 Taschenrechner, das Wetter.
  • Structured Outputs: Sie zwingen die *finale Antwort* des Modells, exakt einem JSON Schema zu entsprechen. Kein Fließtext, keine Markdown-Fences, kein „Klar, hier ist Ihr JSON.“ Nur gültige, parsebare Daten, jedes Mal.

Oft nutzen Sie 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**, also schreiben Sie sie wie 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
    },
    "strict": True
}]

Drei Details machen Aufrufe vorhersehbar:

  • required listet die Argumente auf, die das Modell liefern muss.
  • additionalProperties: False verhindert, dass das Modell zusätzliche Felder erfindet.
  • strict: True sorgt dafür, dass die Argumente exakt dem Schema entsprechen.

Der Tool-Calling-Loop

Hier liegen viele falsch: 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 der Loop, und er liegt in Ihrer Verantwortung.

Der Ablauf:

  1. Senden Sie die Nutzernachricht 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 mit dem aktualisierten Input erneut auf. Jetzt schreibt es die finale Antwort.
python
import json

def get_weather(city):
    # Angenommen, das ruft eine echte API auf.
    return {"city": city, "temp_c": 19}

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

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

# Output des Modells weiterreichen (inklusive aller Reasoning-Items).
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-5",
    tools=tools,
    input=input_list,
)
print(final.output_text)

Beachten Sie call_id. Das Modell kann in einem Turn mehrere Tool Calls anfordern (parallel tool calling), und jedes Output muss über die ID dem passenden Call zugeordnet werden. Hängen Sie alle Ergebnisse an, bevor Sie die Folgeanfrage stellen.

Bei Reasoning-Modellen ist es wichtig, response.output vollständig zurückzugeben. Es trägt die Reasoning-Items des Modells in den nächsten Turn, sodass das Modell zwischen Tool Calls nicht den Faden verliert. Wenn Sie den State stattdessen serverseitig verwalten, übergeben Sie previous_response_id.

Praktische Regeln für den Loop

  • Loopen Sie, statt von einer einzigen Runde auszugehen. Nachdem Sie Tool-Outputs zurückgegeben haben, ruft das Modell womöglich ein weiteres Tool auf. Packen Sie den Schritt aus Anfrage und Ausführung in eine while-Schleife, die so lange läuft, bis keine function_call-Items mehr zurückkommen. Begrenzen Sie sie (etwa auf 8 Iterationen), damit ein verwirrtes Modell nicht endlos kreist.
  • Validieren Sie Argumente vor der Ausführung. Das Schema schränkt das Modell ein, trotzdem sollten Sie Tool-Argumente wie jeden nicht vertrauenswürdigen Input behandeln. Geben Sie sie nie direkt an eine Shell, einen SQL-String oder einen Dateipfad weiter.
  • Halten Sie Funktionen eng gefasst. get_order_status(order_id) ist besser als eine Mega-Funktion mit einem mode-Flag. Eng gefasste Tools kann das Modell leichter richtig auswählen, und Sie können sie leichter absichern.
  • Steuern Sie die Auswahl, wenn nötig.
  • tool_choice="auto" ist der Default.
  • "required" erzwingt *irgendein* Tool.
  • Die Angabe eines bestimmten Tools erzwingt genau dieses eine.
  • Die Option allowed_tools beschränkt das Modell für diesen Turn auf eine Teilmenge Ihrer Tools, ohne dass Sie die vollständige Liste umschreiben müssen. Das hält auch das Prompt Caching wirksam.
  • Setzen Sie parallel_tool_calls=False, wenn Ihre Tools nacheinander laufen müssen.

Eine neuere Option ist ein Custom Tool ("type": "custom"). Es nimmt Freitext statt JSON-Argumenten entgegen, was zu Inputs wie SQL oder Code-Snippets passt. Optional können Sie es mit einer Grammatik einschränken. Verwenden Sie standardmäßig JSON-Function-Tools und greifen Sie nur dann zu Custom Tools, wenn das JSON-Escaping im Weg ist.

Structured Outputs: Schluss mit dem Parsen von Fließtext

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

Der sauberste Weg in Python: Definieren Sie Ihr Schema als Pydantic-Modell und überlassen Sie dem SDK das Parsen.

python
from pydantic import BaseModel

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

response = client.responses.parse(
    model="gpt-5",
    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 mit json.loads verarbeiten müssen, in der Hoffnung, dass es klappt. Wenn Sie nicht in Python arbeiten, übergeben Sie ein rohes JSON Schema in text.format mit "type": "json_schema" und "strict": true. Zurück bekommen Sie garantiert schemakonformen JSON-Text.

Schema-Design, das wirklich funktioniert

  • Jede Property ist faktisch required. Der Strict Mode behandelt alle Keys als required. 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.
  • Nutzen Sie Enums, um Auswahlmöglichkeiten einzuschränken. Wenn status nur open, pending oder closed sein kann, definieren Sie es als Enum. Das Modell kann dann nicht in-progress zurückgeben und Sie downstream überraschen.
  • Feldbeschreibungen im Schema steuern, *was* in jedes Feld kommt, nicht nur die Typen. Nutzen Sie sie.
  • Achten Sie auf die unterstützte Teilmenge. Structured Outputs unterstützt einen definierten Ausschnitt von JSON Schema, und manche Keywords und format-Werte werden nicht durchgesetzt. Prüfen Sie den Structured Outputs guide, bevor Sie sich auf eines davon verlassen.

OpenAI Function Calling and Structured Outputs Explained

Watch on YouTube

Beides kombinieren: das realistische Muster

Die meisten Features in Produktion nutzen beides zusammen. Stellen Sie sich einen Support-Assistenten vor, der eine Frage zu einer Rückerstattung beantwortet:

  1. Das Modell ruft lookup_order(order_id) auf (Tool Calling), um echte Daten abzurufen.
  2. Sie geben den Bestelldatensatz zurück.
  3. Das Modell erzeugt eine finale Antwort, die auf ein RefundDecision-Schema beschränkt ist (Structured Outputs). Das Schema hat ein boolesches eligible, ein Enum reason und einen String customer_message.

Ihr Anwendungscode parst nie Freitext. Er liest decision.eligible und verzweigt. Genau darum geht es. Das Modell übernimmt Sprache und Urteilsvermögen, Ihr Code den Kontrollfluss, und dazwischen steht ein typisierter Vertrag.

Tool-*Parameter* und Structured *Outputs* sind getrennte Schema-Slots. Der eine formt den eingehenden Call, der andere die ausgehende Antwort. Sie können jeden allein oder beide gleichzeitig in derselben Anfrage nutzen.

Wissenscheck

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

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

3. Warum betont die Lektion, die „description“ eines Tools wie 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 wiedergeben.

Wählen Sie alle richtigen Antworten aus.

Fehler, Kosten und Failure Modes

Reale Systeme brechen auf ganz bestimmte Weise. Planen Sie dafür.

  • Halluzinierte Tools oder fehlerhafte Argumente. Mit strikten Tool-Schemas ist das selten. Wenn eine Funktion tatsächlich 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 davon erholen, etwa indem es den Nutzer nach einer korrekten Bestell-ID fragt.
  • Refusals. Structured Outputs kann aus Sicherheitsgründen trotzdem ablehnen. Das SDK macht das sichtbar, prüfen Sie also auf eine Refusal, bevor Sie output_parsed vertrauen. Behandeln Sie sie explizit, statt sie als fehlerhafte Daten abzutun.
  • Truncation. Erreicht das Modell mitten im JSON das Output-Token-Limit, erhalten Sie selbst im Strict Mode unvollständige Daten. Die Einschränkung garantiert eine *gültige Struktur*, keine *Vollständigkeit*. Prüfen Sie den Response-Status auf incomplete und erhöhen Sie max_output_tokens bei großen Objekten. Reasoning-Modelle verbrauchen einen Teil dieses Budgets für Reasoning-Tokens, lassen Sie also Spielraum.
  • Latenz bei der ersten Nutzung eines neuen Schemas. Die erste Anfrage mit einem neuen strikten Schema kann langsamer sein, während die API die Einschränkung vorbereitet. Wiederholt genutzte Schemas sind schnell, verwenden Sie Schemas also wieder, statt sie pro Anfrage dynamisch zu erzeugen.
  • Tokens. Tool-Definitionen und Schemas liegen im Kontextfenster und zählen bei jedem Aufruf als Input-Tokens. Lange, ausführliche Schemas ü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. Übergeordnete OpenAI-Produkte bauen genau auf diesen Primitiven auf:

  • Built-in Tools sind Tool Calls, die die API serverseitig ausführt. Beispiele sind Web Search, File Search und Code Interpreter. Sie aktivieren sie in tools, ohne den Executor selbst zu schreiben.
  • Remote MCP Server sind ein Tool-Typ in der Responses API. Sie verweisen tools auf einen Model Context Protocol Server (zum Beispiel einen Stripe- oder GitHub-Connector oder Ihren eigenen), und die API findet dessen Tools und ruft sie für Sie auf. MCP ist inzwischen der gängige Weg, ein einziges Set an Tools vielen KI-Clients bereitzustellen.
  • GPT Actions in Custom GPTs sind Function Calling auf Basis einer OpenAPI-Spezifikation. Das Muster aus Anfrage und Ausführung übernimmt ChatGPT statt Ihres Codes. Für umfangreichere Integrationen in ChatGPT baut das Apps SDK auf MCP auf.
  • Das Agents SDK kapselt den Tool-Loop, Retries und Handoffs, sodass Sie die while-Schleife nicht mehr von Hand schreiben. Wenn die Orchestrierung komplex wird (mehrere Agents, Guardrails, Tracing), wechseln Sie dorthin, statt eigenen Loop-Code zu pflegen. Siehe die Agents SDK docs.

Wenn Sie den nackten Loop verstanden haben, wirkt nichts davon wie Magie. Es sind Komfortschichten über dem Vertrag, den Sie gerade kennengelernt haben.

Das Wichtigste in Kürze

  • Tool Calling ist ein Loop, den Sie verantworten. Das Modell *fordert* eine Funktion an. Ihr Code führt sie aus, gibt das Output mit call_id als Schlüssel zurück und ruft das Modell erneut auf. Geben Sie das vollständige Output zurück (inklusive Reasoning-Items), loopen Sie, bis keine Tool Calls mehr übrig sind, und setzen Sie eine harte Obergrenze für Iterationen.
  • Nutzen Sie Structured Outputs mit strict: true für jede Antwort, die Ihr Code parst. Das garantiert die Form und ersetzt fragiles JSON nach dem Prinzip Prompten und Hoffen. In Python definieren Sie ein Pydantic-Modell und lesen output_parsed.
  • Entwerfen Sie Schemas defensiv. Behandeln Sie alle Felder als required (mit null-Unions für optionale Felder), schränken Sie Auswahlmöglichkeiten mit Enums ein und prüfen Sie, ob Ihre Keywords zur unterstützten Teilmenge gehören.
  • Geben Sie Tool-Fehler als Daten zurück und prüfen Sie auf Refusals und den Status incomplete, bevor Sie dem Payload vertrauen.
  • Wenn der Loop wächst, greifen Sie zu MCP, dem Agents SDK oder GPT Actions. Sie basieren auf denselben Primitiven, das mentale Modell lässt sich also direkt übertragen.

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 →