🔥 RAG 到底是怎么工作的?掰开揉碎了给你讲明白!

🔥 RAG 到底是怎么工作的?掰开揉碎了给你讲明白!

摘要:面试被问 RAG 支支吾吾?看完这篇,你能把 RAG 的每一步流程画出来讲给面试官听。本文用一个完整 Demo,从零带你走通 RAG 全流程。


📌 前言

最近 AI 领域有个词出现频率极高------RAG(Retrieval-Augmented Generation,检索增强生成)。

不管是面试还是实际项目,RAG 已经成了大模型应用的标配方案。但很多同学对 RAG 的理解还停留在"好像跟向量数据库有关"这个层面。

我自己在项目中用 LangChain + RAG 搭建过知识问答系统,过程中踩了不少坑------chunk size 选错导致检索质量断崖下降、Embedding 模型换了一个效果完全不同、Top-K 设太大反而干扰了 LLM 判断......这些实战经验让我对 RAG 每一步的"为什么"有了更深的理解。

今天这篇文章,我会用一个完整可运行的 Demo,把 RAG 的每一步掰开揉碎了讲清楚。看完之后,你不仅能回答"RAG 是什么",还能回答"RAG 内部到底经历了哪些步骤"。


RAG picture

🎯 本文适合谁

  • 想理解 RAG 底层流程的开发者
  • 面试前需要梳理 RAG 知识点的同学
  • 正在做或准备做大模型应用的工程师
  • 对 LangChain 感兴趣想快速上手的同学

📚 先搞懂:RAG 到底是什么?

一句话解释

RAG = 先从知识库中检索相关内容,再把检索结果喂给大模型生成回答。

为什么要用 RAG?

大模型有两个致命短板:

问题 说明 RAG 怎么解决
🧠 知识有截止日期 GPT 的训练数据有时间边界 实时检索最新知识库
🤥 会一本正经胡说八道 "幻觉"问题 给它真实文档作为依据
📄 无法访问私有数据 不知道你公司的内部文档 把私有数据建成知识库

一句话:RAG 让大模型"开卷考试",而不是"闭卷瞎编"。


🔍 RAG 全流程拆解(重点!)

一张图理解 RAG 的核心流程:

scss 复制代码
┌─────────────────────────────────────────────────────────┐
│                    RAG 完整流程                           │
│                                                         │
│   📄 知识库文档                                          │
│       │                                                 │
│       ▼                                                 │
│   ┌──────────┐    ┌──────────┐    ┌──────────────┐      │
│   │ Document  │───▶│Embedding │───▶│ 向量数据库    │      │
│   │  构建     │    │  向量化   │    │ (VectorStore) │      │
│   └──────────┘    └──────────┘    └──────┬───────┘      │
│                                          │              │
│                                          │ ② 检索       │
│   用户提问 ──── Embedding ───────────────▶│              │
│    "东东和光光                              │              │
│     怎么成为朋友的?"                      ▼              │
│                                    ┌──────────┐          │
│                                    │ Top-K    │          │
│                                    │ 相关文档  │          │
│                                    └────┬─────┘          │
│                                         │               │
│                                         ▼               │
│                              ┌──────────────────┐        │
│                              │  组装 Prompt      │        │
│                              │  = 上下文 + 问题   │        │
│                              └────────┬─────────┘        │
│                                       │                 │
│                                       ▼                 │
│                              ┌──────────────────┐        │
│                              │   LLM 生成回答    │        │
│                              └──────────────────┘        │
└─────────────────────────────────────────────────────────┘

整个流程可以拆成 3 大阶段、6 个步骤

阶段 步骤 说明
📥 索引阶段 ① 构建 Document 把原始数据切成文档片段
② Embedding 向量化 用模型把文本转成向量
③ 存入向量数据库 把向量存起来供后续检索
🔍 检索阶段 ④ 用户提问向量化 把问题也转成向量
⑤ 相似度检索 在向量库中找最相关的 Top-K 文档
✍️ 生成阶段 ⑥ Augmented Prompt + LLM 生成 把检索结果塞进 Prompt,让 LLM 回答

