夢酒館 RAG 品牌大使:語意分塊與 CoT 架構實作
時間: 2024.11
透過實作 RAG 系統探索語意分塊策略。從複合式問句的向量稀釋問題出發,驗證 CoT 三階段架構有效性,並識別 chunking 邊界問題。將 RAG 語意分塊原則應用到部落格 GEO 優化。學習型專案,現已封存不再維護。
專案簡介
核心理念: 透過實作理解「AI 如何閱讀問句與內容」
這是一個學習型 RAG 專案,目標是透過實際開發來理解語意分塊(semantic chunking)如何影響檢索品質。選擇夢酒館作為應用場景,實作「品牌大使」系統來驗證 CoT 三階段架構。
專案定位: 學習實驗 → 已封存,不再維護
核心學習成果:
| 學習面向 | 核心發現 | 實作驗證 |
|---|---|---|
| 問句向量化 | 複合式問句會語意稀釋 | 設計 CoT 三階段架構解決 |
| 語意分塊原則 | 從問句問題領悟內容分塊策略 | 理解「語意」而非「字數」或「實體」 |
| Chunking 邊界 | 發現內容組合錯誤問題 | 提出結構化資料綁定方案 |
| 部署選型 | ML 工作負載需特殊平台 | 從 Zeabur 遷移至 Modal |
關鍵領悟:
RAG 系統的價值不在於使用哪個 LLM(例如 GPT-4.1-nano),而在於如何設計 prompt、組織 chunks、處理向量化問題。LLM 周圍的程式碼比 LLM 本身更重要。
夢酒館 RAG 品牌大使示範影片
專案連結:
問題發現
問題 1:複合式問句造成語意稀釋
問題本質: 問句向量化問題
傳統 RAG 系統一次只能處理單一問題,對於包含多個子句的複合式問句,向量化後會產生語意稀釋,導致檢索不精確。
實際案例:
「我喜歡清爽氣泡的調酒,請你推薦我一杯調酒。同時也請你跟我說明夢酒館在地方所做的貢獻,還有夢酒館登過的國際媒體報導。」
這個問題包含了 3 個不同主題(調酒推薦 + 地方貢獻 + 媒體報導)。
為什麼會語意稀釋?
當一個問句包含多個主題時,該問句向量化之後對應到的位置,會是這所有主題平均值的位置。如果只用一次向量檢索,很可能無法完整回答。
問題範例對比:
| 問句類型 | 向量化結果 | 檢索效果 |
|---|---|---|
| 單一問題 | 100% 專注於調酒推薦 | 精準命中相關 chunks |
| 複合式問句 | 33% 調酒 + 33% 地方貢獻 + 33% 媒體報導 | 每個主題都檢索不完整 |
問題 2:內容組合錯誤(Chunking 邊界問題)
問題描述:
在測試中發現,當詢問「A 調酒的介紹」時,偶爾會混入「用 A 當標題、講 B 風味的組合」。
原因分析:
| 原因 | 說明 |
|---|---|
| 固定長度切分 | 因內容是以長度區分,並非同一調酒都被放在同一個 chunk |
| 向量相似度 | 兩個調酒描述的 chunks 相似度都很高 |
| 缺乏結構化綁定 | LLM 整合時,可能剛好抓到 A 的標題和 B 的描述 |
解決方案
CoT 三階段架構 - 解決複合式問句
流程設計:
使用者輸入(複合式問句)
↓
┌─────────────────────────────────────────────────┐
│ 階段 1:語意拆解 (Query Decomposition) │
│ • GPT-4.1-nano 拆解複合式問句 │
│ • 輸出:JSON array 子問題清單 │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 階段 2:個別檢索 (Individual Retrieval) │
│ • 針對每個子問題進行向量搜尋 │
│ • FAISS + E5 Embedding │
│ • top_k = 3 (平衡相關性與多樣性) │
└─────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────┐
│ 階段 3:整合回答 (Integration & Synthesis) │
│ • 根據原始問句整合所有檢索結果 │
│ • 使用品牌大使 prompt 生成最終回覆 │
└─────────────────────────────────────────────────┘
↓
完整回答(品牌語氣一致)
實測案例:
輸入:「我喜歡清爽氣泡的調酒,請你推薦我一杯調酒。
同時也請你跟我說明夢酒館在地方所做的貢獻,
還有夢酒館登過的國際媒體報導。」
拆解結果(階段 1):
1. 我喜歡清爽氣泡的調酒。
2. 請你推薦我一杯調酒。
3. 請你跟我說明夢酒館在地方所做的貢獻。
4. 請你說明夢酒館登過的國際媒體報導。
每個子問題獨立檢索(階段 2)
→ 整合成完整回覆(階段 3)
技術選擇
向量檢索實作
技術堆疊: E5 Multilingual + FAISS
直接採用教學模板實作方案,專注於理解檢索機制。學習重點放在:
- 理解向量相似度如何影響檢索結果
- 調整參數對結果品質的影響
- 觀察不同 chunking 策略對檢索準確度的差異
部署平台:為何選擇 Modal?
原本嘗試使用 Zeabur 部署,但遇到以下問題:
- 記憶體成本過高 - PyTorch + FAISS 需要大量記憶體
- Build 時間過長 - 實測接近 30 分鐘
因此遷移至 Modal,專門針對 ML/AI 場景優化:
- 自動擴展與縮減至零(空閒時無成本)
- Build 速度快,部署效率高
- 按使用量計費,Demo 專案幾乎零成本
學習收穫
1. 從問句向量化理解內容分塊原則
問題起點: 複合式問句會造成語意稀釋(多個主題的向量平均值)
關鍵領悟: 如果問句包含多個主題會稀釋,那麼內容 chunk 包含多個主題也會稀釋。
分塊原則對比:
| 分塊原則 | 說明 | 為什麼重要 |
|---|---|---|
| 語意分塊 | 一個語意主題 = 一個 chunk | 避免多個主題互相稀釋向量 |
| 實體分塊 | 一個調酒 ≠ 一個 chunk | 調酒可能包含多個語意主題(風味、故事、製作) |
| 字數分塊 | 500 字切一段 | 無視語意邊界,容易切斷完整概念 |
實際應用範例:
調酒 A 的完整介紹(500 字)
→ 不要切成「前 250 字 + 後 250 字」
→ 而是切成:
- Chunk 1: 調酒 A 的風味描述
- Chunk 2: 調酒 A 的品牌故事
- Chunk 3: 調酒 A 的製作理念
分塊策略演進:
以前的想法:
「一個調酒的完整描述應該放在同一個 chunk」
現在的理解:
「一個調酒可能包含多個語意主題(風味、故事、製作理念),
應該依語意切分,而非依實體切分」
為什麼重要?
當 chunk 內只包含單一語意主題時:
- 向量化後的位置更精準(100% 專注於該主題)
- 檢索時更容易命中(不會被其他主題稀釋)
- LLM 整合時更不容易混淆(每個 chunk 主題明確)
2. 識別 Chunking 邊界問題
問題描述:
在實作過程中發現內容組合錯誤(A 調酒混入 B 調酒描述)。這不是 LLM 問題,而是 chunking 策略問題。
原因分析:
| 原因 | 說明 |
|---|---|
| 固定長度切分 | 無法對應語意邊界(例如完整的調酒描述) |
| 向量相似度 | 相似主題的 chunks 容易被同時檢索 |
| 缺乏結構化綁定 | chunks 沒有 metadata 標示所屬實體 |
因為是學習專案,並無繼續把問題解決,僅記錄未來方向參考,但這個發現讓我理解到:
- RAG (甚至所有 AI App)系統的功能強度取決於周圍的資料處理設計,並非 LLM 模型
- Chunking 策略需要配合實際資料結構設計
3. 改進方向(未來實作時參考)
方案 1:結構化資料綁定
| 改進面向 | 技術方法 | 預期效益 |
|---|---|---|
| 資料完整性 | 向量 + metadata 綁定(entity_id, entity_type) | 檢索結果不混合不同實體 |
| 過濾精準度 | 依 entity_id 過濾 | 減少內容組合錯誤 |
| 擴展性 | 支援多種 entity_type(店家、景點、故事) | 應用於更多場景 |
實作構想:
Chunk 1: 調酒 A 的風味描述
metadata: { entity_id: "cocktail_a", entity_type: "cocktail" }
Chunk 2: 調酒 B 的風味描述
metadata: { entity_id: "cocktail_b", entity_type: "cocktail" }
檢索時:先向量搜尋 → 再依 entity_id 分組 → 只使用同一個 entity 的 chunks
方案 2:檢索後意圖過濾
- 再掛一個小型 LLM
- 對檢索結果進行意圖判斷
- 過濾掉與主問句意圖不符的 chunks
- 重新整理後才餵給最終要回覆使用者的 LLM 處理
4. 應用到部落格 GEO 優化
RAG 的語意分塊原則,也適用於部落格寫作
透過這個專案,我理解到如果想要讓我的部落格容易被 AI 理解和閱讀,我應該將內容調整,依照語意分段,讓每一小段的語意密度聚焦。
從 RAG 到 GEO 的連結:
| RAG 系統需求 | 部落格 GEO 優化 | 共同原則 |
|---|---|---|
| 問句向量化 需要精準定位 | AI 引用 需要精準定位段落 | 語意密度越高,越容易被找到 |
| Chunk 包含多個主題 → 向量稀釋 | 段落包含多個主題 → AI 難以引用 | 一個段落只講一件事 |
| 檢索時混淆 A 與 B 的內容 | AI 引用時混淆 不同概念 | 清楚的語意邊界很重要 |
實際應用在部落格寫作:
以前的寫法(多個主題混在一起):
「這個 RAG 專案讓我學到很多,包括語意分塊、CoT 架構、
Modal 部署,還有如何選擇合適的 Embedding 模型。」
現在的寫法(語意分段 = 每個主題有自己的標題):
## 語意分塊的重要性
分塊策略不佳會造成語意稀釋,導致檢索不精確。
## CoT 三階段架構解決複合式問句
實際做法是將問句拆解成子問題,分別檢索後整合回答。
## 部署平台:從 Zeabur 到 Modal
Zeabur 記憶體成本過高,遷移到 Modal 進行 Serverless 部署。
## Embedding 模型選擇考量
需要考慮多語言支援(繁中、英文)和向量維度(效能與精準度平衡)。
為什麼這樣寫對 GEO 有幫助?
當 AI(例如 ChatGPT、Claude、Perplexity)在回答使用者問題時:
- 問題: 「語意分塊的重要性是什麼?」
- AI 檢索: 精準命中「## 語意分塊的重要性」這個段落
- AI 引用: 只引用該段落內容,不會混入 CoT 或部署平台的資訊
對比: 如果多個主題混在同一段(沒有分開標題):
- AI 檢索到這一段,但向量位置是「語意分塊 + CoT + 部署 + Embedding」的平均值
- 語意稀釋,導致檢索排名下降
這個領悟直接影響我的部落格寫作策略:
- 一段一焦點 = 一個標題 - 每個語意主題獨立成段,有自己的 H2/H3 標題
- 用語意切分,而非框架 - 不是「起承轉合各一段」,而是「一個完整概念 = 一個有標題的段落」
- 清楚的段落邊界 - 用標題(##)和分隔線
---明確區分不同主題。 - 重點前置 - 結論放段落開頭,方便 AI 快速理解
專案歷程
技術演進:
- 研究階段 - Jupyter Notebook 探索 chunking 策略
- 實作階段 - 使用 Claude Code 重構為 Flask App(方便展示)
- 部署階段 - 選擇 Modal 進行 Serverless 部署
專案狀態: 已封存(Archived)
線上 Demo 保留供參考,但不再進行功能開發或維護。
相關資源
專案連結:
詳細內容請參考 GitHub:
- Monorepo 結構說明
- Modal 部署步驟(
MODAL_DEPLOYMENT.md) - 本地開發環境設定(
README.md)