據(jù)庫數(shù)據(jù)進(jìn)RAG:從CSV、Excel到LlamaHub的完整實(shí)戰(zhàn))
做RAG做到一定階段你會(huì)發(fā)現(xiàn)一個(gè)尷尬的事實(shí)那些PDF、Markdown、網(wǎng)頁類文檔喂進(jìn)去檢索效果還能湊合一旦上表格和數(shù)據(jù)庫數(shù)據(jù)整個(gè)知識(shí)庫就開始犯迷糊。要么答案張冠李戴要么直接一本正經(jīng)地編造不存在的統(tǒng)計(jì)數(shù)字甚至把A表的字段掛到B表的列頭上。這個(gè)系列前兩篇我們聊了PDF和各類文檔的解析這篇是第三篇專門拆表格和數(shù)據(jù)庫這條線——CSV、Excel以及通過LlamaHub連接數(shù)據(jù)庫并拉進(jìn)RAG管道的完整實(shí)戰(zhàn)。適合正在搭建知識(shí)庫的工程師、被結(jié)構(gòu)化數(shù)據(jù)檢索折磨的算法同學(xué)以及準(zhǔn)備把業(yè)務(wù)數(shù)據(jù)庫接入問答系統(tǒng)的團(tuán)隊(duì)參考。先說結(jié)論表格類數(shù)據(jù)進(jìn)RAG核心不是能不能讀出來而是讀出來之后怎么保真。文本切碎了大不了上下文丟一點(diǎn)表格切碎了那叫災(zāi)難。這篇文章我會(huì)按CSV→Excel→數(shù)據(jù)庫LlamaHub→Chunk策略→踩坑復(fù)盤的順序把每一步的具體操作、參數(shù)選擇和排查思路都攤開講。1. 為什么表格和數(shù)據(jù)庫數(shù)據(jù)是RAG最容易翻車的環(huán)節(jié)表格數(shù)據(jù)在RAG管道里翻車不是某一步代碼寫得不對(duì)而是整條鏈路的設(shè)計(jì)前提跟結(jié)構(gòu)化數(shù)據(jù)的組織方式天然沖突。搞清楚這個(gè)底層原因后面所有調(diào)參和繞坑才有方向。1.1 文本分塊與表格行列的天然矛盾RAG的基石是文本。加載、切分、embedding、召回每個(gè)環(huán)節(jié)都假設(shè)輸入是段落級(jí)別的自然語言。表格不是這么組織的——一張100行5列的銷售明細(xì)表按固定chunk_size512硬切你會(huì)得到一堆殘缺的行片段。更麻煩的是列頭第一列的列名訂單編號(hào)和第二列的列名客戶名稱被切開分屬兩個(gè)chunk檢索客戶名稱時(shí)只命中一半上下文。embedding模型本身也不擅長(zhǎng)表達(dá)行和列之間的位置關(guān)系。文本里的詞是線性的表格里的值既跟左邊的列頭有關(guān)系又跟上面那一行同一個(gè)記錄的其它字段有關(guān)系。兩套關(guān)系同時(shí)在但向量只編碼了線性token位置信息天然丟失。這就是為什么直接拿RAG查表格經(jīng)常出現(xiàn)列頭對(duì)不上值的詭異答案。1.2 三種典型癥狀丟列頭、亂類型、上下文割裂我把實(shí)戰(zhàn)中遇到的翻車歸納成三類方便你對(duì)號(hào)入座丟列頭檢索出來的chunk只有數(shù)值沒有表頭比如3456、789.2、華東區(qū)你根本不知道這段在說什么。原因多半是切分時(shí)列頭所在的chunk沒被召回或者表格被轉(zhuǎn)成了純文本后列頭被當(dāng)成普通文本稀釋掉了。亂類型日期變成浮點(diǎn)數(shù)Excel的經(jīng)典表現(xiàn)金額被讀成字符串百分比被讀成小數(shù)。embedding模型對(duì)這些數(shù)字變化很敏感2024-01-15和45289兩個(gè)表達(dá)方式向量距離有時(shí)比不同日期之間還遠(yuǎn)。上下文割裂一次查詢涉及同一行的多個(gè)字段比如華東區(qū)Q2的退貨率退貨率在某幾列華東區(qū)在某幾列Q2在時(shí)間維度上切分后它們散落在不同chunk檢索只召回部分答案自然殘缺。提示在開始設(shè)計(jì)解析流程之前先明確一個(gè)目標(biāo)——你要保留的是表格的結(jié)構(gòu)可讀性還是表格的語義關(guān)系兩者對(duì)解析方案的選擇完全不同下面每一章都會(huì)回到這個(gè)判斷上。2. CSV導(dǎo)入看似簡(jiǎn)單其實(shí)暗坑都埋在細(xì)節(jié)里CSV是表格數(shù)據(jù)進(jìn)入RAG最直接的方式但恰恰因?yàn)樗?jiǎn)單很多人栽在編碼、分隔符和類型推斷這三個(gè)基礎(chǔ)問題上。2.1 編碼問題UTF-8 BOM與GBK的兼容CSV的編碼是第一個(gè)坑。國(guó)內(nèi)業(yè)務(wù)系統(tǒng)導(dǎo)出的CSV超過一半是GBK或GB18030編碼還有一部分帶BOM頭。如果你直接用默認(rèn)方式讀取GBK文件會(huì)出現(xiàn)亂碼BOM頭則會(huì)被當(dāng)成字符混進(jìn)第一列列名里。我的處理習(xí)慣是讀取前先做編碼探測(cè)不要硬猜。用chardet或charset-normalizer跑一遍或者干脆寫一個(gè)兼容層import pandas as pd def read_csv_auto(path): # 先嘗試常見編碼再降級(jí)到探測(cè)結(jié)果 for enc in (utf-8-sig, gb18030, big5): try: return pd.read_csv(path, encodingenc) except UnicodeDecodeError: continue # 實(shí)在不行用探測(cè) import charset_normalizer raw open(path, rb).read() enc charset_normalizer.from_bytes(raw).best().encoding return pd.read_csv(path, encodingenc)注意utf-8-sig和utf-8的區(qū)別在于前者會(huì)剝離BOM。我通常把utf-8-sig排第一因?yàn)榧词刮募]有BOM它也能正常讀取不會(huì)報(bào)錯(cuò)。2.2 分隔符識(shí)別逗號(hào)、分號(hào)、Tab與引號(hào)轉(zhuǎn)義CSV的CSV全稱是逗號(hào)分隔值但現(xiàn)實(shí)世界里分號(hào)、Tab、豎線都是常見變體。歐洲各區(qū)用分號(hào)數(shù)據(jù)庫導(dǎo)出的TIMESTAMP字段里混著逗號(hào)某些工具導(dǎo)出的是TSV。如果你寫死sep,必炸。更穩(wěn)妥的做法是讓csv.Sniffer去猜分隔符但這個(gè)方案在流式讀取和大文件上有性能坑。我采用的是先抽樣再指定的策略import csv def sniff_delimiter(path, sample_size4096): with open(path, r, encodingutf-8, errorsreplace) as f: sample f.read(sample_size) dialect csv.Sniffer().sniff(sample) return dialect.delimiter抽樣的好處是速度快只讀文件頭部。但要注意如果文件前幾行是注釋或空行sniffer可能猜出錯(cuò)誤結(jié)果。我會(huì)先跳過空行和以#開頭的行再抽樣。還有引號(hào)轉(zhuǎn)義問題。CSV字段里包含換行符、逗號(hào)和引號(hào)時(shí)解析器是否正確處理雙引號(hào)轉(zhuǎn)義直接決定了讀出來的內(nèi)容是否完整。Python的csv模塊處理這個(gè)沒問題但如果你用簡(jiǎn)單的split(,)遇到帶引號(hào)的多行字段會(huì)直接錯(cuò)亂。這個(gè)幾乎是所有CSV踩坑中最隱蔽的。2.3 類型推斷與空值處理pandas的read_csv默認(rèn)會(huì)嘗試類型推斷但這套推斷在RAG場(chǎng)景下不太夠用。比如訂單號(hào)00123會(huì)被轉(zhuǎn)成整數(shù)123后續(xù)檢索訂單號(hào)00123永遠(yuǎn)得不到結(jié)果。處理方式是指定dtype參數(shù)把不可轉(zhuǎn)換的列顯式設(shè)為字符串df pd.read_csv(path, dtype{order_id: str, sku: str})空值的處理也值得注意。RAG管道中表格里的空單元格不應(yīng)該被簡(jiǎn)單丟棄因?yàn)榭罩当旧砜赡軘y帶信息——這個(gè)客戶沒有郵箱和這個(gè)客戶郵箱未采集是兩回事。我會(huì)在導(dǎo)入階段把空值統(tǒng)一標(biāo)記為N/A或保留占位符而不是直接讓NaN進(jìn)入embedding。原因很簡(jiǎn)單NaN在向量化時(shí)通常被忽略但缺失這個(gè)語義信息有價(jià)值的場(chǎng)景非常常見。注意CSV進(jìn)RAG之前做一次df.info()和df.nunique()統(tǒng)計(jì)看列數(shù)是否符合預(yù)期、每列的唯一值數(shù)量是否合理。這一步能攔住絕大多數(shù)臟數(shù)據(jù)問題。3. Excel導(dǎo)入你以為它就是多了個(gè)SheetExcel比CSV麻煩的地方不只是多一個(gè)Sheet這么簡(jiǎn)單。作為二進(jìn)制或壓縮XML格式Excel的讀取鏈路里藏著一堆CSV里根本不存在的坑。3.1 多Sheet與表頭定位一個(gè)工作簿里有多個(gè)Sheet是常態(tài)每個(gè)Sheet可能還有不同層級(jí)的表頭——有的表頭在第1行有的在第3行上面是標(biāo)題和生成日期。直接用pd.read_excel默認(rèn)讀第一個(gè)Sheet表頭位置錯(cuò)一位整個(gè)列映射就全亂了。我一般建議做成顯式配置xl pd.ExcelFile(data.xlsx) for sheet_name in xl.sheet_names: df xl.parse( sheet_name, header2, # 表頭在第3行 skiprows[0, 1], # 跳過前兩行元信息 usecolsA:F # 限定列范圍 )不要偷懶用sheet_name0這種索引因?yàn)樵赗AG項(xiàng)目里根因追溯極其重要——你得能說出這個(gè)chunk來自data.xlsx的哪個(gè)Sheet的哪幾行否則排查檢索質(zhì)量時(shí)無從下手。3.2 合并單元格、公式緩存值與日期序列號(hào)Excel的合并單元格是另一個(gè)經(jīng)典暗坑。openpyxl讀取合并單元格時(shí)只有左上角那個(gè)單元格有值其余返回None。pandas讀出來之后你會(huì)看到大量NaN而這些NaN在業(yè)務(wù)上其實(shí)是繼承上方單元格的值。處理辦法是向前填充forward filldf df.ffill()但要小心這個(gè)操作只適用合并方向是列方向的場(chǎng)景不要對(duì)整個(gè)DataFrame無腦ffill否則會(huì)把不同分組的空值也串起來。公式問題是Excel特有的。openpyxl默認(rèn)讀取的是公式計(jì)算結(jié)果緩存值也就是Excel最后一次保存時(shí)計(jì)算的值。但如果這個(gè)文件是用代碼生成的或者保存時(shí)Excel沒有觸發(fā)重算緩存值可能跟實(shí)時(shí)計(jì)算值不一致。更隱蔽的是用openpyxl的data_onlyTrue讀取時(shí)如果沒有緩存值會(huì)直接返回None。日期序列號(hào)更是經(jīng)典中的經(jīng)典。Excel內(nèi)部把日期存儲(chǔ)為從1900年1月1日起的天數(shù)序列號(hào)比如45289其實(shí)代表2024年1月1日。如果你讀取時(shí)沒有指定parse_datespandas可能給你一串整數(shù)embedding之后完全變成無意義數(shù)字。我在實(shí)戰(zhàn)中一定會(huì)顯式指定df[order_date] pd.to_datetime(df[order_date], errorscoerce)3.3 大文件與內(nèi)存xlsx是zip不是XML很多人以為xlsx就是XML其實(shí)xlsx是一個(gè)zip包內(nèi)部包含多個(gè)XML文件。用openpyxl讀取大文件時(shí)它是把整個(gè)工作簿加載進(jìn)內(nèi)存的50MB的xlsx可能吃滿幾個(gè)GB內(nèi)存。這跟pandas讀取CSV的流式機(jī)制完全不同。如果你要處理超大Excel我建議用openpyxl的read_only模式逐行讀取from openpyxl import load_workbook wb load_workbook(big.xlsx, read_onlyTrue, data_onlyTrue) ws wb[Sheet1] for row in ws.iter_rows(values_onlyTrue): # 逐行處理而不是一次性加載整個(gè)DataFrame process_row(row)不過read_only模式不支持讀取合并單元格的隱藏值兩者是有取舍的。我的經(jīng)驗(yàn)是小文件追求功能完整用普通模式大文件追求內(nèi)存可控用read_only并且提前用腳本把Sheet拆分成CSV中間文件再走CSV流程。4. LlamaHub連庫實(shí)戰(zhàn)數(shù)據(jù)庫到向量庫的完整鏈路LlamaHub是LlamaIndex生態(tài)里的數(shù)據(jù)加載器和市場(chǎng)它最大的價(jià)值不是省去你寫幾行代碼而是把許多數(shù)據(jù)庫的連庫→取數(shù)→結(jié)構(gòu)化的臟活提前封裝好了。下面我講的是通用實(shí)戰(zhàn)鏈路不綁死某個(gè)具體加載器而是把思路和關(guān)鍵參數(shù)講透。4.1 數(shù)據(jù)庫連接前要想清楚的三個(gè)問題不是所有表都適合直接進(jìn)RAG。連庫之前先回答三個(gè)問題哪些表是維度表哪些是事實(shí)表維度表客戶、產(chǎn)品分類擁有大量文本屬性適合作為知識(shí)庫內(nèi)容。事實(shí)表訂單明細(xì)通常只有外鍵和數(shù)字直接進(jìn)RAG的價(jià)值很低且容易產(chǎn)生數(shù)據(jù)噪聲。數(shù)據(jù)量有多大全量拉一張千萬級(jí)流水表進(jìn)向量庫成本高且大部分?jǐn)?shù)據(jù)根本沒有查詢價(jià)值。要設(shè)計(jì)采樣或條件過濾。是否需要實(shí)時(shí)性知識(shí)庫是離線快照還是準(zhǔn)實(shí)時(shí)同步這決定了你是否要引入增量更新機(jī)制。在LlamaIndex中我通常用SQLDatabase來建一個(gè)可查詢的數(shù)據(jù)庫連接對(duì)象再用DatabaseReader的變體或自定義包裝方式把指定表拉出來from sqlalchemy import create_engine from llama_index.core.objects import SQLDatabase engine create_engine(mysqlpymysql://user:passlocalhost/mydb) sql_db SQLDatabase(engine)這一步的核心價(jià)值是讓RAG管道能理解數(shù)據(jù)庫里有哪幾張表、各表有哪些字段而不是直接把SQL結(jié)果一股腦倒進(jìn)embedding模型。4.2 連接配置與數(shù)據(jù)分頁連庫取數(shù)最常見的坑是一次性查詢把所有行都讀進(jìn)內(nèi)存。數(shù)據(jù)庫數(shù)據(jù)動(dòng)輒幾十萬行直接SELECT *會(huì)把應(yīng)用機(jī)器內(nèi)存打爆。正確做法是使用分頁查詢PAGE_SIZE 1000 def fetch_all_data(sql_db, sql, page_sizePAGE_SIZE): # 基于游標(biāo)分段讀取避免一次性加載全量結(jié)果 offset 0 while True: paged_sql f{sql} LIMIT {page_size} OFFSET {offset} chunk sql_db.run_sql(paged_sql) if not chunk: break process_chunk(chunk) offset page_size這里有幾個(gè)參數(shù)需要注意LIMIT/OFFSET分頁在數(shù)據(jù)量大時(shí)越翻越慢更優(yōu)方案是基于主鍵范圍或游標(biāo)WHERE id last_id分頁。每頁大小取決于單行的字段長(zhǎng)度和embedding批量上限一般500-2000之間比較穩(wěn)。超時(shí)設(shè)置很多數(shù)據(jù)庫默認(rèn)查詢超時(shí)很短大查詢要主動(dòng)設(shè)置連接超時(shí)參數(shù)比如MySQL的connect_timeout和read_timeout否則跑到一半被斷開白跑。4.3 增量同步與主鍵去重知識(shí)庫要落地一次性導(dǎo)入是不夠的。業(yè)務(wù)數(shù)據(jù)庫每天在變你的向量庫也得跟著變否則三個(gè)月后知識(shí)庫就成了歷史包袱。我的增量同步模板分三步記錄同步水位線用一張表或者元數(shù)據(jù)文件保存上次同步到哪個(gè)主鍵ID或時(shí)間戳。增量查詢WHERE updated_at last_sync_time或WHERE id last_id。主鍵去重核心是讓chunk的node_id帶上業(yè)務(wù)主鍵這樣寫入向量庫時(shí)天然支持upsert。LlamaIndex中創(chuàng)建帶穩(wěn)定ID的節(jié)點(diǎn)from llama_index.core.schema import TextNode def create_nodes_from_row(row, table_name): node_id f{table_name}:{row[id]}_{row[updated_at]} text format_row_as_text(row) # 轉(zhuǎn)成自然語言/表格文本 return TextNode( texttext, id_node_id, metadata{table: table_name, row_id: row[id]} )把id_設(shè)為業(yè)務(wù)主鍵加時(shí)間戳重建索引時(shí)就能通過ID去重而不是重復(fù)插入相同內(nèi)容。這一步我見過太多團(tuán)隊(duì)漏掉結(jié)果是知識(shí)庫里有幾十份重復(fù)的同一行數(shù)據(jù)檢索結(jié)果全是同一個(gè)信息的變體召回質(zhì)量稀碎。5. 表格數(shù)據(jù)進(jìn)了Chunk之后的語義保真問題數(shù)據(jù)讀進(jìn)來了接下來是更關(guān)鍵的Chunk策略。表格數(shù)據(jù)怎么切直接決定檢索質(zhì)量的上限。文本可以無腦按token切表格不行。5.1 Markdown轉(zhuǎn)表格 vs 每行一個(gè)Chunk我把表格進(jìn)RAG的策略分成兩派方案A轉(zhuǎn)成Markdown表格后整體分割。把表格渲染成Markdown的表格語法再按行或按固定token切。優(yōu)點(diǎn)是保留了列頭與數(shù)值的相對(duì)位置關(guān)系embedding時(shí)表格結(jié)構(gòu)和語義能部分保留。缺點(diǎn)是Markdown表格在切分邊界處依然可能被截?cái)嗲谐鰜淼钠问チ吮砀竦耐暾?。方案B每行轉(zhuǎn)成一個(gè)獨(dú)立Chunk再把列頭拼進(jìn)該行的前面。例如客戶名稱: 張三; 訂單編號(hào): 00123; 產(chǎn)品: 無線耳機(jī); 數(shù)量: 2。這種做法的優(yōu)點(diǎn)是完全避免跨行截?cái)鄦栴}每一條記錄作為一個(gè)獨(dú)立單位被檢索缺點(diǎn)是失去了整張表的全局統(tǒng)計(jì)信息如果你想問這個(gè)月銷售額最高的產(chǎn)品是什么單行Chunk是答不出來的。我的實(shí)踐是混合使用。對(duì)于要支持明細(xì)查詢的場(chǎng)景張三買了什么用方案B對(duì)于要支持統(tǒng)計(jì)查詢的場(chǎng)景總收入多少必須額外保留整張表的大Chunk或走Text-to-SQL路線而不是指望embedding模型從零散行里算匯總。5.2 列頭拼接策略不管用哪種方案列頭拼接都值得單獨(dú)處理。很多人直接把表頭丟掉只喂數(shù)據(jù)行這是最糟糕的做法。我一般用這樣的列頭拼接方式def format_row_as_text(row, columns): parts [] for col in columns: value row[col] if pd.isna(value): value N/A parts.append(f{col}: {value}) return ; .join(parts)關(guān)鍵點(diǎn)是列名本身要保留在文本里因?yàn)樗腔卮饡r(shí)最核心的錨點(diǎn)。用戶問哪個(gè)產(chǎn)品銷量最好如果embedding結(jié)果里沒有產(chǎn)品和銷量這兩個(gè)詞召回必死。有個(gè)細(xì)節(jié)列名可以用中文保留原樣不需要強(qiáng)行翻譯成英文。embedding模型對(duì)中文列名的理解通常比英文翻譯更準(zhǔn)確因?yàn)闃I(yè)務(wù)表格的列名本來就是中文語境下的專有詞匯。5.3 結(jié)構(gòu)數(shù)據(jù)轉(zhuǎn)自然語言的權(quán)衡還有一種思路是結(jié)構(gòu)化轉(zhuǎn)自然語言把每一行翻譯成一句通順的陳述句例如張三在2024年1月15日購買了訂單編號(hào)為00123的無線耳機(jī)數(shù)量為2單價(jià)為399元訂單狀態(tài)為已發(fā)貨。這種做法的好處是embedding效果通常比列名: 值; 列名: 值更好因?yàn)樽匀徽Z言的結(jié)構(gòu)更接近模型訓(xùn)練數(shù)據(jù)的分布。但缺點(diǎn)是轉(zhuǎn)化成本高尤其是復(fù)雜的多級(jí)表頭很難自動(dòng)化生成流暢的句子。我的選擇標(biāo)準(zhǔn)是這樣的數(shù)據(jù)量小、字段少時(shí)用自然語言轉(zhuǎn)換數(shù)據(jù)量大、字段多時(shí)用列名: 值拼接因?yàn)樗米詣?dòng)化、更穩(wěn)定。不要為了追求自然而犧牲批量處理的可控性。6. 實(shí)測(cè)中的翻車案例與解法復(fù)盤最后分享三個(gè)我在實(shí)際項(xiàng)目里踩過的坑。這些問題都有一個(gè)特點(diǎn)代碼不報(bào)錯(cuò)但檢索結(jié)果悄悄變差往往要過很久才被發(fā)現(xiàn)。6.1 案例一CSV的GBK編碼導(dǎo)致檢索亂碼現(xiàn)象知識(shí)庫上線后用戶反饋搜索華東區(qū)能找到內(nèi)容但搜索華東就出現(xiàn)亂碼字符。排查發(fā)現(xiàn)CSV文件是GBK編碼我用默認(rèn)utf-8讀取解析結(jié)果在檢索時(shí)偶爾正常偶爾亂碼——因?yàn)閜andas對(duì)無法解碼的部分默認(rèn)替換成了這些替換符混進(jìn)了embedding文本。解法在讀取函數(shù)中加編碼探測(cè)并且對(duì)結(jié)果做一次是否包含替換字符的校驗(yàn)if \ufffd in raw_text: raise ValueError(CSV存在未識(shí)別的編碼字符請(qǐng)檢查源文件編碼)不要等到檢索結(jié)果出問題再來返工讀取階段就要攔截。6.2 案例二Excel公式緩存值比實(shí)時(shí)值舊現(xiàn)象一個(gè)銷售報(bào)表工作簿里的本月總銷售額列是用Excel公式計(jì)算的系統(tǒng)導(dǎo)出時(shí)沒有強(qiáng)制重算緩存值停留在上周。查詢這個(gè)月賣了多少時(shí)RAG給出的答案和真實(shí)數(shù)據(jù)庫差了38%。解法讀取Excel時(shí)用data_onlyFalse拿到公式本身判斷公式引用的單元格范圍再把對(duì)應(yīng)的數(shù)據(jù)區(qū)域算一遍。如果項(xiàng)目里Excel都涉及公式我建議直接寫一個(gè)獨(dú)立校驗(yàn)?zāi)_本每周對(duì)公式單元格做一次全量重算用Excel的openpyxl寫回結(jié)果。這個(gè)坑的隱蔽性在于你只看到數(shù)字看不到這個(gè)數(shù)字是緩存還是實(shí)時(shí)值。6.3 案例三數(shù)據(jù)庫連表查詢超時(shí)現(xiàn)象用LlamaHub連接業(yè)務(wù)庫時(shí)查詢一個(gè)關(guān)聯(lián)了7張表的視圖RAG管道在那里卡了30秒后超時(shí)。原因是視圖本身在數(shù)據(jù)庫端執(zhí)行要25秒而我在RAG側(cè)設(shè)置了20秒的查詢超時(shí)。解法圍繞性能和穩(wěn)定性做三層處理。第一把視圖的SQL在數(shù)據(jù)庫端先執(zhí)行并落到中間表RAG直接查中間表第二給連接池設(shè)置合理的超時(shí)和重試參數(shù)不讓單次查詢拖垮整個(gè)管道第三對(duì)查詢語句做EXPLAIN分析確認(rèn)沒有全表掃。數(shù)據(jù)量大的表加上合適的索引。提示任何連庫的時(shí)間超過1秒都要警惕。RAG檢索的端到端延遲應(yīng)該控制在幾秒內(nèi)如果數(shù)據(jù)拉取占了大頭用戶體驗(yàn)根本扛不住。寧可離線把表打成快照文件也不要在線查庫。7. 寫在最后表格進(jìn)RAG的幾條經(jīng)驗(yàn)底線表格和數(shù)據(jù)庫導(dǎo)入這條路我走了很多彎路。如果只留幾條經(jīng)驗(yàn)給后來者我會(huì)說第一永遠(yuǎn)不要讓原始表格以原樣進(jìn)入embedding。至少要經(jīng)過列頭拼接、空值標(biāo)記、統(tǒng)一編碼這三道工序才能保證向量化的文本真的讀得懂。第二CSV、Excel、數(shù)據(jù)庫三者的解析策略完全不同不要試圖用一套代碼通吃。CSV要處理的是編碼和分隔符Excel要處理的是合并單元格和公式緩存數(shù)據(jù)庫要處理的是分頁、增量和超時(shí)。每一類數(shù)據(jù)都有自己最核心的那一兩個(gè)坑提前設(shè)計(jì)好后面會(huì)省很多事。第三在引入LlamaIndex這種框架時(shí)不要只看到方便要理解封裝后面的數(shù)據(jù)結(jié)構(gòu)。SQLDatabase到底暴露了哪些表、字段是不是按你期望的類型進(jìn)了DataFrame、主鍵有沒有被保留下來——這些細(xì)節(jié)決定你做增量更新時(shí)會(huì)不會(huì)崩。我現(xiàn)在的標(biāo)準(zhǔn)流程是CSV和Excel先做清洗校驗(yàn)轉(zhuǎn)成UTF-8編碼的中間文件數(shù)據(jù)庫數(shù)據(jù)先落中間表快照再走同一套列頭拼接→文本化→Chunk→索引的流程。這樣架構(gòu)簡(jiǎn)單排錯(cuò)也容易——任何一個(gè)環(huán)節(jié)出問題都能快速定位到具體文件或表而不是在框架的黑盒里猜。最后再補(bǔ)一個(gè)我實(shí)測(cè)很有用的小技巧表格類數(shù)據(jù)進(jìn)索引之后跑一輪檢索冒煙測(cè)試用10到20個(gè)典型業(yè)務(wù)問題去查看返回文本的前100個(gè)字符里有沒有你要的列名和值。這個(gè)測(cè)試成本很低但能在上線前攔住絕大多數(shù)表格解析問題比上線后靠用戶反饋再修要?jiǎng)澦愕枚唷?