接下来我们每一步都用代码讲清楚


🛠️ 技术栈

技术 用途
LangChain LLM 开发框架,提供 RAG 全套组件
@langchain/openai ChatOpenAI + OpenAIEmbeddings
@langchain/core Document 等核心抽象
@langchain/classic MemoryVectorStore 内存向量存储

安装命令:

bash 复制代码
pnpm add @langchain/openai @langchain/core @langchain/classic dotenv

📝 代码实现:一步步走通 RAG

第一步:构建 Document(知识库的最小单元)

RAG 的起点是知识库 。知识库由一个个 Document 组成,每个 Document 包含两部分:

  • pageContent:要被向量化的文本内容(参与语义搜索)
  • metadata:元数据,不参与向量化,但可以用于过滤和溯源
javascript 复制代码
import { Document } from '@langchain/core/documents';

const documents = [
  new Document({
    pageContent: `光光是一个活泼开朗的小男孩,他有一双明亮的大眼睛,
      总是带着灿烂的笑容。光光最喜欢的事情就是和朋友们一起玩耍,
      他特别擅长踢足球,每次在球场上奔跑时,就像一道阳光一样充满活力。`,
    metadata: {
      chapter: 1,
      character: "光光",
      type: "角色介绍",
      mood: "活泼"
    },
  }),
  new Document({
    pageContent: `东东是光光最好的朋友,他是一个安静而聪明的男孩。
      东东喜欢读书和画画,他的画总是充满了想象力。虽然性格不同,
      但东东和光光从幼儿园就认识了,他们一起度过了无数个快乐的时光。`,
    metadata: {
      chapter: 2,
      character: "东东",
      type: "角色介绍",
      mood: "温馨"
    },
  }),
  // ... 更多 Document
];

💡 关键理解pageContent 是会被 Embedding 模型转成向量的,而 metadata 不会。metadata 的作用是在检索之后做过滤、排序、溯源

在真实项目中,知识库可能来自:

  • PDF 文件 → 按段落切分
  • 网页内容 → 按章节切分
  • 数据库记录 → 按条目构建
  • 音视频 → 先转文字再切分

第二步:Embedding 向量化

Embedding 是 RAG 的核心中的核心。

它的作用是把人类的自然语言文本,转换成一组高维浮点数向量。语义相近的文本,向量在空间中的距离就近;语义无关的文本,距离就远。

javascript 复制代码
import { OpenAIEmbeddings } from '@langchain/openai';

const embedding = new OpenAIEmbeddings({
  model: process.env.EMBEDDINGS_MODEL_NAME,  // 如 text-embedding-3-small
  apiKey: process.env.OPENAI_API_KEY,
});

举个直观的例子:

scss 复制代码
"光光喜欢踢足球"  →  [0.12, -0.34, 0.56, ..., 0.78]   (1536维)
"光光擅长运动"    →  [0.11, -0.32, 0.55, ..., 0.80]   (距离很近 ✅)
"今天天气不错"    →  [0.89, 0.12, -0.67, ..., 0.23]   (距离很远 ❌)

💡 为什么要用向量? 因为传统的关键词搜索搜不到语义。你搜"光光擅长什么运动",关键词匹配可能找不到结果,但向量搜索能理解"踢足球"就是一种"运动"。


第三步:存入向量数据库(VectorStore)

有了 Embedding 模型,我们把所有 Document 的 pageContent 向量化后存入向量数据库。

这个 Demo 用的是 MemoryVectorStore------最轻量的内存向量存储,适合学习和原型验证。生产环境一般用 Pinecone、Milvus、Chroma 等。

javascript 复制代码
import { MemoryVectorStore } from '@langchain/classic/vectorstores/memory';

// 一行代码完成:向量化所有 documents → 存入内存向量库
const vectorStore = await MemoryVectorStore.fromDocuments(documents, embedding);

这一行代码内部做了什么?

