一文搞懂 RAG(检索增强生成):从 LLM 幻觉到智能检索,手把手带你理解核心原理与代码实现

4 阅读13分钟

一文搞懂 RAG(检索增强生成):从 LLM 幻觉到智能检索,手把手带你理解核心原理与代码实现

引言

想象一下这个场景:你问 ChatGPT 一个关于你们公司内部制度的问题,它信誓旦旦地给出一段看似合理、实则完全编造的答案——这就是大模型的幻觉(Hallucination)。

大模型就像一个知识渊博但"与世隔绝"的学者,它的知识截止于训练数据的日期。之后发生的事情、你企业内部没有公开的文档,它全都不知道。但关键问题在于——它不会承认自己不知道,而是会一本正经地胡说八道。

那怎么解决呢?答案就是我们今天的主角——RAG(Retrieval-Augmented Generation,检索增强生成)


一、大模型的幻觉从何而来?

1.1 大模型的知识边界

LLM(大语言模型)的知识完全取决于训练时喂给它的数据集。训练数据里没有的东西,它就不可能"知道"。这带来了几个结构性的问题:

问题描述
📚 知识有截止日期训练数据只覆盖到某一时间点,之后的新信息它无从得知
🔒 无法访问私有数据企业内部文档、机密资料不会出现在公开训练数据中
💬 不懂装懂面对不知道的问题,模型倾向于"编造"而非承认无知

1.2 幻觉的本质

所谓"幻觉",就是模型生成的内容与事实不符。这并非模型故意撒谎,而是因为:

  • 模型的输出本质上是概率性的——它只是在预测下一个最可能的 token
  • 当模型缺乏相关知识时,它依然会根据语言模式"推测"出一个答案
  • 这个推测在语言上可能通顺,但在事实上可能完全错误

核心矛盾:用户要查询的内容,模型不知道;模型却能生成一段看似合理的回答。


二、解决幻觉的两条路:Fine-tuning vs RAG

想一下,怎么让大模型知道它不知道的东西?

2.1 微调(Fine-tuning)

把新知识直接训练进模型参数里。

  • ✅ 优点:知识融入模型本身,推理时无需额外检索
  • ❌ 缺点:成本高昂,需要大量标注数据和 GPU 算力,而且知识更新就要重新训练

2.2 RAG(检索增强生成)

不给模型"植入记忆",而是在提问前先去知识库里查资料,把查到的内容塞进 Prompt 里再让模型回答。

  • ✅ 优点:成本低,知识库随时更新,可解释性强
  • ❌ 缺点:依赖检索质量,检索不到就回答不了

类比理解:

  • Fine-tuning = 让一个人去上全日制大学(时间长、成本高、知识固化)
  • RAG = 给一个人配了搜索引擎和百科全书(随时查、成本低、知识实时更新)

对于大多数企业场景来说,RAG 是现阶段性价比最高的方案。


三、什么是 RAG?

3.1 名词拆解

单词含义
Retrieval(检索)去知识库里检索与用户问题相关的文档片段
Augmented(增强)把检索到的内容作为背景知识,增强到 Prompt 里
Generation(生成)让大模型基于增强后的 Prompt 生成回答

3.2 RAG 的工作流程

用户提问 → 检索知识库(找到相关文档片段)→ 拼接到 Prompt 中 → 大模型生成回答

具体来说:

  1. 用户提出一个问题
  2. 去知识库中检索 —— 找到与问题语义最相关的文档片段(分片:按章节/按页/按段落切分的自然语义段落)
  3. 拼接到 Prompt 中 —— 把检索到的文档片段作为背景知识,加入原始 Prompt
  4. 大模型回答 —— 基于这些背景知识生成准确、可靠的回答

这就是 RAG 的整个流程:先检索,再增强,最后生成


四、核心技术:从关键词到向量语义搜索

4.1 关键词搜索为什么不太行?

传统搜索依赖文本匹配

  • 你搜"苹果好吃吗" → 匹配到包含"苹果"的文档
  • 但如果你搜"那种红色的、咬起来嘎嘣脆的水果怎么样?" → 关键词匹配可能完全找不到"苹果"相关内容

问题所在:语言表达千变万化,同一个意思可以有无数种说法。关键词搜索无法理解语义。

4.2 向量语义搜索

这就是 RAG 的第一个技术突破:向量(Vector)语义搜索

核心思想:把文字转换成一个多维空间中的坐标(向量),语义相近的文字在空间中离得近。

