For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Hauptnavigation

Funktionsaufruf

Gib Modellen Zugriff auf neue Funktionen und Daten, mit denen sie Anweisungen befolgen und auf Prompts antworten können.

Funktionsaufrufe (auch Tool-Aufrufe genannt) bieten OpenAI-Modellen eine leistungsfähige und flexible Möglichkeit, mit externen Systemen zu interagieren und auf Daten außerhalb ihrer Trainingsdaten zuzugreifen. Dieser Leitfaden zeigt dir, wie du ein Modell mit Daten und Aktionen verbindest, die deine Anwendung bereitstellt. Wir zeigen dir, wie du Funktionstools (definiert durch ein JSON-Schema) und benutzerdefinierte Tools verwendest, die mit frei formulierten Textein- und -ausgaben arbeiten.

Verwende für Sitzungen der Agents API Funktionen, um Funktionen zu registrieren und Aktionsanfragen in Sitzungen zu verarbeiten. Die Beispiele in diesem Leitfaden zeigen die Integrationen mit der Responses API und Chat Completions.

Wenn deine Anwendung viele Funktionen oder umfangreiche Schemas hat, kannst du Funktionsaufrufe mit der Tool-Suche kombinieren. So werden selten verwendete Tools erst geladen, wenn das Modell sie benötigt. Nur gpt-5.4 und neuere Modelle unterstützen tool_search.

GPT-6 Astra benötigt für Tool-Aufrufe die Responses API. Die Beispiele für Chat Completions verwenden aus Kompatibilitätsgründen GPT-5.6. Im Leitfaden zur Migration erfährst du, wie du eine bestehende Integration aktualisierst.

So funktioniert es

Zunächst klären wir einige wichtige Begriffe rund um Tool-Aufrufe. Auf dieser Grundlage zeigen wir dir anschließend anhand praktischer Beispiele, wie du Tool-Aufrufe umsetzt.

Ablauf eines Tool-Aufrufs

Tool-Aufrufe sind ein mehrstufiger Dialog zwischen deiner Anwendung und einem Modell über die OpenAI API. Der Ablauf umfasst fünf wesentliche Schritte:

  1. Sende eine Anfrage an das Modell mit Tools, die es aufrufen könnte
  2. Empfange einen Tool-Aufruf vom Modell
  3. Führe auf Anwendungsseite Code mit der Eingabe aus dem Tool-Aufruf aus
  4. Sende eine zweite Anfrage mit der Tool-Ausgabe an das Modell
  5. Empfange eine abschließende Antwort vom Modell (oder weitere Tool-Aufrufe)

Diagramm der Schritte eines Funktionsaufrufs

Mit Responses kann deine Anwendung diesen Ablauf für so viele Tool-Aufrufe fortsetzen, wie die Aufgabe erfordert. Wenn du ein Framework suchst, das wiederkehrende Orchestrierungsaufgaben rund um diese Schleife übernimmt, lies den Vergleich der Responses API mit dem Agents SDK.

Beispiel für ein Funktionstool

Sehen wir uns den vollständigen Ablauf eines Tool-Aufrufs für die Funktion get_horoscope an, die ein Tageshoroskop für ein Sternzeichen abruft.

Vollständiges Beispiel für einen Tool-Aufruf
from openai import OpenAI
import json

client = OpenAI()

# 1. Define a list of callable tools for the model
tools = [
    {
        "type": "function",
        "name": "get_horoscope",
        "description": "Get today's horoscope for an astrological sign.",
        "parameters": {
            "type": "object",
            "properties": {
                "sign": {
                    "type": "string",
                    "description": "An astrological sign like Taurus or Aquarius",
                },
            },
            "required": ["sign"],
        },
    },
]


def get_horoscope(sign):
    return f"{sign}: Next Tuesday you will befriend a baby otter."


# Create a running input list we will add to over time
input_list = [{"role": "user", "content": "What is my horoscope? I am an Aquarius."}]

# 2. Prompt the model with tools defined
response = client.responses.create(
    model="gpt-6-astra",
    tools=tools,
    input=input_list,
)

# Save function call outputs for subsequent requests
input_list += response.output

for item in response.output:
    if item.type == "function_call":
        if item.name == "get_horoscope":
            # 3. Execute the function logic for get_horoscope
            sign = json.loads(item.arguments)["sign"]
            horoscope = get_horoscope(sign)

            # 4. Provide function call results to the model
            input_list.append(
                {
                    "type": "function_call_output",
                    "call_id": item.call_id,
                    "output": horoscope,
                }
            )

print("Final input:")
print(input_list)

response = client.responses.create(
    model="gpt-6-astra",
    instructions="Respond only with a horoscope generated by a tool.",
    tools=tools,
    input=input_list,
)

