Skip to content

2.2.3 结构化数据和非结构化数据在 RAG 里分别怎么处理? ​

先给结论:结构化数据和非结构化数据都可以服务 RAG,但它们通常不应该用完全同一种方式处理。

因为这两类数据承载知识的方式不一样。

什么是结构化数据 ​

结构化数据通常指有明确字段和模式的数据,比如:

  • 数据库表
  • CRM 记录
  • 工单记录
  • 订单数据
  • 指标表

它的特点是:

  • 字段明确
  • 记录边界清楚
  • 过滤条件容易表达

这类数据更像“可查询的事实集合”。

什么是非结构化数据 ​

非结构化数据通常指自然语言文档或半结构化内容,比如:

  • PDF
  • 网页正文
  • 手册
  • Wiki
  • 会议纪要
  • 产品说明

它的特点是:

  • 语义主要在长文本里
  • 结构边界较弱
  • 更依赖切块和语义检索

这类数据更像“可阅读的知识材料”。

结构化数据常见的处理方式 ​

对结构化数据来说,核心通常不是“先切块再语义检索”,而是:

  1. 保留字段含义和记录边界。
  2. 给记录建立明确的主键、时间、租户、权限等元数据。
  3. 优先使用过滤、查询、聚合等方式定位结果。
  4. 必要时再把查询结果转成模型可读上下文。

也就是说,结构化数据很多时候更接近:

  • 检索增强查询
  • 工具调用
  • SQL 或条件过滤

而不只是普通向量检索。

更实际一点说,结构化数据最怕被错误地“文本化”。一旦你把字段、条件、时间范围、主外键关系都压成一段自然语言,再去做普通语义召回,就很容易损失:

  • 精确过滤能力
  • 数值比较能力
  • 聚合统计能力
  • 实时查询能力

所以结构化数据的重点往往不是 embedding 做得多漂亮,而是查询语义有没有被稳定保留下来。

非结构化数据常见的处理方式 ​

对非结构化数据来说,重点通常是:

  1. 正确提取正文。
  2. 按合适粒度切块。
  3. 给每个块补充来源和结构信息。
  4. 建立语义检索和关键词检索能力。
  5. 在召回后做排序、去重和上下文构造。

这类链路更符合人们平时说的“经典 RAG”。

非结构化数据这条链路里,最关键的通常也不是“能不能检索”,而是:

  • 抽出来的是不是真正文
  • 切块边界是不是合理
  • 来源、标题、章节、版本有没有保留下来

因为只要这些前提没站稳,后面的语义检索就很容易把错误、噪声和残片一起召回。

为什么两类数据不能简单混在一起 ​

如果把结构化数据完全当长文本处理,会丢失很多本来很强的查询能力。

如果把非结构化数据完全当结构化记录处理,又会丢掉文本中的细节语义和上下文。

所以更好的思路通常是:

  • 对结构化数据,优先保留查询能力
  • 对非结构化数据,优先保留文本语义

然后在回答阶段把两类结果统一组织给模型。

如果强行混在一起,常见后果通常是:

  • 结构化数据失去精确约束,只剩模糊解释
  • 非结构化数据失去上下文,只剩碎片字段
  • 用户问“有多少”时系统给出一堆说明文档
  • 用户问“为什么”时系统只吐出几个字段值

这些现象本质上都说明:问题类型和数据链路没有对齐。

一个更实用的问题路由思路 ​

很多真实问题,并不是一眼就能归到结构化或非结构化,而是可以先看它更偏哪种需求:

更偏结构化的问题 ​

通常长这样:

  • 有多少订单发生了退款
  • 本周哪些工单超时
  • 某个客户当前状态是什么
  • 哪个区域的转化率更高

这类问题的核心是查事实、查状态、查条件、查聚合。

更偏非结构化的问题 ​

通常长这样:

  • 退款规则具体怎么规定
  • 某个流程为什么这样设计
  • 某项制度的适用范围是什么
  • 某个问题的常见处理经验有哪些

这类问题的核心是解释、说明、背景和规则文本。

混合型问题 ​

通常长这样:

  • 这个月退款率为什么升高
  • 哪些客户投诉最多,常见原因是什么
  • 某个功能使用率下降,研发和运营文档里提到过哪些原因

这类问题往往就需要“先查事实,再补文本解释”。

一个常见的混合场景 ​

比如用户问:

“最近一个季度华东区退款率上升,常见原因是什么?”

这个问题里往往同时需要两类数据:

  • 结构化数据:订单、退款率、区域、时间
  • 非结构化数据:工单描述、客服备注、问题归因文档

只靠其中一类,回答都可能不完整。

这时更实际的系统设计往往是:

  1. 先查结构化数据,确定事实范围。
  2. 再检索相关文档和文本材料。
  3. 最后把结构化事实和文档证据一起交给模型组织答案。

这类设计真正想避免的是两种极端:

  • 只查数据库,最后只能给出冷冰冰的数字
  • 只查文档,最后只能给出模糊的经验解释

成熟一点的系统,通常会让结构化数据负责“把事实钉住”,让非结构化数据负责“把原因和背景补齐”。

一个混合处理的示意代码 ​

python
def query_orders(region: str, quarter: str) -> dict:
    return {
        "refund_rate": "4.2%",
        "top_reason_codes": ["late_delivery", "size_issue"]
    }


def retrieve_reason_docs(reason_codes: list[str]) -> list[str]:
    doc_map = {
        "late_delivery": "华东区 2026Q1 工单分析:延迟配送是退款上升的重要原因。",
        "size_issue": "服饰类商品尺码偏差导致退款申请增加。"
    }
    return [doc_map[code] for code in reason_codes if code in doc_map]


def answer_question(region: str, quarter: str) -> str:
    facts = query_orders(region=region, quarter=quarter)
    docs = retrieve_reason_docs(facts["top_reason_codes"])
    context = "\n".join(docs)
    return (
        f"{region} {quarter} 退款率为 {facts['refund_rate']}。\n"
        f"相关说明:\n{context}"
    )

这段示意代码体现的是一种很常见的现实做法:

  • 用结构化查询先拿到“事实范围”
  • 再用文本检索补充“原因解释”
  • 最后把两类结果一起组织成回答

你可以先这样理解 ​

结构化数据擅长回答:

  • 有多少
  • 哪一条
  • 哪个时间段
  • 满足什么条件

非结构化数据擅长回答:

  • 为什么
  • 具体怎么规定
  • 背景上下文是什么
  • 相关说明在哪

成熟的 RAG 往往不是二选一,而是让两种数据各做自己最擅长的部分。

如果你还想记得更牢一点,可以把它理解成:

  • 结构化数据更像“可计算、可过滤、可验证的事实层”
  • 非结构化数据更像“可解释、可引用、可补背景的知识层”

RAG 做得越往后,你越会发现:真正稳的系统,通常不是让一种数据包打天下,而是先分清哪一层该由谁负责。