API 開發
讓 Claude 邊想邊回:串流回應的原理與實作
讓 Claude 邊想邊回:串流回應的原理與實作
做聊天應用有一個很實際的體驗問題:Claude 生成一段長回應可能要好幾十秒,這段時間使用者只能盯著載入動畫,不知道系統是當了還是在忙。串流(streaming)就是為這件事而生:讓文字一小塊一小塊即時出現,像有人在對面打字一樣。
這篇帶你看懂串流背後發生了什麼事、六種事件各代表什麼,以及兩種實作寫法怎麼選。
你將學到什麼
為什麼需要串流
長回應可能要等上數十秒,串流讓使用者立刻看到文字開始出現。
六種串流事件
從 MessageStart 到 MessageStop,每種事件各自代表什麼。
兩種實作方式
stream=True 逐事件處理,或用 SDK 的 text_stream 簡化介面。
串流後拿回完整訊息
用 get_final_message 同時滿足即時顯示與資料儲存。
使用者怕的不是等,是不知道在等什麼
用一般方式呼叫 Claude 時,你的伺服器要等整段回應生成完畢才回傳給前端。回應可能要花 10 到 30 秒生成,這段時間畫面上什麼都沒有,使用者很容易以為壞掉了。
這種「先等再一次給」的設計,回應短的時候沒什麼問題;回應一長,體驗就崩了。更麻煩的是,看不到任何進度的使用者常會重新整理或再按一次送出,多出來的重複請求反而加重系統負擔。
開啟串流後劇本完全不同:Claude 一收到請求就先回覆「收到了,開始生成」,接著把文字一小塊一小塊往回送。你的伺服器把這些片段即時轉發給前端,答案就會像打字一樣逐字浮現。重點是:這一切仍然屬於同一個請求,只是回應的形狀從一大包變成一連串小包。
串流事件有哪六種
開啟串流後,回應不再是一大包,而是一連串各司其職的事件,依序是:
- MessageStart:一則新訊息開始了
- ContentBlockStart:一個內容區塊開始,裡面可能是文字、tool use 或其他內容
- ContentBlockDelta:實際生成的文字片段
- ContentBlockStop:目前的內容區塊結束
- MessageDelta:這則訊息即將完成的相關資訊
- MessageStop:整則訊息結束
其中真正帶著文字的是 ContentBlockDelta,要顯示給使用者的內容就在它身上,其他事件是骨架與訊號。
以一段單純的文字回應為例,事件的節奏會是:MessageStart 開場,ContentBlockStart 宣告文字區塊開始,接著一連串 ContentBlockDelta 把文字一段一段送出,ContentBlockStop 收掉區塊,最後 MessageDelta 與 MessageStop 收尾。先看懂這個節奏,之後遇到 tool use 這類更複雜的回應結構時就不會慌。
基本實作:stream=True
最直接的寫法,是在 messages.create 呼叫裡加上 stream=True:
messages = []
add_user_message(messages, "Write a 1 sentence description of a fake database")
stream = client.messages.create(
model=model,
max_tokens=1000,
messages=messages,
stream=True
)
for event in stream:
print(event)
跑起來你會看到事件一個接一個印出。這種寫法讓你完整掌握每一種事件,適合需要精細控制的場景,例如同時處理 tool use 或多個內容區塊。
第一次實作時,我很建議真的把每個事件印出來看一遍:你會對「回應原來是這樣一塊一塊組出來的」有非常具體的感覺,之後除錯看到事件流也能立刻對上號。
簡化介面:text_stream
多數時候你只想要文字,不想自己解析事件。SDK 提供了更省事的串流介面:
with client.messages.stream(
model=model,
max_tokens=1000,
messages=messages
) as stream:
for text in stream.text_stream:
print(text, end="")
text_stream 會自動濾掉文字以外的所有事件,只把生成的內容一段一段交給你;print 加上 end="" 就能在終端機做出逐字輸出的效果。在真實應用裡,這個迴圈裡做的事就是把片段轉發給你的前端。
兩種寫法怎麼選?想完整掌控每種事件(例如同時處理 tool use),就用 stream=True 自己解析;只是要把文字端給使用者看,text_stream 乾淨得多,也是多數聊天應用的日常選擇。還沒決定的話,先用簡化介面就好,等需求出現再換。
邊串流邊留一份完整訊息
串流片段適合即時顯示,但存進資料庫或做後續處理時,你需要的是完整訊息。串流結束後呼叫 get_final_message() 就能拿到組裝好的結果:
with client.messages.stream(
model=model,
max_tokens=1000,
messages=messages
) as stream:
for text in stream.text_stream:
# 把每個片段轉發給前端
pass
# 串流結束後,拿完整訊息存資料庫
final_message = stream.get_final_message()
一次請求同時滿足兩種需求:使用者看到即時輸出,程式拿到完整物件。這是聊天應用最常見的收尾寫法。
把整條路串起來看:瀏覽器發出請求、你的伺服器帶著金鑰呼叫 Claude 並開啟串流、每收到一個片段就即時轉發回前端、串流結束後把完整訊息存進資料庫。使用者的體感從「等一大段」變成「看著答案長出來」,而你的程式邏輯並沒有因此變得複雜多少。
串流是聊天應用的基本功:它把「模型生成需要時間」這個物理限制,轉化成使用者可以接受、甚至覺得生動的體驗。

