【AI基础篇10】RAG:检索增强生成详解

前言:大模型的知识截止于训练日期,你问它"今天的天气"它不知道,问它"贵公司退货政策"它也不懂。但如果在回答前先查一下天气API、搜一下公司知识库呢?这就是RAG的核心思想——用检索替代记忆,用事实锚定生成。RAG是2024-2026年企业落地AI的第一选择,从客服到法务到医疗,几乎所有需要"私有知识"的场景都在用RAG。本文从基础原理讲到GraphRAG、Agentic RAG等高级范式,一次吃透。


📋 目录


一、为什么需要RAG?

1.1 大模型的三个致命短板

大模型很强大,但在企业应用中,有三个致命短板:

短板1:知识截止于训练日期
  大模型的知识 = 训练数据覆盖的时间范围
  训练于2025年 → 不知道2026年的事
  你问"今年GDP" → 模型只能"猜"

短板2:不知道你的私有数据
  你公司的内部文档、客户记录、产品手册
  模型永远没"见过"这些数据
  你问"我们公司的退货政策" → 模型只能"编"

短板3:无法保证事实准确性
  即使它"知道",也可能记错
  即使在标准测试集上表现好
  在你自己的数据上也可能出错

RAG = 同时解决这三个问题的方案

1.2 大模型 vs RAG vs 微调

企业知识问答场景下的方案对比:

用户问:"我们公司2026年Q1的营收是多少?"

没有RAG没有微调 ❌
  模型不知道公司的私有数据
  → 编造一个数字(幻觉)
  → 即使模型说"我不知道",也等于没用

只微调不RAG ⚠️
  模型记住了训练时的营收数据
  → Q1营收是1.2亿(如果训练数据中有)
  → 但如果Q2数据更新了呢?
  → 微调后不能实时更新,除非重新微调
  → 成本和时效性都是问题

RAG(最佳实践) ✅
  用户问→检索知识库→找到最新财报→基于财报回答
  → "根据公司2026年Q1财报,营收为1.5亿"
  → 数据更新时→只需要更新知识库→不需要重新微调!
  → 成本低、实时性好、可追溯

一句话对比:
  微调是"教会模型记住规则和模式"
  RAG是"每次回答前都去查资料"
  两者不冲突,可以组合使用

1.3 RAG的适用场景

✅ RAG适合的场景:
  客服系统:产品信息、退货政策、FAQ
  企业知识库:内部文档、操作手册、制度文件
  法律助手:法规条文、判例、合同审查
  医疗辅助:诊疗指南、药品说明书、医学文献
  教育培训:教材、题库、学习资料
  数据分析:报表查询、业务指标、KPI监控
  代码助手:公司内部API文档、代码规范
  智能搜索:从海量文档中精准找到答案

❌ RAG不适合的场景:
  创意写作:不需要"查资料",需要"发挥"
  通用聊天:不需要私有知识
  简单计算:直接让模型算更快
  实时交互应用:多一次检索 → 多几百ms延迟

RAG的本质是"知识增强",不是"能力增强"
  它让模型知道它不知道的东西
  但不改变模型本身的能力边界

二、RAG的核心流程:三阶段

2.1 三阶段总览

RAG的完整工作流程分为三个阶段:

┌──────────────────────────────────────────────────────┐
│                                                       │
│  阶段1:索引(Indexing)—— 离线构建                   │
│  ┌──────┐    ┌────────┐    ┌───────────┐            │
│  │文档   │ → │分块    │ → │Embedding  │            │
│  │收集   │    │Chunking│    │向量化     │ → 存入向量DB│
│  └──────┘    └────────┘    └───────────┘            │
│                                                       │
│  阶段2:检索(Retrieval)—— 在线查询                  │
│  ┌─────┐    ┌──────────┐    ┌──────────┐            │
│  │用户  │ → │问题      │ → │向量检索  │            │
│  │问问题│    │Embedding │    │Top-K文档  │            │
│  └─────┘    └──────────┘    └──────────┘            │
│                                                       │
│  阶段3:生成(Generation)—— 组装答案                │
│  ┌──────────┐    ┌──────────┐    ┌───────┐          │
│  │检索结果 +│ → │LLM       │ → │最终   │          │
│  │用户问题  │    │生成回答  │    │输出   │          │
│  └──────────┘    └──────────┘    └───────┘          │
│                                                       │
└──────────────────────────────────────────────────────┘

2.2 一句话版流程

文档 → 分块 → 向量化 → 存向量数据库
                                ↓
用户问题 → 向量化 → 检索Top-K → 拼接Prompt → LLM生成回答

就这么简单。

2.3 关键参数一览

