芥末
发布于 2025-08-28 / 0 阅读
0
0

面向生产环境的 RAG 系统设计:Embedding、Reranker、Text2SQL 与 GraphRAG

RAG(Retrieval-Augmented Generation,检索增强生成)的目标很直接:让大语言模型在回答问题前,先从外部知识库里找到可靠信息,再基于这些信息生成答案。这样可以缓解模型知识过期、幻觉、无法访问企业私有数据等问题。

但真正落地时,RAG 远不只是“向量库 + 大模型”。企业数据通常混在多种形态里:

  • 文档、网页、制度、说明书这类非结构化文本;
  • Excel、CSV、数据库表这类结构化数据;
  • 需要多跳推理的实体关系数据,比如人物、事件、产品、地区、时间之间的关联;
  • 业务问题本身还可能包含模糊表达、省略上下文、多轮追问和复杂条件。

所以,一个完整的 RAG 系统通常需要三类检索能力一起工作:

flowchart LR
    Q[用户问题] --> U[问题理解与改写]

    U --> S1[语义检索]
    U --> S2[结构化检索 / Text2SQL]
    U --> S3[GraphRAG 图检索]

    S1 --> E[Embedding 粗召回]
    E --> R[Reranker 重排序]

    S2 --> SQL[生成 SQL]
    SQL --> DB[(数据库 / 表格引擎)]

    S3 --> G[(知识图谱 / 知识树)]
    G --> GR[路径检索与多跳推理]

    R --> C[证据融合]
    DB --> C
    GR --> C

    C --> LLM[阅读理解与答案生成]
    LLM --> A[最终回答]

从工程角度看,这套系统可以拆成四个核心层次:

  1. Embedding 召回层:把问题和文档编码成向量,快速从海量文本里找出候选片段。
  2. Reranker 精排层:对候选片段逐一判断相关性,把真正有用的证据排到前面。
  3. Text2SQL 结构化查询层:把自然语言问题转换成 SQL,从表格或数据库里拿到精确结果。
  4. GraphRAG 图推理层:把知识组织成图或树,用实体、关系、属性和路径完成复杂推理。

完整架构可以用下面这张图辅助理解:

RAG技术架构

这张架构图的关键点在于:语义检索、结构化检索和图检索不是互相替代的关系,而是分别覆盖不同类型的问题。普通事实问答适合走向量召回;涉及统计、筛选、聚合的问题适合走 Text2SQL;涉及多实体、多关系、多跳推理的问题更适合走 GraphRAG。


1. 语义检索:用 Embedding 召回,再用 Reranker 精排

语义检索的典型流程是两阶段:

sequenceDiagram
    participant User as 用户
    participant Emb as Embedding模型
    participant Vec as 向量库
    participant Rank as Reranker模型
    participant LLM as 大语言模型

    User->>Emb: 输入问题
    Emb->>Vec: 生成查询向量并检索TopK
    Vec-->>Emb: 返回候选文档
    Emb->>Rank: 问题 + 候选文档
    Rank-->>Emb: 相关性分数与排序
    Emb->>LLM: 高相关证据
    LLM-->>User: 生成答案

Embedding 模型负责“快”,Reranker 模型负责“准”。Embedding 通常采用双编码器结构,可以预先把文档离线编码成向量,查询时只需要计算问题向量并做近邻搜索;Reranker 通常把问题和文档拼接起来一起输入模型,能看到两者之间更细的交互关系,但计算成本更高。

1.1 Embedding 模型:召回质量决定 RAG 的上限

Embedding 模型把文本映射到向量空间。理想情况下,语义相关的文本向量距离更近,语义无关的文本向量距离更远。

一个典型的向量检索过程如下:

flowchart LR
    D[文档集合] --> Split[切分为文本块]
    Split --> EncD[离线编码文档向量]
    EncD --> VDB[(向量库)]

    Q[用户问题] --> EncQ[在线编码查询向量]
    EncQ --> Search[近邻搜索]
    VDB --> Search
    Search --> TopK[候选文本块TopK]

多阶段训练:从通用语义到任务适配

基于大语言模型训练 Embedding 时,单阶段训练往往不够。比较稳妥的做法是把训练拆成多个阶段,让模型逐步获得通用语义能力、检索判别能力和任务适配能力。

Embedding多阶段训练管线

这条训练管线可以理解成两层递进:

阶段训练目标核心做法解决的问题
弱监督对比学习建立基础语义空间使用大规模弱标注文本对,引入批内负样本和跨设备负样本让模型先学会“相关”和“不相关”的基本边界
有监督对比学习适配真实检索任务引入高质量正负样本、难负样本和任务指令让模型能处理具体业务场景里的细粒度匹配

对比学习的基本思想是:给定一个查询 q、一个正样本文档 d+ 和多个负样本文档 d-,训练目标是拉近 qd+ 的向量距离,拉远 q 与所有 d- 的向量距离。

