跳至內容

透過 SSE 串流傳送回應

本導覽會追蹤聊天回應從 ASP.NET Core API 傳送至 Blazor WebAssembly 瀏覽器用戶端的過程。您將瞭解確切的 SSE 線路格式、排清的重要性,以及用戶端如何剖析串流區塊。

API 進入點

ChatController.StreamChat 會處理 POST /api/chat/stream。它接受 ChatRequest,選擇要求的模型,若未指定則使用 claude-haiku-4.5 預設值,並將回應設定為伺服器傳送事件:

Response.ContentType = "text/event-stream";
Response.Headers.CacheControl = "no-cache";
Response.Headers.Connection = "keep-alive";

這些標頭會告知中介系統與瀏覽器,這是長時間存續的串流,而不是應緩衝至完成後才傳送的一般 JSON 回應。

線路格式

對於 CopilotChatService.ChatStreamAsync 產生的每個區塊,控制器會序列化一個小型 JSON 物件,並寫入一則 SSE 訊息:

data: {"content":"..."}

串流正常完成時,端點會寫入結束標記:

data: [DONE]

如果串流開始後發生例外狀況,控制器會在相同的 SSE 資料通道中寫入錯誤事件:

data: {"error":"..."}

此時已無法可靠地改用 HTTP 錯誤狀態。狀態碼與回應標頭都已送出,因此若後期發生失敗,唯一實用的回報方式是在串流承載資料中傳送。

排清每個區塊

每個內容區塊之後, ChatController.StreamChat 都會排清回應本文:

await Response.WriteAsync($"data: {data}\n\n", cancellationToken);
await Response.Body.FlushAsync(cancellationToken);

如果沒有 FlushAsync,伺服器、主機、Proxy 或瀏覽器可能會緩衝資料。SDK 可能正確產生增量內容,但使用者在緩衝區填滿或要求結束前都看不到任何內容,因而誤以為串流功能失效。

取消

StreamChat 接受要求 CancellationToken (由 ASP.NET Core 提供)。控制器會將它傳入 CopilotChatService.ChatStreamAsync,檢查 IsCancellationRequested ,並在迴圈期間同時將其傳遞至 WriteAsync 以及 FlushAsync。如果瀏覽器分頁關閉或要求遭放棄,API 便有途徑停止寫入並結束串流工作。

瀏覽器用戶端

Blazor 用戶端程式碼位於 ChatService.StreamChatAsync。它會將 POST 要求傳送至 /api/chat/stream ,並搭配 HttpCompletionOption.ResponseHeadersRead

using var response = await _http.SendAsync(
    httpRequest,
    HttpCompletionOption.ResponseHeadersRead);

ResponseHeadersRead 很重要,因為它會在回應標頭抵達後立即回傳。用戶端接著能逐行讀取本文串流,而不必等待完整回應。

剖析器會忽略空白行、尋找 data: 前置字串,並在遇到下列內容時停止: [DONE],並剖析 JSON 資料事件。 content 值會產生至 UI; error 值會轉換為例外狀況。

零售分析系統提示詞

ChatService.StreamChatAsync 會在呼叫端未提供系統訊息時傳送預設訊息。該提示詞會將助理定位為雜貨零售商的零售分析助理、提供示範內容,並要求使用 Markdown 表格與項目符號,根據資料提出商業洞察。它也會指示助理不要修改程式碼或建議程式碼變更。

Home.razor 會將使用者的提示詞與選取的模型傳遞至 ChatService.StreamChatAsync。因此,在要求送達 API 前,用戶端服務會套用預設系統提示詞。

使用 curl 試用

請將要求指向本機 API 使用的連接埠。例如,如果 API 正在 Web 用戶端的預設 API 基底位址上接聽,請使用下列命令傳送串流要求: curl -N ,讓 curl 不會緩衝回應:

curl -N -X POST http://localhost:5050/api/chat/stream \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Summarise customer C003 and recommend a segment.",
    "model": "claude-haiku-4.5",
    "systemMessage": "You are a retail analytics assistant."
  }'

您應該會看到多則 data: {"content":"..."} 訊息,後接 data: [DONE]