# 5. The model should be able to give a response!
print("Final output:")
print(response.model_dump_json(indent=2))
print("\n" + response.output_text)

Beachte: Bei Reasoning-Modellen wie GPT-5 oder o4-mini müssen alle Reasoning-Elemente, die das Modell zusammen mit Tool-Aufrufen in seinen Antworten zurückgibt, auch zusammen mit den Ausgaben der Tool-Aufrufe zurückgesendet werden.

Funktionen definieren

Funktionen werden üblicherweise im Parameter tools jeder API-Anfrage deklariert. Mit der Tool-Suche kann deine Anwendung zunächst zurückgestellte Funktionen auch später im Verlauf der Interaktion laden. In beiden Fällen verwendet jede aufrufbare Funktion dieselbe Schemastruktur. Eine Funktionsdefinition hat die folgenden Eigenschaften:

FeldBeschreibung
typeDer Wert sollte immer function sein
nameDer Name der Funktion (zum Beispiel get_weather)
descriptionAngaben dazu, wann und wie die Funktion verwendet werden soll
parametersJSON-Schema, das die Eingabeargumente der Funktion definiert
strictOb der strikte Modus für den Funktionsaufruf erzwungen werden soll

Hier ist eine beispielhafte Definition für die Funktion get_weather

{
  "type": "function",
  "name": "get_weather",
  "description": "Retrieves current weather for the given location.",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City and country e.g. Bogotá, Colombia"
      },
      "units": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Units the temperature will be returned in."
      }
    },
    "required": ["location", "units"],
    "additionalProperties": false
  },
  "strict": true
}

Da parameters durch ein JSON-Schema definiert werden, kannst du dessen vielfältige Möglichkeiten nutzen, etwa Eigenschaftstypen, Enums, Beschreibungen sowie verschachtelte und rekursive Objekte.

Namensräume definieren

Verwende Namensräume, um zusammengehörige Tools nach Fachgebiet zu gruppieren, etwa crm, billing oder shipping. Namensräume helfen, ähnliche Tools zu organisieren. Sie sind besonders nützlich, wenn das Modell zwischen Tools für unterschiedliche Systeme oder Zwecke wählen muss, zum Beispiel einem Suchtool für dein CRM und einem weiteren für dein Support-Ticketsystem.

{
  "type": "namespace",
  "name": "crm",
  "description": "CRM tools for customer lookup and order management.",
  "tools": [
    {
      "type": "function",
      "name": "get_customer_profile",
      "description": "Fetch a customer profile by customer ID.",
      "parameters": {
        "type": "object",
        "properties": {
          "customer_id": { "type": "string" }
        },
        "required": ["customer_id"],
        "additionalProperties": false
      }
    },
    {
      "type": "function",
      "name": "list_open_orders",
      "description": "List open orders for a customer ID.",
      "defer_loading": true,
      "parameters": {
        "type": "object",
        "properties": {
          "customer_id": { "type": "string" }
        },
        "required": ["customer_id"],
        "additionalProperties": false
      }
    }
  ]
}

Wenn du dem Modell Zugriff auf ein umfangreiches Ökosystem von Tools geben möchtest, kannst du mit tool_search das Laden einiger oder aller Tools auf später verschieben. Mit dem Tool tool_search kann das Modell nach relevanten Tools suchen, sie seinem Kontext hinzufügen und anschließend verwenden. Nur gpt-5.4 und neuere Modelle unterstützen dies. Weitere Informationen findest du im Leitfaden zur Tool-Suche.