工程上,负样本数量非常关键。一个 batch 内的其他样本可以作为负样本,多张 GPU 上的 batch 也可以共享负样本。这样每个查询能够看到更多反例,模型的判别边界会更清楚。

数据构造:正样本容易造,难负样本更关键

Embedding 的训练数据通常从三元组开始:

(query, positive_document, negative_document)

构造流程可以拆成两步:

flowchart TD
    A[原始文档] --> B[构造问题-正样本文本对]
    B --> C[开源问答数据]
    B --> D[大语言模型生成问题]

    C --> E[问题-正样本集合]
    D --> E

    E --> F[大规模语料库检索]
    F --> G[挖掘难负样本]
    G --> H[过滤假负样本]
    H --> I[训练三元组]

正样本可以来自已有问答数据,也可以由 LLM(Large Language Model,大语言模型)根据文档生成问题。难点在负样本:如果负样本太简单,模型很快就能区分,训练价值有限;如果负样本看起来像负例但实际是正例,就会把模型往错误方向带。

高质量难负样本通常具备两个特点:

  • 表面相似:和问题共享关键词、实体或主题;
  • 语义不匹配:真正回答不了问题,或者只回答了部分条件。

为了减少假负样本,可以引入 Reranker 给训练样本打分,再根据分数分布做清洗:

flowchart LR
    A[候选训练样本] --> B[Reranker相关性评分]
    B --> C{样本类型判断}

    C -->|正例分数过低| D[剔除伪正例]
    C -->|负例分数过低| E[过滤简单负例]
    C -->|负例分数过高| F[识别潜在正例并替换]
    C -->|质量合格| G[进入训练集]

    D --> H[高质量训练集]
    E --> H
    F --> H
    G --> H

Reranker 的评分还可以作为软标签,用于 Embedding 模型训练中的知识蒸馏。这样做不是只告诉模型“相关 / 不相关”,而是告诉模型“相关到什么程度”。

多任务训练:IR 和 STS 不能混成一锅粥

Embedding 常见任务可以分成两类:

  • IR(Information Retrieval,信息检索):给定 query,从文档库中找相关文档;
  • STS(Semantic Textual Similarity,语义文本相似度):判断两段文本语义相似程度。

两类任务的评价指标不同,训练方式也不应该完全相同。

任务关注点常见指标训练重点
IR相关文档是否排在前面nDCG、Recall@K、MRR强化正样本与 query 的相似度,压低难负样本
STS相似度排序是否符合人工标注Spearman 相关系数学习细粒度语义顺序,减少逆序对

nDCG(normalized Discounted Cumulative Gain,归一化折损累计增益)强调高排名位置的正确性。检索任务里,用户通常只看前几个结果,所以正样本排在第 1 位和第 20 位差别很大。

STS 的核心是顺序一致性。模型不只是要判断“像不像”,还要判断“A 比 B 更像,B 比 C 更像”这种相对关系。

多任务联合训练时,不能让不同领域、不同任务的数据在同一个 batch 里随意混合。跨设备负样本共享本来是为了增加负样本数量,但如果一个 GPU 上是法律数据,另一个 GPU 上是医疗数据,强行共享可能引入噪声。更合理的做法是:一次 iteration 内,多张 GPU 取自同一子数据集,同时允许不同任务配置不同 batch size、指令和损失函数。

flowchart TD
    A[多领域训练数据] --> B[按任务和领域分桶]
    B --> C1[IR数据桶]
    B --> C2[STS数据桶]
    B --> C3[业务领域数据桶]

    C1 --> D[动态采样器]
    C2 --> D
    C3 --> D

    D --> E[同一iteration内跨GPU同源数据]
    E --> F[任务特定指令]
    F --> G[任务特定损失]
    G --> H[Embedding模型更新]

指令也很重要。同一段文本在不同任务里可能有不同匹配标准,例如“找定义”“找相似问法”“找能回答问题的文档”并不是完全相同的目标。给输入加上任务指令,可以让模型根据任务调整匹配策略。

模型融合:把不同训练轨迹合成一个更稳的模型

训练结束后,还可以选取多个 checkpoint 做权重融合。ModelSoups 一类方法的思路是:多个模型如果都来自同一底座、训练目标相近,那么它们的权重平均或加权平均可能落在一个更平滑、更稳定的区域。

这类策略不改变推理结构,也不会增加线上推理成本,适合在精调后作为最后一道模型选择和融合步骤。


1.2 Reranker:用更重的模型换更准的排序

Embedding 召回采用双编码器:

score(q, d) = similarity(Encoder(q), Encoder(d))

这种方式快,因为文档向量可以提前算好。但它的限制也很明显:query 和 document 分别编码,模型无法充分建模两段文本之间的 token 级交互。

