Powei Lee

夢酒館 RAG 品牌大使:語意分塊與 CoT 架構實作

時間: 2024.11

透過實作 RAG 系統探索語意分塊策略。從複合式問句的向量稀釋問題出發,驗證 CoT 三階段架構有效性,並識別 chunking 邊界問題。將 RAG 語意分塊原則應用到部落格 GEO 優化。學習型專案,現已封存不再維護。

RAGSemantic ChunkingChain-of-ThoughtVector Search語意密度PythonFlask

專案簡介

核心理念: 透過實作理解「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」的平均值
  • 語意稀釋,導致檢索排名下降

這個領悟直接影響我的部落格寫作策略:

  1. 一段一焦點 = 一個標題 - 每個語意主題獨立成段,有自己的 H2/H3 標題
  2. 用語意切分,而非框架 - 不是「起承轉合各一段」,而是「一個完整概念 = 一個有標題的段落」
  3. 清楚的段落邊界 - 用標題(##)和分隔線---明確區分不同主題。
  4. 重點前置 - 結論放段落開頭,方便 AI 快速理解

專案歷程

技術演進:

  • 研究階段 - Jupyter Notebook 探索 chunking 策略
  • 實作階段 - 使用 Claude Code 重構為 Flask App(方便展示)
  • 部署階段 - 選擇 Modal 進行 Serverless 部署

專案狀態: 已封存(Archived)

線上 Demo 保留供參考,但不再進行功能開發或維護。


相關資源

專案連結:

詳細內容請參考 GitHub:

  • Monorepo 結構說明
  • Modal 部署步驟(MODAL_DEPLOYMENT.md
  • 本地開發環境設定(README.md