Bewährte Methoden zum Definieren von Funktionen

  1. Formuliere Funktionsnamen, Parameterbeschreibungen und Anweisungen klar und ausführlich.

    • Beschreibe ausdrücklich den Zweck der Funktion und jedes Parameters (sowie dessen Format) und erläutere, was die Ausgabe bedeutet.
    • Beschreibe im System-Prompt, wann die einzelnen Funktionen verwendet werden sollen und wann nicht. Sage dem Modell grundsätzlich genau , was es tun soll.
    • Füge Beispiele und Ausnahmefälle hinzu, insbesondere um wiederkehrende Fehler zu beheben. (Hinweis: Zusätzliche Beispiele können die Leistung von Reasoning-Modellen beeinträchtigen.)
    • Gib bei Tools, die erst bei Bedarf geladen werden, ausführliche Hinweise in der Funktionsbeschreibung und halte die Beschreibung des Namensraums kurz. Der Namensraum hilft dem Modell bei der Auswahl dessen, was es laden soll. Die Funktionsbeschreibung hilft ihm, das geladene Tool korrekt zu verwenden.
  2. Wende bewährte Methoden der Softwareentwicklung an.

    • Gestalte Funktionen vorhersehbar und intuitiv (Prinzip der geringsten Überraschung).
    • Verwende Enums und die Objektstruktur, um ungültige Zustände zu verhindern. Beispielsweise lässt toggle_light(on: bool, off: bool) ungültige Aufrufe zu.
    • Bestehe den Test mit einer Person im Praktikum. Kann eine Person im Praktikum oder ein anderer Mensch die Funktion allein mit den Informationen korrekt verwenden, die du dem Modell gegeben hast? (Falls nicht: Welche Fragen stellt sie dir? Ergänze die Antworten im Prompt.)
  3. Entlaste das Modell und verwende nach Möglichkeit Code.

    • Lass das Modell keine Argumente ausfüllen, deren Werte du bereits kennst. Wenn dir beispielsweise aus einem vorherigen Menü bereits eine order_id vorliegt, füge keinen Parameter order_id hinzu. Definiere stattdessen submit_refund() ohne Parameter und übergib die order_id in deinem Code.
    • Fasse Funktionen zusammen, die immer nacheinander aufgerufen werden. Wenn du beispielsweise immer mark_location() nach query_location() aufrufst, verlagere die Markierungslogik einfach in den Aufruf der Abfragefunktion.
  4. Halte die Anzahl der anfangs verfügbaren Funktionen klein, um die Genauigkeit zu erhöhen.

    • Bewerte die Leistung mit unterschiedlich vielen Funktionen.
    • Stelle zu Beginn eines Durchgangs möglichst weniger als 20 Funktionen gleichzeitig bereit. Das ist allerdings nur ein Richtwert.
    • Verwende die Tool-Suche , um umfangreiche oder selten verwendete Teile deines Tool-Angebots erst bei Bedarf zu laden, statt alles von Anfang an bereitzustellen.
  5. Nutze die Ressourcen von OpenAI.

    • Erstelle Funktionsschemas und verbessere sie schrittweise im Playground.
    • Ziehe Fine-Tuning in Betracht, um die Genauigkeit von Funktionsaufrufen zu erhöhen , wenn du viele Funktionen oder schwierige Aufgaben hast. (Cookbook)

Token-Nutzung

Im Hintergrund werden Funktionen in einer Syntax in die Systemnachricht eingefügt, auf die das Modell trainiert wurde. Die Definitionen aufrufbarer Funktionen zählen daher zum Kontextlimit des Modells und werden als Eingabetoken abgerechnet. Wenn du an Token-Limits stößt, empfehlen wir, anfangs weniger Funktionen zu laden, Beschreibungen nach Möglichkeit zu kürzen oder die Tool-Suche zu verwenden, damit zurückgestellte Tools erst bei Bedarf geladen werden.

Wenn du viele Funktionen in deiner Tool-Spezifikation definiert hast, kannst du auch mit Fine-Tuning die Anzahl der verwendeten Token reduzieren.

Funktionsaufrufe verarbeiten

Wenn das Modell eine Funktion aufruft, musst du sie ausführen und das Ergebnis zurückgeben. Da Modellantworten keinen, einen oder mehrere Aufrufe enthalten können, solltest du grundsätzlich mit mehreren Aufrufen rechnen.

Das output-Array der Antwort enthält einen Eintrag, dessen type den Wert function_call hat. Jeder Eintrag enthält eine call_id (die später zur Übermittlung des Funktionsergebnisses verwendet wird), einen name und JSON-kodierte arguments.

Beispielantwort mit mehreren Funktionsaufrufen
[
    {
        "id": "fc_12345xyz",
        "call_id": "call_12345xyz",
        "type": "function_call",
        "name": "get_weather",
        "arguments": "{\"location\":\"Paris, France\"}"
    },
    {
        "id": "fc_67890abc",
        "call_id": "call_67890abc",
        "type": "function_call",
        "name": "get_weather",
        "arguments": "{\"location\":\"Bogotá, Colombia\"}"
    },
    {
        "id": "fc_99999def",
        "call_id": "call_99999def",
        "type": "function_call",
        "name": "send_email",
        "arguments": "{\"to\":\"bob@email.com\",\"body\":\"Hi bob\"}"
    }
]

Wenn du die Tool-Suche verwendest, können vor einem function_call auch Elemente vom Typ tool_search_call und tool_search_output erscheinen. Sobald die Funktion geladen ist, verarbeitest du den Funktionsaufruf genauso wie hier gezeigt.

Funktionsaufrufe ausführen und Ergebnisse anhängen
input_messages += response.output

for tool_call in response.output:
    if tool_call.type != "function_call":
        continue

    name = tool_call.name
    args = json.loads(tool_call.arguments)

    result = call_function(name, args)
    input_messages.append(
        {
            "type": "function_call_output",
            "call_id": tool_call.call_id,
            "output": json.dumps(result),
        }
    )

Im obigen Beispiel verwenden wir eine hypothetische Funktion call_function, um jeden Aufruf weiterzuleiten. So könnte eine Implementierung aussehen:

