一句話說明

Chunking 就是把一份長文件切成一段一段的小段落,讓 AI 能夠有效搜尋和處理裡面的內容。

什麼是 Chunking?

想像你有一本 500 頁的員工手冊,有人問你「請假要提前幾天申請?」。你不會把整本手冊丟給他,而是翻到請假辦法那一頁,指出對應的段落。

Chunking 做的就是這件事。它把大文件切成適當大小的段落(chunk),每個段落各自轉成 Embedding 向量,存進 向量資料庫。當有人問問題時,系統用 語意搜尋 找到最相關的段落,再把這些段落餵給 LLM 來回答。

這是 RAG 系統的必要步驟。切得好,AI 找到的段落就精準;切得不好,AI 可能找到不相關的內容,或者一個段落只有半句話,缺少上下文。

Chunking 怎麼運作?

最基本的概念很簡單:拿到一份文件,按照某種規則把它切成小塊。但實際操作時,有很多細節要考慮。

一個 chunk 的大小通常用 token 數量來衡量。常見的大小從 100 到 2000 個 token 不等,取決於你的應用場景。

切完之後,相鄰的 chunk 之間通常會保留一段重疊(overlap)。這是為了避免重要資訊剛好被切在兩個 chunk 的交界處。例如前一個 chunk 的最後 50 個 token 會出現在下一個 chunk 的開頭。

整個流程是:載入文件 → 前處理(去除多餘空白、格式化)→ 選擇切割策略 → 設定 chunk 大小和 overlap → 執行切割 → 產出 chunk 清單。

常見的 Chunking 策略

不同的切割策略適合不同類型的文件:

固定大小切割(Fixed-size Chunking):最簡單的方式,每 N 個字元或 token 切一段。優點是實作簡單、效果可預測。缺點是可能把一段完整的段落從中間切斷。

from langchain_text_splitters import CharacterTextSplitter

splitter = CharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separator="\n"
)
chunks = splitter.split_text(document)

遞迴切割(Recursive Character Splitting):這是目前最常用的方式。它會先嘗試用段落分隔符切(\n\n),如果段落太大就用換行符切(\n),再不行就用句號切,最後才用字元數硬切。這樣盡量保持語意完整。

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    separators=["\n\n", "\n", "。", ",", " ", ""]
)
chunks = splitter.split_text(document)

注意中文要用中文的標點符號(。,)而不是英文的(. ,)。

語意切割(Semantic Chunking):用 Embedding 模型判斷句子之間的語意相似度,在語意轉換的地方切割。這樣每個 chunk 都是一個語意完整的段落。缺點是需要額外的 Embedding 計算,速度比較慢。

