AI Agent 開發者必學的 Streaming 漸進式輸出技巧

近年來「AI Agent」的開發已經非常普遍,不管是做客服機器人、知識檢索,還是線上客服小幫手,大家常用的API介面幾乎都是 OpenAI 的 Chat Completions API

在大多數情境下,我們會用 非串流(non-streaming) 的方式呼叫,也就是送出一個請求,等伺服器思考完再一次回傳完整的回答。這樣的模式在 LINE、Teams、Slack 這類即時通訊平台的 Chat Bot UI 中沒什麼問題,因為使用者本來就習慣機器人一次回覆一整段文字。

但其實還有一種像是ChatGPT 官網那樣的輸出方式。

漸進式輸出的好處

但如果你用過 ChatGPT 官方網站,就會發現它的回覆方式完全不同:文字會像打字機一樣一個字一個字冒出來。這種「漸進式輸出」的體驗有幾個好處:

  1. 回應更即時
    使用者不需要等模型完整算完才能看到內容,能先讀到前幾個字,就有「系統已經在思考」的感覺。

  2. 降低等待焦慮
    在 UX 上,空白畫面最容易讓人懷疑「是不是壞掉了」。逐字輸出則能持續回饋,讓人安心。

  3. 模擬自然對話
    人類聊天的過程就是一邊想一邊講,這種輸出方式更貼近自然互動。

  4. 更有戲劇感
    對 Demo、教學、產品展示特別有用,可以讓使用者感覺「AI 正在思考」。

因此,雖然很多 Chat Bot 不一定需要這樣的 UI,但如果你的產品本身能夠提供這種 漸進式對話,以「Streaming 方式回覆」可以讓用戶體驗好很多。

Streaming 是怎麼實作的?

那麼,ChatGPT 網站的打字機效果是怎麼做的?
關鍵就在於 OpenAI 提供的 stream: true 參數。

當你在呼叫 /v1/chat/completions API 時,如果設定 stream: true(底下第二行),伺服器就不會一次把整個 JSON 給你,而是會用 Server-Sent Events (SSE) 協議,持續推送「事件」。

{
  "model": "gpt-4o-mini",
  "stream": true,
  "messages": [
    { "role": "system", "content": "你是一個友善的助教。" },
    { "role": "user", "content": "請說明迴圈的用途" }
  ]
}

當這樣呼叫時,回傳時候會以漸進方式輸出,每一行會以 data: 開頭,裡面包的是 JSON 片段,例如:

data: {"id":"...","choices":[{"delta":{"content":"哈"}}]}
data: {"id":"...","choices":[{"delta":{"content":"囉"}}]}
data: [DONE]

每個片段(delta)可能只有幾個字,Client端只要不斷讀取並即時顯示,就能拼湊成完整的回答。

最後一行 data: [DONE] 代表輸出結束。

用 C# 呼叫 API,實現 Streaming

以下是一個簡單的 C# Console 範例,示範如何用 HttpClient 呼叫 OpenAI API,並逐字輸出回覆內容。

// 建立一個 HttpClient 實例,用於發送 HTTP 請求
using var http = new HttpClient();
using var req = new HttpRequestMessage(HttpMethod.Post, "https://api.openai.com/v1/chat/completions");
req.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", apiKey);
var body = new
{
    model = "gpt-4o-mini",
    // 啟用串流模式,讓回應可以逐步傳回
    stream = true,
    messages = new object[]
    {
    new { role = "system", content = "你是一個友善的助教。" },
    new { role = "user",   content = question }
    }
};

// 將請求主體序列化為 JSON 字串
string json = JsonSerializer.Serialize(body);
req.Content = new StringContent(json, Encoding.UTF8, "application/json");
// 發送請求並等待回應
using var resp = await http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead);
resp.EnsureSuccessStatusCode();

// 以串流方式取得回應的內容
await using var stream = await resp.Content.ReadAsStreamAsync();
using var reader = new StreamReader(stream);

Console.WriteLine("\n回答:\n");
// 逐行讀取回應內容
while (!reader.EndOfStream)
{
    var line = await reader.ReadLineAsync();
    // 忽略空行和非數據行
    if (line is null || !line.StartsWith("data: ")) continue;
    // 取得有效的 JSON 資料,並且檢查是否為結束標記
    var payload = line.Substring("data: ".Length);
    if (payload == "[DONE]") break;

    try
    {
        // 解析 JSON 資料
        using var doc = JsonDocument.Parse(payload);
        var root = doc.RootElement;
        // 取得 choices 陣列
        var choices = root.GetProperty("choices");
        // 取得第一個選項的 delta 屬性
        var delta = choices[0].GetProperty("delta");
        // 取得內容
        if (delta.TryGetProperty("content", out var contentElem))
        {
            // 取得內容
            Console.Write(contentElem.GetString());
        }
    }
    catch
    {
        // 解析錯誤...略
    }
}

Console.WriteLine("\n\n--- 完成 ---");

這個程式跑起來後,當用戶問一個問題,就會看到 AI 的回覆逐字出現在 Console 裡,效果跟 ChatGPT 網頁很像。

這段程式碼有幾個關鍵:
  1. 設定 stream: true
    這告訴伺服器要用 SSE 傳輸,而不是一次給完整 JSON。

  2. 使用 HttpCompletionOption.ResponseHeadersRead
    這樣 HttpClient 才會在讀到 Response Header 後立刻開始讀取,不會等整個 Body 收完。

  3. 逐行讀取 StreamReader.ReadLineAsync()
    SSE 的特徵是每行以 data: 開頭,客戶端要逐行處理。

  4. 解析 JSON 片段
    伺服器推來的每段 JSON 都很小,真正的文字藏在:

    choices[0].delta.content
    
  5. 即時輸出
    在 Console 直接 Console.Write() 就能模擬「打字機」的效果。

結語

很多人用 Chat Completions API 時,直覺會用一次請求、一次回覆的模式,這當然也能滿足大多數 Bot 的需求。

但如果你想在 Web UI 或 Desktop App上做出 更接近 ChatGPT 網站的互動體驗效果,那麼 Streaming 輸出就是關鍵。

而實作其實並不複雜,只要在呼叫 API 時加上 stream: true,搭配一點 SSE 的處理邏輯,就能完成。

希望這篇文章能幫助你在下次開發 AI Agent 時,能帶給使用者更流暢、更人性化的對話體驗。

ref repo:
https://github.com/isdaviddong/ex-openai-chat-stream-console.git

留言

這個網誌中的熱門文章

使用LM Studio輕鬆在本地端以API呼叫大語言模型(LLM)

開啟 teams 中的『會議轉錄(謄寫)』與Copilot會議記錄、摘要功能

原來使用 .net 寫個 MCP Server 如此簡單

關於 SSO 登出的那些事:Google、Microsoft、LINE 開發者必讀差異

VS Code的字體大小