用一个直观的例子理解:

假设我们定义两个维度来描述食物:

  • 维度一(食用性) :0 = 不可食用,1 = 高度可食用
  • 维度二(硬度) :0 = 液体状,1 = 非常坚硬

那么:

食物向量坐标 [食用性, 硬度]
🍎 苹果[0.9, 0.3]
🍌 香蕉[0.9, 0.1]
🍓 水果(抽象概念)[0.9, 0.3]
🪨 石头[0.1, 0.9]

向量空间中:

  • 苹果水果的距离很近([0.9, 0.3] ≈ [0.9, 0.3])→ 语义高度相关 ✅
  • 苹果香蕉的距离也较近([0.9, 0.3] vs [0.9, 0.1])→ 水果之间语义相关 ✅
  • 苹果石头的距离很远([0.9, 0.3] vs [0.1, 0.9])→ 语义无关 ❌

这只是一个 2 维的简化示例。在实际应用中,向量通常有几百到上千个维度,可以表达极其丰富的语义信息。

4.3 相似度计算

两个向量的相似度通常用**余弦相似度(Cosine Similarity)**来衡量:

  • 夹角越小 → 余弦值越接近 1 → 语义越相似
  • 夹角越大 → 余弦值越接近 -1 → 语义越不相似

通过这种方式,即使用户的提问用词完全不同,只要能映射到相近的向量空间位置,就能找到语义相关的文档。


五、嵌入模型(Embedding Model)

要把文字变成向量,就需要一个专门的模型——嵌入模型(Embedding Model)

5.1 Embedding Model vs Generative Model

类型用途成本
Embedding Model将文本转换为向量表示💰 相对便宜
Generative Model(如 GPT、Claude)生成文本、对话、推理💰💰💰 昂贵

RAG 中两种模型配合使用:

  • Embedding Model:负责把知识库文档和用户问题都向量化
  • Generative Model:负责根据检索结果生成最终回答

5.2 能处理多种数据格式

Embedding 技术不仅限于文字,还可以处理:

  • 📝 文本(txt、markdown、pdf)
  • 🖼️ 图片(通过多模态 embedding 模型)
  • 🎤 语音(先转文字再 embedding)
  • 📊 各种格式的文档

所有内容向量化后,就都能进行语义搜索了。


六、RAG 的两个关键突破

6.1 文档怎么切分(Chunking)

知识库里的文档往往很长,不可能一次性全部放入 Prompt(有上下文长度限制)。需要把文档切分成一个个有独立语义的片段(Chunk)。

切分策略可以按:

策略适用场景示例
📖 按章节结构化文档、书籍第一章、第二章...
📄 按页PDF 等固定版式文档第1页、第2页...
📝 按段落一般文章、文档每个自然段落一个 chunk
✂️ 按固定大小通用策略每 512 tokens 一个 chunk(可重叠)

好的切分策略是 RAG 效果的关键之一。切得太大 → 检索不精准;切得太小 → 丢失上下文语义。

6.2 文档怎么查(向量检索)

第二个突破:向量数据库(Vector Database)

传统数据库(MySQL、PostgreSQL 等)无法高效处理向量的相似度搜索。向量数据库专为此而生:

  • 📊 存储向量:将切割后的文档片段通过 embedding 模型转换成向量存储起来
  • 🔍 相似度搜索:查询时,把用户问题也向量化,在数据库中找最相似的向量
  • ⚡ 高性能:支持海量向量的快速检索(通过 ANN / 近似最近邻算法)
  • 💾 持久化存储:数据不丢失,可反复使用

常见向量数据库:Milvus、Pinecone、Weaviate、Qdrant、Chroma 等。


七、RAG 完整架构图

把整个流程串起来,RAG 的完整架构如下:

┌─────────────────────────────────────────────────────────────────┐
│                        RAG 系统架构                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────┐     ┌──────────────┐     ┌──────────────────┐    │
│  │ 知识库文档 │ ──► │ 文档切分      │ ──► │ Embedding Model  │    │
│  │ (txt/pdf/ │     │ (Chunking)   │     │ (文本 → 向量)    │    │
│  │  md/...)  │     │              │     │                  │    │
│  └──────────┘     └──────────────┘     └────────┬─────────┘    │
│                                                  │               │
│                                                  ▼               │
│                                         ┌──────────────────┐    │
│                                         │   向量数据库       │    │
│                                         │ (存储+索引)       │    │
│                                         └────────┬─────────┘    │
│                                                  │               │
│  ┌──────────┐     ┌──────────────┐              │               │
│  │ 用户问题   │ ──► │ Embedding    │ ────────────┘               │
│  │          │     │ (问题→向量)   │    相似度搜索                 │
│  └──────────┘     └──────────────┘         │                     │
│                                             ▼                     │
│                                    ┌──────────────────┐         │
│                                    │  相关文档片段      │         │
│                                    │  (检索结果Top-K)   │         │
│                                    └────────┬─────────┘         │
│                                             │                    │
│                                             ▼                    │
│  ┌──────────────────────────────────────────────────────┐       │
│  │  增强后的 Prompt = 原始问题 + 检索到的背景文档片段       │       │
│  └──────────────────────────┬───────────────────────────┘       │
│                             │                                    │
│                             ▼                                    │
│                    ┌──────────────────┐                          │
│                    │  Generative Model │                         │
│                    │  (生成最终回答)    │                         │
│                    └──────────────────┘                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

一句话总结:

在原始 Prompt 给到大模型之前,先查询知识库,把相关的文档片段作为背景知识加入到 Prompt 里,再让大模型回答。这就是 RAG。


八、代码实战:用 LangChain 构建 RAG 系统

光说不练假把式,我们来看一个基于 LangChain 的 RAG 实现示例。

8.1 环境准备

{
  "name": "rag-test",
  "dependencies": {
    "@langchain/core": "^1.2.2",
    "@langchain/openai": "^1.5.5",
    "dotenv": "^17.4.2"
  }
}

三个核心依赖:

  • @langchain/openai:统一封装 OpenAI 兼容的 LLM 和 Embedding 接口
  • @langchain/core:LangChain 核心库,提供 Document、Tool 等抽象
  • dotenv:环境变量管理

8.2 初始化模型

import 'dotenv/config'
import { ChatOpenAI, OpenAIEmbeddings } from 'langchain/openai'
import { Document } from 'langchain/core/documents'

// 初始化 Chat 模型(用于最终生成回答)
const model = new ChatOpenAI({
    temperature: 0,                    // 温度设为 0,确保回答稳定
    model: process.env.MODEL_NAME,     // 例如:gpt-4o / deepseek-chat
    apiKey: process.env.OPENAI_API_KEY,
    configuration: {
        baseURL: process.env.OPENAI_BASE_URL,
    }
})

// 初始化 Embedding 模型(用于文本向量化)
const embeddings = new OpenAIEmbeddings({
    apiKey: process.env.OPENAI_API_KEY,
    model: process.env.EMBEDDINGS_MODEL_NAME,  // 例如:text-embedding-3-small
    configuration: {
        baseURL: process.env.OPENAI_BASE_URL,
    }
})

关键点

  • temperature: 0 → 确保回答的确定性和一致性(RAG 场景不需要创造性)
  • Embedding 模型和 Chat 模型是两个独立的模型,各司其职

8.3 构建知识库文档

我们用一个关于"光光和东东"的儿童故事作为知识库:

const documents = [
  new Document({
    pageContent: `光光是一个活泼开朗的小男孩,他有一双明亮的大眼睛,
    总是带着灿烂的笑容。光光最喜欢的事情就是和朋友们一起玩耍,他特别
    擅长踢足球,每次在球场上奔跑时,就像一道阳光一样充满活力。`,
    metadata: {
      chapter: 1,
      character: "光光",
      type: "角色介绍",
      mood: "活泼"
    },
  }),
  new Document({
    pageContent: `东东是光光最好的朋友,他是一个安静而聪明的男孩。
    东东喜欢读书和画画,他的画总是充满了想象力。虽然性格不同,但东东
    和光光从幼儿园就认识了,他们一起度过了无数个快乐的时光。`,
    metadata: {
      chapter: 2,
      character: "东东",
      type: "角色介绍",
      mood: "温馨"
    },
  }),
  new Document({
    pageContent: `有一天,学校要举办一场足球比赛,光光非常兴奋,
    他邀请东东一起参加。但是东东从来没有踢过足球,他担心自己会拖累
    光光。光光看出了东东的担忧,他拍着东东的肩膀说:"没关系,我们
    一起练习,我相信你一定能行的!"`,
    metadata: {
      chapter: 3,
      character: "光光和东东",
      type: "友情情节",
      mood: "鼓励",
    },
  }),
  // ... 更多文档片段
]

