Passer au contenu principal
Vous attachez une politique firewall à une clé, le modèle streame un appel d’outil en retour, et le stage response le retire ou le réécrit avant que votre agent n’agisse dessus. La décision d’application est identique sur chaque fournisseur — mêmes règles, mêmes verdicts, mêmes événements. Ce qui diffère, c’est la forme du fil que votre client voit une fois que le firewall a agi sur un appel d’outil streamé, parce qu’OpenAI chat, l’API OpenAI Responses, et /v1/messages Claude natif frament chacun les appels d’outils différemment. Cette page est la note ciblée sur ces différences observables par le client. Elle ne redocumente pas le langage des règles — voir Règles du Firewall — ni le modèle de stage, couvert dans Stages. Pour le mécanisme interne de retenir-et-réassembler partagé par les trois, lisez Rouages du streaming.

1. Pourquoi le firewall en streaming par fournisseur diffère selon le fil

Sur une réponse non streamée, le firewall voit toute la réponse d’un coup et décide. Sur un stream, l’appel d’outil du modèle arrive comme une séquence de fragments — un nom dans une frame, du JSON d’arguments distillé à travers de nombreuses autres. Un verdict a besoin de l’appel complet (nom et arguments complets), et un fragment d’appel d’outil, une fois transmis, ne peut pas être rétracté. Donc sur chaque fournisseur, la passerelle fait la même chose : elle laisse le contenu ordinaire streamer en live, et retient les frames d’appel d’outil jusqu’à ce que l’appel soit entièrement assemblé. À la fin du stream, elle évalue chaque appel assemblé et n’émet que les survivants — dans la forme d’événement propre à ce fournisseur.
Votre texte ne cale jamais. Seules les frames d’appel d’outil sont retenues. Le contenu de l’assistant, le raisonnement et les frames de role streament en live et inchangés. La rétention s’applique du premier fragment d’appel d’outil à la fin de ce tour — de sorte qu’une réponse uniquement de chat streame exactement comme si aucun firewall n’était attaché.

2. OpenAI chat completions

Sur /v1/chat/completions, les appels d’outils streament comme des fragments delta.tool_calls keyés par index. La porte retient ceux-là (et la forme delta.function_call legacy) et, à la frame de clôture, émet les appels survivants ré-indexés depuis zéro, suivis d’une frame de fin :
RésultatCe que votre client reçoit
allowLes frames retenues d’origine, octet pour octet — transmission directe véritable.
sanitizeUn delta tool_calls avec des arguments réécrits, puis finish_reason: "tool_calls".
deny (certains appels)Seulement les appels survivants, puis finish_reason: "tool_calls".
deny (tous les appels)Aucun appel d’outil, puis finish_reason: "stop" — le tour donne l’impression que le modèle a choisi de répondre en texte.
Cette dernière ligne est l’indice à tester : quand un firewall retire chaque appel d’outil d’un tour OpenAI chat, votre agent voit un finish_reason: "stop" propre, pas une frame d’erreur. Construisez votre boucle pour traiter « aucun appel d’outil ce tour » comme un résultat valide.

3. API OpenAI Responses

Le stream natif /v1/responses a son propre modèle d’événements — un appel d’outil est un item function_call qui s’ouvre avec response.output_item.added, streame des fragments response.function_call_arguments.delta, et se complète à response.output_item.done. Le firewall évalue à done, le premier point où l’appel est entier :
Les événements added / argument-delta / done de l’item sont émis inchangés une fois l’appel validé.
La coquille added streame, puis un done dont les arguments sont la version redactée — les fragments d’argument-delta d’origine sont abandonnés pour que la valeur non redactée ne vous atteigne jamais.
Les événements en tampon sont abandonnés, et l’item refusé est aussi filtré de l’objet terminal response.completed à partir duquel votre client construit son état final — aucune référence pendante à un appel qui n’a jamais été exécuté.
Les deltas de texte et de raisonnement streament en live tout du long, exactement comme sur chat completions.

4. Claude /v1/messages natif

Un stream Anthropic natif est une autre bête : le contenu arrive comme des blocs indexéscontent_block_startcontent_block_delta (fragments input_json_delta) → content_block_stop — clôturés par un message_delta portant stop_reason. Le firewall retient à partir du premier bloc tool_use, évalue chacun, et reconstruit les blocs survivants avec des indices contigus de sorte qu’un bloc retiré ne laisse aucun trou d’index. L’indice spécifique à Claude est stop_reason. Si chaque bloc tool_use est refusé, un stop_reason de tool_use promettrait à votre client un appel d’outil qui n’arrive jamais — donc la passerelle le réécrit en 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"}
Un retrait partiel garde les blocs tool_use survivants, ré-indexés de façon contiguë, et laisse stop_reason: "tool_use" intact.
Cela s’applique aux streams Claude natifs. Un modèle Claude appelé à travers des endpoints au format OpenAI est appliqué sur le fil OpenAI chat à la place (§2), donc il montre finish_reason: "stop", pas stop_reason: "end_turn". Faites correspondre votre gestion de fin de tour au format de fil que vous avez appelé, pas au modèle sous-jacent.

5. Un exemple concret

La même règle produit la même décision sur chaque fournisseur — seule la forme du fil que votre client lit diffère. Rédigez-la une fois, sur le stage response :
{
  "stage": "response",
  "tool_name_glob": "shell.exec",
  "verdict": "deny",
  "args_match_json": "{\"clauses\":[{\"path\":\"$.command\",\"op\":\"regex\",\"value\":\"rm -rf|mkfs\"}]}"
}
Streamez le même prompt de trois façons et le firewall refuse l’appel rm -rf à chaque fois. Ce que votre client observe :
FilSignal terminal après un retrait complet
OpenAI chatfinish_reason: "stop"
OpenAI Responsesitem absent de response.completed
Claude natifstop_reason: "end_turn"
L’appel correspondant-et-refusé apparaît de façon identique dans les événements firewall quel que soit le fil, de sorte que votre observabilité est indépendante du fournisseur même si le stream ne l’est pas.

6. Ce qui reste constant à travers les fournisseurs

Le fil diffère ; le contrat non :
  • Les verdicts et les règles sont indépendants du fil. allow / audit / deny / sanitize signifient la même chose sur chaque fournisseur. Voir Verdicts.
  • Sanitize ne touche que les arguments de l’appel d’outil, jamais le contenu qu’un outil renvoie — sur chaque fil. Voir Assainir les réponses.
  • Allow est une transmission directe véritable. Quand le firewall ne prend aucune action, les frames retenues sont rejouées comme les octets exacts de l’amont — aucun re-batching, aucun champ spécifique au fournisseur perdu.
  • Le mode shadow s’applique partout. Activez-le et les appels d’outils retenus survivent toujours (rétrogradés en audit) de sorte que vous pouvez mesurer l’impact d’une politique à travers les fournisseurs avant qu’elle ne change le trafic. Voir Mode shadow.

7. Où cela s’insère

Rouages du streaming

Le mécanisme retenir-assembler-réassembler que chaque fournisseur partage.

Stages

Pourquoi l’application des appels d’outils streamés vit sur la surface response.

Verdicts

Les décisions indépendantes du fournisseur en lesquelles un appel streamé se résout.

Filtrage des réponses

Filtrer les appels d’outils qu’un modèle émet, stream ou non.
Pour les menaces que ces vérifications streamées traitent, voir Appels d’outils dangereux et Exfiltration de données ; pour savoir où l’application en stream se situe sur le chemin de la requête, voir Latence du chemin d’application.