本文 AI 產出,尚未審核

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_size10‑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_sizechunk_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)} 個程式碼區塊")

實務建議:將 classdef 放在分割符的最前端,可確保每個 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)。

最佳實踐清單

  1. 先以語意層級決定分割符:段落 → 句子 → 詞彙 → 字元。
  2. 使用 token 為基準的 length_function,避免「字元」與「token」不對等的問題。
  3. 設定 chunk_overlapchunk_size 的 0.1‑0.2,兼顧上下文與成本。
  4. 針對不同檔案類型自訂 separators(如程式碼、Markdown、HTML)。
  5. 產出 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 為基準」的原則,配合適當的 separatorskeep_separator 設定,就能在各種應用場景(知識庫、聊天機器人、程式碼搜尋等)中,快速、穩定地產出高品質的文本 chunk,為後續的向量化、檢索與生成奠定堅實基礎。

祝你在 LangChain 的旅程中,玩得開心、寫得順利! 🚀