LangChain 教學:Documents 與 Text Splitter
主題 – TextSplitter:如何切分文字
簡介
在使用大型語言模型(LLM)進行檢索增強生成(RAG)時,文件的切分是第一道關鍵門檻。
原始的 PDF、Word、網頁或純文字檔往往長度過大,直接送給 LLM 會超過 token 限制,且模型無法有效定位關鍵資訊。
TextSplitter 正是為了解決這個問題而設計的工具:它把長篇文字切割成適合 LLM 處理的「段落」或「chunk」,同時保留上下文與結構資訊。
本篇文章將從概念說明、程式範例、常見陷阱與最佳實踐,帶你一步步掌握 LangChain 中的文字切分技巧,讓你的 RAG 應用更穩健、效能更佳。
核心概念
1. 為什麼需要 TextSplitter?
- Token 上限:大多數 LLM(如 OpenAI 的 gpt‑3.5‑turbo)一次只能接受 4k~8k token。
- 檢索精準:切分後的 chunk 越小,向量化後的相似度搜尋越精準。
- 上下文保留:合理的切分策略可以在保留關鍵上下文的同時,避免資訊斷裂。
重點:切分的目標不是「盡可能小」,而是「在 token 限制內,讓每個 chunk 能完整表達一個概念或段落」。
2. LangChain 提供的主要 Splitter
| Splitter 類別 | 主要特點 | 適用情境 |
|---|---|---|
CharacterTextSplitter |
以固定字元數切分,支援 separator(如 \n\n) |
純文字、簡單段落切分 |
RecursiveCharacterTextSplitter |
先以較大分隔符(段落)切,若太長再遞迴切成較小單位(句子、字元) | 需要保留段落結構的長文件 |
TokenTextSplitter (LangChain 0.1+) |
直接以 token 數為基準切分,與模型 tokenizer 同步 | 精確控制 token 數,避免截斷 |
MarkdownHeaderTextSplitter |
依照 Markdown 標題層級切分 | Markdown 格式的技術文件、筆記 |
HTMLHeaderTextSplitter |
依照 HTML 標題 (<h1>~<h6>) 切分 |
網頁爬蟲取得的 HTML 內容 |
提示:如果你使用的模型是
gpt‑4o(token 上限 128k),仍建議切分成 2k~4k token 的 chunk,以提升檢索速度與相似度品質。
3. 基本使用流程
- 載入文件(
Document物件) - 選擇適合的 Splitter
- 呼叫
split_documents取得切分後的Document列表 - 向量化(Embedding)並建立向量資料庫(如 Pinecone、FAISS)
下面的程式範例會一步步示範這個流程。
程式碼範例
以下範例均採用 Python(LangChain 的主要語言),若你習慣 JavaScript,可參考 langchainjs 的相同 API。
範例 1:最簡單的 CharacterTextSplitter
from langchain.text_splitter import CharacterTextSplitter
from langchain.docstore.document import Document
# 假設我們有一段長文字
raw_text = """LangChain 是一個用於構建 LLM 應用的框架。它提供了
文件管理、檢索、工具整合等模組,讓開發者可以專注在業務邏輯上。"""
# 建立 Document 物件(LangChain 標準資料結構)
doc = Document(page_content=raw_text)
# 使用 CharacterTextSplitter,設定每個 chunk 最多 30 個字元,並以換行作為分隔符
splitter = CharacterTextSplitter(chunk_size=30, separator="\n")
chunks = splitter.split_documents([doc])
for i, chunk in enumerate(chunks):
print(f"--- Chunk {i+1} ---")
print(chunk.page_content)
說明:
chunk_size=30表示每個 chunk 最多 30 個字元(含空白)。separator="\n"讓切分優先在換行處斷開,保留段落結構。
範例 2:遞迴切分 RecursiveCharacterTextSplitter
from langchain.text_splitter import RecursiveCharacterTextSplitter
long_text = """
# 章節一:概述
LangChain 提供了多種模組化元件...
## 小節 1.1:核心概念
- Document
- TextSplitter
- VectorStore
## 小節 1.2:使用流程
以下是一個完整的範例...
"""
doc = Document(page_content=long_text)
# 先以兩個換行(段落)切,若段落仍過長則遞迴切成句子或字元
splitter = RecursiveCharacterTextSplitter(
chunk_size=200, # 每個 chunk 最多 200 個字元
chunk_overlap=20, # 前後重疊 20 個字元,保留上下文
separators=["\n\n", "\n", " ", ""]
)
chunks = splitter.split_documents([doc])
for i, c in enumerate(chunks[:3]): # 只印前 3 個示範
print(f"Chunk {i+1} ({len(c.page_content)}字):")
print(c.page_content[:100] + "...")
重點:
chunk_overlap能避免關鍵資訊被切斷,提升檢索相似度。separators的順序決定遞迴的切分層級,從段落 → 句子 → 空格 → 無分隔。
範例 3:以 Token 數切分 TokenTextSplitter
from langchain.text_splitter import TokenTextSplitter
from langchain.embeddings import OpenAIEmbeddings
# 假設使用 OpenAI 的模型,tokenizer 需要與模型保持一致
splitter = TokenTextSplitter(
chunk_size=500, # 每個 chunk 500 個 token
chunk_overlap=50, # 重疊 50 個 token
model_name="gpt-3.5-turbo"
)
doc = Document(page_content=open("large_document.txt", encoding="utf-8").read())
chunks = splitter.split_documents([doc])
print(f"總共切出 {len(chunks)} 個 chunk")
print(f"第一個 chunk token 數:約 {splitter.get_num_tokens(chunks[0].page_content)}")
為什麼使用 Token 切分?
- Token 數量直接對應 LLM 的輸入限制,避免「字元切分」卻因為中文多字元佔多 token 而超限的情況。
範例 4:Markdown 標題切分 MarkdownHeaderTextSplitter
from langchain.text_splitter import MarkdownHeaderTextSplitter
markdown = """
# 專案概述
這是一個使用 LangChain 的範例專案。
## 功能說明
- 文件載入
- 向量化
- 檢索
### 詳細實作
以下示範如何使用 TextSplitter...
"""
doc = Document(page_content=markdown)
splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=["#", "##", "###"], # 依照標題層級切分
chunk_overlap=30
)
chunks = splitter.split_documents([doc])
for c in chunks:
print(f"Header: {c.metadata.get('header')}")
print(c.page_content[:80] + "...\n")
技巧:
metadata['header']會自動保存切分時的標題,方便後續在向量資料庫中做「標題檢索」。
範例 5:結合切分與向量化(FAISS 為例)
from langchain.vectorstores import FAISS
from langchain.embeddings import OpenAIEmbeddings
# 1. 讀入文件並切分
doc = Document(page_content=open("knowledge_base.txt", encoding="utf-8").read())
splitter = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=50)
chunks = splitter.split_documents([doc])
# 2. 產生向量
embeddings = OpenAIEmbeddings(model="text-embedding-ada-002")
vectorstore = FAISS.from_documents(chunks, embeddings)
# 3. 檢索測試
query = "LangChain 的檔案載入方式是什麼?"
retrieved = vectorstore.similarity_search(query, k=3)
print("檢索結果:")
for r in retrieved:
print("- " + r.page_content[:120] + "...")
說明:
- 只要將 切分後的 Document 直接傳給
FAISS.from_documents,就完成向量化與索引建置。- 之後的檢索只需要提供自然語句,FAISS 會回傳最相關的 chunk。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 / 最佳實踐 |
|---|---|---|
| 切分過小 | 每個 chunk 只有幾個字,會失去上下文,檢索結果變雜訊。 | 設定合理的 chunk_size(> 200 token)並使用 chunk_overlap。 |
| 忽略語言特性 | 中文、日文等語言的 token 與字元比例不同,直接使用 CharacterTextSplitter 可能超出 token 限制。 |
使用 TokenTextSplitter 或先測試 tiktoken 計算 token 數。 |
| 重疊過大 | chunk_overlap 設太大會導致大量重複向量,浪費記憶體與計算資源。 |
依據文件長度調整,一般 10%~20% 為宜。 |
| 忘記保留 Metadata | 切分後失去原始檔案名稱、頁碼、標題等資訊,後續追蹤困難。 | 使用 metadata 參數傳遞額外資訊,或利用 HeaderTextSplitter 自動加入。 |
| 未考慮模型 token 上限 | 切分後的 chunk 仍可能超過模型可接受的 token 數。 | 在切分前先用 tiktoken 或 TokenTextSplitter 檢查 token 數。 |
最佳實踐小結
- 先測試:在正式建置前,用一小段樣本跑
split_documents,觀察每個 chunk 的長度與內容。 - 使用重疊:保留關鍵詞或句子跨 chunk,提升檢索的語意連貫性。
- 保留結構資訊:如標題、段落編號、頁碼,放入
metadata,方便後續顯示或過濾。 - 結合向量化:切分是向量化的前置步驟,兩者缺一不可。
- 自動化流水線:使用
langchain的DocumentLoader+TextSplitter+VectorStore組成完整的資料管線,減少手動錯誤。
實際應用場景
| 場景 | 為何需要 TextSplitter | 示範使用的 Splitter |
|---|---|---|
| 企業內部知識庫(PDF 手冊、Word 報告) | 文件往往上千頁,直接向量化會超出記憶體與 token 限制。 | RecursiveCharacterTextSplitter(段落 → 句子) |
| 客服聊天機器人(FAQ 集合) | 每條 FAQ 可能包含多段說明,需要保留問題與答案的對應關係。 | MarkdownHeaderTextSplitter(以 ## Question 為切點) |
| 法律文件分析(合約、條款) | 法條與條款的層級結構非常重要,切分時不能破壞條款編號。 | HTMLHeaderTextSplitter(若文件已轉成 HTML) |
| 多語言文件(中、英、日混雜) | 不同語言的 token 計算差異大。 | TokenTextSplitter(配合對應模型 tokenizer) |
| 大模型提示工程(LLM 生成長篇報告) | 生成的長文需要分段回傳給前端或儲存。 | CharacterTextSplitter(簡單固定長度) |
案例:某金融公司利用 LangChain 建立「投資說明書」檢索系統,先將 PDF 轉成文字,再使用
RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)切分,最後將每個 chunk 向量化存入 Pinecone。使用者輸入「什麼是資本利得稅?」時,系統能即時返回包含完整條款說明的段落,提升客服效率 30%。
總結
- TextSplitter 是 RAG 工作流中不可或缺的第一步,負責把龐大的原始文件轉換成模型友善的 chunk。
- LangChain 提供多種切分器(
Character、Recursive、Token、MarkdownHeader、HTMLHeader),依照文件類型與應用需求選擇最合適的策略。 - 切分大小、重疊比例、metadata 保留 以及 token 計算 是實務上最常碰到的關鍵參數,掌握這些即可避免大多數問題。
- 結合向量化(FAISS、Pinecone、Weaviate 等)後,切分好的 chunk 能在檢索階段快速定位相關資訊,讓 LLM 的生成回應更精準、可靠。
透過本文提供的概念說明與實作範例,你已具備在 LangChain 中使用 TextSplitter 的完整能力。接下來只要將這條流水線套用到自己的文件來源,就能快速建置出功能強大的檢索增強生成應用。祝開發順利,玩得開心! 🎉