Vai al contenuto principale
Colleghi una policy del firewall a una chiave, il modello fa stream di una chiamata a tool, e lo stage response la rimuove o la riscrive prima che il tuo agent agisca su di essa. La decisione di applicazione è identica su ogni provider — stesse regole, stessi verdetti, stessi eventi. Ciò che differisce è la forma del filo che il tuo client vede una volta che il firewall ha agito su una chiamata a tool in stream, perché OpenAI chat, l’API OpenAI Responses e /v1/messages nativo di Claude inquadrano ciascuno le chiamate a tool diversamente. Questa pagina è la nota focalizzata su quelle differenze osservabili dal cliente. Non ri-documenta il linguaggio delle regole — vedi Regole del Firewall — o il modello degli stage, trattato in Stage. Per il meccanismo interno di trattieni-e-riassembla condiviso da tutti e tre, leggi Internals dello streaming.

1. Perché lo streaming del firewall dei provider differisce per filo

Su una risposta non in stream il firewall vede l’intera risposta in una volta e decide. Su uno stream, la chiamata a tool del modello arriva come una sequenza di frammenti — un nome in un frame, JSON degli argomenti distillato attraverso molti altri. Un verdetto ha bisogno della chiamata completa (nome e argomenti completi), e un frammento di chiamata a tool, una volta inoltrato, non può essere ritrattato. Quindi su ogni provider il gateway fa la stessa cosa: lascia che il contenuto ordinario faccia stream live, e trattiene i frame delle chiamate a tool finché la chiamata non è completamente assemblata. Alla fine dello stream valuta ogni chiamata assemblata ed emette solo le sopravvissute — nella forma di evento di quel provider.
Il tuo testo non si blocca mai. Solo i frame delle chiamate a tool vengono trattenuti. I frame di contenuto, ragionamento e ruolo dell’assistant fanno stream live e invariati. L’hold si applica dal primo frammento di chiamata a tool alla fine di quel turno — così una risposta solo-chat fa stream esattamente come se nessun firewall fosse collegato.

2. OpenAI chat completions

Su /v1/chat/completions, le chiamate a tool fanno stream come frammenti delta.tool_calls indicizzati per indice. Il cancello trattiene quelli (e la forma legacy delta.function_call) e, al frame di chiusura, emette le chiamate sopravvissute ri-indicizzate da zero, seguite da un frame di finish:
EsitoCosa riceve il tuo client
allowI frame trattenuti originali, byte per byte — gli stessi byte dell’upstream.
sanitizeUn delta tool_calls con argomenti riscritti, poi finish_reason: "tool_calls".
deny (alcune chiamate)Solo le chiamate sopravvissute, poi finish_reason: "tool_calls".
deny (tutte le chiamate)Nessuna chiamata a tool, poi finish_reason: "stop" — il turno appare come se il modello avesse scelto di rispondere in testo.
Quell’ultima riga è il segnale su cui testare: quando un firewall rimuove ogni chiamata a tool da un turno OpenAI chat, il tuo agent vede un finish_reason: "stop" pulito, non un frame di errore. Costruisci il tuo loop perché tratti “nessuna chiamata a tool questo turno” come un esito valido.

3. API OpenAI Responses

Lo stream nativo /v1/responses ha il proprio modello di eventi — una chiamata a tool è un item function_call che apre con response.output_item.added, fa stream di frammenti response.function_call_arguments.delta e si completa a response.output_item.done. Il firewall valuta a done, il primo punto in cui la chiamata è completa:
Gli eventi added / delta degli argomenti / done dell’item vengono emessi invariati una volta che la chiamata passa.
La shell added fa stream, poi un done i cui argomenti sono la versione redatta — i frammenti originali dei delta degli argomenti vengono scartati così il valore non redatto non ti raggiunge mai.
Gli eventi bufferizzati vengono scartati, e l’item negato viene anche filtrato fuori dall’oggetto terminale response.completed da cui il tuo client costruisce il suo stato finale — nessun riferimento penzolante a una chiamata che non è mai girata.
I delta di testo e ragionamento fanno stream live per tutto il tempo, esattamente come su chat completions.

