deny나 sanitize — 을 활성화했고, 에이전트가 "stream": true로 게이트웨이를 호출합니다. 실제로 중요한 질문: 스트리밍 응답이
firewall이 결정하기 전에 차단된 툴 호출을 누출할 수 있는가? 그럴 수
없으며, 이 페이지는 그것을 참으로 만드는 한 메커니즘을 설명하여 지연과
클라이언트가 받는 청크에 대해 추론할 수 있게 합니다.
이것은 SSE 동작에 대한 집중된 관찰입니다. 판정 자체는
Verdicts를; 규칙 문법은
규칙 레퍼런스를 참조하세요.
1. 스트리밍 firewall SSE 문제
비스트리밍 응답은 하나의 JSON 본문입니다 — firewall이 전체를 보고,tool_calls를 평가하고, 정화된 결과를 반환합니다. 스트림은
다릅니다: 모델은 툴 호출을 많은 SSE 프레임에 걸쳐 수십 개의 tool_call
델타로 발행하며, 프레임이 일단 전달되면, 에이전트가 이미 그것을
가지고 있습니다 — 보낸 토큰을 철회할 수는 없습니다. 너무 일찍 평가하면
판단할 완전한 호출(이름 + 전체 인자)이 없고; 진행하면서 전달하면 deny는
이미 너무 늦습니다.
게이트웨이는 이것을 간단하고 관측 가능한 계약으로 해결합니다:
콘텐츠는 라이브로 스트리밍
일반 텍스트와 추론 델타는 변경 없이, 실시간으로 통과합니다 —
사용자가 읽는 토큰에 추가 지연 제로.
툴 호출 프레임은 보류
tool_call(또는 레거시 function_call) 델타를 담은 모든 프레임은
호출이 완료되고 평가될 때까지 라이브 스트림에서 보류됩니다.firewall은 보안 게이트이므로, 모든 프레임을 파싱합니다. 원시
바이트에서 프레임이 콘텐츠 전용이라고 추측하지 않습니다 — JSON
이스케이프된
tool_calls 멤버는 매치할 리터럴 부분 문자열이 없으므로,
부분 문자열 지름길은 평가되지 않은 툴 호출을 전달하게 됩니다. SSE
프레임은 작습니다; 게이트는 각각을 파싱합니다.2. 보류-조립-평가 시퀀스
response 표면 정책이 활성인 스트리밍 chat-completions 응답에 대해, 업스트림이 발행하는 각 프레임은 두 경로 중 하나를 취합니다:콘텐츠 / role / 추론 / usage 프레임 → 지금 전달
콘텐츠 / role / 추론 / usage 프레임 → 지금 전달
바이트 단위로 즉시 클라이언트로 스트리밍됩니다. 이것들은 결코 툴
호출을 담지 않으므로, firewall이 결정할 것이 없습니다.
tool_call(또는 레거시 function_call) 프레임 → 보류
tool_call(또는 레거시 function_call) 프레임 → 보류
라이브 스트림 밖으로 버퍼됩니다. 툴 턴의 닫는
finish_reason
프레임은 그것과 함께 보류됩니다. 그것을 일찍 발행하면 firewall이
판정하기 전에 클라이언트에게 턴이 끝났다고 알리게 되기 때문입니다.arguments 조각을 결합), response
표면에서 정책에 대해 모두 평가하고 — 비스트리밍 경로와 동일한 판정 및
규칙 의미 — 살아남은 것만 발행합니다:
| 보류된 호출의 판정 | 클라이언트가 받는 것 |
|---|---|
allow / audit | 변경 없는 원래 보류된 프레임 — 다시 배치된 청크가 아니라 지연된 그대로 전달. |
sanitize | 인자가 다시 쓰인(매치된 시크릿/PII가 타입 지정 토큰으로 교체된) 호출, 재발행됨. |
deny | 호출이 드롭됨. 그것이 턴의 유일한 호출이었으면, 턴은 finish_reason: "stop"으로 닫힙니다 — 스트림은 모델이 툴 호출을 하지 않은 것처럼 보입니다. |
3. 하나의 구체적인 예
*.delete에 대한 deny 규칙(콘솔 규칙 편집기에서 작성)을 가진 response
정책과, 모델이 db.query과 db.delete을 둘 다 호출하기로 결정하는
스트리밍 요청:
db.query만
받습니다 — db.delete은 조립되고, 평가되고, 거부되고, 결코 발행되지
않았습니다. 살아남은 호출은 0부터 재인덱싱되고, 거부된 호출에 대한
firewall 이벤트는 발동한 규칙과 함께
이벤트 로그에 떨어집니다.
4. inbound block은 스트림이 시작되기 전에 단락됩니다
보류 프레임 춤은 response 표면 — 모델이 발행하는 호출 — 만을 위한 것입니다.inbound deny(에이전트가
광고하는 툴)는 업스트림 모델 호출 전에 발동하므로, inbound 규칙에
걸리는 스트리밍 요청은 SSE 스트림을 아예 열지 않습니다: 오류 코드
firewall_blocked와 함께 평범한 HTTP 400을 반환하며,
skip-retry로
표시됩니다. 프레임 없음, 보류 윈도우 없음 — block은 다른 어떤 비스트리밍
오류처럼 떨어집니다.
5. 같은 스트림의 Guardrails
스트리밍 응답은 Guardrail 출력 정책 과 firewall response 정책을 동시에 담을 수 있습니다. 그것들은 서로 다른 것에 작용하며 — guardrails는 모델이 스트리밍하는 텍스트를 스크리닝하고; firewall은 툴 호출을 통제 — 조합됩니다:- 출력 guardrail block (스트리밍): 출력 스캐너는 규칙이 걸리는 순간
스트림을 끊고, 단일 일반 교체 청크 —
finish_reason: "content_filter"를 가진[Response blocked by content policy.]— 를 전달하고, 멈춥니다. 메시지는 의도적으로 일반적입니다(규칙 카테고리 없음). 그래야 탐색자가 정책을 열거할 수 없습니다. 이때 진행 중인 firewall 보류는 폐기되므로, 보류된 툴 호출이 block 후 빠져나갈 수 없습니다. - 출력 guardrail mask (스트리밍): 요청을 모델이 라이브가 되기 전에 마스킹; 스트리밍 출력의 라이브 인밴드 마스킹은 로드맵에 있습니다. 스트림에서 mask 규칙은 매치를 기록하지만 현재 원래 청크를 전달합니다 — 리댁션이 아직 와이어에서 다시 쓰이지 않음을 알고 작성하세요. 출력 block은 스트림에서 완전히 강제됩니다.
이 페이지는 OpenAI chat-completions SSE 형태를 설명합니다. 동일한
보류-평가-발행 계약이 형식별로 배선됩니다 — 네이티브 Anthropic Messages,
Gemini, xAI, 그리고 OpenAI Responses 스트림 각각이 자체 이벤트 형태로
그것을 담습니다 — 그래서 어느 프로바이더가 요청을 서비스했는지와 무관하게
고객 관측 가능한 동작은 동일합니다.
6. 이것이 클라이언트에 의미하는 것
보류 프레임 모델의 몇 가지 실용적 결과:finish_reason가 바뀔 수 있음
finish_reason가 바뀔 수 있음
유일한 툴 호출이 거부된 턴은
"tool_calls" 대신 finish_reason: "stop"으로 닫힙니다 — 에이전트에게는 “모델이 툴을 호출하지 않기로
선택”한 것으로 읽힙니다. 일부 호출이 살아남은 턴은 "tool_calls"로
닫히며, 살아남은 것만 담습니다.usage는 여전히 도착
usage는 여전히 도착
업스트림이 firewall이 보류한 같은 종착 청크에 토큰
usage를 묶으면,
게이트웨이는 그것을 최종 재구성된 프레임에 다시 붙입니다 — 스트림
usage를 요청한 클라이언트는 여전히 그것을 받습니다.툴 호출 청크를 공유한 텍스트는 보존됨
툴 호출 청크를 공유한 텍스트는 보존됨
모델이 같은 프레임에서 콘텐츠 와 툴 호출을 발행했으면, 툴 호출이
벗겨질 때조차 콘텐츠가 복구되고 재발행됩니다 — 한 호출을 차단하는
것이 결코 어시스턴트 텍스트를 드롭하지 않습니다.
에이전트 코드 변경 없음
에이전트 코드 변경 없음
스트림을 이 중 어느 것에도 옵트인하지 않습니다. 정책을 키에
연결하거나(또는 워크스페이스 기본값 설정) 이전과 정확히 동일하게 계속
스트리밍하세요 — 강제는 게이트웨이에 있습니다.
다음으로 갈 곳
스테이지 & 표면
inbound, response, mcp, egress — 각 규칙이 평가되는 곳.
Verdicts
allow, audit, deny, sanitize, pending_approval, cap_cost.
인자 정화
툴 호출의 인자에서 시크릿을 가림 — 인자 계층 전용.
Shadow mode
영향을 측정하는 동안 강제 판정을 audit로 강등합니다.
