跳轉到主要內容
你啟用了一條 response 介面規則——對 你的模型發出的工具呼叫做 denysanitize——而你的代理以 "stream": true 呼叫閘道。真正重要的問題是:一個串流回應能在防火牆 決定之前洩漏一個被封鎖的工具呼叫嗎? 它不能,而本頁解釋讓那為真的 那一個機制,這樣你就能推理延遲與你客戶端收到的區塊。 這是對 SSE 行為的聚焦審視。關於裁決本身,參見 裁決;關於規則文法,參見 規則參考

1. 串流防火牆 SSE 問題

一個非串流回應是一個 JSON 主體——防火牆看見整個東西、評估 tool_calls,並傳回清理後的結果。一個串流則不同:一個模型會把 一個工具呼叫作為跨許多 SSE 框格的數十個 tool_call 差量發出,而 一旦一個框格被轉送,你的代理已經有了它——沒有撤回一個你已送出 的權杖這回事。評估太早,你就沒有完整的呼叫(名稱 + 完整引數)可 判定;邊走邊轉送,一個 deny 就已經太遲。 閘道以一個簡單、可觀察的合約解決這個問題:

內容即時串流

一般的文字與推理差量會不變地、即時地通過——對你使用者讀取的 權杖零附加延遲。

工具呼叫框格被保留

任何攜帶一個 tool_call(或舊版 function_call)差量的框格都會被 扣留在即時串流之外,直到該呼叫完整並被評估。
防火牆是一個安全閘門,所以它解析每一個框格。 它不會從原始位元組 猜測一個框格是僅內容的——一個 JSON 跳脫過的 tool_calls 成員沒有 字面子字串可匹配,所以一個子字串捷徑會轉送一個未評估的工具呼叫。 SSE 框格很小;閘門解析每一個。

2. 保留-組裝-評估序列

對一個 response 介面政策作用中的串流 chat-completions 回應而言,上游 發出的每一個框格走兩條路徑之一:
立即逐位元組串流到你的客戶端。這些絕不攜帶一個工具呼叫,所以 防火牆沒有東西可決定。
被緩衝在即時串流之外。一個工具回合的結束 finish_reason 框格會 與它一起被保留,因為提早發出它會在防火牆裁定之前告訴你的客戶端 該回合已結束。
串流結束時,閘道會把被保留的框格組裝成完整的工具呼叫(接合 每個呼叫串流的 arguments 片段)、在 response 介面上對照你的政策 評估每一個——與非串流路徑相同的裁決與規則語意——並只發出倖存者:
被保留呼叫的裁決你的客戶端收到什麼
allow / audit原始的被保留框格,不變——一次延遲的通過,而非一個重新批次的區塊。
sanitize該呼叫,其引數被改寫(匹配到的密鑰/PII 被替換為一個有類型的權杖),重新發出。
deny該呼叫被丟棄。如果它是該回合唯一的呼叫,該回合以 finish_reason: "stop" 結束——串流看起來就像模型沒做任何工具呼叫。
如果沒有東西匹配,你只付出工具呼叫框格上的緩衝延遲——內容已經 即時串流。防火牆只在它實際行動時(一個 deny 或一個 sanitize)才 重建框格;一個乾淨的 allow 會轉送你上游的精確位元組。

3. 一個具體範例

一個帶有對 *.deletedeny 規則的 response 政策(在主控台規則 編輯器中撰寫它)與一個其模型決定要同時呼叫 db.querydb.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"}]}
你的代理即時讀取助理文字,然後收到 db.query——db.delete 被 組裝、評估、拒絕,而從未被發出。倖存的呼叫從 0 重新索引,而那個 被拒絕呼叫的防火牆事件會帶著觸發的規則落入你的 事件記錄
先在影子模式下推出一個串流 response 政策。在影子模式下,每個強制執行的裁決都被降級為 audit (原因加上前綴 [shadow] would …),而所有工具呼叫框格都會通過 ——所以你能在它開始丟棄呼叫之前,於真實的串流流量上確認該政策 匹配你預期的東西。

4. Inbound 封鎖在串流開始前短路

被保留框格的這套機制只針對 response 介面——模型發出的呼叫。 一個 inbound deny(一個代理公告的 工具)會在上游模型呼叫之前觸發,所以一個絆到 inbound 規則的串流 請求根本不會開啟一個 SSE 串流:它傳回一個普通的 HTTP 400、錯誤 代碼 firewall_blocked,標記為 skip-retry。 沒有框格、沒有被保留窗口——該封鎖就像任何非串流錯誤一樣落下。

5. 同一串流上的防護欄

一個串流回應能同時攜帶一個 防護欄輸出政策 一個防火牆 response 政策。它們作用於不同的東西——防護欄審查模型 串流的文字;防火牆治理工具呼叫——而且它們組合:
  • 輸出防護欄封鎖(串流): 輸出掃描器在一條規則絆到的那一刻切斷 串流,轉送一個單一的通用替換區塊——[Response blocked by content policy.],帶有 finish_reason: "content_filter"——並停止。該訊息 刻意通用(沒有規則類別),這樣一個探測者無法列舉你的政策。當這 發生時一個在途中的防火牆保留會被丟棄,所以一個被扣留的工具呼叫 無法在封鎖後溜出去。
  • 輸出防護欄遮罩(串流): 在模型即時之前遮罩請求;串流輸出的 即時帶內遮罩在路線圖上。在一個串流上,一條 mask 規則會記錄該匹配 但目前轉送原始區塊——撰寫它時要知道遮罩尚未在線路上被改寫。輸出 block 在串流上被完整強制執行。
本頁描述 OpenAI chat-completions 的 SSE 形狀。 同一個保留-評估-發出 合約是按格式接線的——原生 Anthropic Messages、Gemini、xAI,以及 OpenAI Responses 串流各自以它們自己的事件形狀攜帶它——所以無論哪個 供應商服務了該請求,客戶可觀察的行為都相同。

6. 這對你的客戶端意味著什麼

被保留框格模型的幾個實際後果:
一個其唯一工具呼叫被拒絕的回合,會以 finish_reason: "stop" 而非 "tool_calls" 結束——對你的代理而言它讀起來像「模型選擇不呼叫 一個工具」。一個有某些呼叫倖存的回合會以 "tool_calls" 結束, 只攜帶倖存者。
當一個上游把權杖 usage 捆綁在防火牆保留的同一個終端區塊上時, 閘道會把它重新附加到最終重建的框格——一個請求了串流 usage 的 客戶端仍會得到它。
如果模型在同一個框格中發出了內容一個工具呼叫,即使該工具 呼叫被剝除,內容也會被復原並重新發出——封鎖一個呼叫絕不會丟掉 你的助理文字。
你不需要讓一個串流選擇加入這其中任何一項。將一個政策綁定到金鑰 (或設定一個工作區預設值)並像以前一樣繼續串流——強制執行在 閘道處。

接下來去哪裡

介面與介面層

inbound、response、mcp、egress——每條規則在哪裡評估。

裁決

allow、audit、deny、sanitize、pending_approval、cap_cost。

淨化引數

從一個工具呼叫的引數中遮罩密鑰——僅限引數層。

影子模式

在你衡量影響時將強制執行的裁決降級為 audit。
關於這在請求路徑中坐落的位置,參見 OrcaRouter 如何審查強制執行路徑延遲。 關於 response 介面強制執行遏制的威脅,參見 危險的工具呼叫資料外洩。關於完整的規則 文法,參見防火牆規則參考