跳轉到主要內容
你將一個防火牆政策綁定到一個金鑰,模型 把一個工具呼叫串流回來,而 response 介面在你的代理對它採取行動 之前剝除或改寫它。那個強制執行決策在每個供應商上都相同——相同的 規則、相同的裁決、相同的事件。不同的是防火牆對一個串流工具呼叫 採取行動後你客戶端看見的線路形狀,因為 OpenAI chat、OpenAI Responses API,以及原生 Claude /v1/messages 各自以不同方式框出工具 呼叫。 本頁是針對那些客戶可觀察差異的聚焦說明。它不重新記錄規則語言—— 參見防火牆規則——或介面模型,後者 涵蓋於介面。關於三者共享的內部 保留並重組機制,閱讀 串流內部機制

1. 為何防火牆供應商串流依線路而異

在一個非串流回應上,防火牆一次看見整個回覆並決定。在一個串流上, 模型的工具呼叫作為一連串片段抵達——一個名稱在一個框格中,引數 JSON 涓滴在更多框格中。一個裁決需要完整的呼叫(名稱完整 引數),而一個工具呼叫片段一旦被轉送就無法被撤回。 所以在每個供應商上,閘道都做同一件事:它讓一般內容即時串流,並 保留工具呼叫框格直到呼叫被完全組裝。在串流結束時,它評估每個 被組裝的呼叫,並只發出倖存者——以那個供應商自己的事件形狀。
你的文字絕不停滯。 只有工具呼叫框格被保留。助理內容、推理與 role 框格即時且不變地串流。該保留從第一個工具呼叫片段套用到那個 回合的結束——所以一個僅聊天的回應的串流方式,就和沒有綁定任何 防火牆時完全一樣。

2. OpenAI chat completions

/v1/chat/completions 上,工具呼叫作為按索引設鍵的 delta.tool_calls 片段串流。閘門保留那些(以及舊版的 delta.function_call 形狀),並在結束框格處發出從零重新索引的倖存 呼叫,隨後是一個 finish 框格:
結果你的客戶端收到什麼
allow原始的被保留框格,逐位元組——真正的通過。
sanitize一個帶有改寫後引數的 tool_calls 差量,然後 finish_reason: "tool_calls"
deny(某些呼叫)只有倖存的呼叫,然後 finish_reason: "tool_calls"
deny(所有呼叫)沒有工具呼叫,然後 finish_reason: "stop"——該回合看起來像模型選擇以文字回答。
最後那一列是要測試對照的徵兆:當一個防火牆從一個 OpenAI chat 回合 剝除每一個工具呼叫時,你的代理看見一個乾淨的 finish_reason: "stop",而非一個錯誤框格。建構你的迴圈,把「這回合沒有工具呼叫」 當作一個有效的結果來處理。

3. OpenAI Responses API

原生的 /v1/responses 串流有它自己的事件模型——一個工具呼叫是一個 function_call 項目,以 response.output_item.added 開啟、串流 response.function_call_arguments.delta 片段,並在 response.output_item.done 完成。防火牆在 done——呼叫變完整的 第一個點——評估:
一旦呼叫通過,該項目的 added / 引數差量 / done 事件會不變地 發出。
added 外殼串流,然後一個其引數是遮罩後版本的 done——原始的 引數差量片段被丟棄,所以未遮罩的值永遠不會抵達你。
被緩衝的事件被丟棄,而被拒絕的項目也會從你客戶端用來建構其最終 狀態的終端 response.completed 物件中被過濾掉——沒有對一個從未 執行的呼叫的懸空參照。
文字與推理差量全程即時串流,與 chat completions 上完全相同。

4. 原生 Claude /v1/messages

一個原生 Anthropic 串流是另一種野獸:內容作為索引區塊抵達—— content_block_startcontent_block_deltainput_json_delta 片段) → content_block_stop——由一個攜帶 stop_reasonmessage_delta 關閉。防火牆從第一個 tool_use 區塊開始保留、評估每一個,並以 連續索引重建倖存的區塊,這樣一個被剝除的區塊不留下索引缺口。 Claude 特有的徵兆是 stop_reason。如果每一個 tool_use 區塊都被 拒絕,一個 tool_usestop_reason 會向你的客戶端承諾一個從未 抵達的工具呼叫——所以閘道會把它改寫為 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"}
一次部分剝除會保留倖存的 tool_use 區塊、連續地重新索引,並讓 stop_reason: "tool_use" 保持原樣。
這套用於原生 Claude 串流。一個透過 OpenAI 格式端點呼叫的 Claude 模型會改在 OpenAI chat 線路上被強制執行(§2),所以它顯示 finish_reason: "stop",而非 stop_reason: "end_turn"。把你的回合 結束處理匹配到你呼叫的線路格式,而非底層模型。

5. 一個具體範例

同一條規則在每個供應商上產生同一個決策——只有你客戶端讀取的線路 形狀不同。撰寫它一次,在 response 介面上:
{
  "stage": "response",
  "tool_name_glob": "shell.exec",
  "verdict": "deny",
  "args_match_json": "{\"clauses\":[{\"path\":\"$.command\",\"op\":\"regex\",\"value\":\"rm -rf|mkfs\"}]}"
}
以三種方式串流同一個提示,而防火牆每次都拒絕那個 rm -rf 呼叫。 你的客戶端觀察到的:
線路一次完整剝除後的終端訊號
OpenAI chatfinish_reason: "stop"
OpenAI Responses項目在 response.completed 中缺席
原生 Claudestop_reason: "end_turn"
無論線路如何,匹配並拒絕的呼叫都會在 防火牆事件中以相同方式顯示, 所以即使串流並非供應商無關,你的可觀測性也是供應商無關的。

6. 跨供應商保持不變的東西

線路不同;合約不變:
  • 裁決與規則是線路無關的。 allow / audit / deny / sanitize 在每個供應商上意義相同。參見 裁決
  • Sanitize 只觸及工具呼叫引數,絕不觸及工具回傳的內容——在每種 線路上。參見淨化回應
  • Allow 是真正的通過。 當防火牆不採取任何行動時,被保留的框格 會作為精確的上游位元組重放——沒有重新批次、沒有遺失的供應商特定 欄位。
  • 影子模式在各處都適用。 開啟它,被保留的工具呼叫總是倖存 (降級為 audit),這樣你能在一個政策改變流量之前跨供應商衡量它的 影響。參見影子模式

7. 這如何契合

串流內部機制

每個供應商共享的保留-組裝-重組機制。

介面

為何串流工具呼叫強制執行存在於 response 介面。

裁決

一個串流呼叫解析到的供應商無關決策。

Response 過濾

把關一個模型發出的工具呼叫,無論串流與否。
關於這些串流檢查處理的威脅,參見 危險的工具呼叫資料外洩;關於串流強制 執行在請求路徑中坐落的位置,參見 強制執行路徑延遲