Funktionsaufrufe ausführen und Ergebnisse anhängen
def call_function(name, args):
    if name == "get_weather":
        return get_weather(**args)
    if name == "send_email":
        return send_email(**args)
    raise ValueError(f"Unknown function: {name}")

Ergebnisse formatieren

Das Ergebnis, das du in der function_call_output-Nachricht übergibst, sollte in der Regel ein String sein. Das Format kannst du selbst wählen (JSON, Fehlercodes, Klartext usw.). Das Modell interpretiert diesen String nach Bedarf.

Bei Funktionen, die Bilder oder Dateien zurückgeben, kannst du statt eines Strings ein Array von Bild- oder Dateiobjekten übergeben.

Wenn deine Funktion keinen Rückgabewert hat (zum Beispiel send_email), gib einen String zurück, der Erfolg oder Fehlschlag anzeigt, etwa "success".

Ergebnisse in die Antwort einbeziehen

Nachdem du die Ergebnisse an input angehängt hast, kannst du sie an das Modell zurücksenden, um eine abschließende Antwort zu erhalten.

Ergebnisse an das Modell zurücksenden
response = client.responses.create(
    model="gpt-6-astra",
    input=input_messages,
    tools=responses_tools,
)

print(response.output_text)
Abschließende Antwort
"It's about 15°C in Paris, 18°C in Bogotá, and I've sent that email to Bob."

Weitere Konfigurationen

Tool-Auswahl

Standardmäßig entscheidet das Modell, wann und wie viele Tools es verwendet. Mit dem Parameter tool_choice kannst du ein bestimmtes Verhalten erzwingen.

  1. Automatisch: (Standard) Keine, eine oder mehrere Funktionen aufrufen. tool_choice: "auto"
  2. Erforderlich: Eine oder mehrere Funktionen aufrufen. tool_choice: "required"
  3. Erzwungene Funktion: Genau eine bestimmte Funktion aufrufen. tool_choice: {"type": "function", "name": "get_weather"}
  4. Zulässige Tools: Die Tool-Aufrufe des Modells auf eine Teilmenge der verfügbaren Tools beschränken.

Wann du allowed_tools verwenden solltest

Eine allowed_tools-Liste kann sinnvoll sein, wenn du bei Modellanfragen nur eine Teilmenge der Tools verfügbar machen möchtest, ohne die übergebene Tool-Liste zu ändern. So kannst du die Einsparungen durch Prompt-Caching maximieren.

"tool_choice": {
    "type": "allowed_tools",
    "mode": "auto",
    "tools": [
        { "type": "function", "name": "get_weather" },
        { "type": "function", "name": "search_docs" }
    ]
  }
}

Du kannst tool_choice auch auf "none" setzen, um dasselbe Verhalten zu erzielen, als würdest du keine Funktionen übergeben.

Wenn du die Tool-Suche verwendest, gilt tool_choice weiterhin für die Tools, die im aktuellen Durchgang aufrufbar sind. Das ist besonders nützlich, wenn du eine Teilmenge der Tools geladen hast und das Modell auf diese Teilmenge beschränken möchtest.

Parallele Funktionsaufrufe

Bei unterstützten Modellen ab GPT-5 können Funktionen auch dann parallel aufgerufen werden, wenn integrierte Tools verfügbar sind. Integrierte Tools können jedoch nicht Teil einer Gruppe paralleler Funktionsaufrufe sein.

Das Modell kann sich entscheiden, mehrere Funktionen in einem einzigen Durchgang aufzurufen. Du kannst dies verhindern, indem du parallel_tool_calls auf false setzt. Dadurch wird sichergestellt, dass entweder kein oder genau ein Tool aufgerufen wird.

Hinweis: Wenn du ein durch Fine-Tuning angepasstes Modell verwendest und dieses mehrere Funktionen in einem Durchgang aufruft, wird der strikte Modus derzeit für diese Aufrufe deaktiviert.

Hinweis zu gpt-4.1-nano-2025-04-14: Dieser Snapshot von gpt-4.1-nano kann gelegentlich mehrere Aufrufe desselben Tools enthalten, wenn parallele Tool-Aufrufe aktiviert sind. Wir empfehlen, diese Funktion bei Verwendung dieses Snapshots zu deaktivieren.

Strikter Modus

Wenn du strict auf true setzt, halten sich Funktionsaufrufe zuverlässig an das Funktionsschema, statt dessen Einhaltung nur nach bestem Bemühen anzustreben. Wir empfehlen, den strikten Modus immer zu aktivieren.

Der strikte Modus nutzt intern unsere Funktion für strukturierte Ausgaben. Daraus ergeben sich einige Anforderungen:

  1. additionalProperties muss für jedes Objekt in parameters auf false gesetzt werden.
  2. Alle Felder in properties müssen als required gekennzeichnet sein.

