← → 翻页 · ESC 索引
生成式 AI 職訓實務應用班 140hr · 弄一下工作室
D15 / 20
Day 15 · Part 6 · CH6-5 + PRAC6-1

Streaming + RAG

讓 AI 一邊想、一邊說,還會引用文件

上半場把 SSE / AbortController 串好,讓回答逐字浮現而不是空等五秒;
下半場把 D13 預告的 Embedding 補完,再用 RAG pipeline 把公司 FAQ 與會議紀錄變成會引用來源的問答機器人

Vol.15·SSE · Embedding · Vector DB · RAG · Citation·2026
Duration · 4-5 小時授課 · D15 / 20
本日為 Part 6 · AI 應用開發進階的 RAG 接軌日
— D15 / 20 —
Vol.15 · Opening
02 / 30
D13 留下的伏筆 · 今天還

D13 我說過「Embedding
會挪到今天 RAG 那一節再講。
不是因為它難,
沒有 RAG 的 Embedding
學了沒地方用。

今天先讓 Streaming 把 UX 補滿,
下半場 Embedding 接上 RAG pipeline,一次到位

Quote · 銜接 D13 → D15
Page 02
Vol.15 · Learning Outcomes
03 / 30
下課後你會帶走

四個可上線的能力

01
Streaming UX
SSE 後端 + fetch ReadableStream + 打字機 + AbortController 一條龍。
02
Embedding 直覺
把文字變向量、用 cosine 找相似,知道哪些情境用、哪些情境不用。
03
RAG 五步 pipeline
Load → Chunk → Embed → Retrieve → Generate,每一步都有可貼上跑的範本。
04
引用 + Fallback
答案必附 [doc#heading],文件無答案時拒答,不再讓 RAG 編造細節。
4 個能力對應 4-5 小時授課
Page 03 · Outcomes
Act I · CH6-5 · Streaming
04 / 30
Act I · 1.0 hr

別讓使用者
盯著空白

Streaming 不是優化,是體驗的下限

預設 API 等 3-15 秒才一次吐完整段;改 Server-Sent Events 把 token 一個個推出去,使用者第 1 秒就看到第一個字

SSE·fetch + ReadableStream·AbortController
Act I · Streaming Layer
— Page 04 —
Act I · Streaming UX
05 / 30 講義 · CH6-5
同一個 API、兩種體感

非 Streaming vs Streaming

非 Streaming · 預設
  • 等待 3-15 秒,畫面全黑
  • 突然一次出現完整文字
  • 使用者懷疑 是不是當機
  • 適合:批次處理、背景任務
Streaming · 推薦 UX
  • 首個 token < 1 秒出現
  • 逐字打字機效果
  • 使用者感覺 AI 正在思考
  • 適合:聊天、即時問答、Demo

感知速度跟「總生成時間」沒關係,跟「第一個字什麼時候出現」有關。Streaming 把 5 秒的等待從沉默變成過程

UX Compare · TTFT 才是關鍵
Page 05
Act I · SSE Backend
06 / 30 講義 · CH6-5
後端 4 行 Header + 1 個 for-await

Express + SDK stream:true

Step 1 3 個 Header:Content-Type text/event-stream、Cache-Control no-cache、Connection keep-alive。再呼叫 res.flushHeaders() 立刻把 header 寫出去。
Step 2 建 stream:client.messages.stream({ model, messages, max_tokens }),把整段 prompt 丟進去,不要等回傳。
Step 3 逐 chunk 推送:for await (const chunk of stream),當 type 是 content_block_deltares.write('data: ' + chunk.delta.text + '\\n\\n')雙換行是 SSE 事件邊界。
Step 4 收尾:寫一行 data: [DONE]\\n\\n 當訊號,再 res.end()。前端看到 [DONE] 就關閉 reader。
Step 5 監聽 req 斷線req.on('close', () => stream.controller.abort())。客戶端關頁也要記得停上游,不然繼續燒 token
Backend SSE · 5 Steps
Page 06 · 講義含完整 server.js
Act I · Frontend Stream
07 / 30 講義 · CH6-5
EventSource 太弱、用 fetch + Reader

前端讀 stream 三件事

01
用 POST 不用 GET
EventSource 只支援 GET,prompt 一長就爆 URL。改 fetch('/api/stream', {method:'POST', body}),再用 res.body.getReader() 拿 reader。
02
decode + buffer 切事件
每收一塊 {value, done}TextDecoder 解 utf-8、累積到 buffer、按 \\n\\n 切。最後一段沒收完留回 buffer 等下一輪。
03
JSON.parse + DOM append
每事件 line.replace(/^data:\s*/,'')、看到 [DONE] 就 return;其餘 JSON.parsedeltaout.textContent += delta 即時上畫面。

關鍵不是 SSE 多潮,是這三件事都別漏:UTF-8 別截斷、buffer 別吞事件、parse 失敗要 try/catch。

Frontend Reader · 3 Pitfalls
Page 07 · ★ 講義有完整 client.js
Act I · Cancel UX
08 / 30 講義 · CH6-5
把技術變成 UX

打字機 + Cancel + Reset

游標閃爍 純 CSS:.out.streaming::after { content:''; animation: cursor 1s steps(2) infinite; }。stream 結束 removeClass('streaming') 游標自動消失。prefers-reduced-motion 要降為靜態半透明。
Cancel 按鈕 送出前 controller = new AbortController(),fetch 帶 signal: controller.signal。取消按鈕 onclick = () => controller.abort()TCP 真的斷,後端 req 'close' 也會觸發 → 上游一起停。
Reset 狀態 finally 區塊把 sendBtn / cancelBtn 互換、controller = null、清 buffer。再送一次必須乾淨重來,不然會接在舊文字後面或殘留 abort error。
錯誤分流 catch 內判 err.name !== 'AbortError' 才印錯誤;AbortError 不是 bug 是使用者意願,靜默處理就好。

會打字 + 能停 + 能重來——這三件事到齊,使用者才會把它當「成熟的 ChatGPT 介面」而不是 Demo。

Streaming UX Trio
Page 08 · 對應 CH6-5 情境 B
Act II · Embedding 終於登場
09 / 30
Act II · 0.7 hr

Embedding
把文字變座標

D13 留下的伏筆,今天兌現

不講數學、不講 transformer。
三件事:什麼是 embedding、cosine 怎麼比、向量庫該選誰。
講完直接接 RAG,不會像 D13 那樣「學了沒地方用」。

Vector·Cosine Similarity·Vector DB
Act II · Embedding Layer
— Page 09 —
Act II · Why Today
10 / 30
D13 講師 note · 兌現

「沒有 用途 的 Embedding,
學了會忘」

D13 當下 Embedding 只能講「把文字變數字」、再講就空轉。當天主軸是 API 基礎與 token 計費,強行插入會讓 4hr 教完變 6hr
D14 也沒講 D14 主軸是 Function Calling 與 Agent;雖然 RAG-as-Tool 提了一下、但沒有展開 vector 細節,留給今天。
D15 完整接 今天 Embedding 講完 30 分鐘內,立刻接 RAG pipeline、立刻接 PRAC6-1 兩個情境跑出來。學完即用是大腦記憶最強錨點。
學員視角 如果你 D13 / D14 有質疑「怎麼還沒講 Embedding」——今天就是答案。請把那兩天的問題拿出來再對照一次。
Course Design Note · 為什麼挪到今天
Page 10
Act II · Embedding 101
11 / 30 講義 · PRAC6-1
三步、不講數學

文字 → 向量 → 算夾角

① 文字 輸入:任何一段話。例:「請假需要主管簽核嗎?」、「年假怎麼計算?」、「公司有沒有彈性上班?」
② Embedding 模型 丟進 voyage-3 / text-embedding-3-large / cohere-embed-v3。回傳一個 1024 或 1536 維的向量。一次呼叫成本 < 0.0001 美元。
③ Cosine 相似度 兩個向量算 cos(θ),值落在 -1 ~ 1。越接近 1 越像。「請假簽核」和「請假流程」cos ≈ 0.85,「請假簽核」和「停車位」cos ≈ 0.12。
④ 用途 語意搜尋、RAG retrieval、相似內容推薦、自動標籤、duplicate detection。所有「找相似的」需求都是 Embedding 的甜蜜點

不要把 Embedding 跟 LLM 混淆:Embedding 是「把意義量化」,LLM 是「依語意產生新文字」。RAG 把兩者串起來

Embedding 101 · 4 Steps
Page 11 · 對應 PRAC6-1 RAG 段
Act II · Vector DB Picks
12 / 30
4 個選項、選錯就反覆搬家

向量庫 選型決策表

Chroma 本機優先、開箱即用。Python 一行 pip install chromadb,沒裝 server 也能跑。原型 / 內部小工具首選。資料量 < 10 萬 chunk 都流暢。
sqlite-vec 單一 .db 檔、零依賴。SQLite 擴充,可塞進 Cloudflare Worker / Vercel Edge。極小規模、要跟業務資料同檔時最方便。
pgvector 已有 Postgres 就直接加。生產級、可寫 SQL JOIN 業務資料。Supabase / Neon 都內建。不要再開第二個資料庫
Pinecone / Qdrant 千萬向量以上、要 scale 才考慮。雲端託管、有 hybrid search、metadata filter 強。原型階段絕對不需要

選型原則:先估你 6 個月內能蒐到多少 chunk。< 10 萬 → Chroma;已有 Postgres → pgvector;千萬以上 → 託管。不要為「未來可能 scale」付現在的營運成本

Vector DB Decision
Page 12
Act III · RAG Pipeline
13 / 30
Act III · 0.8 hr

RAG 五步
把 Embedding 接到 LLM

Load · Chunk · Embed · Retrieve · Generate

所有 RAG 系統都是這五步。差別只在每一步的細節決策——chunk 多大、top_k 取幾、retrieval 門檻多少、prompt 怎麼寫。

Pipeline·Chunking·Citation
Act III · 5-Step RAG
— Page 13 —
Act III · 5-Step Pipeline
14 / 30 講義 · PRAC6-1
每步都有關鍵參數

RAG 五步速查

Load 把原始檔案讀進來:MD / PDF / DOCX / Notion export。同時保留 metadata:doc_id、heading、source_url、updated_at。後面引用要用。
Chunk 先按 H2 切大段、再每 200 字 + 40 overlap 切小塊。每塊 metadata 帶 {doc_id, heading, chunk_id}chunk 太大檢索不準、太小語意斷裂
Embed 每 chunk 丟 embedding 模型,向量 + metadata 一起存進 Chroma / pgvector。同樣的模型必須用同一版,換版要重做整個庫。
Retrieve query embedding → top_k=5 相似度搜尋 → similarity < 0.35 直接丟掉。寧可 retrieve 0 篇走 fallback,不要拖一堆雜訊餵 LLM
Generate 把 top_k chunks 按順序拼進 system prompt,規定答案必附 [doc_id#heading]。引用是 RAG 防幻覺的第一道牆
5-Step RAG · 每步都有預設值
Page 14 · ★ 對應 PRAC6-1 情境 A
Act III · Chunking
15 / 30 講義 · PRAC6-1
RAG 80% 的問題出在 chunk 切錯

Chunk 三層原則

先語意、後尺寸
先按 H2 / H3 標題切。標題本身就是「人類已分組」的訊號,硬切會把同一個概念拆兩半。
200 字 + 40 overlap
每段再切 200 字 ± 20,相鄰塊重疊 40 字。中文 200 字 ≈ 英文 350 token。overlap 防止關鍵句被切斷在邊界。
Metadata 必填
每 chunk 都帶 doc_id / heading / chunk_id / updated_atGenerate 階段引用 / 過濾 / 顯示來源全靠這個。

新手最常踩的雷:「整篇文章塞一個 chunk」(retrieval 不準、token 爆炸)、「每句話一個 chunk」(語意斷裂、cosine 不可靠)、「不存 metadata」(答案無法引用、無法溯源)。

Chunking · 3 Rules
Page 15 · 對應 PRAC6-1 情境 A 步驟 2
Act III · Generation Prompt
16 / 30 講義 · PRAC6-1
RAG 第二道防線

Generate Prompt 4 條硬規則

規則 1 角色定位:「你是公司內部知識助理。只能根據以下 chunks 回答,不可動用通識。」一句話設邊界。
規則 2 引用格式硬規定:「每段答案結尾必附 [doc_id#heading] 格式來源。多 chunk 多個引用,逗號分隔。」
規則 3 無答案時拒答:「若 chunks 中沒有相關資訊,回覆「文件中未找到相關說明,請聯繫 HR」,不可推測、不可補洞。」
規則 4 chunk 區塊明確分隔:用 <chunk id="..." source="...">...</chunk> XML 包起來。模型對結構化標記比 markdown 敏感。

RAG 編造的細節 90% 是 prompt 沒寫好,不是 retrieval 抓錯。先把這四條釘進 system prompt,幻覺率立刻砍半

Generation Prompt · 4 Rules
Page 16 · ★ 對應 PRAC6-1 補充段
Act IV · Health Metrics 預告
17 / 30
Act IV · 0.3 hr · 預告 D16

RAG 跑得起來,
不代表跑得久

三個指標決定 能不能上線

Golden Set / Cost / Hallucination Rate。
今天先把概念植入腦海,明天 D16 完整跑 Evals + Guardrails + 成本控管 dashboard。

Eval·Cost·Hallucination
Act IV · D16 Preview
— Page 17 —
Act IV · 3 Health Signals
18 / 30
明天 D16 完整跑、今天先植入

三個 必裝 健康指標

01
Golden Set 通過率
20-50 題「輸入 → 預期答案」的測試集。改 prompt / 換模型都跑一次,低於 baseline 5% 立刻 rollback
02
單次成本與每日上限
記錄 input / output tokens、換算單次成本、估每日總額。API 後台設 Usage Limit,超過自動停。
03
Hallucination Rate
每 100 個答案抽 20 個逐句對照 chunk,編造的句子數 / 總句數。RAG 系統的核心生死指標

今天先把這三個欄位的記錄表建好,明天 D16 把完整 eval / guardrail / cost dashboard 補上。沒有指標的 RAG 不能上線

3 Signals · D16 Preview
Page 18
Act V · PRAC6-1 · 動手
19 / 30
Act V · 1.5 hr · 兩條 pipeline

PRAC6-1
跑出 RAG 雛型

公司 FAQ + 會議紀錄問答

兩個情境+一個 fallback 補充+一個自我演練「幻覺獵人」。
做完你會有一條可直接改吃自家文件的 RAG 雛型

情境 A · FAQ·情境 B · 會議·Solo · 幻覺獵人
Act V · Hands-on PRAC6-1
— Page 19 —
Act V · Task Brief
20 / 30 講義 · PRAC6-1
兩個情境、選一個或兩個都做

挑你真用得到的場景

情境 A · 公司 FAQ RAG
30 篇 FAQ MD 檔(HR / IT / 行政)→ chunk 後約 180-240 個 chunk → 向量庫 → 取代「每次問 HR」的內耗。適合一般辦公室、知識管理
情境 B · 會議紀錄問答
近 6 個月會議紀錄(Notion 匯出)→ chunk + metadata 帶日期 / 與會者 → 「上次決議是什麼」、「誰負責」秒查。適合 PM、跨部門協作
時間 每情境 約 22 分鐘。兩情境 + fallback 補充 + quiz 共 約 58 分鐘
DoD 至少跑出 5 個邊界查詢、答案都帶引用無答案時拒答不編造。
Task Brief · A or B or Both
Page 20
Act V · Scenario A Pipeline
21 / 30 講義 · PRAC6-1
情境 A · 公司 FAQ RAG

5 步骤 實作流程

Step 1 · Load glob('faq/*.md') 讀 30 篇 MD,同時抓 frontmatter 的 title / category,存成 {doc_id, content, meta} 列表。
Step 2 · Chunk 先按 H2 切大段,每段再 200 字 + 40 overlap,metadata 帶 {doc_id, heading, chunk_id}。30 篇 → 約 180-240 chunk
Step 3 · Embed 每 chunk 丟 voyage-3text-embedding-3-large(1024 維)。存進 Chroma 本機庫不需要雲端。批次 100 一次,省一半時間。
Step 4 · Retrieve query embedding → top_k=5 → similarity < 0.35 丟掉。剩餘 chunks 按 similarity 排序傳給 generate。
Step 5 · Generate 把 top_k chunks 拼進 system prompt(XML 格式分隔),call Claude Haiku。答案必附 [doc_id#heading],無相關資訊時拒答。
Scenario A · 5-Step Pipeline
Page 21 · ★ 程式碼在講義
Act V · Generate Prompt Template
22 / 30 講義 · PRAC6-1
情境 A · System Prompt 骨架

RAG Generate Prompt 範本

角色 「你是 ACME 公司的內部知識助理。只能根據 <chunks> 內的 FAQ 回答員工問題,不可動用其他知識。」
輸入區 <chunks>
  <chunk id="hr-leave#year-leave">年假計算規則為...</chunk>
  <chunk id="hr-leave#approval">請假需主管...</chunk>
</chunks>
輸出規則 「① 用繁體中文。② 每段答案結尾必附 [doc_id#heading],多來源用逗號分隔。③ 若 chunks 沒有對應資訊,回覆「文件未提及,請聯繫 HR」,不可推測。」
User {{ user_question }} — 員工原話直接傳,不要先讓另一個 LLM 改寫,會多 1 次延遲與成本。

把這四塊釘穩,RAG 答案就會長得跟客服系統一樣穩定——有來源、有邊界、有拒答。

Generate Prompt · 4-Block Template
Page 22 · ★ 可直接複製
Act V · Scenario B Pipeline
23 / 30 講義 · PRAC6-1
情境 B · 會議紀錄問答機器人

會議 RAG 的 三個變奏

變奏 1 metadata 多兩欄meeting_dateattendees。檢索時可加 filter「最近 30 天」、「內含 PM 名字」,大幅提升精準度
變奏 2 chunk 邊界改用「議題」而非字數。會議紀錄通常以條列式呈現,每個議題自成 chunk,重疊不必要。
變奏 3 retrieve 加時間衰減:similarity 分數乘上 exp(-days/180)。半年前的會議和昨天的會議都 0.8 相似,應該優先抓昨天那筆
變奏 4 Generate 加「決議與行動」格式:「請整理出『決議:X』、『負責人:Y』、『期限:Z』,每段必附 [meeting_date#topic]。」

情境 B 顯示 RAG 不是一個 prompt 通天。同樣 5 步、不同領域要做領域特化:metadata、chunk 邊界、檢索 ranking、輸出格式都要改。

Scenario B · 4 Domain Tweaks
Page 23 · 可直接套用
Act V · No-Answer Fallback
24 / 30 講義 · PRAC6-1
RAG 最重要的一段 prompt

拒答」比「編造
有價值 100 倍

為什麼會編造 LLM 預設行為是「盡可能完成回答」。retrieve 沒拿到對的 chunk,模型會用通識補洞,講得頭頭是道但全是錯的
Fallback Prompt 範本 「若 <chunks> 沒有與問題直接相關的資訊,請原文回覆『文件中未找到相關說明。建議聯繫 HR:hr@acme.com』,不要嘗試從通識補答、不要推測、不要部分回答。」
門檻雙保險 應用層也要做:retrieval 後若 top1 similarity < 0.5跳過 LLM 直接回 fallback 文案。省 token、保證一致、不靠模型自律。
使用者教育 UI 要明確顯示「這個答案來自 N 個來源」或「沒有相關文件,請改問題或聯繫人類」。不藏 retrieval 狀態,使用者才會學會怎麼問。

RAG 上線後最快的口碑殺手是「它說得很有信心,但全錯」。讓它敢拒答,是 RAG 變熟的第一個成熟跡象。

Fallback Prompt + UI · Both Layers
Page 24 · ★ 對應 PRAC6-1 補充段
Act V · Solo · Hallucination Hunter
25 / 30
沒有同儕?把 LLM 當對手

RAG 幻覺獵人 三件組

留檔
挑 1 份企業文件,跑 5 個邊界查詢(數字 / 日期 / 例外條款),把 query + 答案 + chunk 全部截圖存 rag-v1-YYYYMMDD.pngbaseline 不可重跑
24h 隔離
放著等 24 小時。隔天打開截圖前,先閉眼回想「自己覺得哪題容易翻車」,寫下 3 個預期漏洞。再對照截圖,差異就是盲點
LLM 三人格
請 LLM 扮 (a) 嚴格知識編輯(逐句對 chunk)、(b) 客戶律師(找法律風險)、(c) 不懂技術素人(找術語不通處)。三輪挑出單人看不到的洞。

自評最大的盲點是「自己做的 RAG 自己覺得沒幻覺」。三件組 = 對未來 24h 的自己 + 三個假想他者,比同儕審查還狠。

Solo Activity · 30 min
Page 25 · 對應 PRAC6-1 自己演練
Act V · Definition of Done
26 / 30
下課前要繳的東西

D15 產出 DoD

產出 1 一個 SSE Streaming demo:後端 /api/stream 跑得起來、前端打字機 + Cancel 都會動、DevTools 可看到 event-stream 與 [DONE]。
產出 2 一條 RAG pipeline:5-10 篇自選文件 → chunk + embed → Chroma 庫 → 5 個邊界查詢 → 答案都附 [doc#heading]
產出 3 一張漏洞清單:用幻覺獵人三件組找到的 3+ 個改版方向,具體到改哪段 prompt 或 chunk size
傳到作品集 D-01 / D-02 截圖或 GitHub link 傳「我的作品集」對應卡片,D16 評估與成本控管還會引用。

今天從「會用聊天」升級成「會把公司文件變成可問的助理」。明天 D16 把它變成可上線、可驗收、可控成本的東西。

DoD · 3 Outputs + Portfolio
Page 26
Act V · Closing Reflection
27 / 30
今天最重要的一句

RAG 不是
技術問題

知識管理問題

Pipeline 五步學一天就會。難的是文件本身有沒有被人類整理過——
標題分層清不清楚、有沒有更新時間、條款有沒有寫完整。
如果文件本身就亂,RAG 救不了

RAG 上線前的真正工作·是把公司文件先整理好
Reflection · 文件先整理、RAG 才有用
— Page 27 —
Closing · Map Forward
28 / 30
D15 → D16 銜接地圖

明天 D16 把 RAG 變成可上線

D16 主題 Evals + Guardrails + Cost(PRAC6-2 / PRAC6-3 / PRAC6-4 三題打包)。把今天 RAG 的健康指標三件補完整。
D16 動手 把今天的 RAG 雛型接 20 題 golden set、加 input/output guardrails、寫一個每日成本 dashboard
作業 D-01 RAG 雛型 + D-02 漏洞清單傳作品集,明天進教室前完成。沒做不能跑 D16 的 eval
遠景 D15 + D16 結束 = Part 6 主課結業。Part 7(D17-D19)就是專題衝刺,用今天的 Streaming + RAG 做你的畢業作。
D15 → D16 → Part 7 路線
Page 28 · Map Forward
Closing · Tool Quick Card
29 / 30
Streaming + RAG 一頁速查

D15 工具卡

Streaming Stack
後端:Express + SDK stream:true
協定:Server-Sent Events(text/event-stream)
前端:fetch + ReadableStream + AbortController
UX:CSS 打字機 + Cancel + Reset 三件事
RAG Stack
Embedding:voyage-3 / text-embedding-3-large(1024 維)
向量庫:Chroma / sqlite-vec / pgvector(按規模選)
Chunk:H2 切大段 + 200 字 + 40 overlap
Retrieve:top_k=5、similarity ≥ 0.35
必裝 prompt 規則 chunks 用 XML 分隔 ② 答案必附 [doc#heading] ③ 無資訊原文回拒答 ④ retrieve top1 < 0.5 跳過 LLM
明天還會用 D16 的 golden set / cost dashboard / hallucination rate 都會回來這張卡上。釘在工作區牆上
Tool Card · D15 全速查
Page 29
End of D15
30 / 30
D15 / 20 · Streaming + RAG · Complete

To Be
Continued

明天 D16 · Evals · Guardrails · Cost

你今天從「會寫 Agent」升級成「能把公司文件變成會引用的問答系統」。
明天我們把它接 eval 框架、加防護、做成本看板——讓老闆相信它可以上線

Vol.15·SSE · Embedding · RAG · Citation · Fallback·2026
弄一下工作室·生成式 AI 職訓 140hr·D15 / 20
感謝今天的投入
— 明天見 · D16 —