文檔之內嵌
Table of contents
區塊尺寸
與文檔之匹配原則
文檔經過Embedded之後、反而不容易有正確的查詢結果,這是否與區塊(chunk)的大小與文檔沒有適當匹配有關係?
是的,你的推測是正確的。文檔經過嵌入(Embedding)之後查詢效果變差,通常與區塊大小設定不當、或 區塊切割方式與文檔結構不匹配有密切關係。我們可以從幾個面向來看:
- 區塊太小
- 如果 區塊太小(例如每段只有 100 tokens),會導致語意過於片段化,模型無法理解完整上下文。這樣會讓查詢時拿到的相關 區塊沒有足夠資訊回應問題。
- 典型現象:查詢時只回傳含有關鍵字的句子,但沒辦法回答「這是什麼意思」、「適用條件是什麼」這類需要上下文的問題。
- 區塊太大
- 若 區塊太大(例如超過 1000 tokens),會導致 embedding 時「語意過於稀釋」,且增加向量搜尋時的語意干擾。
- 典型現象:查詢結果跟關鍵詞接近但內容不相關,或提取到的 區塊包含太多不相干資訊。
- 區塊切割方式不尊重文檔邏輯
- 例如直接用固定長度切段(如每 500 tokens 一段),會打斷段落、標題與表格,導致一段文字在語意上不完整。
- 典型現象:查詢某段落或標題相關內容時,模型無法正確定位資訊來源。
- 文檔本身的結構與語言特性
- 有些報告、法規、規範類文件,本身語言偏硬、結構層層嵌套(如條文、細項、附錄),若沒有針對這類型做「結構感知型 chunking」,很難取得有用資訊。
建議改善方向
- 嘗試不同 區塊大小測試效果:通常 200~500 tokens 是較穩定的範圍,視文檔結構調整。
- 使用結構感知切割(Structured chunking)
- 例如根據 Markdown 標題、PDF 標題、段落、清單符號切段。
- 可結合 recursive text splitter 或自行設計分段邏輯。
- 加入 metadata(例如標題、頁碼)來輔助查詢與回應生成
- 做 embedding 前,先經過語義優化與清洗處理。去除多餘格式、雜訊、重複資訊。
結構性尺寸
我目前是用llama index程式庫。Structured chunking要怎麼進行!
太好了!既然你是用 LlamaIndex,那就非常適合進行 Structured chunking,它內建了許多適合這類需求的工具。
什麼是 Structured chunking?
Structured chunking 指的是根據文檔的結構(例如標題、段落、條列清單、表格等)智能地切割文本,比起單純按 token 數切割效果更好。
LlamaIndex 中進行 Structured chunking 的方式:
LlamaIndex 提供了內建的 HierarchicalNodeParser
(階層式節點解析器)與 SentenceSplitter 等工具,可以結合使用來實現Structured chunking。
基本範例:用 HierarchicalNodeParser
+ SentenceSplitter
from llama_index.node_parser import HierarchicalNodeParser
from llama_index.text_splitter import SentenceSplitter
# 設定 Sentence Splitter,這裡控制每個 區塊約 512 tokens
text_splitter = SentenceSplitter( 區塊_size=512, 區塊_overlap=50)
# 建立階層式 區塊parser(支援標題等結構)
node_parser = HierarchicalNodeParser.from_defaults(text_splitter=text_splitter)
# 假設你已經有 document 物件(可以是從 PDF, Markdown, txt 讀進來的)
nodes = node_parser.get_nodes_from_documents(documents)
說明
SentenceSplitter
:會根據句子邊界切割,而不是硬切 token。HierarchicalNodeParser
:會偵測文檔的結構(例如標題、段落等)自動分層處理,並保留這些結構做為 metadata。- 區塊_overlap:讓相鄰區塊有重疊,避免上下文斷裂。
結合 Markdown / HTML 結構切割
若你的文檔是 Markdown 或 HTML,可以使用:
from llama_index.node_parser import MarkdownNodeParser
parser = MarkdownNodeParser()
nodes = parser.get_nodes_from_documents(documents)
這樣會自動根據 # 標題、段落等結構進行分區塊。
區塊分完後的流程
完成 Structured Chunking 後,你就可以把 nodes 放進你的 VectorStoreIndex
或其他 LlamaIndex pipeline 中,例如:
from llama_index import VectorStoreIndex
index = VectorStoreIndex(nodes)
query_engine = index.as_query_engine()
response = query_engine.query("你的查詢內容")
結語與建議
- 使用 Structured Chunking 可以 大幅提升查詢的準確度與回應品質。
- 若你的文檔很長或結構複雜(如報告、規範、合約),建議強烈採用
HierarchicalNodeParser
。
表格化之文檔
如果是表格化的文件呢?如法律條文、政策說明、或簡短文字的表格,該如何做內嵌? 這是個非常關鍵的問題!表格化的文件(如法律條文、政策說明、合約、表格式規範等)在做內嵌(Embedding)時,需要特別處理格式與語意的保持,否則會造成資訊失真或查詢失效。
以下是針對這類「表格化文檔」的建議方式與工具操作方式:
一、理解表格文檔的特性
表格文檔常見問題:
- 表格內容有「上下文依賴」,像是「某欄的內容解釋需要標題來理解」。
- 表格本身的結構、層次、合併儲存格都是關鍵資訊。
- 傳統純文本 chunking 容易打破表格結構,導致 embedding 語意混亂。
二、處理策略總覽
類型 處理策略 簡單表格(如兩欄對應:條文 vs 說明) 可轉為句子或字典格式後再做 embedding 結構複雜表格(合約、法規) 建議轉成結構化 JSON 或 key-value 形式保留欄位意義 PDF 表格掃描件 需先經過表格抽取(如 pdfplumber、unstructured 或 OCR)
三、LlamaIndex 中的實作建議
- 將表格轉為結構化文字描述
舉例表格原文如下:
條號 | 條文內容 | 備註 |
---|---|---|
第1條 | 本辦法依據 XX 條規定訂定之 | 法規依據 |
第2條 | 用人單位應每月通報人員異動情形 | 含試用期人員 |
轉換後成:
text = """
第1條:本辦法依據 XX 條規定訂定之。(備註:法規依據)
第2條:用人單位應每月通報人員異動情形。(備註:含試用期人員)
"""
然後再使用 SentenceSplitter
或 SimpleNodeParser
切割。
- 如果表格具欄位意義,可轉為 dict 進行嵌入(推薦)
將表格轉為:
records = [
{"條號": "第1條", "內容": "本辦法依據 XX 條規定訂定之", "備註": "法規依據"},
{"條號": "第2條", "內容": "用人單位應每月通報人員異動情形", "備註": "含試用期人員"}
]
然後列成通式(schema):
from llama_index.schema import Document
documents = [
Document(text=f"{r['條號']}:{r['內容']}(備註:{r['備註']})") for r in records
]
再放進你原本的 NodeParser
去切區塊。
- 進階做法:使用 Table-aware 模型或結構保留工具
- 可搭配 unstructured 或 pdfplumber 做結構保留的抽取。
- LlamaIndex 尚未原生支援表格語意建模(Table Semantic Modeling),但你可以將每一個儲存格的單筆資料當成獨立區塊,保留 key-value 結構後做 embedding。
- 若你用的 LLM 支援 JSON 格式查詢與理解(如 GPT-4 Turbo、Claude 等),可以用 dict 結構強化查詢品質。
四、查詢技巧建議
查詢表格化內容時,也建議你在 query 時加一些 context hint:
- 例:”第2條的通報規定是什麼?”
- 可啟用 query rewriting / reranking 提高正確命中率。
五、範例程式(總結)
from llama_index.node_parser import SimpleNodeParser
from llama_index import VectorStoreIndex, ServiceContext
# 假設你已將表格轉成簡單敘述
text = """
第1條:本辦法依據 XX 條規定訂定之。(備註:法規依據)
第2條:用人單位應每月通報人員異動情形。(備註:含試用期人員)
"""
documents = [Document(text=text)]
parser = SimpleNodeParser()
nodes = parser.get_nodes_from_documents(documents)
index = VectorStoreIndex(nodes)
query_engine = index.as_query_engine()
print(query_engine.query("第2條的規定內容是什麼?"))