Du kannst optionale Felder kennzeichnen, indem du null als Option für type hinzufügst (siehe Beispiel unten).

Wenn du strict: true übergibst und dein Schema die oben genannten Anforderungen nicht erfüllt, wird die Anfrage mit Angaben zu den fehlenden Einschränkungen abgelehnt. Wenn du strict weglässt, hängt das Standardverhalten von der API ab: Bei Responses-Anfragen wird nach Möglichkeit versucht, dein Schema für den strikten Modus zu normalisieren. Lässt sich das Schema nicht mit dem strikten Modus kompatibel machen, werden Funktionsaufrufe ohne strikte Schemaeinhaltung nach bestem Bemühen ausgeführt. In diesem Fall zeigt das Tool in der Antwort strict: false an. Bei Chat Completions-Anfragen bleibt der strikte Modus standardmäßig deaktiviert. Um den strikten Modus in Responses zu deaktivieren und Funktionsaufrufe weiterhin ohne strikte Schemaeinhaltung nach bestem Bemühen auszuführen, setze ausdrücklich strict: false.

{
    "type": "function",
    "name": "get_weather",
    "description": "Retrieves current weather for the given location.",
    "strict": true,
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "City and country e.g. Bogotá, Colombia"
            },
            "units": {
                "type": ["string", "null"],
                "enum": ["celsius", "fahrenheit"],
                "description": "Units the temperature will be returned in."
            }
        },
        "required": ["location", "units"],
        "additionalProperties": false
    }
}

Bei allen Schemas, die im Playground erstellt werden, ist der strikte Modus aktiviert.

Wir empfehlen, den strikten Modus zu aktivieren. Er hat jedoch einige Einschränkungen:

  1. Einige Funktionen von JSON Schema werden nicht unterstützt. (Siehe unterstützte Schemas.)

Speziell für durch Fine-Tuning angepasste Modelle gilt:

  1. Schemas werden bei der ersten Anfrage zusätzlich verarbeitet und anschließend zwischengespeichert. Wenn sich deine Schemas von Anfrage zu Anfrage unterscheiden, kann dies zu höheren Latenzen führen.
  2. Schemas werden aus Leistungsgründen zwischengespeichert und können daher nicht unter die Regelung „keine Datenaufbewahrung“ fallen.

Streaming

Mit Streaming kannst du den Fortschritt sichtbar machen: Du kannst anzeigen, welche Funktion aufgerufen wird, während das Modell ihre Argumente ausfüllt, und sogar die Argumente in Echtzeit darstellen.

Das Streaming von Funktionsaufrufen funktioniert ganz ähnlich wie das Streaming regulärer Antworten: Du setzt stream auf true und erhältst verschiedene event-Objekte.

Funktionsaufrufe streamen
from openai import OpenAI

client = OpenAI()

tools = [
    {
        "type": "function",
        "name": "get_weather",
        "description": "Get current temperature for a given location.",
        "parameters": {
            "type": "object",
            "properties": {
                "location": {
                    "type": "string",
                    "description": "City and country e.g. Bogotá, Colombia",
                }
            },
            "required": ["location"],
            "additionalProperties": False,
        },
    }
]

stream = client.responses.create(
    model="gpt-6-astra",
    input=[{"role": "user", "content": "What's the weather like in Paris today?"}],
    tools=tools,
    stream=True,
)

for event in stream:
    print(event)
Ausgegebene Ereignisse
{"type":"response.output_item.added","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":""}}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"{\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"location"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\":\""}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"Paris"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":","}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":" France"}
{"type":"response.function_call_arguments.delta","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"delta":"\"}"}
{"type":"response.function_call_arguments.done","response_id":"resp_1234xyz","item_id":"fc_1234xyz","output_index":0,"arguments":"{\"location\":\"Paris, France\"}"}
{"type":"response.output_item.done","response_id":"resp_1234xyz","output_index":0,"item":{"type":"function_call","id":"fc_1234xyz","call_id":"call_1234xyz","name":"get_weather","arguments":"{\"location\":\"Paris, France\"}"}}

Die einzelnen Teile fügst du dabei jedoch nicht zu einer einzigen content-Zeichenfolge zusammen, sondern zu einem kodierten JSON-Objekt für arguments.

Wenn das Modell eine oder mehrere Funktionen aufruft, wird für jeden Funktionsaufruf ein Ereignis vom Typ response.output_item.added mit den folgenden Feldern ausgegeben:

FeldBeschreibung
response_idDie ID der Antwort, zu der der Funktionsaufruf gehört
output_indexDer Index des Ausgabeelements in der Antwort. Damit werden die einzelnen Funktionsaufrufe in der Antwort identifiziert.
itemDas noch unvollständige Funktionsaufrufelement mit den Feldern name, arguments und id

