before_response_emit hook 的那些坑,我翻 code 整理了一遍
最近在追 before_response_emit hook 的實作細節,翻了一陣子 source code,發現這個 hook 底層有幾個設計決策很值得記錄下來,因為表面的 API 看起來簡單,但真正要做 output policy 的時候,edge case 比想像中多很多。
先說最關鍵的一個:clearAllAssistantContent 的實作不是把內容 blank 掉,而是用 splice/remove 直接把 block 從陣列裡拿走。這個差異很重要——如果只是把 content 清空為空字串,Anthropic API 會噴錯,因為它不接受空的 assistant message。所以「刪除」這件事,在實作層面必須是「移除這個 message」,而不是「把這個 message 的內容清空」。
第二個坑在於 rewriteAllAssistantContent。這個函式要同時處理 output_text 和 input_text 兩種 block type,而且改完之後要跟 allContent 保持一致。allContent 是用來追蹤整個 response 流的累積狀態,如果 rewrite 只改了 message 本身但沒有縮減 allContent,後面的 block-reply chunk 對齊就會出問題,導致 response 在 streaming 過程中結構錯亂。
多輪 tool-call 的場景是第三個需要注意的地方。當 agent 跑了好幾輪 tool call,中間某輪的 assistant message 要被刪除或改寫時,要非常小心不要誤刪其他輪的保留訊息。因為 conversation history 裡可能有多個 assistant message,splice 的 index 稍微算錯就會刪到不該刪的。
還有一個容易忽略的設計原則:fail-closed。如果 hook 執行過程中出錯,預設行為應該是阻擋這條訊息,而不是讓它通過。output policy 的語境下,「不確定」就等於「不安全」,fail-open 是個很危險的預設。
如果你打算自己寫一個 output policy skill,或是用 before_response_emit 做任何內容過濾,這幾個點建議先弄清楚再動手。尤其是 allContent 的一致性問題,在 streaming 模式下特別容易踩到,而且症狀不明顯,可能只是偶發的 chunk 對齊錯誤,很難 debug。
作者:jiaweiOrz