Zum Hauptinhalt springen
Sie hängen eine Firewall-Policy an einen Key, das Modell streamt einen Tool-Call zurück, und die response-Stage entfernt oder schreibt ihn um, bevor Ihr Agent darauf reagiert. Die Durchsetzungs-Entscheidung ist auf jedem Anbieter identisch — dieselben Regeln, dieselben Verdikte, dieselben Events. Was sich unterscheidet, ist die Leitungsform, die Ihr Client sieht, sobald die Firewall auf einen gestreamten Tool-Call reagiert hat, weil OpenAI Chat, die OpenAI-Responses-API und natives Claude /v1/messages Tool-Calls jeweils unterschiedlich framen. Diese Seite ist der fokussierte Hinweis zu diesen kundenbeobachtbaren Unterschieden. Sie re-dokumentiert nicht die Regel-Sprache — siehe Firewall-Regeln — oder das Stage-Modell, das in Stages behandelt wird. Für den internen Hold-and-Reassemble-Mechanismus, den alle drei teilen, lesen Sie Streaming-Interna.

1. Warum Firewall-Provider-Streaming sich nach Leitung unterscheidet

Auf einer nicht-gestreamten Antwort sieht die Firewall die ganze Antwort auf einmal und entscheidet. Auf einem Stream trifft der Tool-Call des Modells als eine Sequenz von Fragmenten ein — ein Name in einem Frame, Argument-JSON über viele weitere verteilt. Ein Verdikt braucht den vollständigen Aufruf (Name und volle Argumente), und ein Tool-Call-Fragment kann, einmal weitergeleitet, nicht zurückgezogen werden. Auf jedem Anbieter tut das Gateway also dasselbe: Es lässt gewöhnlichen Content live durchstreamen und hält die Tool-Call-Frames zurück, bis der Aufruf vollständig zusammengesetzt ist. Am Stream-Ende wertet es jeden zusammengesetzten Aufruf aus und gibt nur die Überlebenden aus — in der eigenen Event-Form dieses Anbieters.
Ihr Text stockt nie. Nur Tool-Call-Frames werden zurückgehalten. Assistant-Content, Reasoning und Rollen-Frames streamen live und unverändert. Das Zurückhalten gilt vom ersten Tool-Call-Fragment bis zum Ende dieses Turns — sodass eine reine Chat-Antwort genau so streamt, als wäre keine Firewall angehängt.

2. OpenAI Chat Completions

Auf /v1/chat/completions streamen Tool-Calls als delta.tool_calls-Fragmente, nach Index verschlüsselt. Das Gate hält diese (und die Legacy-delta.function_call-Form) zurück und gibt am abschließenden Frame die überlebenden Aufrufe, ab null neu indiziert, aus, gefolgt von einem Finish-Frame:
ErgebnisWas Ihr Client empfängt
allowDie originalen zurückgehaltenen Frames, byte-für-byte — echtes Durchreichen.
sanitizeEin tool_calls-Delta mit umgeschriebenen Argumenten, dann finish_reason: "tool_calls".
deny (einige Aufrufe)Nur die überlebenden Aufrufe, dann finish_reason: "tool_calls".
deny (alle Aufrufe)Kein Tool-Call, dann finish_reason: "stop" — der Turn sieht aus, als hätte das Modell beschlossen, in Text zu antworten.
Diese letzte Zeile ist das Anzeichen, gegen das zu testen ist: Wenn eine Firewall jeden Tool-Call aus einem OpenAI-Chat-Turn entfernt, sieht Ihr Agent ein sauberes finish_reason: "stop", keinen Fehler-Frame. Bauen Sie Ihre Schleife so, dass sie „kein Tool-Call diesen Turn” als gültiges Ergebnis behandelt.

3. OpenAI-Responses-API

Der native /v1/responses-Stream hat sein eigenes Event-Modell — ein Tool-Call ist ein function_call-Item, das mit response.output_item.added öffnet, response.function_call_arguments.delta-Fragmente streamt und bei response.output_item.done abschließt. Die Firewall wertet bei done aus, dem ersten Punkt, an dem der Aufruf vollständig ist:
Die added- / Argument-Delta- / done-Events des Items werden unverändert ausgegeben, sobald der Aufruf durchgeht.
Die added-Hülle streamt, dann ein done, dessen Argumente die redigierte Version sind — die originalen Argument-Delta-Fragmente werden verworfen, sodass der unredigierte Wert Sie nie erreicht.
Die gepufferten Events werden verworfen, und das verweigerte Item wird auch aus dem terminalen response.completed-Objekt herausgefiltert, aus dem Ihr Client seinen finalen Zustand baut — keine baumelnde Referenz auf einen Aufruf, der nie lief.
Text- und Reasoning-Deltas streamen durchweg live, genau wie bei Chat Completions.

