Skip to content

Chunking 策略:切塊方式決定 RAG 能不能找到答案

2026年3月12日 1 分鐘
TL;DR 切太大找不準,切太小失去上下文,碰到表格更是全軍覆沒。Chunking 是 RAG 最被低估的環節,策略選錯,後面再多優化都是白費。
目錄
  1. Fixed-size Chunking
  2. Sentence-based Chunking
  3. Recursive Chunking
  4. Semantic Chunking
  5. Late Chunking
  6. 表格的切塊難題
    1. Header Propagation(表頭傳播)
    2. Table-Aware Chunking(結構感知切分)
    3. Proposition Chunking(命題切分)
  7. Chunk 大小的取捨
  8. 在攀岩場景的選擇
  9. 整體來說
  10. 更新紀錄
  11. 參考資料

🌏 English version

RAG 系統的問題,很多時候不是搜尋演算法不好,而是一開始的切塊策略就錯了。

切塊(Chunking)是把長文件分割成可以獨立 embed 的小段。這個決策直接決定了:

  • 每個向量代表多大的語義單元
  • 搜尋命中時 LLM 能看到多少上下文
  • 一份文件被切成幾個向量,影響索引大小和搜尋效率

沒有一個策略適合所有場景。

Fixed-size Chunking

最簡單的做法:按固定字元數或 token 數切割。

function fixedSizeChunk(text: string, chunkSize = 512, overlap = 50): string[] {
  const chunks: string[] = [];
  let start = 0;

  while (start < text.length) {
    const end = Math.min(start + chunkSize, text.length);
    chunks.push(text.slice(start, end));
    start += chunkSize - overlap; // overlap 讓前後 chunk 有重疊
  }

  return chunks;
}

Overlap 是關鍵設計:讓相鄰 chunk 有一段重疊,避免關鍵資訊剛好被切在兩個 chunk 的邊界。

優點:實作簡單,索引大小可預測。

缺點:完全不考慮語義邊界。「龍洞北壁路線難度 5.11a,保護點密集,落點清晰。最難的動作在第三個保護點之後,需要」—— 句子切一半,語義破碎。

適合:結構不明確的文件,或做為其他策略的 fallback。


Sentence-based Chunking

按句子邊界切割,保持每個 chunk 是完整的句子。

function sentenceChunk(text: string, maxTokens = 256): string[] {
  // 用 NLP 句子分割(支援中文斷句)
  const sentences = splitSentences(text);
  const chunks: string[] = [];
  let current = "";

  for (const sentence of sentences) {
    if (tokenCount(current + sentence) > maxTokens) {
      if (current) chunks.push(current.trim());
      current = sentence;
    } else {
      current += " " + sentence;
    }
  }

  if (current) chunks.push(current.trim());
  return chunks;
}

優點:保持語義完整性,每個 chunk 都是可讀的完整陳述。

缺點:句子長度差異大,chunk 大小不均;中文斷句比英文難處理(標點不統一)。

適合:段落結構清晰的敘述性文字(路線評論、攀岩故事)。


Recursive Chunking

LangChain 推廣的方式:先嘗試用大的分隔符(段落、換行)切割,如果 chunk 還是太大,再用更小的分隔符(句號、逗號)繼續切。

const separators = ["\n\n", "\n", "。", ",", " "];

function recursiveChunk(
  text: string,
  maxSize: number,
  separators: string[]
): string[] {
  if (text.length <= maxSize) return [text];

  const sep = separators[0];
  const remaining = separators.slice(1);
  const parts = text.split(sep);
  const chunks: string[] = [];
  let current = "";

  for (const part of parts) {
    if ((current + sep + part).length > maxSize) {
      if (current) chunks.push(current);

      if (part.length > maxSize && remaining.length > 0) {
        // 這段還是太長,用下一級分隔符繼續切
        chunks.push(...recursiveChunk(part, maxSize, remaining));
        current = "";
      } else {
        current = part;
      }
    } else {
      current = current ? current + sep + part : part;
    }
  }

  if (current) chunks.push(current);
  return chunks;
}

優點:盡可能保留自然邊界(段落 > 句子 > 詞),同時控制 chunk 大小。

缺點:實作較複雜;不同文件的結構差異大,分隔符需要根據內容類型調整。

適合:有明確段落結構的技術文件、說明文字。


Semantic Chunking