┌────────────┬──────────────┬──────────────┬─────────────────┐
│ 参数        │ 推荐值       │ 太大/太小    │ 影响             │
├────────────┼──────────────┼──────────────┼─────────────────┤
│ Chunk大小   │ 256-512 token│ 太大:信息混杂 │ 直接影响检索质量  │
│ Chunk重叠   │ 10-20%       │ 太小:信息断裂 │ 保证边界信息完整  │
│ Top-K       │ 3-5          │ 太多:噪声    │ 召回率 vs 精度   │
│ Embedding   │ bge-large-zh │ 小模型准确度低│ 检索质量的核心    │
│ 重排序模型  │ bge-reranker │ 不加可能误检  │ 提升精度10-20%   │
│ 温度        │ 0-0.3        │ 太高乱编     │ 事实性任务要低    │
└────────────┴──────────────┴──────────────┴─────────────────┘

三、文档处理:知识库构建

3.1 文档清洗——90%团队忽略的关键

很多团队做RAG,上来就做向量检索。
但真正决定RAG成败的,是**数据摄入层**——文档清洗。

错误的做法:
  直接把PDF/Word文档扔进向量数据库
  → PDF解析出错 → 乱码 → 向量化错误 → 检索不到正确内容

正确的数据摄入流程:

Step 1:格式统一
  PDF、Word、Excel、HTML、Markdown → 统一转为Markdown
  不同格式用不同的解析器:
    PDF:PyMuPDF(效果好)或 Azure Document Intelligence(商业)
    Word:python-docx
    HTML:BeautifulSoup + Readability
    扫描件PDF:OCR → PaddleOCR / Tesseract

Step 2:内容清洗
  去除页眉页脚、页码、目录 → 噪声
  合并跨页的表格 → 避免信息断裂
  修复编码问题 → 确保中文不乱码
  统一术语 -> 产品名、缩写等前后统一

Step 3:元数据标注
  来源文档名、章节标题、创建日期、作者
  → 检索输出时可以附上来源信息
  → 引用时可以直接说"根据[XX文档第X章]"

Step 4:质量过滤
  过滤掉太短的块(<50字)
  过滤掉纯表格/列表的块(无法向量化)
  人工抽检5-10% → 确保清洗质量

3.2 Chunking:分块策略

Chunking是RAG中最关键也最容易被忽视的步骤。

好的Chunk → 检索命中率高
坏的Chunk → 检索不到正确内容

常见的分割方式:

1️⃣ 固定长度分割(最简单)
  每256个token切一块
  缺点:可能在句子中间切断
  用途:通用文本,快速验证

2️⃣ 递归字符分割(LangChain默认)
  按\n\n → \n → 句号 → 逗号 依次尝试分割
  保证语义完整性
  用途:大多数场景都适用

3️⃣ 语义分割(2024-2026新趋势)
  用Embedding检测"语义边界"
  当连续两句的语义变化超过阈值时切分
  效果最好,但需要额外计算

4️⃣ 结构化分割
  Markdown/HTML按标题层级分割
  H1/H2/H3作为块边界
  对文档类场景非常有效

各场景推荐策略:
  长文档(书籍/手册):语义分割 + 标题层级
  短文本(FAQ/问答):固定512token + 重叠10%
  代码文档:按函数/类定义分割
  表格数据:整个表格作为一个chunk

3.3 Chunk重叠策略

为什么要重叠?
  如果恰好在一个chunk的边界处有重要信息
  两个chunk都不完整 → 信息丢失

重叠解决这个问题:
  控制参数:
    chunk_size = 512
    chunk_overlap = 50-100(10-20%)

  效果:
    文档中"退货政策:7天内可无理由退货"
    如果这句话正好在边界
    有重叠 → 两个chunk都包含这句话
    无重叠 → 可能一个chunk只有"退货政策:",另一个只有"7天内可..."

实践经验:
  文本密集:重叠10-15%
  表格/列表:重叠15-20%
  代码:重叠5-10%(可多分一个整函数)

四、Embedding与向量检索

4.1 Embedding模型选型

Embedding模型决定"检索质量"的上限。

2026年主流Embedding模型对比(中文场景):

┌──────────────────┬────────┬──────────┬──────────┬──────────┐
│ 模型             │ 维度   │ 检索mAP  │ 速度     │ 适用场景  │
├──────────────────┼────────┼──────────┼──────────┼──────────┤
│ BAAI/bge-large-zh│ 1024   │ 最佳     │ 中      │ 中文首选  │
│ BAAI/bge-m3      │ 1024   │ 极佳     │ 中      │ 多语言  │
│ text2vec-large   │ 1024   │ 优秀     │ 中      │ 中文替代  │
│ OpenAI ada-002   │ 1536   │ 优秀     │ 快      │ 英文最优  │
│ Qwen-Embedding   │ 2560   │ 极佳     │ 快(云端)│ 阿里生态  │
│ moka-ai/m3e      │ 768    │ 良好     │ 快      │ 轻量部署  │
└──────────────────┴────────┴──────────┴──────────┴──────────┘

