Streaming
逐步接收模型輸出,並正確處理事件、增量、完成與斷流。
Streaming 讓用戶端在模型生成完整回答之前持續接收增量內容。它適合聊天介面、長文字生成和需要盡快顯示首段結果的情境,但用戶端必須正確處理事件邊界、內容串接、完成狀態和連線中斷。
Streaming 改變回傳方式,不改變任務本身
同一個任務通常可以選擇非串流或串流回傳。先完成非串流請求,再開啟 Streaming,更容易區分請求參數問題和串流解析問題。
使用前確認
- 在模型廣場確認目標模型支援所選協議和 Streaming。
- 根據協議使用對應端點和事件結構,不要讓同一個解析器猜測所有協議。
- 確認用戶端、反向代理和閘道不會緩衝整個回應後再一次回傳。
完整欄位分別由 Chat Completions、Responses、Anthropic Messages 和 Gemini Stream Generate Content Reference 維護。
送出最小串流請求
下面透過 Chat Completions 開啟串流輸出:
curl --no-buffer 'https://api.agtcloud.cn/v1/chat/completions' \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer your-api-key' \
--data '{
"model": "MODEL_ID",
"messages": [
{"role": "user", "content": "分三點解釋指數退避。"}
],
"stream": true
}'--no-buffer 讓 cURL 及時顯示已經收到的資料,不會改變伺服器端事件結構。
按事件讀取,而不是按網路資料包讀取
HTTP 連線每次交給用戶端的位元組數量並不等於一個完整事件。一個 JSON 物件可能被拆成多個資料區塊,多個事件也可能在同一個資料區塊中收到。
對於 SSE,用戶端應先以空行辨識完整事件,再讀取其中的 data: 內容:
const response = await fetch('https://api.agtcloud.cn/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer your-api-key',
},
body: JSON.stringify({
model: 'MODEL_ID',
messages: [{ role: 'user', content: '解釋什麼是指數退避。' }],
stream: true,
}),
});
if (!response.ok || !response.body) {
throw new Error(`請求失敗:${response.status}`);
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const events = buffer.split('\n\n');
buffer = events.pop() ?? '';
for (const event of events) {
const data = event
.split('\n')
.filter((line) => line.startsWith('data:'))
.map((line) => line.slice(5).trimStart())
.join('\n');
if (!data || data === '[DONE]') continue;
const chunk = JSON.parse(data);
const delta = chunk.choices?.[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}
}正式環境程式碼還應處理取消請求、讀取逾時、心跳、非 JSON 事件以及流結束時尚未消費的緩衝區。
四種協議的串流結構
Chat 通常回傳 Chat Completion Chunk。文字位於 choices[].delta.content,工具參數可能出現在 choices[].delta.tool_calls,結束原因位於 finish_reason;事件流可能以 [DONE] 結束。
協議入口存在不代表所有模型都會回傳上述所有內容類型。只實現應用實際使用、並已由目標模型回傳的分支。
串接文字和工具參數
- 文字增量按候選、輸出項或 Content Block 的索引分別串接。
- 工具名稱、呼叫 ID 和參數可能分多次到達,應為每個呼叫維護獨立緩衝區。
- 工具參數只有在完整結束後才能解析為 JSON,不能逐塊執行。
- 推理摘要、Thinking Block 和最終回答使用不同緩衝區,不能混成一段文字。
- 如果協議在最後回傳 Usage,應在收到完成事件前繼續讀取連接。
判斷成功、失敗和中斷
| 現象 | 用戶端處理 |
|---|---|
| 收到協議規定的完成事件 | 儲存完整結果和 Usage,標記請求成功 |
| 收到錯誤或失敗事件 | 儲存已顯示內容,同時將本次請求標記為失敗 |
| 連接提前關閉 | 標記為不完整,不把已有文字冒充完整回答 |
| 使用者主動停止 | 取消讀取並在介面標記“已停止生成” |
| 長時間沒有新事件 | 按應用逾時策略取消,保留必要的疑難排解資訊 |
串流請求已經向使用者顯示部分內容後,不應自動無限重放。重試可能產生另一份不同回答,也可能重複觸發工具或其他有副作用的行為。
上線前檢查
- 頁面能夠逐步顯示文字,而不是等到連接關閉才顯示。
- 中文和多位元組字元不會因資料區塊切分而亂碼。
- 工具參數按呼叫 ID 正確串接,並在執行前完成驗證。
- 使用者取消、網路斷開、逾時和伺服器端錯誤都有明確狀態。
- 記錄不會記錄 API Key、完整敏感 Prompt 或未經去識別化的工具參數。
下一步可閱讀 Tool Calling 和錯誤與疑難排解。