據(jù)進(jìn)RAG全攻略:從CSV到數(shù)據(jù)庫的導(dǎo)入與實(shí)戰(zhàn))
表格類數(shù)據(jù)進(jìn) RAG我一直覺得是被低估的硬骨頭。文本類文檔大家已經(jīng)玩得比較順了PDF、Word、Markdown 各有各的解析套路但一換成 CSV、Excel、數(shù)據(jù)庫表很多人就卡住了直接整表轉(zhuǎn)文本丟進(jìn)向量庫檢索效果稀碎每行單獨(dú)轉(zhuǎn)成 Document又不知道元數(shù)據(jù)怎么掛才合理真要連數(shù)據(jù)庫連接串、查詢語句、增量同步又是一堆新問題。這篇是 RAG 數(shù)據(jù)導(dǎo)入與解析系列的第三篇專門把表格和數(shù)據(jù)庫這條路走一遍從 CSV 到 Excel 再到 LlamaHub 連庫實(shí)戰(zhàn)每一步我都會給出可復(fù)現(xiàn)的代碼和實(shí)際踩坑記錄。先說清楚這篇適合誰你已經(jīng)在用 LlamaIndex 或類似框架處理過文本數(shù)據(jù)想把手里的結(jié)構(gòu)化數(shù)據(jù)訂單表、配置表、產(chǎn)品清單真正接進(jìn) RAG或者你還沒動手但對「表格數(shù)據(jù)到底該怎么進(jìn)知識庫」這件事沒想明白想先看一條完整的落地方案。這篇不堆概念全是實(shí)操。1. 表格數(shù)據(jù)在 RAG 里的特殊位置為什么不能照抄文檔解析的老路子很多人拿到表格數(shù)據(jù)后的第一反應(yīng)是跟 PDF 一樣讀進(jìn)來切塊丟進(jìn)索引完事。這個思路對文本沒問題但對表格幾乎一定會翻車。想明白為什么是這篇所有操作的基礎(chǔ)。1.1 二維數(shù)據(jù)與「一刀切」切塊的沖突文本是線性的段落和段落之間有天然的順序關(guān)系所以按固定窗口切塊、加一點(diǎn)重疊語義損失通??山邮堋5砀袷嵌S的橫向是字段列名縱向是記錄行每一格的語義價值高度依賴它的表頭。如果你把整張表轉(zhuǎn)成一段長文本再按 token 切常見的結(jié)果有兩種。第一種情況一張 50 列、2000 行的表文本化之后可能上萬 token固定窗口切出來有的 chunk 會截斷前幾列有的 chunk 會把同一行數(shù)據(jù)拆到兩個 chunk 里。檢索的時候你命中了一個 chunk但關(guān)鍵的數(shù)值字段在另一個 chunk 里模型根本拿不到完整記錄。第二種情況更隱蔽某些表的列名本身沒有語義比如col1、col2、item_code、region_id。切塊后這些 chunk 里全是孤立數(shù)值embedding 模型對這些短數(shù)字片段的編碼效果非常差檢索召回基本靠運(yùn)氣。所以處理表格數(shù)據(jù)的第一原則是不要默認(rèn)走文本切塊 pipeline先看表的形態(tài)再決定每一行、每一塊到底要以什么粒度進(jìn)入索引。1.2 記錄型與分析型先分清你手頭是什么表我習(xí)慣把表格先分成兩類處理邏輯完全不同。記錄型表格典型是業(yè)務(wù)流水、訂單明細(xì)、用戶列表每一行都是獨(dú)立的、完整的事實(shí)。這種表最適合的行粒度是「一行 一個 Document」因?yàn)橐恍袃?nèi)的字段組合起來構(gòu)成完整語義行與行之間沒有強(qiáng)上下文依賴。檢索場景通常是「查某個特定條件下的記錄」比如某訂單金額、某客戶所在地區(qū)。分析型表格典型是統(tǒng)計(jì)報表、匯總表、指標(biāo)看板行與行、列與列之間存在維度關(guān)系某一格單獨(dú)拿出來沒意義必須依賴表格整體結(jié)構(gòu)。比如「華東區(qū)一季度銷售額」它的上下文是行表頭和列表頭的組合。這種表直接按行拆會丟失結(jié)構(gòu)比較合理的做法是先做「表格摘要 局部摘錄」或者把表按業(yè)務(wù)語義塊拆分再把每個塊轉(zhuǎn)成自然語言描述而不是單純貼數(shù)值。這兩種表如果混在一起統(tǒng)一處理后面召回測試會很難受因?yàn)槟銓Α刚_結(jié)果」的判斷標(biāo)準(zhǔn)都不一樣。1.3 從「最終要回答什么問題」倒推導(dǎo)入方案這是我想強(qiáng)調(diào)的習(xí)慣動手導(dǎo)入之前先列出業(yè)務(wù)側(cè)真實(shí)會問的問題。我做某個項(xiàng)目時對方列了一堆表要導(dǎo)入我讓他們先給我十個問題。結(jié)果發(fā)現(xiàn)他們關(guān)心的幾乎都是「某個商品最近 30 天在華南區(qū)的銷量」「哪幾個訂單超過萬元并且還沒發(fā)貨」這類帶條件的查詢。這些問題直接決定了三件事一是必須保留哪些字段作為過濾條件二是哪些字段應(yīng)該寫進(jìn)正文文本三是每行文本應(yīng)該如何組織語言。反過來看如果一開始就把所有表不分青紅皂白轉(zhuǎn)文本入庫檢索會把大量無關(guān)行撈回來query engine 再聰明也被噪聲淹沒。這個環(huán)節(jié)不寫代碼但我覺得它比代碼更重要。表格數(shù)據(jù)進(jìn) RAG本質(zhì)上不是「把數(shù)據(jù)塞進(jìn)去」而是「為特定問題設(shè)計(jì)一種可檢索的文本視圖」。2. CSV 導(dǎo)入實(shí)戰(zhàn)不止 read_csv 一下就完事CSV 看起來最簡單其實(shí)最容易出問題。我見過太多「CSV 文件插入向量庫之后一問三不知」的情況原因大多不在向量庫而在 CSV 導(dǎo)入那一步的數(shù)據(jù)質(zhì)量和行文本組織。2.1 數(shù)據(jù)體檢編碼、分隔符、空值的那些事CSV 作為純文本格式第一關(guān)就是編碼。國內(nèi)環(huán)境中 GBK 編碼的 CSV 仍然大量存在Excel 導(dǎo)出的老版本 CSV 甚至默認(rèn) ANSI。如果你用 pandas 直接讀遇到 GBK 文件會直接拋 UnicodeDecodeError。處理方式很簡單import pandas as pd # 先探測編碼再讀取 def detect_encoding(file_path): import chardet with open(file_path, rb) as f: raw f.read(100000) return chardet.detect(raw).get(encoding, utf-8) enc detect_encoding(sales.csv) df pd.read_csv(sales.csv, encodingenc)分隔符也值得確認(rèn)一下。部分業(yè)務(wù)系統(tǒng)導(dǎo)出的 CSV 不是逗號分隔而是|或\t還有的企業(yè)數(shù)據(jù)里字段本身含逗號但沒有按 CSV 標(biāo)準(zhǔn)加引號。這個不提前檢查后面整行解析錯位導(dǎo)入的文本全是亂的而且很難發(fā)現(xiàn)。建議讀取前先pd.read_csv一個 5 行的樣本打印出來看一眼。空值處理更是必做項(xiàng)。如果某一行關(guān)鍵字段是 NaN你直接str(row)會把nan寫進(jìn)文本檢索時如果用戶問「哪些訂單沒填寫客戶名」模型會被這些nan誤導(dǎo)。導(dǎo)入前必須明確空值策略要么丟棄該行要么用「未知」等有效文本占位。2.2 每行轉(zhuǎn) Document還是整表轉(zhuǎn)大文本我在 1.1 里說了記錄型表按行轉(zhuǎn) Document。但這里有個容易忽略的細(xì)節(jié)Document的 text 字段不能是「列名: 值」的機(jī)械拼接而是要盡量轉(zhuǎn)成一句完整自然語言。舉個例子下面這樣的原始行order_idproductregionamountstatusA1001無線耳機(jī)華東1280已發(fā)貨機(jī)械拼接的結(jié)果是order_id: A1001, product: 無線耳機(jī), region: 華東, amount: 1280, status: 已發(fā)貨。檢索「華東區(qū)發(fā)了多少錢的貨」時這句文本的語義密度很低因?yàn)?embedding 模型對這種「字段名短值」結(jié)構(gòu)的編碼并不好。我推薦做一次行級文本化def row_to_text(row, table_desc訂單記錄): parts [] if order_id in row and pd.notna(row[order_id]): parts.append(f訂單編號為{row[order_id]}) if product in row and pd.notna(row[product]): parts.append(f商品為{row[product]}) if region in row and pd.notna(row[region]): parts.append(f銷售區(qū)域?yàn)閧row[region]}) if amount in row and pd.notna(row[amount]): parts.append(f金額為{row[amount]}元) if status in row and pd.notna(row[status]): parts.append(f狀態(tài)為{row[status]}) return f這是一條{table_desc}{.join(parts)}。這樣轉(zhuǎn)出來的是自然語言短句embedding 檢索的命中率明顯提升。這個步驟多花不了多少時間但對結(jié)果是決定性的。我在多個項(xiàng)目里對比過機(jī)械拼接檢索 top-5 命中正確行概率大概 40%自然語言化之后能到 75% 以上。2.3 一套可直接用的 CSV 自定義導(dǎo)入模板LlamaHub 上其實(shí)有現(xiàn)成的 CSVReader但我很少直接用原因是它對 CSV 的假設(shè)太理想了默認(rèn) UTF-8、默認(rèn)逗號分隔、沒有空值處理、按整個文件內(nèi)容作為一個大 Document 處理。對干凈的小文件它很方便但對真實(shí)業(yè)務(wù)數(shù)據(jù)不夠用。我更傾向于用 pandas 清洗 自定義 Document 構(gòu)建import pandas as pd from llama_index.core.schema import Document def load_csv_as_documents( file_path, table_desc數(shù)據(jù)表, encodingNone, sep,, drop_emptyTrue, filter_empty_cols[order_id, amount], ): if encoding is None: encoding detect_encoding(file_path) df pd.read_csv(file_path, encodingencoding, sepsep, dtypestr) # 空值處理 df df.dropna(howall) # 全空行直接丟 if drop_empty and filter_empty_cols: df df.dropna(subsetfilter_empty_cols) # 遍歷每一行轉(zhuǎn)自然語言 Document documents [] for idx, raw_row in df.iterrows(): row raw_row.dropna() # 去掉 NaN 字段避免文本里出現(xiàn) nan text row_to_text(row, table_desc) documents.append( Document( texttext, metadata{ source: file_path, table: table_desc, row_index: idx, }, ) ) return documents注意我把dtypestr強(qiáng)制全部轉(zhuǎn)字符串避免 ID 變成科學(xué)計(jì)數(shù)法或日期被 pandas 改格式。然后空值字段直接不寫進(jìn)文本只保留有效字段。這里有個 metadata 設(shè)計(jì)細(xì)節(jié)想多說一句row_index一定要留。后面如果發(fā)現(xiàn)某條檢索結(jié)果內(nèi)容有問題你能直接通過 row_index 回原表定位定位速度比全文搜原文件快得多。數(shù)據(jù)量一大這個習(xí)慣能救你很多次。3. Excel 導(dǎo)入實(shí)戰(zhàn)多 Sheet、合并單元格與公式緩存的坑Excel 比 CSV 麻煩一個數(shù)量級。CSV 是純數(shù)據(jù)Excel 里埋著格式、公式、多個 Sheet、合并單元格、日期類型等各種隱雷。我在項(xiàng)目里處理最多的問題有三個多 Sheet 映射、合并單元格導(dǎo)致的空值、公式值讀不到。3.1 選型PandasExcelReader 還是 openpyxl 自己來LlamaHub 里有兩個相關(guān)的 ReaderPandasExcelReader和PandasCSVReader之類。PandasExcelReader 的定位是「把每個 Excel 文件轉(zhuǎn)成一個或多個 Document」內(nèi)部用pd.read_excel對單 Sheet 的簡單表夠用但對復(fù)雜 Excel 不夠靈活。復(fù)雜場景我基本直接用pd.read_excel(file, sheet_nameNone)把所有 Sheet 一次性讀進(jìn)來再逐個自定義處理。這樣每個 Sheet 是獨(dú)立的 DataFrame你可以為不同 Sheet 配置不同的 table_desc、不同的空值策略、不同的行文本模板。另外一個值得記住的點(diǎn)pd.read_excel需要指定引擎。.xlsx默認(rèn) openpyxl.xls需要 xlrd。舊版.xls文件現(xiàn)在反而少見但遇到時引擎報錯很容易讓人懵建議直接.xls文件先用 openpyxl 另存為.xlsx別跟它較勁。3.2 多 Sheet 怎么拆元數(shù)據(jù)怎么標(biāo)多 Sheet 的坑在于不同 Sheet 可能是完全不同的業(yè)務(wù)表。比如一個「月度銷售報表.xlsx」里Sheet1 是訂單明細(xì)Sheet2 是區(qū)域匯總Sheet3 是產(chǎn)品目錄。如果你把它們當(dāng)成同一個文件處理混在同一個索引里檢索時 Sheet 之間的相互干擾非常嚴(yán)重。正確做法是每個 Sheet 獨(dú)立構(gòu)建 Document并且把sheet_name放進(jìn) metadataimport pandas as pd from llama_index.core.schema import Document def load_excel_workbook(file_path, sheet_to_table_descNone): # sheet_to_table_desc 是 {訂單明細(xì): 訂單記錄, 區(qū)域匯總: 區(qū)域銷售統(tǒng)計(jì)} dfs pd.read_excel(file_path, sheet_nameNone, dtypestr) documents [] for sheet_name, df in dfs.items(): table_desc (sheet_to_table_desc or {}).get(sheet_name, sheet_name) # 清洗邏輯同 CSV略 for idx, raw_row in df.iterrows(): row raw_row.dropna() text row_to_text(row, table_desc) documents.append( Document( texttext, metadata{ source: file_path, sheet: sheet_name, table: table_desc, row_index: idx, }, ) ) return documentsmetadata 里加sheet字段后檢索時可以做到表級過濾——用戶問題里出現(xiàn)「區(qū)域匯總」相關(guān)描述時只在該 Sheet 的 Document 范圍內(nèi)檢索這比把所有 Sheet 混在一起喂給模型要準(zhǔn)得多。后面第 5 章我會細(xì)講過濾和路由的用法。3.3 合并單元格和公式緩存兩個低頻但致命的問題合并單元格是 Excel 導(dǎo)入里最陰間的坑。比如一個產(chǎn)品目錄表產(chǎn)品名稱列做了縱向合并合并后pd.read_excel讀進(jìn)來只有合并區(qū)域的第一行有值其余行全是 NaN。如果你不做處理導(dǎo)入后的文本里那些行就缺了產(chǎn)品名稱而數(shù)據(jù)本身是存在的你只是沒讀到。處理合并單元格的常見辦法是 forward fill# 對需要前向填充的列做合并單元格還原 df[產(chǎn)品名稱] df[產(chǎn)品名稱].ffill()注意ffill只能解決「上方合并」的情形如果有橫向合并單元格需要ffill(axis1)但橫向合并語義復(fù)雜建議先輸出到 Excel 里肉眼確認(rèn)一遍再動。公式緩存又是另一個坑。某些 Excel 單元格是公式比如SUM(C2:C20)。openpyxl 讀單元格值時有data_onlyTrue參數(shù)能返回上次 Excel 打開文件時緩存的計(jì)算結(jié)果但如果文件是程序生成、從來沒被 Excel 打開過緩存就不存在讀出來是 None。pandas 的read_excel內(nèi)部按緩存值讀取一旦遇到無緩存公式你會得到一列空的數(shù)值而用戶看到原始 Excel 里明明是有的。我的建議導(dǎo)入 Excel 之前先用辦公軟件把文件打開并另存一遍這樣公式緩存基本都在如果文件是程序自動生成的優(yōu)先去源頭導(dǎo)出「值」而不是「公式」。另外可以在導(dǎo)入后做個數(shù)量級校驗(yàn)統(tǒng)計(jì)每個數(shù)值列的 sum對比業(yè)務(wù)方給的手工數(shù)字差太多就說明公式或合并單元格出了問題。4. 數(shù)據(jù)庫連庫實(shí)戰(zhàn)LlamaHub DatabaseReader 用法拆解前兩章講的 CSV、Excel 本質(zhì)上還是「文件導(dǎo)入」會有更新不及時、數(shù)據(jù)漂移的問題。數(shù)據(jù)庫表的正確姿勢是連庫直接讀LlamaHub 里的 DatabaseReader 就是干這個的。這一章把配置、查詢參數(shù)、行文本化全部過一遍。4.1 為什么連庫而不是導(dǎo)出 CSV你可能會想「我從數(shù)據(jù)庫里 SELECT 出來導(dǎo)出個 CSV再走第二章的方案不就行了嗎」對于一次性遷移可以但如果是持久化知識庫連庫有不可替代的優(yōu)勢。第一時效性。很多 RAG 項(xiàng)目要求數(shù)據(jù)按天更新你要是定期導(dǎo)出 CSV 再觸發(fā)導(dǎo)入鏈路長、容易漏。連庫讀取可以做到每次構(gòu)建索引時直接查最新數(shù)據(jù)或者做增量查詢。第二數(shù)據(jù)一致性。手工導(dǎo)出 CSV 容易丟類型、丟精度尤其大數(shù)值和帶時區(qū)的時間戳。直接從數(shù)據(jù)庫查詢拿到的值更準(zhǔn)也能保留主鍵和索引字段方便后面增量去重。第三可溯源。連庫導(dǎo)入時可以直接把主鍵或業(yè)務(wù) ID 放進(jìn) metadata出問題查原始記錄比靠 row_index 猜快得多。4.2 DatabaseReader 的配置、查詢參數(shù)與行文本化LlamaHub 的 DatabaseReader 核心依賴 SQLAlchemy所以只要你安裝了對應(yīng)對應(yīng)庫的方言驅(qū)動比如 psycopg2、pymysql就能連 PostgreSQL、MySQL、SQLite、SQL Server 等主流數(shù)據(jù)庫。基礎(chǔ)用法如下from sqlalchemy import create_engine from llama_index.readers.database import DatabaseReader engine create_engine(postgresql://user:passwordlocalhost:5432/mydb) reader DatabaseReader(sql_databaseengine) documents reader.load_data( querySELECT * FROM orders WHERE created_at 2024-01-01 LIMIT 10000 )load_data的不同之處在于它需要你傳入一個query參數(shù)而不是像文件 Reader 那樣傳路徑。每個返回行會被轉(zhuǎn)成 Document列名會變成 metadata 的一部分。這個設(shè)計(jì)的好處是你可以用 SQL 完成大部分清洗工作WHERE過濾、條件聚合、甚至 JOIN 后直接 SELECT 出你想要的上下游上下文。但我實(shí)際操作中的體會是DatabaseReader 直接返回的 Document text 仍然偏機(jī)械基本是 key: value 形式。所以我對它的用法是先用它拿到 DataFrame 或 List[Dict]再走一遍自定義行文本化和 metadata 構(gòu)建。如果你不想改源碼可以不用 load_data而是自己用 SQLAlchemy 查詢import pandas as pd from sqlalchemy import create_engine engine create_engine(postgresql://user:passwordlocalhost:5432/mydb) sql SELECT order_id, product, region, amount FROM orders WHERE created_at 2024-01-01 df pd.read_sql(sql, engine) # 復(fù)用第二章的 row_to_text 和 Document 構(gòu)建邏輯 documents [] for idx, row in df.iterrows(): text row_to_text(row, 訂單記錄) documents.append(Document(texttext, metadata{ source: postgresql://mydb/orders, row_id: row.get(order_id), table: orders, }))這個方案的可控性更高適合不同庫之間字段差異大、需要逐個定制的情況。DatabaseReader 的價值在你圖省事、表結(jié)構(gòu)干凈時最能體現(xiàn)表結(jié)構(gòu)復(fù)雜時我建議自己寫查詢。4.3 行級自然語言轉(zhuǎn)換讓向量檢索真正「看懂」數(shù)值連庫讀數(shù)據(jù)有個額外挑戰(zhàn)數(shù)據(jù)庫表往往字段名是英文或縮寫比如pc_name、qty、amt_usd。如果你直接把這些英文列名帶進(jìn)文本embedding 的效果會打折扣——中文用戶問「哪個產(chǎn)品代碼賣得最多」和你索引里的pc_name完全對不上。我目前最常用的方案是在 SQL 查詢層做好「列名自然語言映射」直接 SELECT 出自帶語義的文本字段SELECT order_id AS 訂單編號, product AS 商品名稱, region AS 銷售區(qū)域, amount AS 訂單金額元, status AS 發(fā)貨狀態(tài) FROM orders WHERE created_at 2024-01-01;然后把 pandas 讀出來的 DataFrame 逐行轉(zhuǎn)自然語言效果比直接讓 reader 轉(zhuǎn)好很多。此外某些庫里有枚舉型、字典型字段比如status 1表示已發(fā)貨。嚴(yán)格來說這屬于數(shù)據(jù)清洗但放在導(dǎo)入鏈路里做最高效SQL 層面 JOIN 字典表把1翻譯成「已發(fā)貨」再進(jìn) RAG。否則你問「已發(fā)貨的訂單」永遠(yuǎn)匹配不到status1。5. 導(dǎo)入只是開始召回驗(yàn)證與數(shù)據(jù)同步表格數(shù)據(jù)導(dǎo)入完成不等于 RAG 就能回答業(yè)務(wù)問題。入庫之后你要回答一個更實(shí)際的問題「它到底能不能把正確的那幾行撈出來?!惯@一章是全篇的臨門一腳也是最容易被跳過的一步。5.1 召回測試問一遍真實(shí)業(yè)務(wù)問題索引構(gòu)建完不要著急接 query engine先做一次最小的召回測試。用index.as_retriever(similarity_top_k5)把業(yè)務(wù)方那十幾個真實(shí)問題逐一輸入直接看召回出來的 top-5 Document 文本。這一步不用讓 LLM 生成回答只看檢索質(zhì)量。舉例來說索引里是訂單表你問「華北區(qū)最近有哪些大額訂單」如果 top-5 里混著華南區(qū)的訂單說明 region 字段沒有在文本和 metadata 里被充分表達(dá)或者行文本生成得不夠清晰。這時候調(diào)行文本模板比調(diào) embedding 模型參數(shù)有效得多。如果發(fā)現(xiàn)某類問題總是召不回正確行大概率是行文本里缺少用戶會用的同義詞。比如用戶習(xí)慣說「大客」你的表里是「重點(diǎn)客戶」那就在行文本生成時把別名也拼進(jìn)去。這類詞表工作很笨拙但效果好。5.2 用 metadata 做表級路由與過濾表格數(shù)據(jù)進(jìn) RAG 后多張表共存時最大的噪聲源是「問 A 表問題召回了 B 表數(shù)據(jù)」。解決的關(guān)鍵是 metadata filter。你可以在構(gòu)建 retriever 時按表過濾retriever index.as_retriever( similarity_top_k5, filtersMetadataFilters( filters[MetadataFilter(keytable, value訂單記錄)] ) )更進(jìn)一步可以用函數(shù)判斷問題的關(guān)鍵詞來決定使用哪個 filter。比如問題里出現(xiàn)「區(qū)域匯總」就過濾table區(qū)域銷售統(tǒng)計(jì)出現(xiàn)「訂單」就過濾table訂單記錄。這就是表級路由比讓 LLM 自己猜要穩(wěn)定得多。我建議每張表導(dǎo)入時都把一個標(biāo)準(zhǔn)化的table字段寫進(jìn) metadata并保證全庫統(tǒng)一命名。命名不規(guī)范比如一張叫「訂單記錄」、另一張叫「orders」路由就得寫兩套邏輯。5.3 增量更新的兩種策略與 doc_id 去重對于數(shù)據(jù)庫表這種變更頻繁的數(shù)據(jù)源全量重建只適合初期或小數(shù)據(jù)量場景。數(shù)據(jù)量大以后必須做增量。我實(shí)踐下來有兩條可行的路。第一條是時間戳增量。在表里加一個updated_at導(dǎo)入時記錄上次同步時間last_sync每次只查WHERE updated_at last_sync。這個方案防漏更新但對刪除操作不敏感某行被刪了索引里還留著。第二條是 doc_id 去重重建。給每個 Document 設(shè)置doc_id為業(yè)務(wù)主鍵比如order_A1001。同步時不管新增還是更新全部插入插入前先把已存在的同 doc_id 文檔刪掉或標(biāo)記覆蓋。LlamaIndex 的index.delete(doc_id)和index.insert(document)配合就能做到。這個方案能處理更新和刪除但每輪要全量拉取需要同步的數(shù)據(jù)適合表數(shù)據(jù)量可控的場景。兩種方案也可以組合時間戳圈定范圍doc_id 去重保證更新。對大多數(shù)中小規(guī)模業(yè)務(wù)表這個組合已經(jīng)非常穩(wěn)了。關(guān)于同步頻率我的經(jīng)驗(yàn)是不要每次都重建整個索引。如果只有幾百行增量直接 insert 新 Document 到已有索引里快很多。如果增量超過總量 30%重建可能更劃算因?yàn)橄蛄克饕膭h除累積會留下碎片檢索性能會慢慢下降。表格數(shù)據(jù)進(jìn) RAG我現(xiàn)在越來越覺得做得好的關(guān)鍵不在于用了多高級的 embedding 或者框架而在于導(dǎo)入前的數(shù)據(jù)建模和導(dǎo)入后的驗(yàn)證閉環(huán)。CSV 要處理編碼和空值Excel 要解決 Sheet 和公式問題數(shù)據(jù)庫連庫要設(shè)計(jì)好查詢和行文本。每一步都有固定的坑踩過一遍之后后面基本就是復(fù)制模板的問題。按照上面這套流程走一遍你的表格知識庫應(yīng)該能覆蓋絕大多數(shù)業(yè)務(wù)問答場景。