本文 AI 產出,尚未審核

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. 基本使用流程

  1. 載入文件Document 物件)
  2. 選擇適合的 Splitter
  3. 呼叫 split_documents 取得切分後的 Document 列表
  4. 向量化(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 數。 在切分前先用 tiktokenTokenTextSplitter 檢查 token 數。

最佳實踐小結

  1. 先測試:在正式建置前,用一小段樣本跑 split_documents,觀察每個 chunk 的長度與內容。
  2. 使用重疊:保留關鍵詞或句子跨 chunk,提升檢索的語意連貫性。
  3. 保留結構資訊:如標題、段落編號、頁碼,放入 metadata,方便後續顯示或過濾。
  4. 結合向量化:切分是向量化的前置步驟,兩者缺一不可。
  5. 自動化流水線:使用 langchainDocumentLoader + 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 提供多種切分器(CharacterRecursiveTokenMarkdownHeaderHTMLHeader),依照文件類型與應用需求選擇最合適的策略。
  • 切分大小重疊比例metadata 保留 以及 token 計算 是實務上最常碰到的關鍵參數,掌握這些即可避免大多數問題。
  • 結合向量化(FAISS、Pinecone、Weaviate 等)後,切分好的 chunk 能在檢索階段快速定位相關資訊,讓 LLM 的生成回應更精準、可靠。

透過本文提供的概念說明與實作範例,你已具備在 LangChain 中使用 TextSplitter 的完整能力。接下來只要將這條流水線套用到自己的文件來源,就能快速建置出功能強大的檢索增強生成應用。祝開發順利,玩得開心! 🎉