19. RAG 怎么处理表格和结构化数据?和处理纯文本有什么不同?
整理表格、结构化数据在 RAG 链路中的检索与回答策略。
简单回答
RAG 处理表格和结构化数据的核心难点是:表格的语义高度依赖二维结构(行/列/表头),切片或转 Embedding 时这种结构很容易被破坏。常见处理思路有几种——把表格序列化成 Markdown / HTML 保留行列对齐、给每个单元格附上行表头和列表头让它在切片后仍然自描述、整张表附加一段 LLM 生成的文字摘要供向量检索、或者干脆让 Agent 把自然语言查询翻成 SQL / Pandas 再去结构化数据源里精确查。具体怎么选取决于表是"事实表"还是"叙述性表格"以及查询是聚合统计还是定点查询。
详细解释
为什么表格不能用纯文本 RAG 那一套
普通文本 RAG(本专题第 03 篇 chunking + 第 04 篇 embedding 选型)的假设是文本可以按字符或 token 数切片、每个切片独立可读。表格违反这两个假设。
表格的语义来自二维对齐。"123" 这个数字单独看没意义,必须和它所在的行("产品 A 2024 Q3")和列("营收(万元)")一起读才有意义。如果按字符数把表格从中间切开,下半部分丢掉了表头就完全无法理解了——这是切片层面的问题。
Embedding 层面也吃亏。直接把整张表序列化成长字符串做 embedding,向量空间里很难体现"产品 A 2024 Q3 营收"这种字段间的精确关系——embedding 擅长的是模糊语义匹配,不是精确字段对齐。
更麻烦的是表格的查询模式不一样。文本 RAG 里用户问的往往是"什么是 X、X 的原理是什么"这种语义查询。涉及表格的查询很多时候是"产品 A 2024 Q3 的营收是多少"、"营收最高的三个产品是哪些"——这本质是结构化查询,模糊匹配很难给出精确答案。
处理思路一:序列化时保留结构
最低成本的做法是把表格转成 Markdown / HTML 这类保留结构的文本格式,然后整体(或合理切片)入库做向量检索。
Markdown 表格的好处是 LLM 看得懂——拼进 Prompt 后模型能正确理解每一格的含义。这个方案在小表格(几十行以内)上效果不错。
但表格一大就出问题。一是切片不能再随便按 token 数切,必须按"完整行"切,且每个切片都要把表头一起带上。二是 Embedding 仍然不擅长在长表格里做精确单元格匹配。三是 Markdown 序列化对带合并单元格、嵌套表头的复杂表格表达不充分。
处理思路二:行级切片 + 行内自描述
对大表格的常见改造是按"行 + 表头"做切片——每一行单独成一个 chunk,但 chunk 内部把这一行的每个单元格都展开成自描述的形式。
比如上面那张表的第一行,chunk 内容是:
每个 chunk 里"产品"、"2024 Q3"、"152" 这些都齐全了,离开整张表也能读懂。Embedding 出来的向量就有了"产品 A"、"Q3"、"152" 这些语义锚点,用户查"产品 A 2024 Q3 营收"时这个 chunk 能被精确召回。
这个方案的代价是 chunk 数量爆炸(一张 1000 行的表会产生 1000 个 chunk),且单元格内的纯数字其实没什么 embedding 上的语义——召回靠的主要是表头关键词的匹配。
处理思路三:表格摘要 + 原表保底
更高级一点的做法是给每张表(或每个表格区块)用 LLM 生成一段自然语言摘要——"这张表展示了 2024 年三个季度三个产品的营收,单位万元,产品 A 增长最快"——把摘要做向量化入库。检索时召回的是摘要,但 metadata 里挂着原始表格的指针,生成回答时把原始表格完整拼进 Prompt 让模型读。
这个思路的优点是把 embedding 用在它擅长的语义层面(摘要是自然语言),同时保留原始数据精度。和本专题第 17 篇 Contextual Retrieval 的思路是一脉相承的——把"原文档语境"显式写进可检索的内容里。
缺点是摘要不可避免有信息损失。如果用户问的是摘要里没提到的细节("产品 B 2024 Q1 营收多少"),仅靠摘要的向量召回不一定能命中——因为摘要里可能没写这个具体数字。所以这种方案常和方法二配合,摘要做粗召回,行级 chunk 做细召回。
处理思路四:Text-to-SQL / Text-to-Pandas
对真正的事实表(数据库表、Excel 表、CSV),最准确的做法不是把它"塞进" RAG,而是把自然语言查询翻成 SQL 或 Pandas 表达式直接查询底层数据。
流程上这其实是本专题第 13 篇 Agentic RAG 的一种形态:
- Agent 拿到 query 后判断它是不是结构化查询
- 是的话调一个
query_database(sql)工具,工具内部用 LLM 生成 SQL 后执行 - 拿到结构化结果(也是一个小表)后再让 LLM 用自然语言总结
这种方式对"营收最高的三个产品"、"Q3 比 Q2 增长率"这种聚合统计类查询比向量检索准确得多——是真的去算,不是模糊匹配。
工程上的难点是 SQL 生成的可靠性。LLM 写错 SQL 的概率不低(字段名拼错、JOIN 关系搞错、聚合函数选错),需要做 schema 注入(把表结构详细写进 Prompt)、SQL 校验(执行前先 lint)、错误重试(执行失败时把错误信息回传给 LLM 重写)。Vanna、LlamaIndex 等开源工具都在这个方向上做了不少工程封装。
怎么选
实际项目里这几种思路通常组合使用,按"表格在文档中的角色"来分:
- 叙述性表格(在长文档里穿插的小表,比如论文里的实验结果表)——序列化成 Markdown + 适当切片就够了。
- 半结构化大表(产品规格表、价格表、产品列表)——行级切片 + 行内自描述,配表级摘要做粗召回。
- 数据库 / 业务事实表(订单表、用户表、交易流水)——直接走 Text-to-SQL,别走 RAG。
判断的关键是问"这张表上的查询主要是定点查(找某行某列)还是聚合统计"。前者向量检索能扛,后者必须走结构化查询。
面试时可以这样答
表格 RAG 和纯文本 RAG 不一样的地方有几个。表格的语义来自二维结构,切片很容易破坏行列对齐让 chunk 失去语义。Embedding 不擅长精确字段匹配。查询模式也不同——表格查询经常是结构化的"X 的 Y 是多少"或者"按 Z 排序前三"。
处理思路有四种。一是序列化成 Markdown 保留结构,小表格直接拼进 Prompt 就行。二是行级切片,每行展开成"字段名: 值"的自描述形式,让 chunk 离开原表也能读懂。三是表格摘要——用 LLM 给整张表生成自然语言描述做向量化,召回后挂回原始表格让模型读。四是 Text-to-SQL,对真正的事实表直接把查询翻成 SQL 去数据库精确查。
实际项目里这几种是组合用的,看表的角色。文档里穿插的小叙述表用 Markdown 就行。大型半结构化表用行级切片配表级摘要。订单表、交易流水这种业务事实表必须走 Text-to-SQL,别硬塞向量库——聚合统计类查询向量检索给不出准确答案,只能靠真的去算。
判断标准就一句话:如果查询主要是定点找某行某列,向量检索能扛;如果涉及聚合排序、跨表 join,老老实实生成 SQL。
常见追问
- Text-to-SQL 的 schema 怎么注入?大表 schema 也很大,要怎么压缩?
- PDF 里的扫描表格用 OCR 拿出来后怎么保留结构?
- 表格更新很频繁的场景下(比如每天变化的库存表),RAG 索引怎么维护?