Vai al contenuto principale
Hai abilitato una regola sulla superficie responsedeny o sanitize sulle chiamate a tool che il tuo modello emette — e il tuo agent chiama il gateway con "stream": true. La domanda che conta davvero: una risposta in streaming può far trapelare una chiamata a tool bloccata prima che il firewall decida? Non può, e questa pagina spiega l’unico meccanismo che lo rende vero così puoi ragionare sulla latenza e sui chunk che il tuo client riceve. Questo è uno sguardo focalizzato sul comportamento SSE. Per i verdetti stessi vedi Verdetti; per la grammatica delle regole vedi il riferimento delle regole.

1. Il problema del firewall SSE in streaming

Una risposta non in streaming è un singolo corpo JSON — il firewall vede l’intera cosa, valuta i tool_calls e restituisce il risultato ripulito. Uno stream è diverso: un modello emette una chiamata a tool come decine di delta tool_call attraverso molti frame SSE, e una volta che un frame è inoltrato, il tuo agent ce l’ha già — non c’è modo di ritrattare un token che hai inviato. Valuta troppo presto e non hai la chiamata completa (nome + argomenti completi) da giudicare; inoltra man mano e un deny è già troppo tardi. Il gateway risolve questo con un contratto semplice e osservabile:

Il contenuto fa stream live

I normali delta di testo e ragionamento passano invariati, in tempo reale — zero latenza aggiunta sui token che il tuo utente legge.

I frame delle chiamate a tool vengono trattenuti

Qualsiasi frame che porta un delta tool_call (o il legacy function_call) viene trattenuto dallo stream live finché la chiamata non è completa e valutata.
Il firewall è un cancello di sicurezza, quindi parsa ogni frame. Non indovina che un frame sia solo-contenuto dai byte grezzi — un membro tool_calls con escape JSON non ha alcuna sottostringa letterale su cui fare match, quindi una scorciatoia su sottostringa inoltrerebbe una chiamata a tool non valutata. I frame SSE sono piccoli; il cancello parsa ciascuno.

2. La sequenza trattieni-assembla-valuta

Per una risposta chat-completions in streaming con una policy sulla superficie response attiva, ogni frame che l’upstream emette prende uno di due percorsi:
Scorre fino al tuo client immediatamente, byte per byte. Questi non portano mai una chiamata a tool, quindi il firewall non ha nulla da decidere.
Bufferizzato fuori dallo stream live. Il frame di chiusura finish_reason di un turno di tool viene trattenuto insieme ad esso, perché emetterlo presto direbbe al tuo client che il turno è finito prima che il firewall abbia deciso.
Alla fine dello stream, il gateway assembla i frame trattenuti in chiamate a tool complete (unendo i frammenti arguments in stream di ciascuna chiamata), valuta ognuna rispetto alla tua policy sulla superficie responsela stessa semantica di verdetto e regole del percorso non in streaming — ed emette solo le sopravvissute:
Verdetto della chiamata trattenutaCosa riceve il tuo client
allow / auditI frame trattenuti originali, invariati — un passaggio ritardato, non un chunk ri-batchato.
sanitizeLa chiamata con i suoi argomenti riscritti (segreti/PII corrisposti sostituiti con un token tipizzato), ri-emessa.
denyLa chiamata viene scartata. Se era l’unica chiamata del turno, il turno chiude con finish_reason: "stop" — lo stream appare come se il modello non avesse fatto alcuna chiamata a tool.
Se nulla ha corrisposto, paghi solo il ritardo di buffering sui frame della chiamata a tool — il contenuto ha già fatto stream live. Il firewall ricostruisce i frame solo quando agisce effettivamente (un deny o un sanitize); un allow pulito inoltra i byte esatti del tuo upstream.

3. Un esempio concreto

Una policy di response con una regola deny su *.delete (scrivila nell’editor delle regole della console) e una richiesta in streaming il cui modello decide di chiamare sia db.query sia db.delete:
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"}]}
Il tuo agent legge il testo dell’assistant in tempo reale, poi riceve solo db.querydb.delete è stato assemblato, valutato, negato e mai emesso. La chiamata sopravvissuta è ri-indicizzata da 0, e l’evento del firewall per la chiamata negata atterra nel tuo log degli eventi con la regola che è scattata.
Fai il rollout di una policy di response in streaming sotto shadow mode prima. In shadow mode ogni verdetto applicativo viene declassato a audit (motivazione prefissata [shadow] would …) e tutti i frame delle chiamate a tool passano — così puoi confermare che la policy corrisponda a ciò che ti aspetti sul traffico in streaming reale prima che inizi a scartare chiamate.

