
簡介這份源碼資源面向正在搭建RAG應(yīng)用、需要優(yōu)化長文檔切分環(huán)節(jié)的開發(fā)者與AI學(xué)習(xí)者圍繞檢索增強(qiáng)生成中的切片策略展開幫助解決文檔切分粒度不當(dāng)導(dǎo)致檢索召回差、生成答案質(zhì)量低的問題。資源包共5個文件以Python腳本為主另含requirements.txt依賴清單、.inscode與.gitignore等工程配置文件整體約13KB輕量易讀可直接運(yùn)行驗證。內(nèi)容覆蓋改進(jìn)的固定長度切片、語義切片、LLM語義切片、層次切片與滑動窗口切片五種方法并配有結(jié)合FAISS搭建本地知識庫的流程圖與選型指南便于對照不同場景選擇合適策略。目前已有201人學(xué)習(xí)下載適合希望快速理解切片原理、動手調(diào)試參數(shù)并落地到本地知識庫項目的中級開發(fā)者參考。1. RAG 切片策略為什么成了知識庫效果的黑匣子做過 RAG 知識庫的人大多有過這種體驗向量庫建好了檢索鏈路也通了模型回答卻總是差一口氣——要么答非所問要么把兩段不相干的內(nèi)容縫在一起。排查到最后問題往往不在 embedding 模型也不在 rerank而是最上游那個最容易被忽略的環(huán)節(jié)切片策略。切片決定了知識以什么粒度進(jìn)入向量空間粒度錯了后面所有環(huán)節(jié)都在為它擦屁股。這份 RAG 切片策略指南要解決的就是把「憑感覺切」變成「按文檔結(jié)構(gòu)切、按檢索目標(biāo)切」。它適合兩類人一類是剛跑通 RAG demo、準(zhǔn)備上真實知識庫的工程師另一類是知識庫已經(jīng)上線、但召回率和答案準(zhǔn)確率卡在瓶頸上不去的從業(yè)者。切片不是玄學(xué)它有一套可復(fù)現(xiàn)的參數(shù)體系和判斷標(biāo)準(zhǔn)源碼層面也就那么幾個關(guān)鍵函數(shù)。下面從原理到代碼把這條鏈路拆開講。2. 切片策略的四種主流方案與選型依據(jù)2.1 固定長度切片最簡單也最容易翻車固定長度切片是最常見的入門做法按字符數(shù)或 token 數(shù)硬切配一個重疊窗口。它的優(yōu)點(diǎn)是實現(xiàn)簡單、切片數(shù)量可控缺點(diǎn)是幾乎不考慮語義邊界一句話被攔腰截斷是常態(tài)。def fixed_chunk(text, chunk_size500, overlap50): # chunk_size 按字符計中文場景下 500 字約等于 350-400 token chunks [] start 0 while start len(text): end start chunk_size chunks.append(text[start:end]) # overlap 防止邊界信息丟失但會帶來冗余 start end - overlap return chunks這段代碼里chunk_size和overlap是兩個必須調(diào)的參數(shù)。中文技術(shù)文檔我一般用 400 到 600 字overlap 取 chunk_size 的 10% 到 15%。overlap 太小跨邊界的問題答不出來太大向量庫里全是重復(fù)內(nèi)容檢索時擠占 top-k 名額。固定切片的適用場景很窄純敘述性、沒有明顯層級的文本比如訪談記錄、會議紀(jì)要。一旦文檔有標(biāo)題、表格、代碼塊固定切片就會把結(jié)構(gòu)信息切碎。2.2 遞歸字符切片按分隔符優(yōu)先級逐層降級遞歸切片是目前工程上最穩(wěn)的默認(rèn)方案LangChain 的RecursiveCharacterTextSplitter就是典型代表。它的思路是給一組分隔符按優(yōu)先級從高到低嘗試先按段落切段落還太大就按句子切句子還大就按逗號切最后才硬切。from langchain.text_splitter import RecursiveCharacterTextSplitter splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap80, # 分隔符順序決定降級策略中文場景要把中文標(biāo)點(diǎn)加進(jìn)去 separators[\n\n, \n, 。, , , , , , ], length_functionlen, ) chunks splitter.split_text(document)separators的順序是關(guān)鍵它決定了切片的語義完整度。中文文檔一定要把全角標(biāo)點(diǎn)放進(jìn)去否則默認(rèn)的英文分隔符在中文里幾乎不起作用退化成固定切片。chunk_overlap建議設(shè)為 chunk_size 的 15% 左右。遞歸切片的局限在于它只認(rèn)字符不認(rèn)文檔的真實結(jié)構(gòu)——一個 Markdown 表格里的行會被當(dāng)成普通文本切表頭和數(shù)據(jù)行可能分到不同 chunk。2.3 結(jié)構(gòu)化切片按標(biāo)題層級和語義單元切當(dāng)知識庫以 Markdown、HTML、Word 這類帶結(jié)構(gòu)的文檔為主時結(jié)構(gòu)化切片的效果明顯優(yōu)于前兩種。核心思路是先解析出文檔的標(biāo)題樹再以最小語義單元一個三級標(biāo)題下的完整內(nèi)容為切片邊界。import re def structure_chunk(markdown_text, max_size800): # 按標(biāo)題層級切分保留標(biāo)題作為上下文前綴 sections re.split(r(?^#{1,3}\s), markdown_text, flagsre.MULTILINE) chunks [] for sec in sections: if not sec.strip(): continue # 超長章節(jié)再按段落二次切分 if len(sec) max_size: paras sec.split(\n\n) buf for p in paras: if len(buf) len(p) max_size: chunks.append(buf) buf p else: buf \n\n p if buf: chunks.append(buf) else: chunks.append(sec) return chunks這里max_size是二次切分的兜底閾值防止某個章節(jié)過長導(dǎo)致單條向量語義過載。結(jié)構(gòu)化切片最大的價值是保留了標(biāo)題路徑檢索命中時能直接知道這段內(nèi)容屬于哪個章節(jié)對生成答案的引用溯源特別有用。代價是需要針對不同文檔格式寫解析邏輯Markdown 好辦PDF 和掃描件就得先過 OCR 和版面分析。2.4 語義切片用 embedding 相似度找斷點(diǎn)語義切片是效果上限最高、成本也最高的方案。它先把文本按句子拆開計算相鄰句子的 embedding 相似度在相似度驟降的地方斷開。import numpy as np from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-small-zh-v1.5) def semantic_chunk(sentences, threshold0.75): embs model.encode(sentences, normalize_embeddingsTrue) chunks, cur [], [sentences[0]] for i in range(1, len(sentences)): # 余弦相似度歸一化后直接點(diǎn)積 sim float(np.dot(embs[i-1], embs[i])) if sim threshold: chunks.append(.join(cur)) cur [sentences[i]] else: cur.append(sentences[i]) chunks.append(.join(cur)) return chunksthreshold是最難調(diào)的參數(shù)中文技術(shù)文檔我一般從 0.7 到 0.8 之間試。閾值太高切片過碎太低等于沒切。語義切片的成本在于每個句子都要過一次 embedding長文檔處理慢而且斷點(diǎn)位置不穩(wěn)定同一份文檔兩次跑可能結(jié)果略有差異。它適合對召回精度要求極高、文檔量不大的場景比如法律條款、醫(yī)療指南。四種方案的選型可以對照下表方案實現(xiàn)成本語義完整度適用文檔主要風(fēng)險固定長度低低純敘述文本語義截斷遞歸字符低中通用文本結(jié)構(gòu)丟失結(jié)構(gòu)化中高M(jìn)arkdown/HTML解析依賴語義切片高高高精度小庫參數(shù)敏感3. 從源碼角度拆解切片函數(shù)的參數(shù)與邊界3.1 chunk_size 到底該按字符還是按 token 算這是被問得最多的問題。結(jié)論是切片階段按字符算入庫前按 token 校驗。原因是 embedding 模型有最大輸入長度限制比如 bge 系列通常是 512 token如果切片按字符算出來 600 字中文里可能超過 512 token超長部分會被靜默截斷你根本不知道丟了什么。from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(BAAI/bge-small-zh-v1.5) def validate_chunk(chunks, max_tokens500): valid [] for c in chunks: ids tokenizer.encode(c, add_special_tokensFalse) if len(ids) max_tokens: # 超長切片按 token 邊界二次切 for i in range(0, len(ids), max_tokens): valid.append(tokenizer.decode(ids[i:imax_tokens])) else: valid.append(c) return validmax_tokens要留出余量模型上限 512 的話切片控制在 480 到 500 之間給特殊 token 留位置。這一步是很多知識庫效果差的隱藏原因切片看著沒問題實際入庫時被截斷檢索到的內(nèi)容缺頭少尾。3.2 overlap 設(shè)置多少才不浪費(fèi)向量空間overlap 的本質(zhì)是用存儲冗余換邊界信息。經(jīng)驗值是 chunk_size 的 10% 到 20%。低于 10%跨 chunk 的問題容易丟上下文高于 20%向量庫里重復(fù)內(nèi)容占比明顯上升檢索時 top-5 里可能三條都是同一段的重疊部分。判斷 overlap 是否合理有個簡單方法隨機(jī)抽 20 個跨邊界的問題看檢索結(jié)果里是否至少有一條能覆蓋完整答案。如果經(jīng)常需要拼兩條 chunk 才能答全說明 overlap 偏小如果 top-k 里大量重復(fù)說明偏大。這個驗證比拍腦袋定參數(shù)靠譜得多。3.3 元數(shù)據(jù)怎么隨切片一起入庫切片不只是切文本元數(shù)據(jù)決定了檢索時能不能過濾、生成時能不能溯源。我一般給每個 chunk 掛這幾項來源文件名、標(biāo)題路徑、chunk 序號、字符數(shù)。def build_chunk_records(chunks, source, title_path): records [] for idx, c in enumerate(chunks): records.append({ text: c, metadata: { source: source, title_path: title_path, chunk_index: idx, char_len: len(c), } }) return recordstitle_path在結(jié)構(gòu)化切片里尤其重要它讓檢索結(jié)果自帶層級上下文。生成答案時把 title_path 拼在 chunk 前面模型對內(nèi)容歸屬的判斷會準(zhǔn)很多。chunk_index用于相鄰 chunk 擴(kuò)展檢索命中某條后可以把前后各一條一起喂給模型彌補(bǔ)切片邊界的信息損失。4. 切片效果排查五類高頻翻車現(xiàn)場4.1 檢索命中但答案答非所問現(xiàn)象檢索返回的 chunk 看起來相關(guān)但模型生成的答案跑偏。原因通常是 chunk 里混入了多個主題向量被平均化相似度虛高。解決檢查該 chunk 是否跨越了標(biāo)題邊界把結(jié)構(gòu)化切片的最小單元收緊到三級標(biāo)題超長章節(jié)強(qiáng)制二次切分。4.2 表格和代碼塊被切碎現(xiàn)象問表格里的某個數(shù)值檢索到的 chunk 只有半張表表頭在另一條里。原因遞歸切片把表格行當(dāng)普通文本處理。解決解析階段識別表格和代碼塊把它們作為不可分割單元整塊作為一個 chunk超長時按行切但每片都帶上表頭。4.3 短切片召回噪聲大現(xiàn)象top-5 里全是幾十字的短句信息量不足。原因語義切片閾值設(shè)太高或者文檔里本身短句多。解決設(shè)一個最小 chunk 長度比如 100 字低于這個長度的切片合并到相鄰切片同時檢查閾值是否過嚴(yán)。4.4 同一文檔切片結(jié)果不穩(wěn)定現(xiàn)象重新跑一次切片chunk 數(shù)量和邊界都變了。原因語義切片依賴 embedding模型版本或歸一化方式變動都會影響斷點(diǎn)。解決切片結(jié)果落盤緩存入庫和檢索用同一份切片產(chǎn)物不要每次動態(tài)切。4.5 多文檔標(biāo)題層級不一致導(dǎo)致切片粒度混亂現(xiàn)象有的文檔切出來 200 字一條有的 2000 字一條。原因不同來源文檔的標(biāo)題層級深度不同結(jié)構(gòu)化切片按標(biāo)題切時粒度失控。解決統(tǒng)一預(yù)處理把各級標(biāo)題歸一化到固定層級再按 max_size 兜底二次切分保證切片長度分布集中。5. 把切片質(zhì)量量化一套可復(fù)用的驗證腳本切片調(diào)完之后怎么知道它到底好不好靠肉眼看幾條是不夠的得有一套量化指標(biāo)。我常用的做法是構(gòu)造一組「問題-答案-來源片段」的測試集跑檢索看命中率。def eval_retrieval(questions, retriever, top_k5): hit, total 0, 0 for q in questions: results retriever.search(q[query], top_ktop_k) total 1 # 命中判定檢索結(jié)果里是否包含答案所在片段的關(guān)鍵句 for r in results: if q[answer_key] in r[text]: hit 1 break return hit / total if total else 0.0answer_key是答案片段里的一個特征字符串用它判斷檢索是否命中。這個腳本跑下來命中率低于 80% 就說明切片或檢索參數(shù)有問題。測試集不用多30 到 50 條覆蓋各類問題就夠關(guān)鍵是要包含跨 chunk 的問題專門驗證 overlap 和相鄰擴(kuò)展是否生效。除了命中率我還會看兩個分布指標(biāo)切片長度分布和切片數(shù)量。長度分布應(yīng)該集中在目標(biāo)區(qū)間出現(xiàn)大量超短或超長切片就是參數(shù)沒調(diào)好切片數(shù)量突然比預(yù)期多很多往往是分隔符配置有問題把不該切的地方切了。調(diào)參的順序建議是先定切片方案再調(diào) chunk_size 和 overlap最后用測試集驗證。不要一上來就同時改多個參數(shù)那樣出了問題根本不知道是哪個引起的。我自己的習(xí)慣是每次只動一個參數(shù)跑一遍測試集記錄命中率找到拐點(diǎn)再動下一個。切片這件事沒有一勞永逸的最優(yōu)解文檔變了、問題類型變了參數(shù)就得重新看。把這套驗證腳本留著每次知識庫更新后跑一遍比事后拍腦袋強(qiáng)得多。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取