deny или sanitize на вызовах инструментов, которые выдаёт ваша модель — и
ваш агент вызывает шлюз с "stream": true. Вопрос, который реально важен:
может ли стриминговый ответ утечь заблокированный вызов инструмента до того,
как firewall решит? Не может, и эта страница объясняет один механизм,
который делает это правдой, так чтобы вы могли рассуждать о задержке и чанках,
которые получает ваш клиент.
Это сфокусированный взгляд на поведение SSE. О самих вердиктах см.
Вердикты; о грамматике правил см.
справочник правил.
1. Проблема стримингового firewall sse
Нестриминговый ответ — это одно JSON-тело — firewall видит всё это целиком, вычисляетtool_calls и возвращает очищенный результат. Стрим другой:
модель выдаёт вызов инструмента как десятки дельт tool_call через множество
SSE-фреймов, и как только фрейм переслан, ваш агент его уже имеет — нельзя
отозвать токен, который вы отправили. Вычислите слишком рано — и у вас нет
полного вызова (имя + полные аргументы) для суждения; пересылайте по ходу — и
deny уже слишком поздно.
Шлюз решает это простым, наблюдаемым контрактом:
Контент стримится вживую
Обычные текстовые дельты и дельты рассуждения проходят без изменений, в
реальном времени — нулевая добавленная задержка на токенах, которые
читает ваш пользователь.
Фреймы вызовов инструментов удерживаются
Любой фрейм, несущий дельту
tool_call (или legacy function_call),
удерживается из живого стрима, пока вызов не завершён и не вычислен.Firewall — это шлюз безопасности, так что он парсит каждый фрейм. Он не
угадывает, что фрейм только-контентный, по сырым байтам — JSON-экранированный
член
tool_calls не имеет литеральной подстроки для сопоставления, так что
подстрочный shortcut переслал бы невычисленный вызов инструмента. SSE-фреймы
малы; шлюз парсит каждый из них.2. Последовательность hold-assemble-evaluate
Для стримингового ответа chat-completions с активной политикой поверхности response каждый фрейм, который выдаёт upstream, идёт по одному из двух путей:Фрейм content / role / reasoning / usage → пересылается сейчас
Фрейм content / role / reasoning / usage → пересылается сейчас
Стримится вашему клиенту немедленно, побайтно. Они никогда не несут вызов
инструмента, так что firewall’у нечего решать.
Фрейм tool_call (или legacy function_call) → удерживается
Фрейм tool_call (или legacy function_call) → удерживается
Буферизуется вне живого стрима. Закрывающий фрейм
finish_reason хода с
инструментом удерживается вместе с ним, потому что выдача его рано сказала
бы вашему клиенту, что ход окончен, до того как firewall вынес решение.arguments каждого вызова), вычисляет каждый
против вашей политики на поверхности response — та же семантика вердикта и
правила, что у нестримингового пути — и выдаёт только выживших:
| Вердикт удержанного вызова | Что получает ваш клиент |
|---|---|
allow / audit | Оригинальные удержанные фреймы, без изменений — отложенный pass-through, не пере-собранный в батч чанк. |
sanitize | Вызов с переписанными аргументами (совпавшие секреты/PII заменены типизированным токеном), повторно выданный. |
deny | Вызов отбрасывается. Если это был единственный вызов хода, ход закрывается с finish_reason: "stop" — стрим выглядит так, будто модель не сделала вызова инструмента. |
3. Один конкретный пример
Политика response с правиломdeny на *.delete (создайте его в консольном
редакторе правил) и стриминговый запрос, чья модель решает вызвать оба
db.query и db.delete:
db.query — db.delete был собран, вычислен, отклонён и никогда не выдан.
Выживший вызов переиндексируется с 0, а событие firewall для отклонённого
вызова приземляется в вашем журнале событий
с правилом, которое сработало.
4. Inbound-блокировки замыкаются накоротко до начала стрима
Танец с удержанными фреймами — только для поверхности response — вызовов, которые модель выдаёт.inbound deny
(инструмент, который агент рекламирует) срабатывает до вызова вышестоящей
модели, так что стриминговый запрос, который срабатывает inbound-правилом,
вообще никогда не открывает SSE-стрим: он возвращает обычный HTTP 400 с
кодом ошибки firewall_blocked, помеченный
skip-retry.
Нет фреймов, нет окна удержания — блокировка приземляется как любая
нестриминговая ошибка.
5. Guardrails на том же стриме
Стриминговый ответ может нести политику вывода Guardrail и политику response firewall одновременно. Они действуют на разные вещи — guardrails проверяют текст, который стримит модель; firewall управляет вызовами инструментов — и они компонуются:- Блокировка вывода guardrail (стриминг): сканер вывода обрезает стрим в
тот момент, когда правило срабатывает, пересылает единственный обобщённый
чанк замены —
[Response blocked by content policy.]сfinish_reason: "content_filter"— и останавливается. Сообщение намеренно обобщённое (нет категории правила), так что зондировщик не может перечислить вашу политику. Удержание firewall в полёте, когда это происходит, отбрасывается, так что удержанный вызов инструмента не может проскользнуть после блокировки. - Маскировка вывода guardrail (стриминг): маскировка запроса до модели живая; живая in-band маскировка стримированного вывода в дорожной карте. На стриме правило маскировки записывает совпадение, но в настоящее время пересылает оригинальный чанк — создавайте его, зная, что редактирование пока не переписывается на проводе. Output block полностью применяется на стримах.
Эта страница описывает форму SSE OpenAI chat-completions. Тот же контракт
hold-evaluate-emit подключён по форматам — native Anthropic Messages, Gemini,
xAI и стрим OpenAI Responses каждый несут его в собственной форме событий — так
что наблюдаемое клиентом поведение идентично независимо от того, какой
провайдер обслужил запрос.
6. Что это значит для вашего клиента
Несколько практических следствий модели удержанных фреймов:finish_reason может измениться
finish_reason может измениться
Ход, чей единственный вызов инструмента был отклонён, закрывается с
finish_reason: "stop" вместо "tool_calls" — для вашего агента это
читается как «модель решила не вызывать инструмент». Ход, где выжили
некоторые вызовы, закрывается с "tool_calls", неся только выживших.usage всё равно приходит
usage всё равно приходит
Когда upstream упаковывает
usage токенов в тот же терминальный чанк,
который firewall удержал, шлюз повторно прикрепляет его к финальному
реконструированному фрейму — клиент, запросивший usage стрима, всё равно
его получает.текст, разделивший чанк вызова инструмента, сохраняется
текст, разделивший чанк вызова инструмента, сохраняется
Если модель выдала контент и вызов инструмента в том же фрейме, контент
восстанавливается и повторно выдаётся, даже когда вызов инструмента убран —
блокировка одного вызова никогда не отбрасывает ваш текст ассистента.
без изменений в коде агента
без изменений в коде агента
Вы не подключаете стрим ни к чему из этого. Привяжите политику к ключу (или
задайте default рабочего пространства) и продолжайте стримить ровно как
раньше — применение на шлюзе.
Куда двигаться дальше
Стадии и поверхности
inbound, response, mcp, egress — где вычисляется каждое правило.
Вердикты
allow, audit, deny, sanitize, pending_approval, cap_cost.
Очистка аргументов
Отредактировать секреты из аргументов вызова инструмента — только уровень аргументов.
Shadow-режим
Понизить применяющие вердикты до audit, пока измеряете влияние.