Reranker 通常采用交叉编码方式:

score(q, d) = Model("[query] q [document] d")

模型一次看到问题和文档,因此能判断更细的关系,例如:

  • 文档是否真的回答了问题;
  • 是否只是关键词重合;
  • 是否遗漏了时间、地点、实体、条件;
  • 是否存在语义反转或范围不一致。

从 BERT 类 Reranker 到 LLM Reranker

传统 Reranker 常基于 BERT、RoBERTa 等模型,参数量多在 110M 到 400M 之间,输入长度通常受限于 512 token。面对长文档、复杂问题、多条件查询时,理解能力和上下文长度都会成为瓶颈。

LLM Reranker 的优势主要有三点:

能力BERT 类 RerankerLLM Reranker
输入长度多数为 512 token可扩展到 8k 或更长
复杂语义理解对长逻辑链支持有限更适合复杂问题和长文档
任务适配主要靠微调数据可结合任务指令调整评分标准

代价也很明确:LLM Reranker 推理成本更高,延迟更大。所以线上常见做法是让 Embedding 先召回较小的候选集,例如 Top 50 或 Top 100,再交给 Reranker 精排。

分层知识蒸馏:让浅层也能输出可用分数

Reranker 训练通常会使用两类信号:

  1. 对比学习损失:让相关文档得分高,不相关文档得分低;
  2. 知识蒸馏损失:让学生模型学习教师模型给出的相关性分数。

分层知识蒸馏进一步把监督信号加到多个 Transformer 层上,而不是只约束最后一层输出。

Reranker分层知识蒸馏损失策略

这张图展示的是 Layerwise 蒸馏思想:教师模型或最后一层输出给出目标分数,中间层也要学习输出接近目标的相关性判断。训练完成后,Reranker 不一定非要跑完整个模型才能出分,可以根据业务延迟要求选择较浅层或较深层输出。

这会带来一个实用能力:可调节精度与延迟

使用层数推理速度排序质量适合场景
浅层输出略低高并发、低延迟搜索
中层输出平衡中等偏高常规 RAG 问答
深层输出更高高价值问答、离线评测、低并发场景

业务训练数据:先实体粗筛,再 LLM 精评分

特定业务里,Reranker 往往缺高质量标注数据。手工标注成本高,而且很难覆盖长尾问题。一个可自动化的数据构造流程是:先用实体召回过滤明显无关文档,再用 LLM 对剩余文档精评分,最后自适应选择正负样本。

高相关性数据筛选流程

流程可以拆成七步:

flowchart TD
    A[Query] --> B[可选:复杂问题拆解]
    B --> C[向量召回候选文档]
    A --> D[Query实体识别]
    D --> E[客观实体 / 时间实体]

    C --> F[文档实体召回打分]
    E --> F
    F --> G[过滤实体召回为0的简单负例]
    G --> H[LLM相关性精评分]
    H --> I[结合实体召回做分数校准]
    I --> J[按分数分布自适应选正例]
    J --> K[按比例补充难负例]
    K --> L[Reranker训练数据]

这里有两个关键设计:

  • 实体召回先行:如果问题里有明确实体或时间,而文档完全不包含这些信息,通常不需要进入昂贵的 LLM 精评分。
  • 分数校准:LLM 打分可能受表达方式影响而产生误判,实体召回分数可以作为约束,纠正明显不合理的高分或低分。

正负样本选择也不能固定阈值一刀切。不同 query 的候选文档分数分布不同:有些问题只有少数高分文档,有些问题多个文档都能回答。按单个 query 的分数分布自适应选择正例,再按比例补充难负例,会比固定取 TopN 更稳。


2. 结构化信息检索:RAG 与 Text2SQL 融合

很多企业知识并不在文档里,而在数据库表、Excel、CSV 或后台系统中。对于这类数据,只做向量检索会遇到明显问题:

  • “销售额最高的城市是哪一个?”需要排序和聚合;
  • “2024 年 Q3 环比增长多少?”需要时间过滤和计算;
  • “满足 A、B、C 三个条件的客户数量是多少?”需要精确筛选;
  • “某个字段值是否存在?”需要查表,而不是找相似文本。

这时应该让 Text2SQL 参与进来。Text2SQL 的任务是把自然语言问题转换成 SQL,然后通过数据库执行获得精确结果。

2.1 多源数据:文本切片和 SQL 查询结果一起进入答案生成

结构化问答不应该完全替代文本检索。很多问题既需要精确数据,也需要解释性上下文。合理做法是同时保留两条证据链:

  1. 文本切片检索:提供背景、规则、说明和上下文;
  2. Text2SQL 查询:提供字段、统计、筛选、聚合等精确结果。

不同数据源载入问答系统