css 复制代码
documents[0].pageContent  ──Embedding──▶  向量[0]  ──存入──▶  向量数据库
documents[1].pageContent  ──Embedding──▶  向量[1]  ──存入──▶  向量数据库
documents[2].pageContent  ──Embedding──▶  向量[2]  ──存入──▶  向量数据库
...                         ...            ...                  ...

💡 索引阶段到此结束。 此时知识库已经建好,向量数据库里存满了文档的向量表示,随时可以被检索。


第四步 & 第五步:检索(Retriever)

LangChain 提供了 Retriever(检索器),它是向量检索的标准入口。

javascript 复制代码
// 创建检索器,k=3 表示返回最相似的 3 条文档
const retriever = vectorStore.asRetriever({ k: 3 });

const question = "东东和光光是怎么成为朋友的?";

// 检索!
const docs = await retriever.invoke(question);

retriever.invoke(question) 内部做了什么?

ini 复制代码
"东东和光光是怎么成为朋友的?"
        │
        ▼
  Embedding 向量化
        │
        ▼
  在向量数据库中计算余弦相似度
        │
        ▼
  按相似度排序,取 Top-K(k=3)
        │
        ▼
  返回 3 个最相关的 Document 对象
进阶:查看相似度分数

如果你不仅想看结果,还想看每条结果有多相关 ,可以用 similaritySearchWithScore

javascript 复制代码
const scoredResults = await vectorStore.similaritySearchWithScore(question, 3);

scoredResults.forEach(([doc, score], i) => {
  // score 是距离,越小越相似
  // 转换为相似度:similarity = 1 - score
  const similarity = (1 - score).toFixed(4);
  console.log(`[文档 ${i + 1}] 相似度: ${similarity}`);
  console.log(`内容: ${doc.pageContent.substring(0, 50)}...`);
  console.log(`元数据: 章节=${doc.metadata.chapter}, 角色=${doc.metadata.character}`);
});

💡 retriever.invoke vs similaritySearchWithScore

方法 说明
retriever.invoke() 标准检索入口,内部会做去重、过滤、rerank 等处理
similaritySearchWithScore() 纯向量查询,返回结果 + 距离分数,不做额外处理

一般用 retriever.invoke() 就够了,需要调优时再用 similaritySearchWithScore() 看分数。


第六步:Augmented Prompt + LLM 生成

这是 RAG 的最后一步------把检索到的文档塞进 Prompt,让 LLM 基于这些真实内容生成回答。

javascript 复制代码
// 1. 把检索到的文档拼成上下文字符串
const context = docs
  .map((doc, i) => `[片段${i}]\n${doc.pageContent}`)
  .join("\n\n-----\n\n");

// 2. 组装 Augmented Prompt
const prompt = `
你是一个讲友情故事的老师。基于以下故事片段回答问题,
用温暖生动的语言。如果故事中没有提到,
就说"这个故事里还没有提到这个细节"。

故事片段:
${context}

问题:${question}

老师的回答:
`;

// 3. 调用 LLM 生成回答
const response = await model.invoke(prompt);
console.log(response.content);

💡 Augmented 的含义就在这里 ------Prompt 不是空洞地问 LLM,而是用检索到的真实文档"增强"了 Prompt。LLM 有了上下文依据,就不会胡编乱造了。


🧠 核心概念速查表

概念 一句话解释 类比
Document 知识库的最小单元,包含文本和元数据 一本书中的一段话 + 批注
Embedding 把文本转成高维向量 给每段话生成一个"语义指纹"
VectorStore 存储和检索向量的数据库 按"语义指纹"查找的图书馆
Retriever 向量检索的标准接口 图书馆的"智能搜索栏"
Top-K 返回最相似的 K 条结果 搜索结果只看前 K 条
Augmented Prompt 把检索结果塞进 Prompt 开卷考试的"参考资料"
相似度分数 向量之间的距离,越小越相似 两段话"有多像"的量化指标

⚠️ 我踩过的 3 个坑(实战经验)

坑 1:切分粒度选错了,检索质量断崖下降