Anschließend erhältst du eine Reihe von Ereignissen vom Typ response.function_call_arguments.delta, die jeweils das delta des Felds arguments enthalten. Diese Ereignisse haben folgende Felder:

FeldBeschreibung
response_idDie ID der Antwort, zu der der Funktionsaufruf gehört
item_idDie ID des Funktionsaufrufelements, zu dem das Delta gehört
output_indexDer Index des Ausgabeelements in der Antwort. Damit werden die einzelnen Funktionsaufrufe in der Antwort identifiziert.
deltaDas Delta des Felds arguments.

Der folgende Codeausschnitt zeigt, wie du die deltas zu einem vollständigen tool_call-Objekt zusammenfügst.

Deltas für tool_call zusammenfügen
final_tool_calls = {}

for event in stream:
    if event.type == "response.output_item.added":
        final_tool_calls[event.output_index] = event.item
    elif event.type == "response.function_call_arguments.delta":
        index = event.output_index

        if final_tool_calls[index]:
            final_tool_calls[index].arguments += event.delta
Zusammengefügtes final_tool_calls[0]
{
    "type": "function_call",
    "id": "fc_1234xyz",
    "call_id": "call_2345abc",
    "name": "get_weather",
    "arguments": "{\"location\":\"Paris, France\"}"
}

Wenn das Modell die Funktionsaufrufe abgeschlossen hat, wird ein Ereignis vom Typ response.function_call_arguments.done ausgegeben. Dieses Ereignis enthält den vollständigen Funktionsaufruf einschließlich der folgenden Felder:

FeldBeschreibung
response_idDie ID der Antwort, zu der der Funktionsaufruf gehört
output_indexDer Index des Ausgabeelements in der Antwort. Damit werden die einzelnen Funktionsaufrufe in der Antwort identifiziert.
itemDas Funktionsaufrufelement mit den Feldern name, arguments und id.

Benutzerdefinierte Tools

Benutzerdefinierte Tools funktionieren ähnlich wie Funktionstools, die auf einem JSON-Schema basieren. Du gibst dem Modell jedoch nicht explizit vor, welche Eingabe dein Tool benötigt. Stattdessen kann das Modell eine beliebige Zeichenfolge als Eingabe an dein Tool übergeben. So vermeidest du es, eine Antwort unnötig in JSON zu verpacken, oder kannst eine benutzerdefinierte Grammatik auf die Antwort anwenden (mehr dazu weiter unten).

Das folgende Codebeispiel zeigt, wie du ein benutzerdefiniertes Tool erstellst, das als Antwort eine Zeichenfolge mit Python-Code erwartet.

Beispiel für den Aufruf eines benutzerdefinierten Tools
from openai import OpenAI

client = OpenAI()

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the code_exec tool to print hello world to the console.",
    tools=[
        {
            "type": "custom",
            "name": "code_exec",
            "description": "Executes arbitrary Python code.",
        }
    ],
)
print(response.output)

Wie zuvor enthält das Array output einen vom Modell generierten Tool-Aufruf. Diesmal wird die Eingabe für den Tool-Aufruf jedoch als reiner Text übergeben.

[
  {
    "id": "rs_6890e972fa7c819ca8bc561526b989170694874912ae0ea6",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6890e975e86c819c9338825b3e1994810694874912ae0ea6",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_aGiFQkRWSWAIsMQ19fKqxUgb",
    "input": "print(\"hello world\")",
    "name": "code_exec"
  }
]

Kontextfreie Grammatiken

Eine kontextfreie Grammatik (CFG) ist ein Regelwerk, das festlegt, wie gültiger Text in einem bestimmten Format erzeugt wird. Für benutzerdefinierte Tools kannst du eine CFG angeben, die die zulässigen Texteingaben des Modells für das jeweilige Tool einschränkt.

Wenn du ein benutzerdefiniertes Tool konfigurierst, kannst du über den Parameter grammar eine eigene CFG angeben. Derzeit unterstützen wir zwei CFG-Syntaxvarianten zur Definition von Grammatiken: lark und regex.

Lark-CFG

Beispiel für eine kontextfreie Grammatik in Lark
from openai import OpenAI

client = OpenAI()

grammar = """
start: expr
expr: term (SP ADD SP term)* -> add
| term
term: factor (SP MUL SP factor)* -> mul
| factor
factor: INT
SP: " "
ADD: "+"
MUL: "*"
%import common.INT
"""

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the math_exp tool to add four plus four.",
    tools=[
        {
            "type": "custom",
            "name": "math_exp",
            "description": "Creates valid mathematical expressions",
            "format": {
                "type": "grammar",
                "syntax": "lark",
                "definition": grammar,
            },
        }
    ],
)
print(response.output)