选择建议:
  中文场景:bge-large-zh-v1.5(开源,效果最好)
  多语言场景:bge-m3
  英文为主:OpenAI text-embedding-ada-002
  快速验证:m3e-base(速度快,性价比高)
  阿里生态:Qwen-Embedding(深度整合)

4.2 向量数据库选型

2026年主流向量数据库对比:

┌──────────────┬──────────┬──────────┬──────────┬────────────┐
│ 数据库        │ 部署方式  │ 索引类型  │ 亿级      │ 特点       │
│              │          │          │ 性能      │            │
├──────────────┼──────────┼──────────┼──────────┼────────────┤
│ Milvus       │ 集群部署  │ IVF/    │ ✅       │ 企业级,    │
│              │          │ HNSW    │          │ 功能最全    │
│ Chroma       │ 本地嵌入  │ HNSW    │ ❌       │ 轻量,适合  │
│              │          │          │          │ 快速原型     │
│ Qdrant       │ 单机/集群 │ HNSW    │ ✅       │ Rust实现,  │
│              │          │          │          │ 性能好      │
│ Weaviate     │ 单机/集群 │ HNSW    │ ✅       │ 内置MLOps  │
│ FAISS        │ 库(非DB) │ IVF/    │ ✅       │ 学术首选    │
│              │          │ HNSW    │          │            │
│ pgvector     │ PostgreSQL│ IVFFlat│ 中       │ 不想引入    │
│              │ 插件      │ HNSW    │          │ 新DB        │
└──────────────┴──────────┴──────────┴──────────┴────────────┘

选择建议:
  小项目/原型:Chroma(一行代码启动)
  中等规模:Qdrant 或 pgvector
  企业级:Milvus(可扩展,功能全)
  Postgres用户:pgvector(零额外运维)
  学术研究:FAISS(最灵活)

4.3 向量检索的核心原理

向量检索 = 最大余弦相似度/最小欧氏距离

文本 → [0.12, 0.45, -0.23, 0.89, ..., 0.01]  ← 一个高维向量
                        ↓
检索:找一个向量和"用户问题的向量"最像的

常用距离度量:
  余弦相似度(最常用):
    cos(A, B) = A·B / (|A| × |B|)
    只关心方向,不关心长度 → 适合文本

  欧氏距离:
    d(A, B) = √Σ(A_i - B_i)²
    关心绝对距离 → 少用

检索算法(ANN近似最近邻):
  HNSW(Hierarchical Navigable Small World,层级导航小世界)
  → 2026年最流行的向量检索算法
  → 构建多层图结构
  → 从顶层快速定位 → 逐层细化

  IVF(Inverted File,倒排文件)
  → 先聚类 → 在最近的几个聚类中心里搜索
  → 速度更快但精度略低

五、检索优化:混合检索与重排序

5.1 混合检索——向量+关键词

纯向量检索的问题:
  "退货政策"和"退款流程"语义相似 → 向量距离近
  但用户问题中出现了精确的关键词 → 向量检索可能忽略

混合检索 = 向量检索 + BM25关键词检索

BM25(关键词检索):
  基于词频-逆文档频率的经典算法
  擅长:精确匹配特定术语
  不擅长:语义相似匹配

向量检索:
  基于语义匹配
  擅长:同义词、近义词
  不擅长:精确匹配专属名词

混合检索方式:

方式1:加权融合
  score = α × vector_score + (1-α) × bm25_score
  α通常在0.5-0.8之间
  
方式2:两阶段(推荐)
  Step 1:向量检索 Top-100
  Step 2:BM25检索 Top-100
  Step 3:合并并去重 → 取Top-5
  → 兼顾召回和精度

方式3:RRF(Reciprocal Rank Fusion)
  对多个检索结果进行排序融合
  RRF_score(d) = Σ 1/(k + rank(d))
  不需要调α权重

实践建议:
  通用知识:α=0.7(偏向量)
  专业术语多:α=0.5(平衡)
  代码/编号查询:α=0.3(偏BM25)

5.2 重排序(Re-ranking)

为什么需要重排序?
  向量检索是"粗略"的——从百万文档中快速筛出Top-100
  但Top-100中只有3-5个是真正相关的
  
  重排序 = 用更精细的模型对Top-100重新打分

重排序模型 vs 检索模型:
  检索模型(bge-large-zh):
    对整个文档库做检索
    速度快(毫秒级)
    精度足够筛到Top-100

  重排序模型(bge-reranker-large):
    只对Top-100做排序
    速度慢(但100条只需要几十ms)
    精度高(考虑query-doc交互)