4. Natives Claude /v1/messages

Ein nativer Anthropic-Stream ist ein anderes Biest: Content trifft als indizierte Blöcke ein — content_block_startcontent_block_delta (input_json_delta-Fragmente) → content_block_stop — abgeschlossen durch ein message_delta, das stop_reason trägt. Die Firewall hält ab dem ersten tool_use-Block zurück, wertet jeden aus und rekonstruiert die überlebenden Blöcke mit fortlaufenden Indizes, sodass ein entfernter Block keine Indexlücke hinterlässt. Das Claude-spezifische Anzeichen ist stop_reason. Wenn jeder tool_use-Block verweigert wird, würde ein stop_reason von tool_use Ihrem Client einen Tool-Call versprechen, der nie eintrifft — daher schreibt das Gateway ihn zu end_turn um:
upstream:  content_block_start (tool_use) … message_delta {stop_reason: "tool_use"}
            ↓ firewall denies the only tool_use
client:    (no tool_use block)            … message_delta {stop_reason: "end_turn"}
Ein partielles Entfernen behält die überlebenden tool_use-Blöcke, fortlaufend neu indiziert, und lässt stop_reason: "tool_use" intakt.
Dies gilt für native Claude-Streams. Ein Claude-Modell, das über OpenAI-Format-Endpunkte aufgerufen wird, wird stattdessen auf der OpenAI-Chat-Leitung durchgesetzt (§2), sodass es finish_reason: "stop" zeigt, nicht stop_reason: "end_turn". Passen Sie Ihre End-of-Turn-Behandlung an das Leitungsformat, das Sie aufgerufen haben, an, nicht an das zugrunde liegende Modell.

5. Ein konkretes Beispiel

Dieselbe Regel erzeugt dieselbe Entscheidung auf jedem Anbieter — nur die Leitungsform, die Ihr Client liest, unterscheidet sich. Verfassen Sie sie einmal, auf der response-Stage:
{
  "stage": "response",
  "tool_name_glob": "shell.exec",
  "verdict": "deny",
  "args_match_json": "{\"clauses\":[{\"path\":\"$.command\",\"op\":\"regex\",\"value\":\"rm -rf|mkfs\"}]}"
}
Streamen Sie denselben Prompt auf drei Wegen, und die Firewall verweigert den rm -rf-Aufruf jedes Mal. Was Ihr Client beobachtet:
LeitungTerminales Signal nach einem vollständigen Entfernen
OpenAI Chatfinish_reason: "stop"
OpenAI ResponsesItem abwesend aus response.completed
Natives Claudestop_reason: "end_turn"
Der gematchte-und-verweigerte Aufruf taucht identisch in Firewall-Events auf, unabhängig von der Leitung, sodass Ihre Observability anbieter-agnostisch ist, auch wenn der Stream es nicht ist.

6. Was über Anbieter hinweg konstant bleibt

Die Leitung unterscheidet sich; der Vertrag nicht:
  • Verdikte und Regeln sind leitungs-agnostisch. allow / audit / deny / sanitize bedeuten auf jedem Anbieter dasselbe. Siehe Verdikte.
  • Sanitize rührt nur Tool-Call-Argumente an, niemals den Inhalt, den ein Tool zurückgibt — auf jeder Leitung. Siehe Antworten bereinigen.
  • Allow ist echtes Durchreichen. Wenn die Firewall nicht handelt, werden die zurückgehaltenen Frames als die exakten Upstream-Bytes wiedergegeben — kein Neu-Batchen, keine verlorenen anbieter-spezifischen Felder.
  • Shadow-Mode gilt überall. Schalten Sie ihn ein, und die zurückgehaltenen Tool-Calls überleben immer (auf audit herabgestuft), sodass Sie die Wirkung einer Policy über Anbieter hinweg messen können, bevor sie den Traffic verändert. Siehe Shadow-Mode.

7. Wo das hineinpasst

Streaming-Interna

Der Hold-Assemble-Reassemble-Mechanismus, den jeder Anbieter teilt.

Stages

Warum gestreamte Tool-Call-Durchsetzung auf der response-Surface lebt.

Verdikte

Die anbieter-agnostischen Entscheidungen, zu denen ein gestreamter Aufruf auflöst.

Response-Filterung

Die Tool-Calls gaten, die ein Modell ausgibt, Stream oder nicht.
Für die Bedrohungen, die diese gestreamten Prüfungen adressieren, siehe Gefährliche Tool-Calls und Datenexfiltration; dafür, wo Stream-Durchsetzung auf dem Request-Pfad sitzt, siehe Enforcement-Pfad-Latenz.