这张图表达的是多源数据接入方式:数据库表、表格文件和文本资料都进入统一问答系统。结构化数据走 SQL 查询,非结构化文本走切片检索,两类证据一起交给阅读理解模型做答案生成。

可以抽象成下面的流程:

flowchart LR
    Q[用户问题] --> P[问题理解]

    P --> T[文本检索]
    T --> Chunks[相关文本切片]

    P --> S[Text2SQL]
    S --> SQL[SQL语句]
    SQL --> DB[(DB / 表格引擎)]
    DB --> Result[结构化查询结果]

    Chunks --> Fuse[证据融合]
    Result --> Fuse
    Fuse --> Reader[阅读理解模型]
    Reader --> Ans[自然语言答案]

2.2 Text2SQL 的数据合成:用自动化降低场景适配成本

Text2SQL 模型要学会三件事:

  • 理解用户问题;
  • 理解数据库 schema;
  • 生成符合数据库方言的 SQL。

真实业务库千差万别,人工标注每个场景的训练数据成本很高。自动化数据合成可以批量生成:

数据库表结构 schema
自然语言问题 question
带推理过程的 SQL answer
数据库方言 dialect

例如,一个合成样本可能长这样:

{
  "dialect": "mysql",
  "schema": {
    "tables": {
      "sales_order": ["order_id", "city", "amount", "order_date"]
    }
  },
  "question": "2024年销售额最高的城市是哪一个?",
  "reasoning": "需要筛选2024年的订单,按城市分组,对amount求和,再按总额降序取第一名。",
  "sql": "SELECT city, SUM(amount) AS total_amount FROM sales_order WHERE order_date >= '2024-01-01' AND order_date < '2025-01-01' GROUP BY city ORDER BY total_amount DESC LIMIT 1;"
}

带推理过程的 SQL 答案很有价值,因为模型不仅学习结果,还学习从问题到 SQL 的构造路径。面对新业务 schema 时,这种训练信号能帮助模型更快适配复杂查询逻辑。

2.3 Multi-Agent Text2SQL:Selector、Decomposer、Refiner 分工协作

单个 LLM 直接生成 SQL 在简单问题上可行,但复杂业务库会带来几个难题:

  • 表很多,相关表和字段难以定位;
  • 问题复杂,需要拆成多个子查询;
  • 生成的 SQL 可能语法错、字段错、条件错;
  • 执行结果为空时,不知道是数据为空还是 SQL 写错。

多智能体协作可以把 Text2SQL 拆成三个角色:

Agent作用输入输出
Selector选择相关表和字段用户问题、数据库 schema候选表、候选列
Decomposer拆解复杂问题用户问题、候选 schema子问题、子 SQL 或推理步骤
Refiner执行并修正 SQLSQL、数据库反馈修正后的 SQL

MAC-SQL技术架构概览

这张架构图的核心是“生成 SQL 前先缩小 schema,生成 SQL 后再用执行反馈修正”。Selector 可以减少无关表对模型的干扰,Decomposer 让复杂问题逐步求解,Refiner 则把数据库执行结果作为外部反馈,修正语法错误、字段错误或条件错误。

整体流程可以写成伪代码:

def text2sql_answer(question, database):
    schema = database.load_schema()

    # 1. 选择相关表和字段
    selected_schema = selector(question, schema)

    # 2. 拆解复杂问题
    plan = decomposer(question, selected_schema)

    # 3. 生成初始 SQL
    sql = generate_sql(question, selected_schema, plan)

    # 4. 执行并根据反馈修正
    for _ in range(3):
        result, error = database.execute(sql)
        if error is None:
            return sql, result
        sql = refiner(question, selected_schema, sql, error)

    raise RuntimeError("SQL generation failed after refinement")

3. 表格文件场景:先结构化解析,再查询

数据库表通常格式规范,但真实业务里经常遇到 Excel、PDF 表格、Word 表格截图等非标准数据。这类表格可能包含合并单元格、嵌套表头、多层标题、备注行、空白列。如果直接交给 Text2SQL,模型很难知道哪些是字段、哪些是值、哪些只是说明。

3.1 智能结构化解析:把非标准表格变成标准表

表格解析需要解决三个问题:

  1. 判断表格是否是可查询的知识表;
  2. 识别表头层级和字段含义;
  3. 把视觉或文件中的表格元素整理成标准二维表。
flowchart TD
    A[原始表格文件] --> B{是否为结构化知识表}
    B -->|否| C[转入文本切片或丢弃]
    B -->|是| D[表头识别]
    D --> E[单元格归并与层级展开]
    E --> F[字段标准化]
    F --> G[可被Text2SQL查询的结构化表]

非标准表格转标准表时,要尽量保留表头语义。例如多级表头:

地区2024 年收入2024 年成本
华东1000600

可以展开为:

地区指标年份收入成本
华东20241000600

