Zum Hauptinhalt springen
Sie haben eine Response-Surface-Regel aktiviert — deny oder sanitize auf den Tool-Calls, die Ihr Modell ausgibt — und Ihr Agent ruft das Gateway mit "stream": true auf. Die Frage, die wirklich zählt: Kann eine Streaming-Antwort einen blockierten Tool-Call leaken, bevor die Firewall entscheidet? Sie kann es nicht, und diese Seite erklärt den einen Mechanismus, der das wahr macht, sodass Sie über Latenz und die Chunks, die Ihr Client empfängt, nachdenken können. Dies ist ein fokussierter Blick auf das SSE-Verhalten. Für die Verdikte selbst siehe Verdikte; für die Regel-Grammatik siehe die Regel-Referenz.

1. Das Streaming-Firewall-SSE-Problem

Eine Nicht-Streaming-Antwort ist ein JSON-Body — die Firewall sieht das Ganze, wertet die tool_calls aus und gibt das bereinigte Ergebnis zurück. Ein Stream ist anders: Ein Modell gibt einen Tool-Call als Dutzende von tool_call-Deltas über viele SSE-Frames aus, und sobald ein Frame weitergeleitet ist, hat Ihr Agent ihn bereits — es gibt kein Zurückziehen eines Tokens, das Sie gesendet haben. Werten Sie zu früh aus, und Sie haben nicht den vollständigen Aufruf (Name + volle Argumente), um zu beurteilen; leiten Sie unterwegs weiter, und ein deny ist bereits zu spät. Das Gateway löst dies mit einem einfachen, beobachtbaren Vertrag:

Content streamt live

Normale Text- und Reasoning-Deltas gehen unverändert, in Echtzeit durch — null zusätzliche Latenz auf den Tokens, die Ihr Nutzer liest.

Tool-Call-Frames werden zurückgehalten

Jeder Frame, der ein tool_call- (oder Legacy-function_call-)Delta trägt, wird aus dem Live-Stream zurückgehalten, bis der Aufruf vollständig und ausgewertet ist.
Die Firewall ist ein Sicherheits-Gate, also parst sie jeden Frame. Sie errät nicht aus den rohen Bytes, dass ein Frame nur Content ist — ein JSON-escapetes tool_calls-Member hat keinen literalen Teilstring zum Matchen, sodass eine Teilstring-Abkürzung einen unausgewerteten Tool-Call weiterleiten würde. SSE-Frames sind klein; das Gate parst jeden einzelnen.

2. Die Hold-Assemble-Evaluate-Sequenz

Für eine Streaming-Chat-Completions-Antwort mit aktiver Response-Surface-Policy nimmt jeder Frame, den der Upstream ausgibt, einen von zwei Pfaden:
Streamt sofort zu Ihrem Client durch, byte-für-byte. Diese tragen nie einen Tool-Call, sodass die Firewall nichts zu entscheiden hat.
Aus dem Live-Stream gepuffert. Der abschließende finish_reason-Frame eines Tool-Turns wird daneben zurückgehalten, weil ihn früh auszugeben Ihrem Client mitteilen würde, dass der Turn vorbei ist, bevor die Firewall entschieden hat.
Am Stream-Ende setzt das Gateway die zurückgehaltenen Frames zu vollständigen Tool-Calls zusammen (verbindet die gestreamten arguments-Fragmente jedes Aufrufs), wertet jeden gegen Ihre Policy auf der response-Surface aus — dieselbe Verdikt- und Regel-Semantik wie der Nicht-Streaming-Pfad — und gibt nur die Überlebenden aus:
Verdikt des zurückgehaltenen AufrufsWas Ihr Client empfängt
allow / auditDie originalen zurückgehaltenen Frames, unverändert — ein verzögertes Durchreichen, kein neu gebatchter Chunk.
sanitizeDer Aufruf mit umgeschriebenen Argumenten (gematchte Secrets/PII durch ein typisiertes Token ersetzt), neu ausgegeben.
denyDer Aufruf wird verworfen. War er der einzige Aufruf des Turns, schließt der Turn mit finish_reason: "stop" — der Stream sieht aus, als hätte das Modell keinen Tool-Call gemacht.
Wenn nichts gematcht hat, zahlen Sie nur die Pufferverzögerung auf den Tool-Call-Frames — Content streamte bereits live. Die Firewall rekonstruiert Frames nur, wenn sie tatsächlich handelt (ein deny oder ein sanitize); ein sauberes allow leitet die exakten Bytes Ihres Upstreams weiter.

3. Ein konkretes Beispiel

Eine Response-Policy mit einer deny-Regel auf *.delete (verfassen Sie sie im Regel-Editor der Konsole) und ein Streaming-Request, dessen Modell sowohl db.query als auch db.delete aufrufen will:
SSE timeline (what your agent receives)
───────────────────────────────────────
data: {"choices":[{"delta":{"content":"Looking that up…"}}]}   ← live
data: {"choices":[{"delta":{"content":" one moment."}}]}        ← live
                                                                ← db.query + db.delete
                                                                  tool_call frames HELD
─── end of stream ───
data: {"choices":[{"delta":{"role":"assistant",
        "tool_calls":[{"index":0,"function":{"name":"db.query",…}}]}}]}
