Function Calling: Responses API vs. Chat Completions

Wire-Level-Vergleich von Function Definitions, Call IDs, Ergebnissen, Streaming Arguments, Autorisierung, Idempotenz und Routing.

Responses und Chat Completions können eine Anwendung zur Ausführung einer Funktion auffordern, stellen den Tool Loop aber unterschiedlich dar. Chat Completions definiert Funktionen unter tools[].function, liefert message.tool_calls und nimmt Ergebnisse als role: "tool" entgegen. Responses verwendet flache Function Definitions, typisierte function_call Output Items und über call_id verknüpfte function_call_output Items.

Das Modell führt die Funktion nicht aus. Anwendungscode muss Argumente validieren, die Operation autorisieren, sie kontrolliert ausführen, das Ergebnis zurückgeben und doppelte Side Effects bei Retry verhindern.

Der Tool Loop hat vier Schritte

1. Erlaubte Funktion und Argument-Schema deklarieren
2. Einen oder mehrere Modellvorschläge empfangen
3. Jeden Call in der Anwendung validieren, autorisieren und ausführen
4. Jedes Ergebnis mit der Correlation ID des Calls zurückgeben

Erst nach Schritt vier kann das Modell eine Antwort auf Basis des Tool-Ergebnisses erzeugen. Fordert es ein weiteres Tool an, beginnt eine neue Runde mit neuer Call ID. Der OpenAI Function Calling Guide beschreibt denselben mehrstufigen Austausch.

Wire Contracts vergleichen

Aspekt Responses API Chat Completions
Definition Flaches tools[] Item Felder unter tools[].function
Vorgeschlagener Call function_call Output Item Assistant tool_calls[]
Korrelation call_id id, Ergebnis als tool_call_id
Name function_call.name tool_calls[].function.name
Argumente JSON-String in function_call.arguments JSON-String in tool_calls[].function.arguments
Ergebnis function_call_output Message mit role: "tool"
Streaming Typisierte Argument-Events delta.tool_calls[] Fragmente
Finaler Text Output Items / Helper choices[0].message.content

Array-Positionen sind keine stabile Zuordnung. Parallel Calls und Chunks können anders eintreffen als die Anwendung sie beendet. Die explizite ID ist der Join Key.

Ein striktes Function Schema definieren

{
  "type": "object",
  "properties": {
    "order_id": { "type": "string", "pattern": "^ORDER-[0-9]{4}$" }
  },
  "required": ["order_id"],
  "additionalProperties": false
}

strict: true fordert Schema-Konformität, ersetzt aber Parsing und Validierung in der Anwendung nicht. pattern und andere Keywords können je Anbieter variieren; deshalb gilt derselbe Test wie für Structured Outputs.

Den Loop mit Responses API implementieren