Die Ausgabe des Tools sollte dann der von dir definierten Lark-CFG entsprechen:

[
  {
    "id": "rs_6890ed2b6374819dbbff5353e6664ef103f4db9848be4829",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6890ed2f32e8819daa62bef772b8c15503f4db9848be4829",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_pmlLjmvG33KJdyVdC4MVdk5N",
    "input": "4 + 4",
    "name": "math_exp"
  }
]

Grammatiken werden mit einer Variante von Lark definiert. LLGuidance schränkt das Sampling des Modells ein. Einige Funktionen von Lark werden nicht unterstützt:

  • Lookarounds in regulären Ausdrücken des Lexers
  • Nicht-gierige Modifikatoren (*?, +?, ??) in regulären Ausdrücken des Lexers
  • Prioritäten von Terminalsymbolen
  • Vorlagen
  • Importe (außer dem integrierten %import common)
  • %declare-Deklarationen

Wir empfehlen die Lark IDE, um mit eigenen Grammatiken zu experimentieren.

Komplexität der Grammatik begrenzen

Beschränke deine Grammatik auf die Regeln und Muster, die dein Tool benötigt. Die OpenAI API kann einen Fehler zurückgeben, wenn die Grammatik zu komplex ist. Prüfe daher vor der Verwendung in der API, ob deine gewünschte Grammatik kompatibel ist.

Lark-Grammatiken zu perfektionieren kann schwierig sein. Weniger komplexe Grammatiken funktionieren am zuverlässigsten. Bei komplexen Grammatiken musst du häufig die Grammatikdefinition selbst, den Prompt und die Tool-Beschreibung schrittweise überarbeiten, damit das Modell keine Ausgaben außerhalb seiner Trainingsverteilung erzeugt.

Korrekte und fehlerhafte Muster

Korrekt (ein einzelnes, begrenztes Terminal):

start: SENTENCE
SENTENCE: /[A-Za-z, ]*(the hero|a dragon|an old man|the princess)[A-Za-z, ]*(fought|saved|found|lost)[A-Za-z, ]*(a treasure|the kingdom|a secret|his way)[A-Za-z, ]*\./

Gehe NICHT so vor (Aufteilung auf mehrere Regeln oder Terminals). Hier wird versucht, Freitext durch Regeln auf Terminals aufzuteilen. Der Lexer erfasst die Freitextabschnitte nach dem Greedy-Prinzip, sodass du die Kontrolle verlierst:

start: sentence
sentence: /[A-Za-z, ]+/ subject /[A-Za-z, ]+/ verb /[A-Za-z, ]+/ object /[A-Za-z, ]+/

Kleingeschriebene Regeln beeinflussen nicht, wie Terminals aus der Eingabe abgegrenzt werden. Das bestimmen allein die Terminaldefinitionen. Wenn du „Freitext zwischen Ankern“ brauchst, fasse ihn in einem einzigen großen Regex-Terminal zusammen. So erfasst ihn der Lexer genau einmal mit der von dir vorgesehenen Struktur.

Terminals und Regeln im Vergleich

Lark verwendet Terminals für Lexer-Token (konventionsgemäß UPPERCASE) und Regeln für Parser-Produktionen (konventionsgemäß lowercase). Am einfachsten bleibst du innerhalb des unterstützten Funktionsumfangs und vermeidest Überraschungen, indem du deine Grammatik explizit definierst, unnötige Komplexität vermeidest und die Zuständigkeiten von Terminals und Regeln klar trennst.

Terminals verwenden die Syntax des Rust-Crates regex, nicht die des Python-Moduls re.

Grundlagen und bewährte Methoden

Der Lexer läuft vor dem Parser

Der Lexer erfasst Terminals nach dem Greedy-Prinzip (der längste Treffer gewinnt), bevor die Logik der CFG-Regeln angewendet wird. Wenn du versuchst, ein Terminal durch Aufteilung auf mehrere Regeln zu „formen“, können diese Regeln den Lexer nicht steuern. Das können nur die regulären Ausdrücke der Terminals.

Verwende möglichst ein einzelnes Terminal, um Text aus frei formulierten Abschnitten zu extrahieren

Wenn du ein Muster erkennen musst, das in beliebigen Text eingebettet ist (zum Beispiel natürliche Sprache mit „beliebigem Inhalt“ zwischen Ankern), drücke es als einzelnes Terminal aus. Versuche nicht, Freitext-Terminals mit Parser-Regeln zu verschachteln. Der gierige Lexer hält die von dir vorgesehenen Grenzen nicht ein, und das Modell wird mit hoher Wahrscheinlichkeit Ausgaben außerhalb seiner Trainingsverteilung erzeugen.

Verwende Regeln, um einzelne Token zusammenzusetzen