如果不加重排序:
  用户:公司2026年招聘计划
  Top-5:[2026年预算案, 2025年招聘总结, 2026年1月会议纪要, 2026年培训计划, 部门重组方案]
  第一个结果不直接相关!
  → LLM会基于不完整的上下文回答

加了重排序后:
  Top-5:[2026年招聘计划, 2026年1月会议纪要中提到了招聘, 2026年预算案中的人力资源部分, ...]
  → 排除了不相关项
  → LLM基于高质量上下文回答

效果提升:
  不加重排序:检索精度 60-70%
  加重排序:检索精度 80-90%
  → 提升15-25个百分点

5.3 查询重写

用户的问题通常不是"检索友好"的:

不好的问题:
  "我想问一下,那个退货的事..."

好的检索query:
  "退货政策"

查询重写 → 用LLM把用户问题改写为检索友好的格式:

def rewrite_query(user_input):
    prompt = f"""将用户的问题改写成适合检索的关键词:
    
    用户输入:{user_input}
    
    检索关键词:"""
    
    response = llm.generate(prompt)
    return response

示例:
  用户:"帮我看看上次那个关于AI的那篇文章"
  改写后:"AI 文章 上次"
  检索命中率大幅提升!

六、RAG的高级范式演进

6.1 五代RAG演进

RAG从2020年提出到今天,经历了五代演进:

┌──────────────────────────────────────────────────────┐
│ 2020-2023:Naive RAG(朴素RAG)                      │
│   文档 → 分块 → 向量化 → 检索 → 拼接 → 生成         │
│   问题:检索质量不高,缺乏上下文意识                  │
│                                                       │
│ 2023-2024:Advanced RAG(进阶RAG)                    │
│   + 查询重写 + 重排序 + 混合检索 + 上下文压缩         │
│   提升检索精度,减少噪声                              │
│                                                       │
│ 2024-2025:Modular RAG(模块化RAG)                   │
│   自适应检索策略、多跳检索、检索路由                  │
│   不是一个固定流程,而是按需组合模块                  │
│                                                       │
│ 2025-2026:Agentic RAG(智能体RAG)                   │
│   Agent自行决定"什么时候检索、检索什么、怎么检索"     │
│   可以多次检索、多工具组合、多步推理                  │
│                                                       │
│ 2026+:融合架构(Fusion Architecture)                │
│   RAG + 长上下文模型 + Agent + 记忆系统               │
│   根据任务自动切换"是直接推理"还是"先检索再回答"      │
└──────────────────────────────────────────────────────┘

6.2 Naive RAG(朴素RAG)

最简单的RAG流程:

用户问题 → 向量检索 → Top-K文档 → 拼接Prompt → LLM生成

代码极为简单:
  docs = vector_db.similarity_search(query)
  prompt = f"基于以下文档回答问题:\n{docs}\n\n问题:{query}"
  answer = llm.generate(prompt)

优点:实现简单,一行代码
缺点:
  ❌ 一次检索,无法多步
  ❌ 检索质量不稳定
  ❌ 如果检索不到 → 直接失败
  ❌ 无法处理复杂查询

使用场景:快速验证、Demo
不适用:生产环境

6.3 Advanced RAG(进阶RAG)

进阶RAG在朴素RAG基础上增加了多个优化模块:

┌──────────────────────────────────────────────────────┐
│  用户问题                                               │
│    ↓                                                    │
│  查询重写 → 转为检索友好的关键词                        │
│    ↓                                                    │
│  混合检索 → 向量检索 + BM25                            │
│    ↓                                                    │
│  合并排序 → RRF融合多路结果                            │
│    ↓                                                    │
│  重排序 → bge-reranker精细排序                         │
│    ↓                                                    │
│  上下文压缩 → 去除冗余、只留关键信息                   │
│    ↓                                                    │
│  生成 → LLM基于高质量上下文回答                        │
└──────────────────────────────────────────────────────┘

每个模块的收益:
  查询重写:提升检索命中率 10-20%
  混合检索:提升召回率 10-15%
  重排序:提升精确率 15-25%
  上下文压缩:减少token消耗 30-50%,同时减少噪声

进阶RAG解决了朴素RAG的"检索质量差"问题
2026年大多数生产系统处于这个阶段

6.4 Corrective RAG与Self-RAG

Corrective RAG(纠正式RAG,2024):
  核心思想:检索后先判断"检索结果是否相关"
  如果检索结果不相关 → 修正检索策略

  流程:
  检索 → 检索质量评估
    ├─ 质量好 → 正常生成
    └─ 质量差 → 重新检索(改写后再查)
                ├─ 找到 → 正常生成
                └─ 找不到 → 诚实说"不知道"

  评估方式:用LLM判断或专门的小模型打分