最複雜的方式:先 embed 每個句子,計算相鄰句子的語義距離,在語義「斷層」處切割。

先講結論再看程式碼:這個方法聽起來最聰明,但沒有證據顯示它值得那個成本。2024 年一篇系統性評測(arXiv:2410.13070, Is Semantic Chunking Worth the Computational Cost?)在文件檢索、證據檢索、檢索式問答三種任務上比較 semantic chunking 與單純的固定大小切塊,結論是「semantic chunking 的運算成本無法被穩定的效能提升所證成」。Chroma 的切塊評測技術報告也指出,參數調得好的 RecursiveCharacterTextSplitter 這類啟發式策略在實務上往往就夠用。

所以請把它當成「有預算、且已經量測過確實有幫助」時才上的選項,而不是預設起手式。

async function semanticChunk(
  sentences: string[],
  threshold = 0.8,
  env: Env
): Promise<string[]> {
  // 每個句子 embed
  const embeddings = await Promise.all(
    sentences.map(s => embed(s, env))
  );

  const chunks: string[] = [];
  let currentChunk = [sentences[0]];

  for (let i = 1; i < sentences.length; i++) {
    const similarity = cosineSimilarity(embeddings[i - 1], embeddings[i]);

    if (similarity < threshold) {
      // 語義斷層:開始新的 chunk
      chunks.push(currentChunk.join(" "));
      currentChunk = [sentences[i]];
    } else {
      currentChunk.push(sentences[i]);
    }
  }

  if (currentChunk.length > 0) {
    chunks.push(currentChunk.join(" "));
  }

  return chunks;
}

優點:切割點在語義真正轉換的地方,每個 chunk 主題聚焦。

缺點

  • 每個句子都要 embed,索引成本高(N 個句子 = N 次 embedding)
  • Threshold 需要根據內容調整,沒有通用值
  • 可能產生過長或過短的 chunk
  • 上述評測顯示,這些成本換不到穩定的檢索品質提升

適合:內容結構不固定、主題轉換頻繁的文件;而且你已經用自己的語料量測過它確實比 Recursive 好


Late Chunking

Jina AI 在 2024 年提出的另一條路(arXiv:2409.04701):不是「切完再各自 embed」,而是先用長上下文 embedding 模型把整份文件的所有 token 編碼完,再在 transformer 之後、mean pooling 之前才切。因為切割發生在模型看完全文之後,每個 chunk 的向量天然帶有前後文資訊。

它和 Contextual Retrieval 解的是同一個問題(chunk 失去上下文),但走的是完全不同的路:Contextual Retrieval 用 LLM 生成文字再 embed,Late Chunking 只改 pooling 的時機、不需要額外的 LLM 呼叫。

2025 年一篇比較研究(arXiv:2504.19754)把兩者放在一起測:Contextual Retrieval 在保留語義連貫性上較好,但運算成本高;Late Chunking 效率高得多,代價是相關性與完整性有所犧牲。

限制:需要一個 context window 夠長的 embedding 模型,而且平台實際允許的輸入長度可能遠低於模型宣稱值——上線前先確認自己用的推論平台真正吃得下多長的輸入。


表格的切塊難題

上面介紹的策略都是為「連續文字」設計的——段落、句子、語義片段。碰到表格,所有文字切割器都踩到同一個結構性問題:表頭只出現在第一個 chunk,後續 chunk 變成一堆沒有欄位名稱的數值碎片。

一份 PDF 表格被 SentenceSplitter(chunk_size=512) 切成 8 塊,只有第一塊帶表頭。後面 7 塊的 embedding 向量無法反映「這是哪張表的哪個欄位」,語意檢索自然漏掉關鍵列。2026 年一篇 Towards Data Science 分析直接稱表格為「The Retrieval Black Hole」——向量搜得到的機率跟運氣差不多。

三種應對思路,由簡到繁:

Header Propagation(表頭傳播)

切完之後加一步 post-processing:偵測 Markdown 表格結構(|...| + |---|),把表頭行 prepend 到每個缺少表頭的後續 chunk。

Before:
  Chunk 5: 不動產限臺灣本島... | 代償對象 | ... | 禁止條款 | ...

After:
  Chunk 5: | 專案名稱 | 【B1】| 【B2】| 【B3】| 【B4】|  ← 表頭 prepend
           不動產限臺灣本島... | 代償對象 | ... | 禁止條款 | ...

