RAG 系统设计学习笔记

学习日期:2026-04-21 | 难度:中级


什么是 RAG,为什么需要它

LLM 有两个天然缺陷:知识有截止日期,以及不知道你的私有数据

RAG(Retrieval-Augmented Generation,检索增强生成)的思路很直接:用户提问时,先去外部知识库里找到相关内容,再把这些内容连同问题一起交给 LLM 生成回答。这样 LLM 就不再依赖训练时的记忆,而是”现查现答”。

flowchart LR
    U([用户提问]) --> R[检索相关文档]
    R --> L[LLM 生成回答]
    DB[(知识库)] --> R
    L --> A([回答])

听起来简单,但魔鬼在细节里——检索不准,后面全白费。这也是 RAG 工程的核心难点所在。


什么时候用 RAG,什么时候不用

不是所有场景都需要 RAG,先做两步判断:

flowchart TD
    Q[有外部知识需求?] -->|否,通用知识| A[直接用 LLM]
    Q -->|是,私有/实时数据| B[数据量多大?]
    B -->|1本文档,结构清晰| C[目录 index + LLM 二次查询\nAgentic RAG]
    B -->|几百份以上 / 每日更新| D[RAG\n自动 Indexing Pipeline]
    D --> E{需要精确过滤?\n时间、分类、标签}
    E -->|是| F[RAG + Metadata Filter]
    E -->|否| G[标准 RAG]

本质分界线是维护成本:一本手册可以手动建目录,几百本手册每天更新就必须自动化。RAG 的 Indexing Pipeline 就是把”手动建索引”这件事自动化。

Agentic RAG:不用向量相似度决定查哪里,而是把决策权交给 LLM——让它看目录自己决定去哪个章节找答案。适合结构清晰、边界明确的文档,是 Long Context 和传统 RAG 之间的折中方案。


完整系统架构

RAG 系统由两条完全独立的流水线组成:离线入库在线查询

flowchart TD
    subgraph Indexing ["📥 Indexing Pipeline(离线,批量跑)"]
        P[PDF / 文档] --> C[Chunking 分块]
        C --> E[Embedding 向量化]
        E --> S[(pgvector + ES\n同时存向量和倒排索引)]
    end

    subgraph Query ["🔍 Query Pipeline(在线,低延迟)"]
        U([用户提问]) --> QR[Query Rewriting / HyDE]
        QR --> MF[Metadata Filter 前置过滤]
        MF --> HS[Hybrid Search\nBM25 + 向量检索]
        HS --> RRF[RRF 合并排序]
        RRF --> RR[Reranker 精排]
        RR --> LLM[LLM 生成回答]
        LLM --> ANS([最终回答])
    end

    S --> HS

两条流水线解耦的好处:Indexing 可以慢慢跑,Query 只需要关注延迟。


核心概念详解

1. Query-Document Asymmetry(查询-文档语义不对称)

这是 RAG 最根本的问题,很多人忽视它。

用户提问用的是问句(“苹果公司赚钱吗”),文档里写的是陈述句(“净利润率 21%,同比增长 3%”)。两者语义相关,但在 embedding 向量空间里,问句和陈述句的结构不同,向量距离天然就远——相关内容反而检索不到。

解法一:HyDE(Hypothetical Document Embedding)

不直接用问题去检索,而是先让 LLM 生成一段”假答案”,用假答案的 embedding 去检索。用”答案形式”找”答案形式”,向量空间里天然对齐。

flowchart LR
    Q["用户问:苹果公司赚钱吗?"] --> LLM["LLM 生成假答案\n'苹果2024年净利润约XXX亿,利润率21%...'"]
    LLM --> E[假答案 Embedding]
    E --> DB[(向量库检索)]
    DB --> R[真实文档]

解法二:Query Rewriting / Query Decomposition

把模糊的短查询扩写成完整问题,或者拆成多个子问题分别检索再合并。


2. Hybrid Search(混合检索)

单一检索方式都有盲区:

方式擅长失效场景
向量检索语义相似,同义词问句/答句结构不对称
BM25 关键词精确词匹配语义相关但用词不同

解法是两路同时跑,用 RRF(Reciprocal Rank Fusion) 合并结果:

flowchart TD
    Q[用户问题] --> B[BM25 关键词检索]
    Q --> V[向量相似度检索]
    B --> |文档A排1, 文档C排2| RRF[RRF 合并]
    V --> |文档C排1, 文档D排2| RRF
    RRF --> |文档C两边都有,得分最高| TOP[Top-K 候选文档]

RRF 公式score(d) = Σ 1 / (k + rank_i(d))

在两个列表都出现的文档,两项相加,分数最高。只在一个列表出现的分数较低。

工具推荐:pgvector 或 Elasticsearch 8.x 都原生支持 Hybrid Search,一个库搞定,不需要维护两套系统。


3. Chunking 策略(分块)

文档不能整篇塞进向量库,要切成小块(chunk)再 embedding。但切法有讲究:

  • 切太小 → 上下文丢失,LLM 拿到片段用不好
  • 切太大 → 噪音多,embedding 质量下降,检索也不准

Semantic Chunking:按语义边界切,而不是固定字数。章节、段落是天然的切分点。

Parent-Child Chunking(更优解):同时存两种粒度,各司其职:

flowchart TD
    subgraph 存储
        P["Parent Chunk(大块,500-1000字)\n保留完整上下文"]
        P --> C1["Child Chunk 1(小块,100字)"]
        P --> C2["Child Chunk 2(小块,100字)"]
        P --> C3["Child Chunk 3(小块,100字)"]
    end

    subgraph 检索
        Q[用户问题] --> VC[向量检索 Child Chunks\n小块精准匹配]
        VC --> |找到 Child Chunk 2| PP[返回对应 Parent Chunk\n大块喂给 LLM]
    end