一开始我按整段切分,结果一个 chunk 里藏了三个不相关的知识点,检索出来的文档相关性很低。后来改成按 200-300 token 切分,检索准确率明显提升。

建议:chunk size 一般在 200-500 token 之间效果最好,太小丢上下文,太大会引入噪声。

坑 2:Embedding 模型选型差异巨大

用 OpenAI 的 text-embedding-3-small 做英文效果很好,但换到中文场景,BGE-M3 的召回率明显更高。模型选型要根据你的实际语言场景来,别盲目跟风。

建议:中文场景优先考虑 BGE、Jina 等对中文优化过的模型,效果可能比 OpenAI 的好一个量级。

坑 3:Top-K 不是越大越好

一开始我设 k=10,结果后面的文档跟问题关系不大,反而干扰了 LLM 的判断,生成的回答里掺了很多无关信息。后来改回 k=3,效果反而更好。

建议:Top-K 设 3-5 即可,配合 Rerank 模型做二次筛选效果更佳。宁可精准,不要贪多。


🏗️ 从 Demo 到生产:你需要知道的

Demo 用的是 MemoryVectorStore(内存存储),生产环境需要考虑更多:

维度 Demo 生产
向量存储 MemoryVectorStore(内存) Pinecone / Milvus / Chroma / Weaviate
文档切分 手动写 Document RecursiveCharacterTextSplitter 自动切分
Embedding 模型 OpenAI text-embedding-3-small 可选 BGE、Jina 等开源模型
检索策略 纯向量相似度 混合检索(向量 + 关键词)、Rerank
对话记忆 加入 ConversationBufferMemory

生产级 RAG 架构

css 复制代码
原始文档 → 文档加载 → 文本切分 → Embedding → 向量数据库
                                                      │
用户提问 → Embedding → 混合检索 → Rerank → Top-K ─────┘
                                                │
                                                ▼
                                    Augmented Prompt → LLM → 回答

💡 重点总结

  1. RAG 的本质:让大模型"开卷考试",先检索再生成,减少幻觉
  2. 三大阶段:索引(建库)→ 检索(找相关文档)→ 生成(增强 Prompt)
  3. Embedding 是核心:把文本变成向量,实现语义级别的搜索
  4. Document 的 pageContent 参与向量化,metadata 不参与
  5. retriever.invoke() 是标准检索入口,内部做了去重、过滤等处理
  6. Augmented Prompt = 检索到的上下文 + 用户问题,这是 RAG 生成质量的关键

🔗 参考资料

💬 交流讨论

你在实际项目中用 RAG 时,遇到最头疼的问题是什么?

是切分粒度不好把握?检索召回率不够?还是 LLM 生成的答案偏了题?

在评论区聊聊,说说你的经历,大家一起交流 👇


觉得这篇文章帮到了你?

  • 👍 点赞让更多人看到
  • 收藏方便下次复习
  • 👆 关注我,后续会更新 RAG 生产级实战、Rerank 优化、混合检索等进阶内容!
相关推荐
Hilaku1 小时前
前端大裁员背后的恐慌:我们还剩什么底牌?
前端·javascript·程序员
数聚天成DeepSData2 小时前
遥感农业数据集下载全攻略
数据库·人工智能·深度学习·机器学习·自然语言处理·数据挖掘
wuhanzhanhui2 小时前
重塑供应链!2026武汉数字孪生产业展览会亮相,打造工业未来新基石
大数据·人工智能
先吃饱再说2 小时前
花 600 万就能复刻百亿模型?蒸馏技术到底是什么
人工智能·llm
新知图书2 小时前
多模态响应解析与后处理
人工智能·agent·多模态·ai agent·智能体
techdashen2 小时前
Go 1.26 新增 `bytes.Buffer.Peek`:只看数据,不移动读取位置
开发语言·后端·golang
Reart2 小时前
Leetcode 198.打家劫舍(716)
后端·算法
CAIE研习社2 小时前
产品经理方向学生可以考哪些证书
人工智能
汤姆小白2 小时前
08-应用部署
人工智能·python·机器学习·numpy·transformer