data: {"choices":[{"finish_reason":"tool_calls"}]}
Ihr Agent liest den Assistant-Text in Echtzeit und empfängt dann nur db.querydb.delete wurde zusammengesetzt, ausgewertet, verweigert und nie ausgegeben. Der überlebende Aufruf wird ab 0 neu indiziert, und das Firewall-Event für den verweigerten Aufruf landet in Ihrem Events-Log mit der Regel, die feuerte.
Rollen Sie eine Streaming-Response-Policy zuerst unter dem Shadow-Mode aus. Im Shadow-Mode wird jedes durchsetzende Verdikt auf audit herabgestuft (Grund vorangestellt [shadow] would …), und alle Tool-Call-Frames gehen durch — sodass Sie bestätigen können, dass die Policy auf echtem gestreamtem Traffic matcht, was Sie erwarten, bevor sie anfängt, Aufrufe zu verwerfen.

4. Inbound-Blocks kürzen ab, bevor der Stream startet

Der Zurückhalte-Frame-Tanz ist nur für die Response-Surface — Aufrufe, die das Modell ausgibt. Ein inbound-deny (ein Tool, das ein Agent anbietet) feuert vor dem Upstream-Modellaufruf, sodass ein Streaming-Request, der eine inbound-Regel auslöst, gar nie einen SSE-Stream öffnet: Er gibt ein einfaches HTTP 400 mit Fehlercode firewall_blocked zurück, markiert als skip-retry. Keine Frames, kein Zurückhalte-Fenster — der Block landet wie jeder Nicht-Streaming-Fehler.

5. Guardrails auf demselben Stream

Eine Streaming-Antwort kann gleichzeitig eine Guardrail-Output-Policy und eine Firewall-Response-Policy tragen. Sie handeln auf verschiedenen Dingen — Guardrails screenen den Text, den das Modell streamt; die Firewall steuert die Tool-Calls — und sie komponieren:
  • Output-Guardrail-Block (Streaming): Der Output-Scanner schneidet den Stream in dem Moment ab, in dem eine Regel auslöst, leitet einen einzelnen generischen Ersatz-Chunk weiter — [Response blocked by content policy.] mit finish_reason: "content_filter" — und stoppt. Die Nachricht ist bewusst generisch (keine Regel-Kategorie), sodass ein Sondierer Ihre Policy nicht enumerieren kann. Ein Firewall-Hold, der in dem Moment in Flug ist, wird verworfen, sodass ein zurückgehaltener Tool-Call nicht nach dem Block herausrutschen kann.
  • Output-Guardrail-Maskierung (Streaming): Das Maskieren des Requests ist vor dem Modell live; Live-In-Band-Maskierung von gestreamtem Output ist auf der Roadmap. Auf einem Stream zeichnet eine Mask-Regel den Treffer auf, leitet aber derzeit den Original-Chunk weiter — verfassen Sie sie im Wissen, dass die Redaktion auf der Leitung noch nicht umgeschrieben wird. Output-Block wird auf Streams vollständig durchgesetzt.
Diese Seite beschreibt die OpenAI-Chat-Completions-SSE-Form. Derselbe Hold-Evaluate-Emit-Vertrag ist pro Format verdrahtet — native Anthropic Messages, Gemini, xAI und der OpenAI-Responses-Stream tragen ihn jeweils in ihrer eigenen Event-Form — sodass das kundenbeobachtbare Verhalten identisch ist, unabhängig davon, welcher Anbieter den Request bediente.

6. Was das für Ihren Client bedeutet

Ein paar praktische Konsequenzen des Zurückhalte-Frame-Modells:
Ein Turn, dessen einziger Tool-Call verweigert wurde, schließt mit finish_reason: "stop" statt "tool_calls" — für Ihren Agenten liest es sich als „das Modell entschied sich, kein Tool aufzurufen”. Ein Turn, in dem einige Aufrufe überlebten, schließt mit "tool_calls" und trägt nur die Überlebenden.
Wenn ein Upstream Token-usage auf denselben terminalen Chunk bündelt, den die Firewall zurückgehalten hat, hängt das Gateway sie an den finalen rekonstruierten Frame wieder an — ein Client, der Stream-Usage angefordert hat, bekommt sie trotzdem.
Wenn das Modell Content und einen Tool-Call im selben Frame ausgab, wird der Content wiederhergestellt und neu ausgegeben, selbst wenn der Tool-Call entfernt wird — das Blockieren eines Aufrufs verwirft nie Ihren Assistant-Text.
Sie melden einen Stream für nichts davon an. Hängen Sie eine Policy an den Key (oder setzen Sie einen Workspace-Default) und streamen Sie weiter genau wie zuvor — die Durchsetzung ist am Gateway.

Wohin als Nächstes

Stages & Surfaces

inbound, response, mcp, egress — wo jede Regel auswertet.

Verdikte

allow, audit, deny, sanitize, pending_approval, cap_cost.

Argumente bereinigen

Secrets aus den Argumenten eines Tool-Calls redigieren — nur auf der Argument-Ebene.

Shadow-Mode

Durchsetzende Verdikte auf audit herabstufen, während Sie die Wirkung messen.
Dafür, wo dies im Request-Pfad sitzt, siehe Wie OrcaRouter inspiziert und Enforcement-Pfad-Latenz. Für die Bedrohungen, die Response-Surface-Durchsetzung eindämmt, siehe Gefährliche Tool-Calls und Datenexfiltration. Für die vollständige Regel-Grammatik siehe die Firewall-Regel-Referenz.