小块负责”找得准”,大块负责”用得好”。


4. Reranking(重排序)

向量检索返回的 Top-20 文档,排第一的不一定是最能回答问题的。向量相似度衡量的是”语义接近”,不是”能回答这个问题”。

Cross-Encoder 解决这个问题:把问题和文档同时输入模型,让模型直接判断”这篇文档能不能回答这个问题”。

flowchart LR
    subgraph Embedding模型
        Q1[query] --> EQ[向量]
        D1[doc] --> ED[向量]
        EQ --> DIST[算距离\n快,适合大规模召回]
    end

    subgraph Cross-Encoder
        QD["(query, doc) 拼在一起"] --> SCORE[直接输出相关性分数\n准,适合精排]
    end

为什么不一开始就用 Cross-Encoder:对百万级文档逐一跑太慢。所以先用向量检索快速缩小到 Top-20,再用 Reranker 精排——这是”粗排 + 精排”的经典两阶段架构。

优化优先级理解

  • Query Rewriting 影响的是召回率(能不能找到相关文档)
  • Reranker 影响的是精确率(找到的文档排序对不对)
  • 实际优化要先诊断瓶颈在哪一侧

5. Embedding 模型选择与微调

Embedding 模型的质量直接决定向量检索的上限。

选型参考

场景推荐模型
通用场景 + 预算有限text-embedding-3-small、BGE-M3
中文场景BGE 系列(BAAI 出品,中文效果强)
多语言BGE-M3 或 multilingual-e5
领域术语密集 + 有标注数据在通用模型基础上微调

什么时候值得微调:两个条件都满足才值得——领域术语密集(法律、金融、医疗),且有标注数据(如”相似文档对”)。

微调方式是 Contrastive Learning(对比学习):把相似文档对的向量拉近,把不相关文档的向量推远。


6. Metadata Filtering(元数据过滤)

纯向量检索处理不了结构化条件。用户问”最近一周看好新能源的研报”,“最近一周”和”新能源”这两个条件向量检索根本不理解。

解法是在向量检索之前先用 metadata 过滤,把候选集从百万砍到万级,后面的检索和重排都在小范围内进行:

flowchart LR
    Q["最近一周看好新能源的研报"] --> P[解析结构化条件]
    P --> |time: last_7_days\nsector: 新能源| MF[Metadata Filter\n先过滤]
    MF --> |候选集从100万 → 1万| HS[Hybrid Search]
    HS --> RR[Reranker]
    RR --> LLM

常用 metadata 字段:时间戳、行业分类、评级(买入/持有/卖出)、机构名称、股票代码。


7. RAGAS 评估体系

系统上线前需要量化质量,RAGAS 提供 4 个维度:

flowchart TD
    subgraph 检索侧
        CP["Context Precision\n检索回来的文档,有多少是真正相关的\n→ 衡量精准度"]
        CR["Context Recall\n回答所需的信息,有多少被检索回来了\n→ 衡量覆盖度"]
    end

    subgraph 生成侧
        F["Faithfulness\nLLM 的回答是否忠实于检索文档\n没有凭空捏造\n→ 最难提升"]
        AR["Answer Relevancy\n回答是否真正切题\n→ 用户最直接感受"]
    end

最难提升的是 Faithfulness:Context Precision 可以靠 Reranker 持续优化,但 LLM 幻觉是模型本身的行为问题,工程手段有限。

问题诊断链路:收到用户投诉时按这个顺序查:

flowchart TD
    BUG["用户反馈:回答答非所问"] --> AR[先查 Answer Relevancy\n回答跑题了吗?]
    AR --> |低| CP[再查 Context Precision\n检索回来的文档相关吗?]
    CP --> |低| FIX1[问题在检索侧\n优化 Hybrid Search / Reranker]
    CP --> |正常| F[查 Faithfulness\nLLM 有没有幻觉?]
    F --> |低| FIX2[问题在生成侧\n优化 prompt 或换模型]

大规模场景性能优化(十万级文档)

文档量上来之后,延迟瓶颈主要来自三个地方:向量检索本身、召回候选太多、Reranker 逐一打分。

优化手段(按性价比排序)

手段效果成本
Metadata Filter 前置候选集从百万砍到万级,后面全链路都快极低,加个 where 条件
控制召回数量Top-20 进 Reranker,不要 Top-100极低,改个参数
ANN 索引(HNSW)向量检索本身 ms 级低,向量库默认支持
Query 缓存相同问题直接返回,跳过整条链路中,需要缓存层
Reranker 缓存高频 (query, doc) 打分结果缓存中,需要缓存层

ANN(近似最近邻)原理:不对所有向量逐一算距离,而是先把向量空间分区,查询时只搜最相关的分区。牺牲一点精度,换来几个数量级的速度提升。pgvector 和 Pinecone 默认使用 HNSW 算法。


易错点备忘

  1. Hybrid Search ≠ 分层检索:不是”大目录用关键词、细节用向量”,而是同时跑两路检索再用 RRF 合并
  2. 优化优先级:召回率(Query Rewriting)和精确率(Reranker)是两个独立维度,先诊断瓶颈在哪再优化
  3. RAGAS 最难指标:是 Faithfulness(幻觉),不是 Context Precision(有工程手段可优化)
  4. 诊断顺序:答非所问先查 Answer Relevancy,不是 Faithfulness

下次复习重点

  • Chunking 策略(Parent-Child 的实现细节)
  • RAGAS 诊断链路(从症状到根因的定位流程)