用 Claude API 的時候把串流打開("stream": true),回覆會變成一連串 server-sent events,文字會一小段一小段送過來,可以邊收取並同時印出文字。Claude 決定呼叫工具的時候,工具的參數也是用同樣的方式分段送來,只是這些片段是 JSON 的一部分,處理方式不太一樣。
官方的串流文件裡有個查天氣的例子:使用者問舊金山的天氣,Claude 先回了一句話(index 0 的文字區塊),接著開了 index 1 的工具區塊,呼叫 get_weather。下面是事件:
event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_01T1x1fJ34qAmk2tNTrN7Up6","name":"get_weather","input":{}}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"{\"location\":"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" \"San"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" Francisc"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"o,"}}
event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":" CA\"}"}}
event: content_block_stop
data: {"type":"content_block_stop","index":1}
仔細看這六段字串,第一段是空的,第二段是 {"location":,只有左大括號和一個 key; "San 開了引號沒有關, Francisc 則斷在單字的中間。每段都不能單獨拿去給 JSON 解析器,那肯定會錯,因為它們只是同一串文字切開後的段落。要等到 CA"} 送來,頭尾符號都有了,接著 content_block_stop 表示這個區塊結束;這時把六段照順序接起來,才會是 {"location": "San Francisco, CA"}。
所以程式在收參數的時候,要做的事情其實很簡單。fine-grained tool streaming 文件把它寫成三步:收到 tool_use 的 content_block_start 時準備一個空字串,每個 input_json_delta 來就把 partial_json 接到後面,等到 content_block_stop 再解析,解析要包在 try 裡。如果用的是 Python、TypeScript 或 Go 的 SDK,裡面已經有 helper 會把片段接好;但如果是直接處理事件,或是想自己決定怎麼處理的時候,才需要照這三步驟。
eager_input_streaming
查天氣的參數只有一個城市名,很短也很快就送完了。如果參數很長,像是整份文件或整段程式碼,預設的做法就會讓人等比較久:API 會先把每個參數值緩衝起來、驗證過之後才送出。
在自己定義的工具上把 eager_input_streaming 設成 true,請求本身也記得要設成串流,這個參數就不經過伺服器端的緩衝跟 JSON 驗證,Claude 開始的同時,片段也就跟著跑出來。fine-grained tool streaming 文件的範例就打開了這個設定,片段一到就印出來,讓人看到參數寫到哪裡;不過要知道此時印出來的是還沒接完的字串,還不能當參數用。接的方式跟前面一樣,但是伺服器沒有先驗證,所以到了 content_block_stop,接完的字串不保證是合法的 JSON。
因此,我們在使用時,要不要替某個工具打開 eager_input_streaming,可以確認情境,有時候很長再空等待那也許是個可以考慮的方向。
總之這篇提供個 Claude 的不同小用法,有任何討論和想法歡迎提供。

