Saltar al contenido principal
Habilitaste una regla de superficie responsedeny o sanitize sobre las llamadas a herramienta que tu modelo emite — y tu agente llama al gateway con "stream": true. La pregunta que realmente importa: ¿puede una respuesta en streaming filtrar una llamada a herramienta bloqueada antes de que el firewall decida? No puede, y esta página explica el único mecanismo que lo hace verdad para que puedas razonar sobre la latencia y los chunks que tu cliente recibe. Esta es una mirada enfocada al comportamiento SSE. Para los veredictos en sí ver Veredictos; para la gramática de reglas ver la referencia de reglas.

1. El problema del firewall en streaming SSE

Una respuesta no en streaming es un solo cuerpo JSON — el firewall ve la cosa entera, evalúa los tool_calls, y devuelve el resultado limpio. Un stream es diferente: un modelo emite una llamada a herramienta como decenas de deltas tool_call a lo largo de muchos frames SSE, y una vez que un frame se reenvía, tu agente ya lo tiene — no hay forma de retractar un token que has enviado. Evalúa demasiado pronto y no tienes la llamada completa (nombre + argumentos completos) que juzgar; reenvía sobre la marcha y un deny ya es demasiado tarde. El gateway resuelve esto con un contrato simple y observable:

El contenido se transmite en vivo

Los deltas normales de texto y razonamiento pasan sin cambios, en tiempo real — cero latencia añadida en los tokens que tu usuario lee.

Los frames de llamada a herramienta se retienen

Cualquier frame que lleva un delta tool_call (o el legado function_call) se retiene del stream en vivo hasta que la llamada esté completa y evaluada.
El firewall es una compuerta de seguridad, así que parsea cada frame. No adivina que un frame es solo de contenido a partir de los bytes en bruto — un miembro tool_calls escapado en JSON no tiene subcadena literal con la que coincidir, así que un atajo de subcadena reenviaría una llamada a herramienta sin evaluar. Los frames SSE son pequeños; la compuerta parsea cada uno.

2. La secuencia retener-ensamblar-evaluar

Para una respuesta de chat-completions en streaming con una política de superficie response activa, cada frame que el upstream emite toma una de dos rutas:
Se transmite a tu cliente de inmediato, byte a byte. Estos nunca llevan una llamada a herramienta, así que el firewall no tiene nada que decidir.
Almacenado en buffer fuera del stream en vivo. El frame de cierre finish_reason de un turno de herramienta se retiene junto a él, porque emitirlo temprano le diría a tu cliente que el turno terminó antes de que el firewall haya fallado.
Al final del stream, el gateway ensambla los frames retenidos en llamadas a herramienta completas (uniendo los fragmentos arguments transmitidos de cada llamada), evalúa cada una contra tu política en la superficie responsela misma semántica de veredicto y regla que la ruta no en streaming — y emite solo las supervivientes:
Veredicto de la llamada retenidaQué recibe tu cliente
allow / auditLos frames retenidos originales, sin cambios — un paso retrasado, no un chunk reagrupado.
sanitizeLa llamada con sus argumentos reescritos (secretos/PII coincidentes reemplazados con un token tipado), reemitida.
denyLa llamada se descarta. Si era la única llamada del turno, el turno cierra con finish_reason: "stop" — el stream parece como si el modelo no hubiera hecho ninguna llamada a herramienta.
Si nada coincidió, solo pagas el retraso de almacenamiento en buffer en los frames de llamada a herramienta — el contenido ya se transmitió en vivo. El firewall reconstruye frames solo cuando realmente actúa (un deny o un sanitize); un allow limpio reenvía los bytes exactos de tu upstream.

3. Un ejemplo concreto

Una política de response con una regla deny sobre *.delete (autórala en el editor de reglas de la consola) y una solicitud en streaming cuyo modelo decide llamar tanto a db.query como a 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"}]}
Tu agente lee el texto del asistente en tiempo real, luego recibe solo db.querydb.delete fue ensamblada, evaluada, denegada y nunca emitida. La llamada superviviente se reindexa desde 0, y el evento del firewall para la llamada denegada aterriza en tu registro de eventos con la regla que se disparó.
Lanza una política de respuesta en streaming bajo modo shadow primero. En modo shadow cada veredicto de aplicación se degrada a audit (razón prefijada con [shadow] would …) y todos los frames de llamada a herramienta pasan — así que puedes confirmar que la política coincide con lo que esperas en tráfico transmitido real antes de que empiece a descartar llamadas.