4. I block inbound fanno short-circuit prima che lo stream inizi

La danza dei frame trattenuti è solo per la superficie response — le chiamate che il modello emette. Un deny inbound (un tool che un agent pubblicizza) scatta prima della chiamata al modello upstream, quindi una richiesta in streaming che fa scattare una regola inbound non apre mai alcuno stream SSE: restituisce un semplice HTTP 400 con codice di errore firewall_blocked, marcato skip-retry. Nessun frame, nessuna finestra di hold — il block atterra come ogni errore non in streaming.

5. Guardrails sullo stesso stream

Una risposta in streaming può portare una policy di output dei Guardrail e una policy di response del firewall in una volta. Agiscono su cose diverse — i guardrails filtrano il testo che il modello fa stream; il firewall governa le chiamate a tool — e si compongono:
  • Block del guardrail di output (streaming): lo scanner di output taglia lo stream nel momento in cui una regola scatta, inoltra un singolo chunk di sostituzione generico — [Response blocked by content policy.] con finish_reason: "content_filter" — e si ferma. Il messaggio è deliberatamente generico (nessuna categoria di regola) così un prober non può enumerare la tua policy. Un hold del firewall in volo quando questo accade viene scartato, così una chiamata a tool trattenuta non può sgusciare fuori dopo il block.
  • Mask del guardrail di output (streaming): il mascheramento della richiesta prima che il modello sia attivo; il mascheramento in-band live dell’output in stream è sulla roadmap. Su uno stream una regola di mask registra il match ma attualmente inoltra il chunk originale — scrivila sapendo che la redazione non è ancora riscritta sul filo. Il block di output è pienamente applicato sugli stream.
Questa pagina descrive la forma SSE chat-completions di OpenAI. Lo stesso contratto trattieni-valuta-emetti è cablato per formato — Anthropic Messages native, Gemini, xAI e lo stream OpenAI Responses lo portano ciascuno nella propria forma di evento — così il comportamento osservabile dal cliente è identico indipendentemente da quale provider ha servito la richiesta.

6. Cosa significa questo per il tuo client

Qualche conseguenza pratica del modello dei frame trattenuti:
Un turno la cui unica chiamata a tool è stata negata chiude con finish_reason: "stop" invece di "tool_calls" — per il tuo agent si legge come “il modello ha scelto di non chiamare un tool.” Un turno dove alcune chiamate sono sopravvissute chiude con "tool_calls", portando solo le sopravvissute.
Quando un upstream raggruppa l’usage dei token sullo stesso chunk terminale che il firewall ha trattenuto, il gateway lo ri-attacca al frame finale ricostruito — un client che ha richiesto l’usage in stream lo ottiene comunque.
Se il modello ha emesso contenuto e una chiamata a tool nello stesso frame, il contenuto viene recuperato e ri-emesso anche quando la chiamata a tool viene rimossa — bloccare una chiamata non scarta mai il tuo testo dell’assistant.
Non fai aderire uno stream a nulla di tutto questo. Collega una policy alla chiave (o imposta un default del workspace) e continua a fare stream esattamente come prima — l’applicazione è al gateway.

Dove andare dopo

Stage e superfici

inbound, response, mcp, egress — dove valuta ciascuna regola.

Verdetti

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

Sanitizza gli argomenti

Redige i segreti dagli argomenti di una chiamata a tool — solo a livello di argomenti.

Shadow mode

Declassa i verdetti applicativi ad audit mentre misuri l’impatto.
Per dove si colloca questo nel percorso della richiesta, vedi come OrcaRouter ispeziona e latenza del percorso di applicazione. Per le minacce che l’applicazione sulla superficie response contiene, vedi chiamate a tool pericolose e esfiltrazione di dati. Per la grammatica completa delle regole, vedi il riferimento delle regole del firewall.