只有结构化之后,Text2SQL 才能稳定生成查询条件、聚合和排序。

3.2 语义窗口切分:表头和内容要一起保留

表格也可以做文本切片,但不能像普通文档一样按固定长度切。表格里的单元格值如果脱离表头,会失去语义。

错误切法:

华东 1000 600
华南 800 500

模型不知道 1000 是收入、销量还是人数。

更合理的切法是把表头属性和行内容组合起来:

地区=华东;年份=2024;收入=1000;成本=600
地区=华南;年份=2024;收入=800;成本=500

窗口大小可以按业务调整:

切分粒度形式适合问题
行级窗口一行一段查单条记录、条件筛选
多行窗口多行合并查相邻记录、范围解释
分组窗口按类别或章节合并查同一主题下的统计说明

3.3 双引擎查询:MySQL 管精确计算,ES 管模糊匹配

表格结构化之后,可以同时写入 MySQL 和 ES(Elasticsearch):

  • MySQL 适合精确筛选、聚合、排序和数值计算;
  • ES 适合全文检索、模糊匹配、同义词召回和字段值不完全匹配。
flowchart LR
    Q[自然语言问题] --> SQL[Text2SQL生成SQL]
    SQL --> AST[AST解析与校验]

    AST --> M{查询类型}
    M -->|精确过滤 / 聚合 / 排序| MySQL[(MySQL)]
    M -->|模糊匹配 / 文本泛化| ES[(Elasticsearch)]

    MySQL --> R[查询结果]
    ES --> R
    R --> LLM[答案生成]

AST(Abstract Syntax Tree,抽象语法树)在这里有两个作用:

  1. SQL 语法校验和自动修正:把 SQL 转成树结构,更容易定位字段、条件、函数和排序子句;
  2. 方言转换:把 SQL AST 转换为 ES DSL,实现同一语义在不同引擎上的执行。

例如用户问:

售卖模式为“一次性售卖与租赁模式”的产品有哪些?

如果数据库里实际存的是:

一次性售卖模式
租赁模式

MySQL 精确匹配可能查不到结果,而 ES 可以通过分词、同义词或模糊匹配召回相关记录。双引擎的价值就在于:既保留 SQL 的精确性,又补上业务表达不一致带来的召回缺口。


4. 通用 DB 场景:Schema Linking、Value Linking 与上下文改写

面对真实数据库,Text2SQL 的难点不只是生成 SQL,而是把用户语言和数据库结构对齐。

4.1 DDL 与 SimpleDDL:提示词长度和准确性的取舍

给模型输入 schema 时,常见两种格式:

Schema 提示格式包含信息优点代价
DDL表名、字段名、字段类型、主键、外键、约束信息完整,利于复杂 SQL输入长,推理慢,成本高
SimpleDDL表名、字段名简洁,速度快缺少类型和关系信息,复杂查询容易错

DDL 示例:

CREATE TABLE student (
    student_id INT PRIMARY KEY,
    name VARCHAR(64),
    age INT
);

CREATE TABLE course (
    course_id INT PRIMARY KEY,
    course_name VARCHAR(128)
);

CREATE TABLE enrollment (
    student_id INT,
    course_id INT,
    score DECIMAL(5,2),
    FOREIGN KEY (student_id) REFERENCES student(student_id),
    FOREIGN KEY (course_id) REFERENCES course(course_id)
);

SimpleDDL 示例:

student(student_id, name, age)
course(course_id, course_name)
enrollment(student_id, course_id, score)

如果库很大,直接把所有 DDL 塞进提示词会浪费上下文,也会增加模型混淆概率。更稳的做法是先用语义向量做 schema 召回,只把相关表和字段放进提示词。

4.2 Schema Linking:把问题里的概念映射到表和字段

Schema Linking 指的是把自然语言问题中的概念映射到数据库 schema 元素。

例如:

查询年龄大于20岁的学生姓名

需要建立映射:

学生 -> student 表
年龄 -> student.age 字段
姓名 -> student.name 字段

这个过程可以结合关键词、字段描述、向量相似度和业务词典完成。

flowchart LR
    Q[用户问题] --> E[抽取概念]
    E --> V[向量召回候选表字段]
    V --> L[Schema Linking]
    L --> P[生成SQL提示词]

4.3 Value Linking:把问题里的条件值映射到真实存储值

Value Linking 关注的是查询条件中的值。

例如:

查 CS 专业成绩超过 90 分的学生

数据库里可能存的是:

Computer Science

这时需要把:

CS -> Computer Science

再生成 SQL:

SELECT s.name
FROM student s
JOIN enrollment e ON s.student_id = e.student_id
JOIN major m ON s.major_id = m.major_id
WHERE m.major_name = 'Computer Science'
  AND e.score > 90;

Value Linking 还需要处理日期表达:

用户表达SQL 条件
上个月date >= '2026-05-01' AND date < '2026-06-01'
去年date >= '2025-01-01' AND date < '2026-01-01'
最近 7 天date >= CURRENT_DATE - INTERVAL 7 DAY

4.4 多轮上下文改写:把省略问题补完整

多轮问答里,用户经常省略前文条件:

用户:2024年华东区销售额是多少?
用户:那华南呢?
用户:再看一下利润率。

第三个问题里的“利润率”需要继承“2024 年”和“华南区”。如果不做上下文改写,Text2SQL 很可能生成不完整 SQL。

一种做法是构造编辑信号,把当前问题相对于历史问题的插入、替换关系编码进模型:

flowchart TD
    A[历史问题] --> C[上下文表示]
    B[当前问题] --> D[编辑矩阵: 插入 / 替换]
    C --> E[问题改写信号]
    D --> E
    E --> F[与表格-文本链接矩阵融合]
    F --> G[Self-Attention]
    G --> H[上下文相关SQL]

改写后的问题会变成:

查询2024年华南区的利润率。

再进入 Text2SQL,生成结果会稳定得多。


5. 答案生成:结构化结果和文本证据要一起解释

Text2SQL 的输出通常是表格、数值或 SQL 执行结果,不一定适合直接展示给用户。例如:

{
  "city": "上海",
  "total_amount": 12345678.90
}

一个更好的回答应该说明数据含义、筛选条件和计算口径:

按订单日期筛选 2024 年数据,并按城市汇总销售额后,上海的销售额最高,为 12,345,678.90。

如果问题涉及趋势分析,答案还需要结合文本切片里的业务说明。例如:

  • 数据库给出销售额、成本、利润率;
  • 文本切片给出促销活动、区域政策、产品结构变化;
  • 阅读理解模型综合两类证据,生成带数据支撑的解释。
flowchart LR
    SQLR[SQL查询结果] --> F[证据融合]
    TXT[文本切片证据] --> F
    F --> R[阅读理解模型]
    R --> A[可读答案]

这里要注意,答案生成模型不应该随意扩展结论。结构化结果负责事实边界,文本证据负责解释范围,两者都没有支持的信息不应出现在最终回答里。


6. GraphRAG:为复杂多跳问题组织知识

普通向量检索适合找“相似文本”,但复杂问题往往需要找“关系链”。

例如:

某位人物参与的事件中,哪些事件发生在其任职期间,并且与某个地区政策相关?

这类问题涉及人物、职位、时间、事件、地区、政策等多个实体和关系。如果只用 query 向量去匹配文本切片,可能召回到相关片段,但不一定能把关系链拼起来。

GraphRAG 的思路是把文本中的知识抽成图结构,再沿着实体和关系检索。

flowchart LR
    Docs[原始文档] --> Extract[实体/关系/属性抽取]
    Extract --> KG[(知识图谱)]
    KG --> Community[社区摘要]
    Q[复杂问题] --> QP[问题解析]
    QP --> Retrieve[图检索]
    KG --> Retrieve
    Community --> Retrieve
    Retrieve --> Reason[路径推理]
    Reason --> Ans[答案生成]

6.1 GraphRAG 评测要看四个维度

只比较回答准确率不够。GraphRAG 还有构图成本、查询延迟和推理质量等问题。

维度关注点典型问题
构图成本离线建图耗时和 token 消耗是否需要大量 LLM 调用?大规模文档能否承受?
检索效率单次查询图检索耗时查询是否能控制在秒级或十几秒内?
回复准确率最终答案是否正确图检索是否找到足够证据?
推理能力是否给出正确理由是否能完成多跳关系推导?

GraphRAG-Bench构建逻辑及评测设计

这张图展示的是 GraphRAG-Bench 的构建与评测思路:数据集需要覆盖领域知识和复杂推理问题,评估时不能只看最终答案,还要看构图成本、检索效率和推理过程。

6.2 从知识图谱和树结构,走向知识树

当前 GraphRAG 大致有两类路线:

路线代表思路优点限制
知识图谱路线抽取实体、关系、三元组粒度细,适合关系推理构图质量依赖抽取,图检索成本高
树结构路线对文本切片层次化总结摘要效率较高,适合全局概览细粒度关系挖掘不足

知识树试图结合两者优势:保留图的细粒度关系,也保留树的层次化摘要。

Knowledge Tree与图/树GraphRAG方法对比

这张图的核心是异构知识组织。知识树不是只存实体节点,而是引入多层粒度:

  • 属性节点:描述实体特征;
  • 知识图三元组:表达实体之间的关系;
  • 关键词节点:连接主题和语义单元;
  • 社区节点:总结一组相关子图的核心信息。

可以把它抽象为:

flowchart TD
    C[社区节点: 主题摘要]
    K1[关键词节点]
    K2[关键词节点]

    T1[三元组: 实体A-关系-实体B]
    T2[三元组: 实体B-关系-实体C]

    P1[属性节点: 实体A属性]
    P2[属性节点: 实体B属性]

    C --> K1
    C --> K2
    K1 --> T1
    K2 --> T2
    T1 --> P1
    T1 --> P2
    T2 --> P2

这种结构的好处是:局部问题可以沿三元组和属性精确检索,全局问题可以先看社区摘要,再下钻到相关子图。

6.3 社区检测:同时看结构和语义

传统图社区检测算法往往依赖连接关系。如果图构建质量高,这种方法很有效;但如果实体关系抽取有缺失或噪声,单纯依赖拓扑结构就会把相关语义拆开,或者把无关节点合在一起。

更合理的社区检测应该同时考虑两类信号:

  1. 结构感知:节点和候选社区在邻居连接上的重合程度;
  2. 语义感知:节点文本特征和候选社区语义特征的相似度。
flowchart LR
    A[锚节点] --> S1[结构相似度]
    B[候选社区子图] --> S1
    A --> S2[语义相似度]
    B --> S2

    S1 --> Score[综合社区归属分数]
    S2 --> Score
    Score --> C{是否并入社区}

结构相似度可以通过稀疏邻接矩阵计算 Jaccard 相似度,衡量锚节点邻居和社区邻居的重合度;语义相似度可以通过节点文本向量和社区子图向量计算,衡量它们是否表达相近主题。

这样做能减少两类问题:

  • 关系边漏抽导致相关节点被拆散;
  • 表面连接较多但语义无关的节点被强行归入同一社区。

6.4 图 Schema 自适应:让不同领域有不同抽取规则

不同领域的图谱 schema 不同。

人物领域可能关注:

人物、组织、职位、时间、事件
任职于、参与、发生于、隶属于

产品领域可能关注:

产品、型号、功能、参数、供应商
包含、依赖、适配、替代

事件领域可能关注:

事件、地点、参与方、时间、结果
发生于、导致、影响、关联

如果所有领域都用同一套实体类型和关系类型,抽取质量会受影响。更稳的做法是预置多类中英文领域 schema,并让模型在构图时根据数据内容补充和调整 schema。这样既减少人工配置,也能让图结构更贴合业务语义。


7. 复杂 Query 理解:用 Agentic GraphQ 做多跳拆解

复杂问题直接做向量匹配效果通常不好,因为一个长问题里可能包含多个实体、多个关系和多个约束。GraphRAG 如果只拿整个 query 去匹配节点或摘要,很容易漏掉关键条件。

Agentic GraphQ 的思路是:让问题理解过程感知图 schema,把复杂问题拆成多个单跳或低复杂度子任务。

Agentic GraphQ复杂问题解耦

这张图表达的是 schema 感知的问题解耦过程。模型先识别 query 中的实体、关系和属性,再根据图谱 schema 推断它们之间的依赖关系,最后把多跳问题拆成可执行的子查询。

例如复杂问题:

找出参与A事件且后来加入B组织的人,并说明这些人与C政策之间的关系。

可以拆成:

子任务1:A事件有哪些参与者?
子任务2:这些参与者中哪些后来加入B组织?
子任务3:这些人与C政策之间有哪些关系路径?
子任务4:基于路径证据生成回答。

拆解后的检索可以走多路策略:

检索方式作用
主题词或关键词检索快速定位相关社区和候选节点
Query-Triple 向量匹配用“实体-关系-实体”结构匹配,避免只看单节点
路径 DFS 检索沿图结构寻找多跳关系链

DFS(Depth-First Search,深度优先搜索)适合从锚点实体出发,沿关系边扩展路径。但扩展不能无限制,否则会引入大量噪声。常见约束包括:

def graph_retrieve(start_nodes, max_depth=3, max_paths=50):
    paths = []

    def dfs(node, path, depth):
        if depth > max_depth or len(paths) >= max_paths:
            return

        for edge in graph.out_edges(node):
            next_node = edge.target

            if is_irrelevant(edge, next_node):
                continue

            new_path = path + [(edge, next_node)]
            if is_answer_candidate(new_path):
                paths.append(new_path)

            dfs(next_node, new_path, depth + 1)

    for node in start_nodes:
        dfs(node, [], 0)

    return rerank_paths(paths)

路径检索后还要做相关性剪枝和重排序,否则模型会被大量弱相关路径干扰。


8. GraphRAG 的成本和效果取舍

GraphRAG 的一个主要工程难点是成本。建图通常需要实体抽取、关系抽取、属性归纳、社区摘要等步骤,如果每一步都大量调用 LLM,成本会很快失控。

8.1 构图成本:token 消耗必须可控

GraphRAG构图效率对比

