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 dietool_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:Content- / Rollen- / Reasoning- / Usage-Frame → jetzt weitergeleitet
Content- / Rollen- / Reasoning- / Usage-Frame → jetzt weitergeleitet
Streamt sofort zu Ihrem Client durch, byte-für-byte. Diese tragen nie einen
Tool-Call, sodass die Firewall nichts zu entscheiden hat.
tool_call- (oder Legacy-function_call-)Frame → zurückgehalten
tool_call- (oder Legacy-function_call-)Frame → zurückgehalten
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.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 Aufrufs | Was Ihr Client empfängt |
|---|---|
allow / audit | Die originalen zurückgehaltenen Frames, unverändert — ein verzögertes Durchreichen, kein neu gebatchter Chunk. |
sanitize | Der Aufruf mit umgeschriebenen Argumenten (gematchte Secrets/PII durch ein typisiertes Token ersetzt), neu ausgegeben. |
deny | Der 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. |
3. Ein konkretes Beispiel
Eine Response-Policy mit einerdeny-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:
db.query — db.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.
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. Eininbound-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.]mitfinish_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:finish_reason kann sich ändern
finish_reason kann sich ändern
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.usage trifft trotzdem ein
usage trifft trotzdem ein
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.Text, der einen Tool-Call-Chunk teilte, bleibt erhalten
Text, der einen Tool-Call-Chunk teilte, bleibt erhalten
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.
keine Änderung am Agenten-Code
keine Änderung am Agenten-Code
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.
