踐)
文檔處理流水線PDF解析與分塊策略詳解RAG系統(tǒng)的效果好不好很大程度上取決于知識(shí)庫(kù)的質(zhì)量。而知識(shí)庫(kù)的質(zhì)量第一步就在文檔處理。文檔從原始文件到變成可以檢索的向量塊中間要經(jīng)過(guò)好幾步。加載、解析、清洗、分塊、向量化。每一步?jīng)]做好都會(huì)影響最終的檢索效果。很多人做RAG上來(lái)就搞向量數(shù)據(jù)庫(kù)、搞高級(jí)檢索算法。結(jié)果文檔處理沒(méi)做好后面再怎么優(yōu)化都有限。地基打歪了樓蓋得再高也不結(jié)實(shí)。這一篇我們講文檔處理的完整流程。每一步做什么有哪些常見(jiàn)的坑怎么選擇合適的分塊策略。文檔處理的完整流程一個(gè)標(biāo)準(zhǔn)的文檔處理流水線大概分這么幾步。第一步加載。把原始文件讀進(jìn)來(lái)PDF、Word、Excel、網(wǎng)頁(yè)各種格式都有。這一步的目標(biāo)是把不同格式的文件統(tǒng)一轉(zhuǎn)換成純文本。第二步清洗。原始文本里有很多沒(méi)用的東西。頁(yè)眉頁(yè)腳、頁(yè)碼、目錄、水印、廣告、導(dǎo)航欄。這些東西要清理掉不然會(huì)混進(jìn)檢索結(jié)果里影響質(zhì)量。第三步分塊。把長(zhǎng)文本切成一小塊一小塊的。塊太大了搜不準(zhǔn)太小了又丟失上下文。切多大、怎么切里面有學(xué)問(wèn)。第四步向量化。把每個(gè)文本塊轉(zhuǎn)換成向量存到向量數(shù)據(jù)庫(kù)里。這樣后面才能做語(yǔ)義檢索。第五步元數(shù)據(jù)。給每個(gè)塊加上元數(shù)據(jù)。來(lái)源文件、頁(yè)碼、章節(jié)、作者、時(shí)間。這些信息后面檢索的時(shí)候能用得上也方便溯源。這五步看起來(lái)簡(jiǎn)單每一步都有不少細(xì)節(jié)。我們一個(gè)一個(gè)說(shuō)。文檔加載加載是第一步不同格式有不同的加載器。上一篇文件處理工具里講過(guò)這里簡(jiǎn)單回顧一下。PDF用PyMuPDFLoader或者UnstructuredPDFLoader。效果好的優(yōu)先選PyMuPDF有圖片和復(fù)雜排版的用Unstructured加OCR。Word用Docx2txtLoader。需要結(jié)構(gòu)信息用python-docx自己解析。Excel用pandas處理最方便。網(wǎng)頁(yè)用BeautifulSoup或者UnstructuredHTMLLoader。加載階段最容易出的問(wèn)題是格式解析不準(zhǔn)確。特別是PDF同樣是PDF用不同工具生成的解析出來(lái)的質(zhì)量天差地別。我的建議是先拿你實(shí)際的文檔樣本測(cè)一下??纯唇馕龀鰜?lái)的文本對(duì)不對(duì)、順序亂不亂、表格能不能讀、圖片里的文字要不要OCR。測(cè)完了再選合適的工具。別想當(dāng)然地覺(jué)得所有PDF都能用同一種方式處理。實(shí)際項(xiàng)目里不同來(lái)源的PDF可能要寫不同的處理邏輯。文本清洗加載出來(lái)的原始文本通常是不干凈的。有很多噪音需要清理。常見(jiàn)的噪音有這些。頁(yè)眉頁(yè)腳和頁(yè)碼。幾乎所有長(zhǎng)文檔都有。每頁(yè)重復(fù)出現(xiàn)不清理的話會(huì)被當(dāng)成正文內(nèi)容影響檢索。目錄和索引。文檔前面的目錄后面的索引都是導(dǎo)航用的不是正文。重復(fù)的版權(quán)聲明和免責(zé)聲明。很多文檔每頁(yè)底部都有重復(fù)很多遍。網(wǎng)頁(yè)的導(dǎo)航欄、廣告、推薦閱讀。爬下來(lái)的網(wǎng)頁(yè)大部分內(nèi)容都是這些正文只占一小部分。亂碼和特殊字符。格式轉(zhuǎn)換的時(shí)候容易出現(xiàn)。清理的方法根據(jù)不同的情況來(lái)。頁(yè)眉頁(yè)腳可以根據(jù)位置信息去掉。PyMuPDF能拿到每個(gè)文本塊的坐標(biāo)根據(jù)坐標(biāo)判斷是不是頁(yè)眉頁(yè)腳。重復(fù)內(nèi)容可以用相似度檢測(cè)。連續(xù)很多頁(yè)都出現(xiàn)的相同內(nèi)容大概率是頁(yè)眉頁(yè)腳或者版權(quán)聲明。網(wǎng)頁(yè)用專門的正文提取庫(kù)。比如BeautifulSoup配合規(guī)則或者用newspaper3k、trafilatura這類專門的正文提取工具。清洗這一步很重要。臟數(shù)據(jù)進(jìn)臟數(shù)據(jù)出。文本不干凈后面檢索出來(lái)的結(jié)果也會(huì)亂七八糟。文本分塊分塊是文檔處理里最關(guān)鍵的一步。切多大、怎么切直接影響檢索效果。分塊太大有什么問(wèn)題。一個(gè)塊里內(nèi)容太多可能只有一小部分跟問(wèn)題相關(guān)但整塊都被檢索出來(lái)了。無(wú)關(guān)信息太多會(huì)稀釋有效內(nèi)容影響大模型的判斷。也浪費(fèi)Token。分塊太小又有什么問(wèn)題。一個(gè)完整的意思被切成好幾塊單看哪一塊都不完整。檢索的時(shí)候可能只搜到一半上下文丟了回答就不準(zhǔn)確。所以塊大小要合適。多大算合適沒(méi)有標(biāo)準(zhǔn)答案。跟你的文檔類型、用戶提問(wèn)方式、用的Embedding模型都有關(guān)系。常見(jiàn)的經(jīng)驗(yàn)值是普通文檔200到500個(gè)中文字或者500到1000個(gè)英文詞。代碼文檔可以大一點(diǎn)800到1500個(gè)Token。FAQ類的可以小一點(diǎn)一個(gè)問(wèn)答對(duì)就是一塊。這只是經(jīng)驗(yàn)值。實(shí)際項(xiàng)目里最好的辦法是做實(shí)驗(yàn)。試幾種不同的塊大小看哪種效果最好。常見(jiàn)的分塊方法固定大小分塊。最簡(jiǎn)單的方法。按字符數(shù)或者Token數(shù)切每塊固定大小。塊之間可以有一些重疊防止意思被切斷。LangChain里的RecursiveCharacterTextSplitter就是干這個(gè)的。它會(huì)優(yōu)先按段落、句子、單詞來(lái)切盡量保持語(yǔ)義完整。fromlangchain.text_splitterimportRecursiveCharacterTextSplitter splitterRecursiveCharacterTextSplitter(chunk_size500,chunk_overlap50,separators[\n\n,\n,。,, ],)chunkssplitter.split_text(long_text)這種方法簡(jiǎn)單通用大部分場(chǎng)景都能用。效果也還可以。按結(jié)構(gòu)分塊。根據(jù)文檔的結(jié)構(gòu)來(lái)切。標(biāo)題、段落、列表、表格按結(jié)構(gòu)單元來(lái)分。一塊就是一個(gè)相對(duì)完整的語(yǔ)義單元。比如Markdown文檔可以按標(biāo)題層級(jí)來(lái)分。一個(gè)H2下面的內(nèi)容作為一塊?;蛘咭粋€(gè)H3下面的內(nèi)容作為一塊。Word和HTML也可以按結(jié)構(gòu)來(lái)分。標(biāo)題、段落、表格每個(gè)元素單獨(dú)處理。按結(jié)構(gòu)分塊的好處是語(yǔ)義更完整。一個(gè)章節(jié)講的是同一個(gè)主題放在一塊很合理。效果通常比固定大小分塊好。缺點(diǎn)是需要解析文檔結(jié)構(gòu)。格式不規(guī)范的文檔結(jié)構(gòu)解析不準(zhǔn)分出來(lái)的塊也有問(wèn)題。語(yǔ)義分塊。更高級(jí)的分法。用Embedding來(lái)判斷每句話的語(yǔ)義相似度語(yǔ)義變化大的地方就切一刀。這樣每一塊內(nèi)部的語(yǔ)義是連貫的。聽(tīng)起來(lái)很美好實(shí)際用起來(lái)效果不一定更好。而且速度慢、成本高。除非對(duì)分塊質(zhì)量要求特別高不然不太推薦。我的建議說(shuō)了這么多分塊方法到底用哪個(gè)。我的建議是先用最簡(jiǎn)單的。遞歸字符分割chunk_size設(shè)500overlap設(shè)50。先把系統(tǒng)跑起來(lái)看效果怎么樣。效果不好再分析原因。是檢索不到相關(guān)內(nèi)容還是搜到的內(nèi)容不完整。是塊太大了還是太小了。然后針對(duì)性地調(diào)。不要一開(kāi)始就追求最復(fù)雜的方案。很多時(shí)候固定大小分塊就夠用了。把精力花在更影響效果的地方比如重排序、Prompt優(yōu)化、HyDE這些收益更大。還有一個(gè)經(jīng)驗(yàn)。塊里最好帶上上文信息。比如每一塊的開(kāi)頭加上所屬的文檔標(biāo)題和章節(jié)標(biāo)題。這樣檢索的時(shí)候塊的上下文更完整相關(guān)性判斷也更準(zhǔn)。元數(shù)據(jù)每個(gè)文本塊除了內(nèi)容本身還要附上一些元數(shù)據(jù)。常見(jiàn)的元數(shù)據(jù)有這些。來(lái)源信息。文件名、文件路徑、URL。用來(lái)溯源。位置信息。頁(yè)碼、行號(hào)、章節(jié)。告訴用戶答案在文檔的什么位置。分類信息。文檔類型、產(chǎn)品分類、部門。用來(lái)做過(guò)濾。時(shí)間信息。創(chuàng)建時(shí)間、更新時(shí)間??梢园磿r(shí)間排序也可以判斷新舊。作者信息。誰(shuí)寫的誰(shuí)負(fù)責(zé)。元數(shù)據(jù)的用處很大。檢索的時(shí)候可以按元數(shù)據(jù)過(guò)濾只在指定的范圍內(nèi)搜?;卮饐?wèn)題的時(shí)候可以引用來(lái)源增加可信度。不要嫌麻煩。加元數(shù)據(jù)花不了多少時(shí)間后面會(huì)很有用。下一篇我們講向量數(shù)據(jù)庫(kù)。切好的文本塊怎么存進(jìn)去怎么搜出來(lái)。