Document 的两个核心字段:

字段作用是否参与向量化
pageContent文档正文内容,是检索匹配的核心✅ 是
metadata元信息(章节、角色、类型、情感等),用于后续过滤和溯源❌ 否

metadata 不参与向量化计算,但它非常重要——可以帮助你在检索后做元数据过滤来源标注分类展示等。

提问:

const question = '光光和东东怎么成为朋友的?'

8.4 RAG 检索流程(核心逻辑扩展)

虽然原始代码在此处结束了,但完整的 RAG 流程应该是这样的:

// Step 1: 将知识库文档向量化并存入向量数据库
const vectorStore = await MemoryVectorStore.fromDocuments(
    documents,
    embeddings
)

// Step 2: 将用户问题向量化,并在向量数据库中检索
const relevantDocs = await vectorStore.similaritySearch(question, 3)
// 返回与问题语义最相似的前 3 个文档片段

// Step 3: 构建增强后的 Prompt
const context = relevantDocs.map(doc => doc.pageContent).join('\n\n')

const augmentedPrompt = `
请基于以下背景知识回答问题。如果背景知识中没有相关信息,请明确说明。

【背景知识】
${context}

【用户问题】
${question}

【回答】
`

// Step 4: 调用大模型生成回答
const response = await model.invoke(augmentedPrompt)
console.log(response.content)

预期的大模型回答(基于检索到的文档):

光光和东东从幼儿园就认识了。虽然性格不同——光光活泼开朗、擅长踢足球,东东安静聪明、喜欢读书画画——但他们从幼儿园开始就是最好的朋友。光光邀请东东一起参加足球比赛,并耐心地教东东踢球;东东也为光光画画作为回报。在足球比赛中,东东传出一个漂亮的球,光光射门得分,他们的友谊变得更加深厚了。

这样的回答完全基于知识库中检索到的真实内容,不会产生幻觉


九、RAG 在 Agent 生态系统中的定位

RAG 不是一个孤立的技术,它是构建AI Agent 的核心模块之一。

9.1 Agent 的公式

Agent = LLM + Memory + Tool + RAG + MCP + Skills
模块解决的问题说明
LLM基础推理能力大模型的"大脑"
Memory无状态问题记住上下文、用户偏好、项目规范
Tool无法操作外部世界调用 API、操作文件、执行命令
RAG知识盲区检索内部知识库获取私有信息
MCP跨进程/跨语言工具调用标准化 Tool 通信协议
Skills复杂任务编排可复用的"技能包"

9.2 RAG vs MCP:两种上下文扩展方式

  • RAG:先检索,再增强 —— 解决"知识不在训练数据中"的问题
  • MCP:跨进程提供 Tool + Resource —— 解决"工具跨语言/跨项目复用"的问题

两者并不互斥,可以结合使用。例如:通过 MCP 连接一个远程的知识库服务,然后在 Agent 内部用 RAG 的方式检索其中的文档。


十、RAG 的进阶挑战与优化方向

掌握 RAG 的基本概念只是第一步,在实际落地中你还会遇到这些挑战:

挑战描述优化方向
🔪 文档切分策略chunk 太大检索不精准,太小丢失语义语义切分、滑动窗口、层级索引
🔍 检索精度召回率 vs 准确率的平衡混合检索(向量+关键词)、ReRank 重排序
📊 知识更新文档变更后如何同步增量索引、版本管理
🎯 多轮对话后续问题可能指代之前的上下文Query 改写、对话历史向量化
🖼️ 多模态 RAG图片、表格、代码等非文本内容多模态 Embedding + 结构化解析

十一、总结

RAG(检索增强生成)的精髓可以用三句话概括:

  1. 大模型不知道的东西,它不会承认不知道,而是会胡说(幻觉)
  2. RAG 的核心思路:问之前先去知识库查一下,查到的东西塞进 Prompt 再让模型回答
  3. 向量 + 语义搜索是 RAG 实现的技术基石,嵌入模型把文字变成向量,向量数据库做相似度检索

从更大的视角看,RAG 是 Harness Engineering(挽具工程) 的典型代表之一。LLM 就像一个强大的引擎,但引擎再牛,没有好的变速箱、刹车和仪表盘,这辆车就没法上路。RAG 就是给模型装上了一个"知识检索系统",让它的能力可以被稳定、可靠地驾驭。

引擎是模型,Harness 就是装着引擎的车。RAG 是这辆车上最关键的零件之一。


参考资料