ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RAG表格数据导入与查询实战:CSV/Excel处理及LlamaHub连库方案

RAG表格数据导入与查询实战:CSV/Excel处理及LlamaHub连库方案 做 RAG 的朋友十有八九会踩到同一个坎PDF 好说网页好说一到表格数据就开始离谱。问它 Excel 里某个季度销售额它一本正经给你编一个数字问 CSV 文件里有没有某个客户它反问你“大概是在哪一列”。这不是模型不行而是我们喂数据的方式有问题。表格、CSV、数据库这类结构化数据在 RAG 里的导入和解析完全不是纯文本那套玩法。今天这篇是系列第三篇主要聊清楚三件事CSV 怎么导入、Excel 怎么处理、以及通过 LlamaHub 直连数据库的实战方案。内容基于我自己跑过的几个项目整理工具以 LlamaIndex 和 LangChain 为主但思路对任何 RAG 框架都通用。适合已经跑通基础 RAG、准备把表格数据纳入知识库的团队和个人。1. 表格数据进 RAG 的根本痛点1.1 为什么表格数据是RAG的“重灾区”纯文本的切分规则是“按语义块拆”段落、句子、章节都有天然边界。但表格不一样一个表格的最小语义单元是“字段名 单元格”而不是一整个段落。如果你把 CSV 当成纯文本一整块塞进去向量化之后的结果就是一坨混合向量问“哪个产品销售额最高”的时候检索出来的可能是某一行含有“产品名”三个字但跟具体数值完全不相干。更麻烦的是表格还自带行列关系。同样一句话“2023年华东区销售额是 120 万”放在表格里可能拆成“年份”“区域”“指标”“数值”四列字面上根本找不到完整句子。LLM 不是不能理解表格而是需要你把表格还原成它有“空间感”的格式。这也是为什么很多人的表格 RAG 问起来答非所问——不是模型笨是数据形态没转换对。我见过的项目里表格类数据占整个知识库的比重可能不大但使用频率极高。业务方最常问的就是“上个月某 SKU 在华南区的退货率是多少”这种结构化查询。这类问题恰恰是纯文本 RAG 的盲区。所以表格导入这一关必须从底层设计上想清楚你要的不是“原文检索”而是“结构可查”。1.2 三种典型处理思路转文本、转Markdown、查询接口基于上面的痛点表格数据进 RAG 基本有三种路线路线一转成自然语言文本。每一行数据展开成一句话或一段描述比如“2023年华东区销售额为120万元”。这种方式适合数据量小、字段少、查询模式相对固定的场景。优点是简单缺点是信息密度低大表展开后 token 消耗和向量数量会成倍膨胀。路线二转成 Markdown 表格。保留原始行列结构再用 Markdown 语法表达。LLM 对 Markdown 表格的理解能力比纯文本强很多而且支持按行分割、保留列名。这是目前中小型表格最推荐的方案兼顾表达完整性和切分可行性。路线三不存文档直接连数据库。把 RAG 里的“检索”变成“查询”。用户问问题系统先解析出 SQL再去数据库里查最后把结果喂给 LLM 生成回答。这种方案叫 Text-to-SQL它绕过了向量化从根源上解决了结构化数据的精度问题代价是需要额外处理数据库连接、权限和安全。我实际做项目时往往是三条路线混用几百行的小配置表转 Markdown几万行的明细表连库查询而那种用来给模型做参考的字段说明文档直接转自然语言。先摸清数据特点再决定手法别上来就一把梭。2. 基础篇CSV 文件导入的完整流程2.1 加载 CSV 的两种主流方式pandas LlamaIndex/ LangChainCSV 是表格数据里最“老实”的格式没有格式、没有公式、也没有隐藏列但恰恰因为太简单很多人随手一读就出问题。先说加载方式。第一种是直接用框架自带的 Reader。LlamaIndex 里有SimpleCSVReader和CSVReaderLangChain 里有CSVLoader。这些 Reader 会把 CSV 的每一行转成一个 Document列名保留在 content 里。优点是一行代码搞定缺点是细粒度控制弱——比如你想跳过表头、自定义分隔符、处理空值都得绕到源码里改。第二种是自己用 pandas 读再手动转成 Document。这种方式更适合“脏数据”多的场景。我先用pd.read_csv把文件读进来设置好encoding、sep、skiprows、dtype这些参数清洗完再转。代码看起来多几行但每一步都在掌控之内。我这里强烈建议只要 CSV 不是那种非常干净的标准表都走 pandas 路线。尤其是从业务系统导出的 CSV经常带 BOM 头、单元格里有换行符、数字列混着文本直接用框架内置 Reader 很容易把换行符当成真实换行导致列错位。pandas 至少能让你在写入向量库之前看一眼df.info()和df.head()。2.2 表格内容怎么切分才不破坏语义切分是表格 RAG 的生死线。纯文本切坏了大不了丢半句话表格切坏了直接行列错乱检索出来的结果连列名都对不上。我的原则是切分粒度最小到“行”最大到“批”绝对不按字符数硬切。具体来说TextSplitter里的chunk_size500这种参数对表格毫无意义因为你不知道第 500 个字符落在哪一行哪个列。正确做法是按行分组。假设你有 5000 行 CSV每一行的字段有“日期、区域、SKU、销售额”。如果把整表塞进一个 Document检索时命中率高但上下文太大LLM 可能忽略细节如果按单行切每行是一个独立 Document检索精度高但用户问“某区域整体销售额”时向量检索只能捞回几行凑不齐全量数据。所以我通常选择“按 20~50 行一组切块”组内保留完整的表头组间允许少量重叠。为什么是 50因为普通 CSV 一行平均 100 个字符左右50 行就是 5000 字符落在大多数模型 4K 上下文能接受的范围内。如果某行字段特别长比如描述性文本列那就取 20 行一组切分参数根据实际数据量调。还有一个小技巧在每一组前面加一行“这段数据来自文件 xx.csv表头为字段A、字段B、字段C”。这行前缀能显著提升向量召回时对表格结构的识别率。我测试过加这行前缀后问“销售额最高的五个 SKU”这类问题命中准确率能提升十几个百分点。2.3 实战代码CSV转文档并写入向量库下面是一套我常用的 CSV 导入完整流程基于 LlamaIndex Chroma。这套流程也适用于其他向量库只要替换VectorStore即可。import pandas as pd from llama_index.core import Document from llama_index.core.node_parser import SimpleNodeParser from llama_index.core import VectorStoreIndex, StorageContext from llama_index.vector_stores.chroma import ChromaVectorStore import chromadb # 1. 读取 CSV处理编码和脏数据 df pd.read_csv(sales_data.csv, encodingutf-8-sig) # 数据清洗去空行、统一列名、兜底填充 df df.dropna(howall) df.columns [str(c).strip() for c in df.columns] # 把 NaN 转成空字符串避免切片时报错 df df.fillna() # 2. 转换行数据为文档按批次分组 chunk_size 50 documents [] for start in range(0, len(df), chunk_size): batch df.iloc[start:start chunk_size] # 把 batch 转成 Markdown 表格文本 md_text batch.to_markdown(indexFalse) # 给每段加一个结构说明前缀增强检索效果 header_line f以下是文件 sales_data.csv 的部分数据包含字段{, .join(df.columns)}。\n doc Document(textheader_line md_text, metadata{source: sales_data.csv, start_row: start, end_row: min(start chunk_size, len(df)) - 1}) documents.append(doc) # 3. 解析节点并写入向量库 parser SimpleNodeParser.from_defaults(chunk_size1024, chunk_overlap20) nodes parser.get_nodes_from_documents(documents) # 初始化 Chroma client chromadb.PersistentClient(path./chroma_db) collection client.get_or_create_collection(sales_data) vector_store ChromaVectorStore(chroma_collectioncollection) storage_context StorageContext.from_defaults(vector_storevector_store) # 构建索引 index VectorStoreIndex(nodes, storage_contextstorage_context) print(CSV 导入完成共写入, len(nodes), 个节点)这套代码里有几个细节值得展开说一下。第一to_markdown(indexFalse)需要 pandas 的tabulate依赖如果你不想装可以手写字符串拼接。但 Markdown 表格对 LLM 的理解确实比纯逗号分隔友好我建议装上。第二SimpleNodeParser的chunk_size这里设 1024 是因为前面已经按行分块了节点解析时会尽量保留文档结构。如果解析器发现某个 Document 还是超长它会继续切但因为每个 Document 已经是独立表格块切割时会尽可能在语义边界断开。第三metadata 里记录了start_row和end_row这个很有用。后续问答阶段可以根据召回节点的行号范围去原始 DataFrame 里做二次确认或者直接返回“数据来自第 100~150 行”这样的回答业务方更容易信服。3. Excel 导入多Sheet、公式与格式的坑3.1 用 openpyxl 和 pandas 处理 ExcelExcel 比 CSV 多出的麻烦全在格式上。一个.xlsx文件里可能有多个 Sheet每个 Sheet 还有合并单元格、填充色、公式、数据验证甚至还有图表和图片。而 RAG 真正需要的是 Sheet 里的表格数据不是视觉效果。处理 Excel 我基本只用两个库pandas和openpyxl。pandas 负责读数据pd.read_excel底层虽然依赖openpyxl但用起来更符合 DataFrame 的习惯openpyxl负责处理 pandas 读不出来的那些东西比如公式缓存值、合并单元格、部分格式信息。这里有个大坑pd.read_excel默认读的是单元格的缓存值也就是 Excel 保存文件时最后计算出来的结果。如果你手里的 xlsx 是从另一个系统导出的公式计算完才保存那没问题但如果是一个半成品文件单元格里写的是SUM(A1:A10)而 Excel 还没来得及自动重算pandas 读出来的就是None或者公式字符串本身。你拿这种数据去建索引用户问“合计是多少”模型说“无法确定”。所以我的习惯是如果 Excel 里有公式先用openpyxl加载并把data_onlyTrue打开看看缓存值是否存在。如果不存在宁可让业务方先导出成 CSV 再导入也不要用一个充满公式单元格的文件去建知识库否则后面全是坑。3.2 多Sheet与合并单元格的处理策略多 Sheet 的处理要说简单也简单遍历一下就行但要注意每个 Sheet 的数据结构可能完全不同。第一个 Sheet 是“销售明细”列名是日期、区域、金额第二个 Sheet 是“产品目录”列名是 SKU、名称、供应商。如果你把两个 Sheet 无脑拼成一个大 DataFrame列名冲突会直接毁掉整个索引。我建议每个 Sheet 独立建一个 Document 前缀或者更稳妥的做法是每个 Sheet 生成独立的索引。哪怕你最后把它们放进同一个向量库也要在 metadata 里标记sheet_name和source_file检索时才能按文件筛。合并单元格就更麻烦。Excel 合并单元格在 pandas 里读出来之后只有左上角那个单元格有值其余是 NaN。比如一个区域有多行数据区域名合并到一行里其余行其实是空但语义上它们都归属这个区域。如果直接fillna()就会丢失归属关系。处理办法是先横向填充ffill把合并单元格的值向下复制到同一区域的所有行。但要注意ffill会误伤原本就真的是空白的数据列。所以只能对已知需要填充的列设置ffill方向或者干脆根据 Excel 的 merged_cells 信息把值填回去。用 openpyxl 可以拿到ws.merged_cells.ranges然后遍历合并范围将值填到所有被合并的单元格。代码不算多但能救命。from openpyxl import load_workbook wb load_workbook(data.xlsx, data_onlyTrue) ws wb[明细] # 合并单元格填值 for merge in ws.merged_cells.ranges: top_left merge.start_cell value top_left.value for row in ws.iter_rows(min_rowmerge.min_row, max_rowmerge.max_row, min_colmerge.min_col, max_colmerge.max_col): for cell in row: cell.value value这段代码跑完之后再把ws转成 pandas DataFrame 就不会有空值了。如果你有几十个 Sheet可以用循环批量处理注意data_onlyTrue是取缓存值前面说过的公式问题依然存在。3.3 实战代码Excel 转结构化文档下面是我的 Excel 导入完整代码处理了多 Sheet 和合并单元格并统一输出为 Markdown 文档节点。import pandas as pd from openpyxl import load_workbook from llama_index.core import Document, VectorStoreIndex from llama_index.core.node_parser import SimpleNodeParser def excel_to_documents(path: str): # Step 1: 用 openpyxl 处理合并单元格并读取缓存值 wb load_workbook(path, data_onlyTrue) documents [] for sheet_name in wb.sheetnames: ws wb[sheet_name] # 1. 填充合并单元格 merged_filled_rows [] for merge in ws.merged_cells.ranges: top_left_value merge.start_cell.value for row in ws.iter_rows(min_rowmerge.min_row, max_rowmerge.max_row, min_colmerge.min_col, max_colmerge.max_col): for cell in row: cell.value top_left_value # 2. 取出所有行的原始值 rows [] for row in ws.iter_rows(values_onlyTrue): rows.append(row) # 3. 转成 DataFrame第一行作为列名并清理空行 if not rows: continue header rows[0] data rows[1:] df pd.DataFrame(data, columnsheader) df df.dropna(howall).fillna() # 4. 按 Sheet 切块每 30 行一个文档 chunk_size 30 for start in range(0, len(df), chunk_size): batch df.iloc[start:start chunk_size] md_text batch.to_markdown(indexFalse) doc Document( textf以下来自 Excel 文件 {path} 的 Sheet「{sheet_name}」字段为 {list(df.columns)}。\n{md_text}, metadata{ source: path, sheet_name: sheet_name, start_row: start } ) documents.append(doc) return documents # 使用 docs excel_to_documents(业务数据.xlsx) parser SimpleNodeParser.from_defaults() nodes parser.get_nodes_from_documents(docs) print(f生成 {len(docs)} 个文档解析为 {len(nodes)} 个节点)这里有个容易被忽略的点iter_rows(values_onlyTrue)时如果你没有先处理合并单元格那么合并区域里非左上角的单元格全部会是None。所以我上面在处理完合并之后立刻从 sheet 取值顺序不能乱。还有列名的处理。有些 Sheet 的第一行不是标题而是说明文字比如“销售数据导出日期2024-01-01”这时候rows[0]会被误当成列名。我一般会根据第一行内容判断一下如果第一行包含“日期”“时间”“导出”这类词就要手动跳过到第二行。你可以写个小函数检测第一行的单元格值是否都像标题或者直接让业务方保证文件第一行是表头。实操中后者更省心。4. LlamaHub 连库实战SQL 数据库接入 RAG4.1 为什么需要连数据库而不是导出CSV很多人会问既然 CSV 和 Excel 能导入为什么要费劲去连数据库直接用 SQL 导出 CSV 再导入不也一样吗表面看是一样的但实际用起来差很多。第一是数据时效性。数据库里的数据是实时变化的你今天导出的 CSV 到明天就过时了。RAG 场景下用户问“上个月的销售额”你导出时是月初等月中他再问答案还停留在月初的数据系统却以为这是最新状态这就成了“一本正经地胡说八道”。连库查询则每次实时取数不存在过期问题。第二是数据量。几十万行的数据库表全量导出成 CSV 再向量化时间和存储成本都很高。而连库查询走的是精确检索不需要建向量查询速度比向量相似度检索还快。尤其适合那些“答案必须精确”的业务场景比如报表系统、订单系统。第三是权限控制。数据库你可以在连接层面设置只读账号、限制表权限、限制返回行数CSV 文件一旦导入向量库谁拿到向量库谁就看到了所有数据。从数据安全角度敏感业务数据根本不该导出成文件。所以我现在的做法是把静态的知识类表格比如产品目录、操作手册里的参数表向量化把动态的业务数据表放在数据库里做 Text-to-SQL 查询。一静一动各自发挥优势。4.2 LlamaHub 里的数据库加载器与工具链LlamaHub 是 LlamaIndex 的加载器生态里面有大量数据源对应的 Reader。数据库相关的主要是llama-index-readers-database它封装了 SQLAlchemy可以连接多种数据库包括 SQLite、MySQL、PostgreSQL、SQL Server 等。但连数据库这件事我认为核心不只是“加载数据”而是怎么把“自然语言问题”转成“SQL 查询”。这就要用到 LlamaIndex 的SQLDatabase和NLSQLTableQueryEngine自然语言 SQL 查询引擎。这套工具链的逻辑是先连接数据库扫描表结构和元数据当用户提问时根据表结构生成 SQL 候选执行 SQL 得到结果把结果和表结构作为上下文交给 LLM 生成回答。听起来很美好但实际使用有几个必要条件表结构要清晰、字段命名要规范、涉及 join 的多表查询要提前定义好关系。如果你的数据库有一堆col1、col2、fk_xxx这种字段名LLM 生成 SQL 的效果会很差因为模型根本猜不到col1是日期还是金额。所以连库之前我一般会先做一次“模式增强”给重要表写一段描述说明这张表是干什么的每个字段代表什么含义可能的枚举值有哪些。这些描述可以存成 JSON 或 Markdown之后塞进 system prompt 或者作为表结构的补充信息给 LLM 参考。4.3 实战用 SQLDatabase 连接 MySQL 并构建查询引擎下面用 LlamaIndex 自带的SQLDatabase连接 MySQL构建一个自然语言查询引擎。你需要先装好依赖pip install llama-index llama-index-readers-database pymysql sqlalchemy然后写代码from sqlalchemy import create_engine from llama_index.core import SQLDatabase from llama_index.core.query_engine import NLSQLTableQueryEngine # 1. 创建数据库连接生产环境建议用环境变量保存凭证 user rag_query password your_password host 127.0.0.1 port 3306 database sales engine create_engine( fmysqlpymysql://{user}:{password}{host}:{port}/{database}, pool_pre_pingTrue ) # 2. 包装成 SQLDatabase 对象 sql_database SQLDatabase( engine, include_tables[order_detail, product_info, region_info], sample_rows_in_table_info2, ) # 3. 构建自然语言查询引擎 query_engine NLSQLTableQueryEngine( sql_databasesql_database, verboseTrue, ) # 4. 查询 response query_engine.query(2024年3月华东区销量最高的前5个SKU是什么) print(response)这里几个参数要重点解释。include_tables限定可查询的表这是我强烈建议加的。如果你不填系统会扫描所有表包括日志表、临时表、配置表LLM 在生成 SQL 时会被无关表干扰甚至选错表。sample_rows_in_table_info2表示在 prompt 中带上每张表的 2 行样本数据。这个很有用LLM 可以通过样本推断字段的数据类型和格式比如日期字段是2024-03-01还是2024年3月1日。但样本不要带太多否则 prompt 太长影响生成效率。另外NLSQLTableQueryEngine的每次查询会先看表结构再生成 SQL。如果你加的数据库表特别多这个过程会比较慢。解法是先用SQLTableRetrieverQueryEngine通过自然语言检索相关表替代全表扫描但它配置起来更复杂先跑通简单的再优化。4.4 安全与性能只读、分页、防注入连库查询最怕的不是答错而是搞挂业务系统。我见过有人用默认管理员账号直接连生产库结果用户问“删除所有订单”LLM 生成了一条DELETE语句还好平台有拦截否则数据全没了。所以连库 RAG 的安全底线必须在一开始就设好。第一数据库账号必须最小化权限。最好新建一个只读账号只授予SELECT权限禁止INSERT、UPDATE、DELETE、DROP、ALTER。如果是 MySQL可以这样建CREATE USER rag_query% IDENTIFIED BY strong_password; GRANT SELECT ON sales.* TO rag_query%;第二SQL 语句要限制返回行数。LLM 生成的 SQL 可能会漏掉 LIMIT导致一次返回几十万行拖垮网络和内存。可以在连接层或者查询引擎层做兜底比如在create_engine里面执行 SQL 时给它包一层from sqlalchemy import event event.listens_for(engine, before_cursor_execute) def limit_sql(conn, cursor, statement, parameters, context, executemany): # 如果语句不是分页查询追加 LIMIT 100 lower_stmt statement.strip().lower() if lower_stmt.startswith(select) and limit not in lower_stmt: if ; in statement: statement statement.replace(;, LIMIT 100;) else: statement LIMIT 100 cursor._execution_context context这个 hack 不优雅但非常实用。更严谨的做法是在写 NLSQL 引擎时用自定义 prompt 要求 LLM 必须带 LIMIT或者用数据库代理层做 SQL 审计。第三防注入。虽然这是 LLM 生成的 SQL还是建议把所有表名和列名限制在预定义的白名单里。include_tables已经筛了表列名可以通过sql_database.table_info校验。如果业务要求严格可以再接一个 SQL 解析器拦截非 SELECT 语句和危险表名。性能方面如果数据库本身很大不要直接对原表做查询可以建一个独立的只读副本或者物化视图。RAG 查询频率通常不高但最怕 LLM 生成的 SQL 带全表扫描和四个表 join。我给客户做的时候习惯在连接参数里设置pool_size5、max_overflow2防止瞬时并发把数据库连接池打爆。5. 常见问题与排查实录5.1 表格导入后问答“睁眼瞎”的排查“我明明导入了 CSV为什么问它里面的数据它说没有”这是后台收到最多的吐槽。通常有四个环节出错第一步看向量库。有没有真的写入节点用 Chroma 的话可以查一下collection.count()如果为 0说明是在解析或写入阶段出了问题多半是文档生成了空文本或者编码错误。第二步看检索召回。直接打印retriever.retrieve(question)的结果看召回节点里有没有和表格相关的文本。如果召回了无关节点说明查询向量和表格向量不在一个空间大概率是表格转文本的方式不对。比如你只用逗号分隔的原始文本而没有 Markdown 结构语义表达就很弱。第三步看生成阶段的上下文。把最终送入 LLM 的 prompt 打开看检索到的表格有没有被大模型忽略。有时候召回了但节点太多表格信息被前面的 PDF 文本冲掉了。这时候需要调低 top_k或者用 metadata filter 强制只在表格索引里检索。第四步看问题本身。不是所有问题都适合表格 RAG。问“总销售额最高的月份”是检索问题但问“3月和4月相比增长率是多少”是计算问题需要 LLM 结合多个节点做数值运算。如果你用的是小模型算力不够就会瞎编。这种情况要么换更强的模型要么走数据库查询路线。5.2 编码与格式问题速查表表格导入的异常我整理成了一张速查表几乎覆盖了九成的实际问题。新手照着排查就行。现象可能原因解决方案CSV 中文乱码文件编码不是 UTF-8 而是 GBK用pd.read_csv(..., encodinggbk)或转成utf-8-sig首列多了一个“\ufeff”文件带 BOM 头用encodingutf-8-sig读取DataFrame 列名全是数字第一行不是标题用headerNone指定列名或skiprows1Excel 公式单元格读到 None用了data_onlyFalse或文件无缓存值用data_onlyTrue无缓存值时让提供方导出 CSVExcel 合并单元格多行空值未做 ffill 或合并填充用 openpyxl 遍历 merged_cells 并填值数值列小数点丢失pandas 把数字读成浮点并转为科学计数设置dtypestr或dtype{金额: float}日期列被解析为时间戳pd.read_excel 默认推断类型parse_datesFalse或统一转为字符串再存入大文件内存爆炸一次性读入几十万行分块读取chunksize10000分批生成节点这些问题的共性是你对数据格式做了隐式假设。表格数据的格式坑不会因为向量库先进就消失反而会因为“向量化之后看不出原始格式”而更难排查。所以我每接一批数据都会先写一个数据质量报告输出行列数、空值比例、列名、前几行样本预览确认无误再进索引。5.3 混合表格图文、多层表头的兜底方案还有一类表格既不是纯 CSV 也不是规整的 Excel而是 PDF 里转过来的、扫描件里的、带图片的“伪表格”。PDF 表格提取在 RAG 里是另一个深坑很多开源库提取出来的表格行列错位、单元格里有乱码。遇到这种我的兜底方案是分三层递进。第一层如果能拿到原始数据文件直接找文件源不要用 PDF。这是最有效的方案业务方的“最终版”PDF 通常只是给人看的原始 Excel 或数据库才是有数据价值的。第二层PDF 提取后用规则修正。用pdfplumber或Camelot提取表格然后写校验规则列数是否一致、数值单元格是否为数字、日期是否符合格式。把不符合规则的单元格标记出来宁可丢弃那一行也不要留着错位的行污染索引。第三层实在修不了的表格走“文档截图 OCR 文本 人工标注”的组合。就是把这个表格区域截图保存OCR 成文本作为检索内容图片作为展示。这种方案不追求完全结构化只要求问答时有线索。我在做某个人力资源项目时几十份员工福利 PDF 表格就是这么处理的效果不算完美但至少能回答“某个岗位的福利有哪些”这种粗粒度问题。多层表头比如“上半年”下面再分“1月”“2月”的处理思路是扁平化。把两层表头拼接成一层例如“上半年_1月”“上半年_2月”这样既保留层级语义又符合 DataFrame 的 column 要求。拼接规则可以用_.join处理要是嫌翻译成英文不好理解直接保留中文拼接也行。关键是不要保留嵌套结构因为大多数 RAG 链路不支持多层表头。做 RAG 数据导入我一直觉得“先脏后净”是被动挨打的玩法。正确顺序是先做数据勘察再设计导入方案然后才写代码。表格和数据库这块尤其如此——CSV 的编码、Excel 的合并单元格、数据库的权限任何一个环节不提前考虑后面都要花十倍时间补救。拿我自己来说现在新项目里的表格数据第一选择永远是数据库直连查不出来或者没必要查的才转成 CSV/Excel 做向量化。而向量化时也用固定的“表头前缀 Markdown 表格”模板切分时宁可多切几段也不跨越多个表。这些习惯说不上多高级但真能让踩坑次数直线下降。如果你正在做表格类 RAG建议先挑一个几十行的真实表格跑通全流程把 CSV 和 Excel 的导入都试一遍再加一个 SQLite 数据库连一遍之后再上大表和生产库。这比你直接拿十万行数据硬灌进向量库要靠谱得多。下一篇我准备聊聊 PDF 表格提取的详细技巧以及如何把提取结果整合进现有的 RAG 管线。到时候见。
返回列表