const tools = [{
  type: "function",
  name: "get_delivery_status",
  description: "Return the current delivery status for one order.",
  parameters: {
    type: "object",
    properties: { order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" } },
    required: ["order_id"],
    additionalProperties: false,
  },
  strict: true,
}];

const first = await client.responses.create({
  model, input: "Where is ORDER-1001?", tools, parallel_tool_calls: false,
});
const calls = first.output.filter(item => item.type === "function_call");
const outputs = calls.map(call => ({
  type: "function_call_output",
  call_id: call.call_id,
  output: JSON.stringify(executeTool(call.name, call.arguments)),
}));
const final = await client.responses.create({
  model, input: [...first.output, ...outputs], tools, parallel_tool_calls: false,
});

call.call_id und das call_id des Ergebnisses müssen übereinstimmen. Ein stateless Austausch sendet vorherige Output Items und Resultate gemeinsam zurück. Stateful Continuation darf nur genutzt werden, wenn Route und Storage Policy sie nachweislich unterstützen.

Den Loop mit Chat Completions implementieren

const tools = [{
  type: "function",
  function: {
    name: "get_delivery_status",
    description: "Return the current delivery status for one order.",
    parameters: {
      type: "object",
      properties: { order_id: { type: "string", pattern: "^ORDER-[0-9]{4}$" } },
      required: ["order_id"],
      additionalProperties: false,
    },
    strict: true,
  },
}];

const first = await client.chat.completions.create({ model, messages, tools });
const assistant = first.choices[0].message;
messages.push(assistant);
for (const call of assistant.tool_calls ?? []) {
  messages.push({
    role: "tool",
    tool_call_id: call.id,
    content: JSON.stringify(executeTool(call.function.name, call.function.arguments)),
  });
}

Die Assistant Message mit den Calls muss vor den Tool-Ergebnissen in messages stehen. Eine fehlende Message oder eine nicht passende tool_call_id erzeugt einen ungültigen Transcript.

Streaming-Argumente als Fragmente behandeln

{"order ist unvollständiges, nicht ungültiges JSON. Pro Call ist ein eigener Buffer nötig: Delta anhängen, auf Arguments Done oder Completed warten, den vollständigen String einmal parsen und erst danach validieren und ausführen. Responses nutzt typisierte Events, Chat Completions choices[].delta.tool_calls[]. Beim ersten Fragment darf keine Funktion starten. Siehe AI API Streaming Guide.

Tool-Ausführung sicher und idempotent machen

  • ausschließlich registrierte Function Names erlauben;
  • Argumentgröße begrenzen und vollständiges Schema validieren;
  • User oder Workload für die Ressource autorisieren;
  • Read-only Tools und Side Effects trennen;
  • Secrets vor der Rückgabe entfernen;
  • Deadlines und begrenzte Downstream-Retries verwenden;
  • eine sichere Operation Reference statt privater Inhalte speichern.

Für Side Effects wird ein Idempotency Key aus stabilem Application Request und Call Identity gebildet. Das Ergebnis muss vor der Modellantwort gespeichert werden. Ein Replay liefert das gespeicherte Ergebnis, statt eine zweite Erstattung, Nachricht, Bereitstellung oder Datenbankmutation auszuführen.

Function Name und Arguments aus dem Modell sind untrusted input. Ein gültiges Schema erteilt keine Berechtigung und ersetzt keine Transaktion.

Mehrere Calls bewusst behandeln

Mit parallel_tool_calls: false bleibt die State Machine übersichtlich. Bei paralleler Ausführung müssen Ergebnisse über Call IDs korreliert, Anzahl und Concurrency begrenzt, Fehlerregeln definiert und ordnungsabhängige Side Effects serialisiert werden. Parallelität ist für unabhängige Reads nützlich, erhöht aber Autorisierungs-, Retry- und Partial-Failure-Komplexität.

Die Modelflare-Kompatibilitätsgrenze verstehen

Modelflare erhält strikte Function Definitions und mappt unterstützte Call-Formen auf zulässigen OpenAI/Codex-Routen zwischen Responses und Chat Completions. Das Mapping ist absichtlich kleiner als die vollständige Responses Tool Surface.

Application-defined Functions lassen sich in beiden Formaten ausdrücken. Provider Hosted Tools wie Search oder Code Execution sind nicht gleichwertig und dürfen nicht als konvertierbar angenommen werden. Andere OpenAI-compatible Familien bleiben bis zur Prüfung Raw Chat Completions Pass-through; der Upstream-Vertrag bestimmt tools, strict, Parallel Calls, Streaming Arguments und tool_choice.

Test Erforderliche Evidenz
Read-only Call Name, Parsed Arguments, Call ID, Result Linkage, finaler Text
Ungültige Argumente Expliziter Fehler ohne Ausführung
Unbekannte Funktion Ablehnung durch Allowlist
Kein Tool nötig Normaler Text ohne erfundenen Call
Streaming Rekonstruierte Argumente und korrekte ID
Wiederholter Request Ein Side Effect oder gespeichertes Ergebnis
Mehrere Calls Korrelation unabhängig von Abschlussreihenfolge
Route Fallback Gleiches Modell, Protokoll, Schema und Result Contract

Reliable AI API Routing hält Routenwechsel explizit; AI API Key Security trennt Workloads und Rechte.

Das Format nach dem Anwendungsvertrag wählen

Responses eignet sich für Typed Output Items, das Responses Event Model, State Continuation und andere verifizierte Funktionen. Chat Completions passt, wenn die Anwendung einen stabilen Message Transcript besitzt und der Tool-Vertrag des Providers geprüft ist. Siehe Responses API vs. Chat Completions.

Der Standard bleibt gleich: explizite Tool Allowlist, striktes Argument Schema, anwendungsseitige Validierung, Autorisierung vor Ausführung, Korrelation per Call ID, Idempotenz für Side Effects und ein vollständiges Ergebnis für das Modell. Diese Kontrollen schaffen Zuverlässigkeit, nicht der Endpoint-Name.