Self-RAG(自省式RAG,2024):
  核心思想:检索结果要"引用"和"自评"
  
  流程:
  1. 模型自行决定是否需要检索
  2. 如果需要,检索并生成带引用的回答
  3. 模型自己评估每个引用是否支持回答
  4. 如果不支持 → 修正或删除该部分

  效果:通过"内部审核"机制,显著降低RAG输出中的幻觉

两者的区别:
  Corrective RAG:外部的"检索质量检查"
  Self-RAG:模型内部的"回答质量检查"
  两者可以组合使用

6.5 Agentic RAG(智能体RAG)

Agentic RAG = Agent + RAG
  不再是固定的"检索→生成"流程
  而是Agent根据情况自主决策检索策略

Agentic RAG的能力:

1️⃣ 要不要检索?
  简单问题:"2+2=?"
  → 不需要检索,直接回答

  事实性问题:"公司退货政策"
  → 需要检索,开始查知识库

2️⃣ 从哪里检索?
  内部知识库:"产品使用手册"
  → 从向量数据库检索

  实时信息:"今天的天气"
  → 调用天气API

  结构化数据:"上季度营收"
  → 查询数据库

3️⃣ 检索几次?
  单次检索:简单问题,一次性查
  多轮检索:复杂问题,查完一次基于结果再查
  
  多轮示例:
  Q:"对比GPT-4和Claude 3.5的数学能力"
  Round 1:检索"GPT-4数学能力"
  Round 2:检索"Claude 3.5数学能力"
  Round 3:检索"GPT-4 Claude 3.5 对比"
  → 逐步收敛信息

4️⃣ 怎么组合信息?
  从多个文档中提取信息
  对比、综合、总结
  → 生成完整回答

Agentic RAG的框架:
  LangGraph / CrewAI / AutoGen
  → 用Agent框架编排多步检索策略

七、RAG评估与调优

7.1 RAG评估框架

RAG评估需要分别评估"检索质量"和"生成质量":

┌──────────────────────────────────────────────────────┐
│ 维度              │ 指标         │ 目标值            │
├──────────────────┼─────────────┼──────────────────┤
│ 检索-命中率       │ Hit Rate     │ >90%             │
│ 检索-精确率       │ Precision@K  │ >80%             │
│ 检索-MRR          │ Mean Reciprocal Rank│ >0.85    │
│ 生成-忠实度       │ Faithfulness │ >90%             │
│ 生成-相关性       │ Relevancy    │ >85%             │
│ 生成-无幻觉       │ No Hallucination │ <5%         │
│ 端到端-满意度     │ User Rating  │ >4.0/5.0        │
└──────────────────────────────────────────────────────┘

RAGAS(RAG Assessment)框架:
  专门为RAG设计的评估框架
  包含:忠实度、相关性、上下文精度等指标
  使用LLM作为评估器(LLM-as-judge)

测试集构建:
  最少需要100-200条测试用例
  覆盖:常见问法、边缘情况、复杂查询
  每一条包含:问题 + 期望答案 + 知识库中的来源文档

7.2 常见问题与解决方案

RAG系统常见的问题和解决方案:

问题1:检索结果不相关
  表现:LLM生成的回答和问题无关
  原因:向量检索到的文档并不包含答案
  解决:
    ✅ 加混合检索(向量+BM25)
    ✅ 加重排序
    ✅ 检查chunk大小是否合适
    ✅ 改进嵌入模型

问题2:LLM不遵循上下文
  表现:检索到了正确文档,但模型自己编答案
  原因:System Prompt不够强硬
  解决:
    ✅ 加防幻觉Prompt:"只基于提供的上下文回答"
    ✅ 降低temperature到0
    ✅ 加引用要求:"引用文档编号"
    ✅ 加拒绝选项:"如果找不到相关信息,说不知道"

问题3:知识库覆盖不全
  表现:部分问题总是"找不到信息"
  原因:知识库中没有相关文档
  解决:
    ✅ 补充知识库
    ✅ 加搜索引擎回退
    ✅ 加"我不知道"处理流程

问题4:延迟太高
  表现:用户等太久
  原因:重排序 + LLM推理 + 网络消耗
  解决:
    ✅ 使用缓存(相同或相似问题直接返回)
    ✅ 优化chunk大小(减少上下文token数)
    ✅ 用更快的Embedding模型
    ✅ 流式输出(SSE)

问题5:上下文太长(超出模型窗口)
  表现:模型截断或丢失信息
  原因:Top-K文档加起来太长
  解决:
    ✅ 减小Top-K(从5降到3)
    ✅ 减小chunk大小
    ✅ 上下文压缩(去除冗余)
    ✅ 换更大窗口的模型

7.3 RAG调优路线图

按优先级排序的调优路线:

第一阶段(快速见效,0-1天):
  ✅ chunk_size调整(256→512→1024对比)
  ✅ Top-K调整(3→5→10对比)
  ✅ temperature降到0
  ✅ 加防幻觉System Prompt

第二阶段(进阶优化,1-3天):
  ✅ 换更好的Embedding模型(如bge-large-zh)
  ✅ 加混合检索(向量+BM25)
  ✅ 加重排序(如bge-reranker)

第三阶段(高级优化,3-7天):
  ✅ 查询重写
  ✅ 上下文压缩
  ✅ 多轮检索
  ✅ Corrective RAG(检索质量检查)

第四阶段(持续迭代):
  ✅ 构建测试集
  ✅ 自动化评估(RAGAS)
  ✅ A/B测试不同策略
  ✅ 用户反馈闭环

90%团队忽视的数据摄入层(第一阶段前就应该做好):
  ✅ 文档清洗(去除PDF乱码/页眉页脚)
  ✅ 语义分块(非固定长度)
  ✅ 元数据标注(来源可追溯)

八、2026年RAG全景:GraphRAG、Agentic RAG、融合架构

8.1 GraphRAG:知识图谱+向量检索

传统RAG的问题:
  文档分块后 → 块与块之间的"关系"丢失了

  "在第3章中提到张三负责项目A"
  "在第7章中提到项目A使用了技术B"
  → 两个块之间没有"关系链接"
  → 问"张三用了什么技术?"
  → 单独检索不到这个关系信息

GraphRAG = 知识图谱 + 向量检索

构建过程:
  1. 文档分块 → 向量化 → 存入向量DB(传统RAG)
  2. 同时提取实体和关系 → 构建知识图谱
     提取实体:张三、项目A、技术B
     提取关系:张三→负责→项目A
              项目A→使用→技术B
  3. 检索时:
     向量检索 → 找到相关块
     图检索 → 找到相关实体和关系
     合并 → 提供更完整的上下文

效果:
  需要"关系推理"的问题上表现更好
  "张三、李四和王五是什么关系?" 
  → 图检索直接给出关系路径
  → 传统向量检索很难做到

代价:
  构建知识图谱需要额外的计算
  需要调用LLM提取实体关系(成本较高)

8.2 Agentic RAG:智能体自主检索

2026年最前沿的RAG范式:

Agentic RAG不是"一个检索流程"
而是"一个能自我决策的检索系统"

核心能力:

Step 1:意图识别
  "把发票号INV-2026-001的付款状态发邮件给张三"
  → Agent理解这是一个"查询+发送"任务
  → 需要:查数据库 + 获取邮件地址 + 发送邮件

Step 2:工具选择
  Agent决定使用哪些工具:
  1. 查询发票数据库(SQL查询)
  2. 查找张三的邮件地址(检索通讯录)
  3. 发送邮件(调用邮件API)

Step 3:多步执行
  Round 1:查询数据库 → 发票状态"已付款"
  Round 2:检索通讯录 → zhang@company.com
  Round 3:调用邮件API → 发送成功

Step 4:结果综合
  "已完成:已查询发票INV-2026-001的付款状态(已付款),
   并已将结果发送至zhang@company.com。"

Agentic RAG的代价:
  需要更复杂的编排(如LangGraph)
  响应时间更长(多步执行)
  Agent可能"跑偏"(需要加约束)

8.3 长上下文+RAG融合

2026年的新趋势:
  当模型的上下文窗口达到1M token时
  还需要RAG吗?

答案是:需要,但用法变了。

长上下文不能解决所有问题:
  1M token ≈ 一本厚书
  但把整本《三体》放进上下文
  → 模型能够处理,但"注意力被稀释"
  → 问"罗辑的面壁计划" → 模型需要在100万字中找到相关内容

所以RAG + 长上下文结合:
  RAG先做"初筛":从1000万字的公司文档中
  筛出最相关的2万字的上下文
  长上下文再做"精读":在2万字的上下文中
  精确定位答案

这叫"分级检索":
  第一层:RAG从海量数据快速召回(百万→万级)
  第二层:长上下文窗口处理召回结果(万级→答案)
  第三层:需要时再深入(多轮追问)

这种融合架构是2026年企业级RAG的趋势方向

8.4 RAG演进全景图

RAG技术演进路线(2020-2026):

2020: Naive RAG
  └─ 基础检索+生成
  └─ "能用了,但不好用"

2023: Advanced RAG
  └─ +查询重写 + 混合检索 + 重排序
  └─ "生产可用了"

2024: Corrective RAG / Self-RAG
  └─ +检索质量自检 + 生成质量自检
  └─ "更可靠了"

2025: GraphRAG
  └─ +知识图谱实体关系
  └─ "能理解关系了"

2025-2026: Agentic RAG
  └─ +Agent自主决策检索策略
  └─ "会自己选择了"