Regeln eignen sich ideal, um explizit abgegrenzte Terminals (Zahlen, Schlüsselwörter, Satzzeichen) zu größeren Strukturen zusammenzusetzen. Sie eignen sich nicht dazu, „den Inhalt dazwischen“, also zwischen zwei Terminals, einzuschränken.

Halte Terminals auf eine Aufgabe beschränkt, begrenzt und in sich geschlossen

Bevorzuge explizite Zeichenklassen und begrenzte Quantifizierer ({0,10}, statt überall unbegrenztes * zu verwenden). Wenn du „beliebigen Text bis zu einem Punkt“ erfassen möchtest, verwende beispielsweise /[^.\n]{0,10}*\./ statt /.+\./, um unkontrolliertes Wachstum zu vermeiden.

Verwende Regeln, um Token zu kombinieren, nicht um das interne Verhalten regulärer Ausdrücke zu steuern

Beispiel für einen sinnvollen Einsatz von Regeln:

start: expr
NUMBER: /[0-9]+/
PLUS: "+"
MINUS: "-"
expr: term (("+"|"-") term)*
term: NUMBER

Behandle Whitespace explizit

Verlasse dich nicht auf unbegrenzte %ignore-Direktiven. Unbegrenzte Ignore-Direktiven können die Grammatik zu komplex machen und/oder dazu führen, dass sich das Modell außerhalb seiner erlernten Verteilung bewegt. Füge stattdessen überall dort explizite Terminals ein, wo Whitespace erlaubt ist.

Fehlerbehebung

  • Wenn die API die Grammatik als zu komplex ablehnt, vereinfache die Regeln und Terminals und entferne unbegrenzte %ignore-Direktiven.
  • Wenn benutzerdefinierte Tools mit unerwarteten Token aufgerufen werden, prüfe, ob sich Terminals überschneiden, und überprüfe das Greedy-Verhalten des Lexers.
  • Wenn sich das Modell außerhalb seiner erlernten Verteilung bewegt (erkennbar an übermäßig langen oder repetitiven Ausgaben, die syntaktisch gültig, aber semantisch falsch sind):
    • Schränke die Grammatik stärker ein.
    • Überarbeite den Prompt schrittweise (füge Few-Shot-Beispiele hinzu) und passe die Tool-Beschreibung an (erkläre die Grammatik und weise das Modell an, sie zu durchdenken und einzuhalten).
    • Experimentiere mit einem höheren Reasoning-Aufwand (z. B. durch einen Wechsel von Mittel auf Hoch).

Regex-CFG

Beispiel für eine kontextfreie Grammatik mit Regex
from openai import OpenAI

client = OpenAI()

grammar = r"^(?P<month>January|February|March|April|May|June|July|August|September|October|November|December)\s+(?P<day>\d{1,2})(?:st|nd|rd|th)?\s+(?P<year>\d{4})\s+at\s+(?P<hour>0?[1-9]|1[0-2])(?P<ampm>AM|PM)$"

response = client.responses.create(
    model="gpt-6-astra",
    input="Use the timestamp tool to save a timestamp for August 7th 2025 at 10AM.",
    tools=[
        {
            "type": "custom",
            "name": "timestamp",
            "description": "Saves a timestamp in date + time in 24-hr format.",
            "format": {
                "type": "grammar",
                "syntax": "regex",
                "definition": grammar,
            },
        }
    ],
)
print(response.output)

Die Ausgabe des Tools sollte dann der von dir definierten Regex-CFG entsprechen:

[
  {
    "id": "rs_6894f7a3dd4c81a1823a723a00bfa8710d7962f622d1c260",
    "type": "reasoning",
    "content": [],
    "summary": []
  },
  {
    "id": "ctc_6894f7ad7fb881a1bffa1f377393b1a40d7962f622d1c260",
    "type": "custom_tool_call",
    "status": "completed",
    "call_id": "call_8m4XCnYvEmFlzHgDHbaOCFlK",
    "input": "August 7th 2025 at 10AM",
    "name": "timestamp"
  }
]

Wie bei der Lark-Syntax verwenden reguläre Ausdrücke die Syntax des Rust-Crates regex, nicht die des Python-Moduls re.

Einige Regex-Funktionen werden nicht unterstützt:

  • Lookarounds
  • Nicht-gierige Modifikatoren (*?, +?, ??)

Grundlagen und bewährte Methoden

Das Muster muss in einer einzigen Zeile stehen

Wenn du einen Zeilenumbruch in der Eingabe erkennen musst, verwende die Escape-Sequenz \n. Verwende nicht den Verbose-/Extended-Modus, der mehrzeilige Muster erlaubt.

Gib den regulären Ausdruck als einfache Zeichenfolge mit dem Muster an

Schließe das Muster nicht in // ein.