4. Claude /v1/messages nativo

Uno stream Anthropic nativo è una bestia diversa: il contenuto arriva come blocchi indicizzaticontent_block_startcontent_block_delta (input_json_delta frammenti) → content_block_stop — chiusi da un message_delta che porta stop_reason. Il firewall trattiene dal primo blocco tool_use, valuta ciascuno, e ricostruisce i blocchi sopravvissuti con indici contigui così un blocco rimosso non lascia alcun gap di indice. Il segnale specifico di Claude è stop_reason. Se ogni blocco tool_use viene negato, uno stop_reason di tool_use prometterebbe al tuo client una chiamata a tool che non arriva mai — quindi il gateway lo riscrive in end_turn:
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"}
Una rimozione parziale mantiene i blocchi tool_use sopravvissuti, ri-indicizzati in modo contiguo, e lascia stop_reason: "tool_use" intatto.
Questo si applica agli stream nativi di Claude. Un modello Claude chiamato attraverso endpoint in formato OpenAI viene applicato sul filo OpenAI chat invece (§2), quindi mostra finish_reason: "stop", non stop_reason: "end_turn". Adatta la tua gestione di fine turno al formato del filo che hai chiamato, non al modello sottostante.

5. Un esempio concreto

La stessa regola produce la stessa decisione su ogni provider — differisce solo la forma del filo che il tuo client legge. Scrivila una volta, sullo stage response:
{
  "stage": "response",
  "tool_name_glob": "shell.exec",
  "verdict": "deny",
  "args_match_json": "{\"clauses\":[{\"path\":\"$.command\",\"op\":\"regex\",\"value\":\"rm -rf|mkfs\"}]}"
}
Fai stream dello stesso prompt in tre modi e il firewall nega la chiamata rm -rf ogni volta. Cosa osserva il tuo client:
FiloSegnale terminale dopo una rimozione completa
OpenAI chatfinish_reason: "stop"
OpenAI Responsesitem assente da response.completed
Claude nativostop_reason: "end_turn"
La chiamata corrisposta-e-negata compare in modo identico negli eventi del firewall indipendentemente dal filo, così la tua osservabilità è agnostica rispetto al provider anche se lo stream non lo è.

6. Cosa resta costante tra i provider

Il filo differisce; il contratto no:
  • Verdetti e regole sono agnostici rispetto al filo. allow / audit / deny / sanitize significano la stessa cosa su ogni provider. Vedi Verdetti.
  • Sanitize tocca solo gli argomenti della chiamata a tool, mai il contenuto che un tool restituisce — su ogni filo. Vedi Sanitizza le risposte.
  • Allow consegna i byte esatti dell’upstream. Quando il firewall non compie alcuna azione, i frame trattenuti vengono riprodotti come i byte esatti dell’upstream — nessun ri-batching, nessun campo specifico del provider perso.
  • La shadow mode si applica ovunque. Attivala e le chiamate a tool trattenute sopravvivono sempre (declassate a audit) così puoi misurare l’impatto di una policy tra i provider prima che modifichi il traffico. Vedi Shadow mode.

7. Dove si inserisce

Internals dello streaming

Il meccanismo trattieni-assembla-riassembla che ogni provider condivide.

Stage

Perché l’applicazione delle chiamate a tool in stream vive sulla superficie response.

Verdetti

Le decisioni agnostiche rispetto al provider in cui una chiamata in stream si risolve.

Filtraggio della response

Governare le chiamate a tool che un modello emette, in stream o no.
Per le minacce che questi controlli in stream affrontano, vedi Chiamate a tool pericolose e Esfiltrazione di dati; per dove si colloca l’applicazione in stream sul percorso della richiesta, vedi Latenza del percorso di applicazione.