2026+: 融合架构
  └─ RAG + 长上下文 + Agent + 记忆系统
  └─ "按需组合,适应一切场景"

九、实战:构建一个完整的RAG系统

9.1 Step 1:文档处理与索引

import os
from typing import List
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma

class DocumentIndexer:
    """文档索引器:加载 → 分块 → 向量化 → 存储"""
    
    def __init__(self, persist_dir: str = "./chroma_db"):
        self.persist_dir = persist_dir
        
        # Embedding模型(中文场景首选)
        self.embeddings = HuggingFaceEmbeddings(
            model_name="BAAI/bge-large-zh-v1.5",
            model_kwargs={'device': 'cpu'},
            encode_kwargs={'normalize_embeddings': True}
        )
        
        # 向量数据库
        self.vector_store = None
    
    def load_documents(self, docs_dir: str) -> List:
        """加载文档目录中的所有文件"""
        # 支持txt、md、pdf等格式
        loader = DirectoryLoader(
            docs_dir,
            glob="**/*.md",
            loader_cls=TextLoader,
            loader_kwargs={'encoding': 'utf-8'}
        )
        documents = loader.load()
        print(f"加载了 {len(documents)} 个文档")
        return documents
    
    def split_documents(self, documents: List) -> List:
        """智能分块"""
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=512,       # 每块大小
            chunk_overlap=64,     # 重叠大小
            separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""],
            length_function=len,
        )
        chunks = text_splitter.split_documents(documents)
        print(f"分割为 {len(chunks)} 个块")
        return chunks
    
    def build_index(self, docs_dir: str):
        """完整索引流程"""
        # 1. 加载
        docs = self.load_documents(docs_dir)
        
        # 2. 分块
        chunks = self.split_documents(docs)
        
        # 3. 向量化并存储
        self.vector_store = Chroma.from_documents(
            documents=chunks,
            embedding=self.embeddings,
            persist_directory=self.persist_dir
        )
        self.vector_store.persist()
        print(f"索引构建完成!共 {len(chunks)} 个块已存入 {self.persist_dir}")

# 使用
indexer = DocumentIndexer()
indexer.build_index("./company_docs")  # 你的文档目录

9.2 Step 2:检索器(混合检索+重排序)

from langchain.retrievers import BM25Retriever, EnsembleRetriever
from langchain_community.cross_encoders import HuggingFaceCrossEncoder

class AdvancedRetriever:
    """高级检索器:混合检索 + 重排序"""
    
    def __init__(self, vector_store, docs_chunks):
        # 向量检索器
        self.vector_retriever = vector_store.as_retriever(
            search_type="similarity",
            search_kwargs={"k": 20}  # 先召回20条
        )
        
        # BM25关键词检索器
        self.bm25_retriever = BM25Retriever.from_documents(
            docs_chunks,
            k=20
        )
        
        # 混合检索器
        self.ensemble_retriever = EnsembleRetriever(
            retrievers=[self.bm25_retriever, self.vector_retriever],
            weights=[0.3, 0.7]  # BM25占30%,向量占70%
        )
        
        # 重排序模型
        self.reranker = HuggingFaceCrossEncoder(
            model_name="BAAI/bge-reranker-v2-m3"
        )
    
    def retrieve(self, query: str, top_k: int = 5) -> List:
        """检索 + 重排序"""
        
        # 1. 混合检索 → 召回20条
        initial_docs = self.ensemble_retriever.invoke(query)
        
        # 2. 重排序 → 选出Top-5
        pairs = [[query, doc.page_content] for doc in initial_docs]
        scores = self.reranker.predict(pairs)
        
        # 按重排序分数排序
        scored_docs = sorted(
            zip(initial_docs, scores),
            key=lambda x: x[1],
            reverse=True
        )
        
        # 返回Top-K
        return [doc for doc, score in scored_docs[:top_k]]

# 使用
retriever = AdvancedRetriever(vector_store, chunks)
top_docs = retriever.retrieve("公司退货政策是什么")

9.3 Step 3:生成器(带防幻觉System Prompt)

from openai import OpenAI