結構化切割(Document-specific Splitting):根據文件的結構來切。Markdown 文件就用標題(#、##)切割,HTML 用標籤切割,程式碼用函式或類別切割。這個方式保留了文件原有的結構。

from langchain_text_splitters import MarkdownTextSplitter

splitter = MarkdownTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_text(markdown_text)

Chunk 大小怎麼選?

這是 Chunking 裡最常被問的問題。答案是:看你的應用場景。

Chunk 大小 適合場景 優點 缺點
小(100-200 token) 精確問答、FAQ 搜尋精準度高 缺少上下文,LLM 可能看不懂
中(300-500 token) 通用 RAG 平衡精準度和上下文 大部分場景的合理選擇
大(500-1000 token) 摘要、長文分析 上下文完整 搜尋精準度降低,塞滿 context window
很大(1000+ token) 文件分段、章節層級 保留完整論述 不適合精確搜尋

一般建議從 300-500 token 開始,overlap 設在 chunk 大小的 10-20%(例如 chunk_size=500, overlap=50-100)。然後根據實際搜尋結果的品質來調整。

如果你發現搜尋回來的 chunk 常常少了重要的上下文,就把 chunk 加大。如果搜尋回來的 chunk 裡有太多不相關的內容,就把 chunk 縮小。

實際應用場景

企業知識庫:公司的 SOP、規章制度、技術文件。這類文件通常有明確的標題結構,適合用結構化切割,按照標題層級來分段。

法律文件:合約、法規條文。法律文件的每個條款通常是獨立的語意單位,用結構化切割(依條號分段)效果最好。需要注意有些條款會引用其他條款,單獨一個 chunk 可能不夠完整。

技術文件:API 文件、程式碼說明。可以按照函式或 API endpoint 來切割。程式碼片段盡量保持完整,不要把一個函式切成兩半。

客服對話紀錄:每一組問答作為一個 chunk。如果對話很長,可以用語意切割在話題轉換的地方分段。

研究報告:學術論文、市場調查報告。通常按照章節切割,每個章節的摘要可以額外建立一個 chunk 來提升搜尋效果。

台灣使用情境

台灣企業在做 Chunking 時,有幾個特別需要注意的地方:

中文斷詞問題:中文不像英文有空格分隔單字,token 的計算方式不同。用 OpenAI 的 tokenizer,一個中文字通常是 1-2 個 token。設定 chunk_size 的時候要考慮這個差異。

中英混用:台灣的技術文件、商業文件常常中英夾雜。切割的時候要注意不要把英文術語從中間切斷,建議用遞迴切割搭配適當的分隔符。

法規格式:台灣的法規文件有自己的格式慣例(第X條、第Y項、第Z款)。如果你要處理法規文件,寫一個自訂的切割器來辨識這些結構會比通用切割器效果好。

安全考量與限制

Chunking 本身看似是個簡單的文字處理步驟,但有幾個容易忽略的問題:

資訊外洩:切割後的 chunk 會被送去做 Embedding,如果用的是雲端 API,代表你的文件內容會被送到外部。機敏文件需要評估風險,或使用本地 Embedding 模型。

上下文遺失:切割必然會遺失部分上下文。例如一份報告的表格跟它的說明被切到不同的 chunk,搜尋到表格時看不到說明。可以在每個 chunk 前面加上文件標題和章節標題作為補充上下文。

切割後的敏感資訊:一份文件裡可能只有一小段是機密的,但切割後那段機密內容會變成一個獨立的 chunk,被搜尋引擎索引。需要在切割前先做好資料分類和脫敏。

品質不一致:同一種切割策略在不同類型的文件上效果差異很大。PDF 轉出來的文字可能有格式問題(表格變亂碼、頁首頁尾重複出現),直接切割效果會很差。建議先做好文件前處理。

過度切割:chunk 切太小,每個 chunk 的資訊密度太低,搜尋回來一堆片段但每個都不完整。過度切割比切太大更容易造成問題。

怎麼開始?

  1. 確認你的文件格式:PDF、Word、Markdown、HTML 各有不同的處理方式。先把文件轉成乾淨的純文字。

  2. 選擇切割策略:大部分情境從 RecursiveCharacterTextSplitter 開始就好。如果文件有明確結構(Markdown 標題、法規條號),用結構化切割。

  3. 設定參數:chunk_size 從 400-500 token 開始,overlap 設 50-100 token。

  4. 測試切割結果:實際看看切出來的 chunk 品質如何。每個 chunk 是否語意完整?有沒有重要資訊被切斷?

  5. 用實際查詢測試:準備 10-20 個測試問題,檢查搜尋回來的 chunk 是否包含正確答案。根據結果調整參數。

  6. 持續優化:不同類型的文件可能需要不同的切割策略。建立一套評估機制,定期檢查 RAG 的回答品質。

一個完整的處理範例:

from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader

# 載入 PDF
loader = PyPDFLoader("company_handbook.pdf")
pages = loader.load()

# 切割
splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=80,
    separators=["\n\n", "\n", "。", ",", " ", ""]
)
chunks = splitter.split_documents(pages)

# 檢查結果
for i, chunk in enumerate(chunks[:3]):
    print(f"--- Chunk {i} ({len(chunk.page_content)} chars) ---")
    print(chunk.page_content[:200])
    print()

知識檢測

讀完文章後,測試一下你對這個主題的理解。

常見問題

Chunk 大小要設多少?

沒有放諸四海皆準的答案。一般建議從 300-500 token 開始,然後根據實際搜尋結果調整。如果你的問答比較精確(FAQ 類型),chunk 可以小一點(200-300)。如果需要更多上下文(分析報告),chunk 可以大一點(500-1000)。

Overlap 一定要設嗎?

強烈建議設。Overlap 的目的是確保重要資訊不會因為剛好被切在兩個 chunk 中間而遺失。通常設在 chunk 大小的 10-20% 就夠了。例如 chunk_size=500,overlap=50-100。

中文和英文的 chunk 大小設定一樣嗎?

概念上一樣,但實際的字元數會不同。因為中文字的 token 密度比英文高(一個中文字約 1-2 token,一個英文字約 1-4 token),設定 chunk_size=500 token 時,中文大約是 250-400 個中文字,英文大約是 125-500 個英文字。建議用 token 數量而不是字元數量來設定。

除了文字,圖片和表格怎麼處理?

這是 Chunking 的一大挑戰。目前常見的做法是:表格轉成 Markdown 格式再切割、圖片用 多模態模型 生成描述文字後再切割、或者把表格和圖片連同周圍的文字一起作為一個 chunk。沒有完美的解決方案,需要根據你的資料特性來處理。

總結

Chunking 是 RAG 系統中看似簡單但影響很大的步驟。切得好,搜尋結果精準,AI 回答品質高。切得不好,後面再怎麼調都很難補救。

實務上,從 RecursiveCharacterTextSplitter 開始,chunk 大小 400-500 token、overlap 50-100 token,然後根據實際測試結果來調整。不同類型的文件適合不同的策略,沒有一招打天下的做法。

參考資料