一文搞懂 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 中 → 大模型生成回答
具体来说:
- 用户提出一个问题
- 去知识库中检索 ------ 找到与问题语义最相关的文档片段(分片:按章节/按页/按段落切分的自然语义段落)
- 拼接到 Prompt 中 ------ 把检索到的文档片段作为背景知识,加入原始 Prompt
- 大模型回答 ------ 基于这些背景知识生成准确、可靠的回答
这就是 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 的完整架构如下:
scss
┌─────────────────────────────────────────────────────────────────┐
│ RAG 系统架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ 知识库文档 │ ──► │ 文档切分 │ ──► │ Embedding Model │ │
│ │ (txt/pdf/ │ │ (Chunking) │ │ (文本 → 向量) │ │
│ │ md/...) │ │ │ │ │ │
│ └──────────┘ └──────────────┘ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ 向量数据库 │ │
│ │ (存储+索引) │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌──────────┐ ┌──────────────┐ │ │
│ │ 用户问题 │ ──► │ Embedding │ ────────────┘ │
│ │ │ │ (问题→向量) │ 相似度搜索 │
│ └──────────┘ └──────────────┘ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ 相关文档片段 │ │
│ │ (检索结果Top-K) │ │
│ └────────┬─────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 增强后的 Prompt = 原始问题 + 检索到的背景文档片段 │ │
│ └──────────────────────────┬───────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ Generative Model │ │
│ │ (生成最终回答) │ │
│ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
一句话总结:
在原始 Prompt 给到大模型之前,先查询知识库,把相关的文档片段作为背景知识加入到 Prompt 里,再让大模型回答。这就是 RAG。
八、代码实战:用 LangChain 构建 RAG 系统
光说不练假把式,我们来看一个基于 LangChain 的 RAG 实现示例。
8.1 环境准备
perl
{
"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 初始化模型
arduino
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 构建知识库文档
我们用一个关于"光光和东东"的儿童故事作为知识库:
go
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不参与向量化计算,但它非常重要------可以帮助你在检索后做元数据过滤 、来源标注 、分类展示等。
提问:
ini
const question = '光光和东东怎么成为朋友的?'
8.4 RAG 检索流程(核心逻辑扩展)
虽然原始代码在此处结束了,但完整的 RAG 流程应该是这样的:
javascript
// 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 的公式
ini
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(检索增强生成)的精髓可以用三句话概括:
- 大模型不知道的东西,它不会承认不知道,而是会胡说(幻觉)
- RAG 的核心思路:问之前先去知识库查一下,查到的东西塞进 Prompt 再让模型回答
- 向量 + 语义搜索是 RAG 实现的技术基石,嵌入模型把文字变成向量,向量数据库做相似度检索
从更大的视角看,RAG 是 Harness Engineering(挽具工程) 的典型代表之一。LLM 就像一个强大的引擎,但引擎再牛,没有好的变速箱、刹车和仪表盘,这辆车就没法上路。RAG 就是给模型装上了一个"知识检索系统",让它的能力可以被稳定、可靠地驾驭。
引擎是模型,Harness 就是装着引擎的车。RAG 是这辆车上最关键的零件之一。
参考资料
- LangChain 官方文档:js.langchain.com/
- OpenAI Embeddings 文档:platform.openai.com/docs/guides...
- 向量数据库选型对比:Milvus / Pinecone / Chroma / Weaviate / Qdrant