class RAGGenerator:
    """生成器:基于检索结果生成回答"""
    
    def __init__(self, api_key: str, model: str = "deepseek-chat"):
        self.client = OpenAI(
            api_key=api_key,
            base_url="https://api.deepseek.com"  # 或 OpenAI 的 base_url
        )
        self.model = model
        
        # 防幻觉System Prompt
        self.system_prompt = """你是一个基于知识库回答的助手。
        
核心规则:
1. 只基于"提供的上下文"回答问题
2. 如果上下文没有相关信息,直接说"根据提供的资料,我无法回答这个问题"
3. 绝不编造事实、数据或引用
4. 引用时标注来源文档编号
5. 如果只能部分回答,明确指出哪些有依据、哪些是推断

回答格式:
- 有明确答案:根据[文档X],...
- 部分覆盖:根据资料,我可以回答...,但关于...没有相关信息
- 无法回答:抱歉,资料中没有相关信息。

置信度标注(回答末尾):
{置信度: 高/中/低/无法回答}"""

    def generate(self, query: str, context_docs: List) -> str:
        """基于上下文生成回答"""
        
        # 构建带引用的上下文
        context = "\n\n".join([
            f"[文档{i+1}]: {doc.page_content}"
            for i, doc in enumerate(context_docs)
        ])
        
        # 构造Prompt
        user_prompt = f"""请基于以下资料回答问题:

===== 资料 =====
{context}

===== 问题 =====
{query}

===== 回答 ====="""
        
        # 调用LLM(低温度,防幻觉)
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": self.system_prompt},
                {"role": "user", "content": user_prompt}
            ],
            temperature=0.1,        # 低温度
            max_tokens=1024,
            stream=False
        )
        
        answer = response.choices[0].message.content
        
        # 附上来源信息
        sources = [f"文档{i+1}" for i in range(len(context_docs))]
        
        return f"{answer}\n\n---\n📚 来源: {', '.join(sources)}"

9.4 Step 4:完整RAG系统

class CompleteRAG:
    """完整的RAG问答系统"""
    
    def __init__(self, api_key: str):
        # 索引器
        self.indexer = DocumentIndexer()
        
        # 加载已有索引
        self.vector_store = Chroma(
            persist_directory="./chroma_db",
            embedding_function=self.indexer.embeddings
        )
        
        # 检索器
        all_docs = self.vector_store.get()
        # 注意:需要把Chroma文档转回LangChain文档格式
        # 此处简化处理
        
        # 生成器
        self.generator = RAGGenerator(api_key)
    
    def ask(self, query: str) -> str:
        """完整流程:检索 → 生成"""
        
        # 1. 检索
        docs = self.vector_store.similarity_search(query, k=5)
        
        # 2. 检查检索结果
        if not docs:
            return "抱歉,知识库中没有相关信息。"
        
        # 3. 生成
        answer = self.generator.generate(query, docs)
        
        return answer
    
    def ask_with_trace(self, query: str) -> dict:
        """带追踪信息的查询(调试用)"""
        docs = self.vector_store.similarity_search(query, k=5)
        
        return {
            "query": query,
            "retrieved_docs": [
                {"content": d.page_content[:100] + "...", "source": d.metadata.get("source", "未知")}
                for d in docs
            ],
            "answer": self.generator.generate(query, docs)
        }

# 使用
rag = CompleteRAG(api_key="your-api-key")

# 普通查询
print(rag.ask("公司年假政策是什么?"))

# 带追踪的查询
result = rag.ask_with_trace("2026年招聘计划")
print(f"问题: {result['query']}")
print(f"检索来源: {[d['source'] for d in result['retrieved_docs']]}")
print(f"回答: {result['answer']}")

📌 总结

RAG核心要点:

1️⃣ 为什么需要RAG
   模型知识截止于训练日期
   模型不知道你的私有数据
   模型可能编造答案
   RAG用"检索替代记忆"

2️⃣ 三代演进
   Naive RAG(2020):基础检索+生成
   Advanced RAG(2023):+重写+混合+重排序
   Agentic RAG(2025-2026):Agent自主决策检索策略

3️⃣ 关键组件
   文档清洗 ← 最容易被忽视但最关键
   Chunking → 256-512token + 10-20%重叠
   Embedding → bge-large-zh(中文首选)
   混合检索 → 向量+BM25
   重排序 → bge-reranker

4️⃣ 2026年最前沿
   GraphRAG:知识图谱+向量检索
   Agentic RAG:Agent自动决策
   融合架构:RAG+长上下文+Agent+记忆系统

5️⃣ 调优优先级
   ① 数据清理+分块策略(基础)
   ② 混合检索+重排序(快速见效)
   ③ 查询重写+上下文压缩(进一步提升)
   ④ 构建测试集+自动化评估(持续优化)


🔗 延伸阅读

  • 【AI基础篇08】大模型评估指标:困惑度、BLEU、ROUGE
  • 【AI基础篇09】大模型幻觉问题:为什么AI会一本正经地胡说八道?
  • 【AI基础篇11】大模型部署:从vLLM到ollama(下一篇)
  • 【AI基础篇04】Tokenization

觉得有帮助?点赞收藏!基础概念篇10篇完成,下⼀篇我们进入模型架构与应用模块——大模型部署:从vLLM到ollama! 🚀

标签:人工智能、大模型、RAG、检索增强生成、GraphRAG、Agentic RAG、向量检索、知识库

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