LangChain 教學:Documents 與 Text Splitter — RecursiveCharacterTextSplitter 最佳化
簡介
在使用大語言模型(LLM)進行檢索增強生成(RAG)時,文件切分是不可或缺的前置作業。
如果直接把整篇長文丟給模型,往往會超過 token 限制,或是讓模型無法聚焦於關鍵資訊。
RecursiveCharacterTextSplitter 是 LangChain 中最常用的切分器之一,它能夠根據自訂的分割符號、字元長度與重疊區塊(overlap)遞迴切分文本,兼顧 完整性 與 效率。本篇文章將深入探討如何最佳化 RecursiveCharacterTextSplitter 的參數設定與實作技巧,讓你在建構 RAG 系統時既能保留語意,又不會浪費過多 token。
核心概念
1. 為什麼選擇 RecursiveCharacterTextSplitter?
| 特色 | 說明 |
|---|---|
| 遞迴切分 | 先以較粗的分割符(如段落)切分,若段落仍過長再以較細的分割符(如句子)切分,直至滿足長度限制。 |
| 重疊機制 | 每個切分結果會保留前後 chunk_overlap 個字元,避免關鍵資訊跨段落被割斷。 |
| 彈性設定 | 可自行定義分割符序列、目標長度 chunk_size、最小長度 min_length 等參數。 |
使用遞迴策略的好處是 保留自然語言的結構(段落 > 句子 > 詞彙),相較於單純的固定長度切分,能減少語意斷裂的風險。
2. 基本參數說明
| 參數 | 型別 | 預設值 | 功能 |
|---|---|---|---|
chunk_size |
int |
1000 |
每個 chunk(切分單位)的最大字元數。 |
chunk_overlap |
int |
200 |
前後重疊的字元數,通常設定為 chunk_size 的 10‑20%。 |
separators |
list[str] |
["\n\n", "\n", " ", ""] |
從粗到細的分割符序列。 |
length_function |
Callable[[str], int] |
lambda x: len(x) |
自訂字元長度計算方式,常用於計算 token 數。 |
keep_separator |
bool |
False |
是否在切分結果中保留分割符本身。 |
3. 何時需要自訂 length_function?
LangChain 預設以 字元長度 作為切分依據,但在實務上,我們更關心 token 數(尤其使用 OpenAI、Claude 等模型時)。若要依 token 數切分,可使用 tiktoken(OpenAI)或 anthropic 的 tokenizer,將 length_function 替換為計算 token 數的函式。
import tiktoken
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 取得對應模型的 tokenizer
enc = tiktoken.encoding_for_model("gpt-4o-mini")
def token_length(text: str) -> int:
return len(enc.encode(text))
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 目標 500 tokens
chunk_overlap=100, # 100 tokens 重疊
length_function=token_length,
separators=["\n\n", "\n", " ", ""]
)
4. 程式碼範例:從基礎到進階的最佳化技巧
範例 1️⃣ 基本使用(字元長度)
from langchain.text_splitter import RecursiveCharacterTextSplitter
text = """LangChain 是一個用於建構 LLM 應用的框架。...
(此處省略長段落)"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # 每塊最多 1000 個字元
chunk_overlap=200 # 前後重疊 200 個字元
)
chunks = splitter.split_text(text)
print(f"總共產生 {len(chunks)} 個 chunk")
print(chunks[0][:200]) # 顯示第一個 chunk 的前 200 個字元
重點:
chunk_overlap應設定為chunk_size的 10‑20%,可在保留上下文的同時減少重複 token。
範例 2️⃣ 以 token 數 為基準的切分
import tiktoken
from langchain.text_splitter import RecursiveCharacterTextSplitter
enc = tiktoken.encoding_for_model("gpt-4o-mini")
def token_len(text: str) -> int:
return len(enc.encode(text))
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 目標 500 tokens
chunk_overlap=100, # 100 tokens 重疊
length_function=token_len,
separators=["\n\n", "\n", " ", ""]
)
chunks = splitter.split_text(long_document)
# 檢查每個 chunk 的 token 數
token_counts = [token_len(c) for c in chunks]
print(token_counts[:5]) # 前 5 個 chunk 的 token 數
技巧:使用
token_len後,chunk_size與chunk_overlap直接對應模型的 token 限制,避免「切分後仍超過上限」的問題。
範例 3️⃣ 保留分割符號(keep_separator=True)
有時候分割符號(如段落標記)本身也是語意的關鍵,尤其在 Markdown 或 程式碼 文件中。
splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=150,
keep_separator=True,
separators=["\n\n", "\n", " "]
)
chunks = splitter.split_text(markdown_content)
print(chunks[0].endswith("\n\n")) # True → 段落分割符被保留
注意:保留分割符會稍微增加每個 chunk 的長度,務必在
chunk_size上留有緩衝空間。
範例 4️⃣ 自訂分割符序列以適應程式碼文件
對於程式碼(如 Python、JavaScript)文件,我們可以先以 函式或類別 為單位切分,再細分為 行。
splitter = RecursiveCharacterTextSplitter(
chunk_size=1200,
chunk_overlap=200,
separators=["\n\nclass ", "\n\ndef ", "\n", " "],
keep_separator=False
)
code_chunks = splitter.split_text(python_source)
print(f"切分後共有 {len(code_chunks)} 個程式碼區塊")
實務建議:將
class、def放在分割符的最前端,可確保每個 chunk 盡量完整地包含一個類別或函式。
範例 5️⃣ 結合 metadata 產出 Document 物件
在 LangChain 中,常會把切分結果包成 Document,以便於後續向量化與檢索。
from langchain.schema import Document
def split_to_documents(text: str, source: str) -> list[Document]:
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", " ", ""]
)
chunks = splitter.split_text(text)
return [
Document(page_content=chunk, metadata={"source": source, "chunk_index": i})
for i, chunk in enumerate(chunks)
]
docs = split_to_documents(long_article, source="https://example.com/article")
print(docs[0].metadata)
好處:加入
metadata(如來源 URL、段落索引)可以在檢索結果中直接回溯原始位置,大幅提升使用者體驗。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
| 切分後仍超過模型 token 限制 | 使用字元長度切分,卻忘記不同模型的 token/字元比例不同。 | 改用 length_function 計算 token 數,或在 chunk_size 上留 10‑20% 緩衝。 |
| 重疊區塊太大 | chunk_overlap 設得過高會造成大量重複 token,浪費向量化成本。 |
依模型 token 限制設定 10‑20% 為佳;可透過實驗調整。 |
| 分割符號不夠細緻 | 僅使用 "\n" 切分長段落,導致段落內仍過長。 |
依需求加入更細的分割符(如 " "、"."),或自訂正則表達式。 |
忘記 keep_separator |
某些格式(Markdown、程式碼)需要保留分割符,否則語意斷裂。 | 根據檔案類型決定是否啟用 keep_separator=True。 |
| 未加入 metadata | 切分後的 chunk 難以追溯來源,檢索結果缺乏上下文。 | 在產生 Document 時加入 metadata(source、page、chunk_index)。 |
最佳實踐清單
- 先以語意層級決定分割符:段落 → 句子 → 詞彙 → 字元。
- 使用 token 為基準的
length_function,避免「字元」與「token」不對等的問題。 - 設定
chunk_overlap為chunk_size的 0.1‑0.2,兼顧上下文與成本。 - 針對不同檔案類型自訂
separators(如程式碼、Markdown、HTML)。 - 產出
Document時加入完整 metadata,方便後續追蹤與調試。
實際應用場景
| 場景 | 需求 | 如何使用 RecursiveCharacterTextSplitter |
|---|---|---|
| 企業內部知識庫 | 需要把 PDF、Word、HTML 等文件切分後向量化,支援多輪對話檢索。 | 以 "\n\n"、"\n"、" " 為分割符,chunk_size 設為 500 tokens,chunk_overlap 為 100 tokens,並加入 metadata(檔案名稱、頁碼)。 |
| 聊天機器人 | 回答使用者問題時,需要即時取得相關段落。 | 使用 keep_separator=True 保留 Markdown 標題,讓模型能辨識「##」層級,提升答案的結構性。 |
| 程式碼輔助工具 | 解析大型程式庫,支援「問我這個函式」的查詢。 | 自訂 separators=["\n\nclass ", "\n\ndef ", "\n", " "],確保每個 chunk 包含完整的類別或函式定義。 |
| 多語言文件 | 同一份文件包含中、英文混合,需要保持語言切分一致。 | 在 separators 中加入 "。"、"?"、".",並使用 token_len 兼容不同語言的 token 計算。 |
| 長篇小說或報告 | 需要將小說切成章節、段落,供模型生成摘要。 | 先以 "\n\n" 切章節,再以 "\n" 切段落,chunk_size 設為 800 tokens,chunk_overlap 為 150 tokens,保留段落斷行 (keep_separator=True)。 |
總結
RecursiveCharacterTextSplitter 是 LangChain 中最靈活、最符合實務需求的文字切分工具。透過 遞迴切分、重疊機制、以及 自訂長度函式,我們可以:
- 精準控制 token 數,避免模型輸入超限。
- 保留語意完整性,減少跨段落斷裂的問題。
- 依檔案類型調整分割符,讓切分結果更貼合原始結構。
- 加入 metadata,提升檢索與除錯的可觀測性。
只要遵守「10‑20% 重疊」與「以 token 為基準」的原則,配合適當的 separators 與 keep_separator 設定,就能在各種應用場景(知識庫、聊天機器人、程式碼搜尋等)中,快速、穩定地產出高品質的文本 chunk,為後續的向量化、檢索與生成奠定堅實基礎。
祝你在 LangChain 的旅程中,玩得開心、寫得順利! 🚀