这张结果图对比了多个数据集上的构图效率。重点不是某个单点数值,而是方向:如果社区检测、schema 适配和知识组织方式更高效,GraphRAG 才能从实验走向生产环境。

GraphRAG构图Token消耗对比

这张图展示的是构图阶段的大模型调用成本差异。GraphRAG 的落地门槛很大程度取决于离线构图 token 消耗,尤其是企业知识库需要周期性更新时,亿级 token 和百万级 token 的成本差距非常明显。

降低成本的常见手段包括:

  • 使用轻量模型做初筛和候选抽取;
  • 对相似文本块去重,减少重复建图;
  • 增量更新图谱,而不是每次全量重建;
  • 社区摘要按需更新,只重算受影响子图;
  • 抽取结果缓存,避免同一文本重复调用 LLM。

8.2 检索效果:复杂推理数据集更能体现 GraphRAG 价值

GraphRAG检索效果对比

这张图对比的是复杂数据集上的回答准确率。GraphRAG 的优势通常不会在简单事实问答里完全体现,因为简单问题用普通向量检索就能解决;它真正有价值的场景是跨文档、多实体、多关系、多条件推理。

适合 GraphRAG 的问题通常长这样:

问题类型示例为什么需要图
多跳关系A 的上级组织参与过哪些 B 类事件?需要沿实体关系跳转
时间约束某人在任职期间发生的相关事件有哪些?需要实体、职位、时间共同约束
路径解释X 和 Y 为什么有关联?需要给出关系链
领域推理某政策影响了哪些产品线?需要跨文档聚合关系

不适合优先上 GraphRAG 的场景也很明确:

场景更合适方案
简单 FAQ向量检索 + Reranker
精确统计查询Text2SQL
实时写入频繁、关系变化很快轻量索引或增量图
数据规模小、关系不复杂普通 RAG 足够

9. 一套可落地 RAG 系统的设计清单

把语义检索、结构化检索和图检索放到同一套系统里,需要明确每个模块的边界。

模块核心目标关键设计常见风险
文档切分保留语义完整性按标题、段落、表格结构切分切得太碎导致上下文丢失
Embedding高召回多阶段训练、难负样本、任务指令只在榜单好,业务集不稳
Reranker高精排LLM Reranker、分层蒸馏、业务数据精调延迟过高
Text2SQL精确查询Schema Linking、Value Linking、SQL Refine误查敏感数据、SQL 幻觉
表格解析非标准表转结构表表头识别、合并单元格展开字段语义丢失
GraphRAG多跳推理知识树、社区检测、路径检索建图成本高、图噪声大
答案生成证据到自然语言结构化结果 + 文本证据融合模型扩展无依据结论

线上系统还需要加几层保护:

flowchart TD
    Q[用户问题] --> Safe[权限与安全检查]
    Safe --> Route[问题路由]

    Route -->|文本问答| RAG[语义RAG]
    Route -->|表格/统计| SQL[Text2SQL]
    Route -->|复杂关系| Graph[GraphRAG]

    RAG --> Evidence[证据池]
    SQL --> Evidence
    Graph --> Evidence

    Evidence --> Verify[证据校验]
    Verify --> Generate[答案生成]
    Generate --> Guard[输出审查]
    Guard --> A[返回答案]

特别是 Text2SQL,一定要控制权限和执行范围。比较稳的策略包括:

  • 只允许只读查询;
  • 禁止 DROPDELETEUPDATE 等写操作;
  • 给 SQL 执行设置超时和行数限制;
  • 对敏感字段做脱敏或拒答;
  • 记录生成 SQL、执行结果和证据来源,方便追踪问题。

10. RAG 正在从线性流程走向闭环系统

传统 RAG 是一条线:

检索 -> 生成

生产环境里的 RAG 更像一个闭环:

规划 -> 检索 -> 验证 -> 反思 -> 再检索 -> 推理 -> 生成

Agentic RAG 的价值就在这里。对于复杂问题,系统需要先规划子任务,再调用向量检索、SQL 查询、图检索、计算工具等不同能力;拿到结果后,还要验证证据是否足够,不够就继续补检索。

可以把未来形态抽象成:

flowchart LR
    Q[问题] --> Plan[任务规划]
    Plan --> Tool[工具选择]
    Tool --> Retrieve[检索/查询/图推理]
    Retrieve --> Check[证据验证]
    Check -->|不足| Plan
    Check -->|足够| Reason[推理]
    Reason --> Ans[生成答案]

RAG 的难点不再只是“召回多少文本”,而是系统能否根据问题类型选择正确的数据源、正确的检索方式和正确的推理路径。Embedding、Reranker、Text2SQL 和 GraphRAG 分别解决不同层面的知识获取问题,把它们组合起来,才更接近可用、可控、可解释的企业级智能问答系统。


评论