改動最小,可寫確定性 unit test(「每個表格 chunk 都以表頭開頭」),直接改善 embedding 品質。代價是每個 chunk 多一行表頭(約 50 tokens),受影響的文件需重新 embed。開源實作可參考 Chonkie 的 TableChunker——「splits markdown tables by row, always preserving the header」;Microsoft Azure Data Parsing 也內建了 column headers restoration 與跨頁表格合併。

Table-Aware Chunking(結構感知切分)

從根本改變切分邏輯:以 row 為最小單位,不讓表格被橫切。2025 年的 STC 框架(arXiv:2605.00318, Structure-Aware Chunking for Tabular Data in RAG)分三步:

  1. Row Tree:表格轉 root → sheet → row 階層,每 row 用 key-value 格式
  2. Token-budget 遞迴切分:超出 budget 才往下切
  3. Greedy 合併:相鄰小 chunk 合起來

在 MAUD 法律文件資料集上,Recall@1 從 baseline 的 0.347 提升到 0.539(+55%),chunk 數量反而減少 40%——切得更聰明,索引更小。BM25-only 的場景改善更大,Recall@1 從 0.366 跳到 0.754

限制:需要替換現有的 SentenceSplitter,改 ingestion pipeline;論文評估在 CSV/Excel 格式,PDF → Markdown 的轉換品質是前提。

Proposition Chunking(命題切分)

另一個極端:用 LLM 把文字拆成原子事實命題(arXiv:2312.06648, Dense X Retrieval, EMNLP 2024)。每個命題是一個獨立、自足的陳述。

Recall@5 提升約 +12%,但 chunk 數量暴增(一段文字拆出 5-10 倍的命題),LLM 處理成本高。對表格這種已經結構化的資料,拆成命題格式未必比保留原始表格結構更好——結構已經在那裡,問題不是缺結構,而是切割破壞了結構。


Chunk 大小的取捨

chunk 大小搜尋精度上下文完整性索引大小
小(128 tokens)高(精確命中)低(片段)
中(512 tokens)
大(1024 tokens)低(模糊)高(完整)

表中的數字是說明用的量級,不是建議值。實際上限由你用的 embedding 模型(以及推論平台)能吃多長的輸入決定——很多託管平台對單次輸入的 token 上限比模型本身的規格低很多,切塊前先查清楚該模型在你的平台上的實際上限。

解法:Parent Document Retriever(兩級架構)

  • 小 chunk 用來搜尋(精確命中)
  • 命中後取出它所屬的大 chunk(完整上下文)給 LLM
索引:
  小 chunk(128 tokens)→ embedding
  大 chunk(512 tokens)→ 存文字,與小 chunk 關聯

搜尋:
  query → 找到最相關的小 chunk
  → 取出對應的大 chunk
  → 送給 LLM 生成

這個設計讓搜尋精度和上下文完整性不互相妥協。

在攀岩場景的選擇

路線描述的結構很固定(名稱、難度、類型、描述、注意事項),適合 Recursive Chunking,按段落邊界切割,每個 chunk 是一個語義完整的描述段。

再搭配 Contextual Retrieval(注入文件摘要),彌補小 chunk 失去上下文的問題。

整體來說

Chunking 是 RAG 系統裡最底層也最影響全局的決策。後面加多少 HyDE、Multi-Query、Reranker,都建立在「indexing 索引裡有正確的語義單元」這個前提上。索引本身有問題,搜尋再好也找不到正確答案。

最務實的建議:從 Recursive Chunking + Contextual Retrieval 開始,然後根據實際的搜尋品質(查看 trace 裡命中的 chunk 是否有意義)決定要不要換策略。

延伸閱讀:Hierarchical Chunking + Auto-Merge:小 chunk 精準命中,大 chunk 保完整上下文Table Serialization:表格該怎麼轉成文字才能讓 RAG 找到Agentic Parsing:讓 Agent 決定怎麼解析文件


更新紀錄

  • 2026-09-03:新增「表格的切塊難題」章節,補充 Header Propagation、Table-Aware Chunking(STC)、Proposition Chunking 三種應對策略及相關參考文獻
  • 2026-08-19:對照官方文件逐篇查證翻新,移除易腐內容,並收進「RAG 技法大全」系列

參考資料