4. Los bloqueos inbound hacen corto-circuito antes de que el stream empiece

El baile de frames retenidos es solo para la superficie response — llamadas que el modelo emite. Un deny inbound (una herramienta que un agente anuncia) se dispara antes de la llamada al modelo upstream, así que una solicitud en streaming que dispara una regla inbound nunca abre un stream SSE en absoluto: devuelve un HTTP 400 simple con el código de error firewall_blocked, marcado como skip-retry. Sin frames, sin ventana de retención — el bloqueo aterriza como cualquier error no en streaming.

5. Guardrails en el mismo stream

Una respuesta en streaming puede llevar una política de salida de Guardrail y una política de respuesta del firewall a la vez. Actúan sobre cosas diferentes — los guardrails examinan el texto que el modelo transmite; el firewall gobierna las llamadas a herramienta — y se componen:
  • Bloqueo de guardrail de salida (streaming): el escáner de salida corta el stream en el momento en que una regla se dispara, reenvía un único chunk de reemplazo genérico — [Response blocked by content policy.] con finish_reason: "content_filter" — y se detiene. El mensaje es deliberadamente genérico (sin categoría de regla) para que un sondeador no pueda enumerar tu política. Una retención del firewall en vuelo cuando esto ocurre se descarta, así que una llamada a herramienta retenida no puede escaparse después del bloqueo.
  • Enmascaramiento de guardrail de salida (streaming): el enmascaramiento de la solicitud está antes del modelo en vivo; el enmascaramiento en banda en vivo de la salida transmitida está en la hoja de ruta. En un stream una regla de máscara registra la coincidencia pero actualmente reenvía el chunk original — autórala sabiendo que la redacción aún no se reescribe en el cable. El bloqueo de salida está plenamente aplicado en los streams.
Esta página describe la forma SSE de chat-completions de OpenAI. El mismo contrato retener-evaluar-emitir está cableado por formato — Anthropic Messages nativo, Gemini, xAI, y el stream de OpenAI Responses lo llevan cada uno en su propia forma de evento — así que el comportamiento observable por el cliente es idéntico sin importar qué proveedor atendió la solicitud.

6. Qué significa esto para tu cliente

Algunas consecuencias prácticas del modelo de frames retenidos:
Un turno cuya única llamada a herramienta fue denegada cierra con finish_reason: "stop" en vez de "tool_calls" — para tu agente se lee como “el modelo eligió no llamar a una herramienta”. Un turno donde algunas llamadas sobrevivieron cierra con "tool_calls", llevando solo las supervivientes.
Cuando un upstream agrupa el usage de tokens en el mismo chunk terminal que el firewall retuvo, el gateway lo readjunta al frame reconstruido final — un cliente que solicitó usage de stream aún lo obtiene.
Si el modelo emitió contenido y una llamada a herramienta en el mismo frame, el contenido se recupera y reemite incluso cuando la llamada a herramienta se quita — bloquear una llamada nunca descarta tu texto de asistente.
No optas un stream por nada de esto. Adjunta una política a la clave (o establece un valor por defecto del espacio de trabajo) y sigue transmitiendo exactamente como antes — la aplicación está en el gateway.

Dónde ir a continuación

Etapas y superficies

inbound, response, mcp, egress — dónde evalúa cada regla.

Veredictos

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

Sanear argumentos

Redacta secretos de los argumentos de una llamada a herramienta — solo capa de argumentos.

Modo shadow

Degrada los veredictos de aplicación a audit mientras mides el impacto.
Para dónde se sitúa esto en la ruta de la solicitud, ver cómo inspecciona OrcaRouter y latencia de la ruta de aplicación. Para las amenazas que la aplicación de superficie response contiene, ver llamadas a herramienta peligrosas y exfiltración de datos. Para la gramática de reglas completa, ver la referencia de reglas del firewall.