RAG 入门:检索增强生成核心原理
- [什么是 RAG?](#什么是 RAG?)
-
- 大模型的"阿喀琉斯之踵"
- [RAG 的解决方案](#RAG 的解决方案)
- [RAG 的核心价值](#RAG 的核心价值)
- [RAG VS 模型微调:选型对比](#RAG VS 模型微调:选型对比)
- [RAG 全流程执行链路拆解](#RAG 全流程执行链路拆解)
- [前端落地 RAG 应用场景](#前端落地 RAG 应用场景)
- [学习 RAG 必备知识储备](#学习 RAG 必备知识储备)
- [国内模型适配 RAG 的注意事项](#国内模型适配 RAG 的注意事项)
- 结语
什么是 RAG?
大模型的"阿喀琉斯之踵"
想象一个场景:你问一个 AI 助手"我们公司的最新政策是什么?",它要么胡说八道,要么说"我不知道"------因为它训练时的知识截止到几个月前,甚至没有公司内部的私有政策文档。
这就是大模型的核心痛点:
| 问题 | 说明 | 前端类比 |
|---|---|---|
| 知识截止 | 模型训练后知识就固定了 | 就像打包后的静态网站,内容无法更新 |
| 幻觉问题 | 不知道的事情会编造 | 类似 undefined 被强制转成字符串 |
| 私有知识 | 无法访问企业内部文档 | 无法读取 localhost 的 API |
RAG 的解决方案
RAG(检索增强生成) 的核心思想很简单:先查资料,再回答问题。
javascript
// 传统 LLM:直接回答
const answer = await llm.ask("公司最新政策是什么?");
// 可能输出:编造的内容
// RAG:先查资料,再回答
const docs = await searchDocs("公司最新政策"); // 从知识库检索
const answer = await llm.askWithContext(docs, "公司最新政策是什么?");
// 输出:基于真实文档的回答
用前端技术栈类比 RAG:
用户提问 → 向量检索(类似数据库查询) → 上下文增强(类似状态注入) → LLM 生成(类似模板渲染)
整个过程可以理解为:大模型在回答问题时,先"翻书查资料",然后根据查到的内容来回答------就像开卷考试,而不是闭卷瞎蒙。
RAG 的核心价值
| 价值 | 说明 |
|---|---|
| 实时更新知识 | 无需重新训练,更新知识库即可 |
| 降低幻觉 | 回答基于检索到的真实文档 |
| 可溯源 | 可以告诉用户答案来自哪份文档 |
| 成本可控 | 比模型微调便宜得多 |
RAG VS 模型微调:选型对比
核心差异速览
| 维度 | RAG | 模型微调 |
|---|---|---|
| 知识更新 | 即时(更新知识库即可) | 需重新训练(数小时到数天) |
| 开发成本 | 低(调用 API + 向量数据库) | 高(需要 GPU 资源和训练经验) |
| 幻觉控制 | 强(基于检索内容) | 中(依赖训练数据质量) |
| 私有知识 | ✅ 天然支持 | ⚠️ 需要训练数据 |
| 可解释性 | ✅ 可溯源到文档 | ❌ 黑盒 |
| 前端适配性 | ✅ 轻量,调用 API 即可 | ⚠️ 需要后端训练服务 |
什么时候选 RAG?
| 场景 | 原因 |
|---|---|
| 企业内部知识库问答 | 文档经常更新,需要实时同步 |
| 客服问答系统 | 需要可溯源的答案,回答必须准确 |
| 代码文档助手 | 需要基于最新 API 文档回答 |
| 个人学习助手 | 成本低,快速搭建 |
什么时候选微调?
| 场景 | 原因 |
|---|---|
| 改变模型风格/语气 | 如让模型模仿特定人物说话 |
| 提升特定任务能力 | 如代码生成、SQL 改写 |
| 大批量相同格式输出 | 如固定格式的报告生成 |
两者也可以结合
在实际生产环境中,RAG 和微调并非互斥,而是互补关系:
用户提问 → RAG 检索(获取相关文档)→ 微调后的模型(生成回答)
例如:先微调让模型学会"客服语气",再用 RAG 注入企业最新政策文档。
RAG 全流程执行链路拆解
整体架构
#mermaid-svg-vYbzNeD8gLQqINaM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vYbzNeD8gLQqINaM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vYbzNeD8gLQqINaM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vYbzNeD8gLQqINaM .error-icon{fill:#552222;}#mermaid-svg-vYbzNeD8gLQqINaM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vYbzNeD8gLQqINaM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vYbzNeD8gLQqINaM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vYbzNeD8gLQqINaM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vYbzNeD8gLQqINaM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vYbzNeD8gLQqINaM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vYbzNeD8gLQqINaM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vYbzNeD8gLQqINaM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vYbzNeD8gLQqINaM .marker.cross{stroke:#333333;}#mermaid-svg-vYbzNeD8gLQqINaM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vYbzNeD8gLQqINaM p{margin:0;}#mermaid-svg-vYbzNeD8gLQqINaM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vYbzNeD8gLQqINaM .cluster-label text{fill:#333;}#mermaid-svg-vYbzNeD8gLQqINaM .cluster-label span{color:#333;}#mermaid-svg-vYbzNeD8gLQqINaM .cluster-label span p{background-color:transparent;}#mermaid-svg-vYbzNeD8gLQqINaM .label text,#mermaid-svg-vYbzNeD8gLQqINaM span{fill:#333;color:#333;}#mermaid-svg-vYbzNeD8gLQqINaM .node rect,#mermaid-svg-vYbzNeD8gLQqINaM .node circle,#mermaid-svg-vYbzNeD8gLQqINaM .node ellipse,#mermaid-svg-vYbzNeD8gLQqINaM .node polygon,#mermaid-svg-vYbzNeD8gLQqINaM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vYbzNeD8gLQqINaM .rough-node .label text,#mermaid-svg-vYbzNeD8gLQqINaM .node .label text,#mermaid-svg-vYbzNeD8gLQqINaM .image-shape .label,#mermaid-svg-vYbzNeD8gLQqINaM .icon-shape .label{text-anchor:middle;}#mermaid-svg-vYbzNeD8gLQqINaM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vYbzNeD8gLQqINaM .rough-node .label,#mermaid-svg-vYbzNeD8gLQqINaM .node .label,#mermaid-svg-vYbzNeD8gLQqINaM .image-shape .label,#mermaid-svg-vYbzNeD8gLQqINaM .icon-shape .label{text-align:center;}#mermaid-svg-vYbzNeD8gLQqINaM .node.clickable{cursor:pointer;}#mermaid-svg-vYbzNeD8gLQqINaM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vYbzNeD8gLQqINaM .arrowheadPath{fill:#333333;}#mermaid-svg-vYbzNeD8gLQqINaM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vYbzNeD8gLQqINaM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vYbzNeD8gLQqINaM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vYbzNeD8gLQqINaM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vYbzNeD8gLQqINaM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vYbzNeD8gLQqINaM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vYbzNeD8gLQqINaM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vYbzNeD8gLQqINaM .cluster text{fill:#333;}#mermaid-svg-vYbzNeD8gLQqINaM .cluster span{color:#333;}#mermaid-svg-vYbzNeD8gLQqINaM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vYbzNeD8gLQqINaM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vYbzNeD8gLQqINaM rect.text{fill:none;stroke-width:0;}#mermaid-svg-vYbzNeD8gLQqINaM .icon-shape,#mermaid-svg-vYbzNeD8gLQqINaM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vYbzNeD8gLQqINaM .icon-shape p,#mermaid-svg-vYbzNeD8gLQqINaM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vYbzNeD8gLQqINaM .icon-shape .label rect,#mermaid-svg-vYbzNeD8gLQqINaM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vYbzNeD8gLQqINaM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vYbzNeD8gLQqINaM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vYbzNeD8gLQqINaM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 在线问答阶段
用户提问
Query
向量检索
Retrieval
LLM 生成
Generation
离线索引阶段
文档处理
Loading
向量化
Embedding
存储索引
Storing
向量数据库
阶段一:文档处理(索引侧)
目标:将原始文档转换为可检索的格式。
| 步骤 | 任务 | 说明 |
|---|---|---|
| 文档加载 | 读取各类格式文档 | PDF、Markdown、Word、网页等 |
| 文本分割 | 将长文档切分成小块 | 控制每块大小(如 500-1000 字符) |
| 元数据提取 | 记录来源信息 | 文件名、页码、章节等 |
javascript
// 代码示例:文本分割
import { RecursiveCharacterTextSplitter } from "@langchain/textsplitters";
// 创建分割器
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
});
// 定义要分割的文本内容
const longText = `
这里是你要分割的超长文本内容。
可以是文章、文档、说明、任意内容。
`;
const chunks = await splitter.splitText(longText);
阶段二:向量化与存储(索引侧)
目标:将文本块转换为向量并建立索引。
| 步骤 | 任务 | 说明 |
|---|---|---|
| 嵌入生成 | 调用 Embedding 模型 | 将文本转为向量(如 1536 维) |
| 向量存储 | 存入向量数据库 | Chroma、Pinecone、Qdrant 等 |
| 索引构建 | 建立高效检索结构 | HNSW、IVF 等算法 |
javascript
// 代码示例:向量化与存储
import { OpenAIEmbeddings } from "@langchain/openai";
import { Chroma } from "@langchain/community/vectorstores/chroma";
const embeddings = new OpenAIEmbeddings({ model: "text-embedding-3-small" });
const vectorStore = await Chroma.fromTexts(
chunks,
chunks.map((_, i) => ({ id: i, source: "knowledge" })),
embeddings,
{
collectionName: "knowledge_base",
}
);
阶段三:检索增强(查询侧)
目标:根据用户问题检索相关知识。
| 步骤 | 任务 | 说明 |
|---|---|---|
| 问题向量化 | 将用户问题转为向量 | 使用相同 Embedding 模型 |
| 相似度检索 | 查找最相似的文档块 | 余弦相似度、欧氏距离等 |
| 结果重排序 | 优化检索结果顺序 | 可选,提升相关性 |
javascript
// 代码示例:相似度检索
const relevantDocs = await vectorStore.similaritySearch(question, 5);
// 返回最相似的 5 个文档块
阶段四:生成回答(查询侧)
目标:结合检索结果生成最终回答。
| 步骤 | 任务 | 说明 |
|---|---|---|
| 上下文构建 | 将检索结果拼接到提示词 | 格式:文档1 文档2... |
| 提示词增强 | 注入检索内容和回答要求 | "只根据以下内容回答" |
| LLM 生成 | 调用大模型生成答案 | 保证回答基于检索内容 |
javascript
// 代码示例:构建增强提示词
const prompt = `
请根据以下文档内容回答用户问题。
【文档1】${doc1}
【文档2】${doc2}
用户问题:${question}
注意:
1. 如果文档中没有相关信息,请明确说"没有找到相关信息"
2. 不要使用你自己知识库中的内容
3. 回答要简洁准确
回答:
`;
const answer = await llm.invoke(prompt);
前端落地 RAG 应用场景
场景一:企业文档智能问答
需求:公司内部有大量文档(员工手册、技术规范、产品说明),员工查找困难。
前端实现:
typescript
// 企业文档问答界面
class CompanyDocQA {
async search(question: string) {
// 1. 检索相关文档片段
const docs = await vectorStore.similaritySearch(question, 3);
// 2. 显示来源引用
return {
answer: await this.generateAnswer(question, docs),
sources: docs.map(d => ({ file: d.metadata.file, page: d.metadata.page })),
};
}
}
前端 UI 要点:
- 显示答案来源(支持点击跳转到原文)
- 支持文档预览浮层
- 提供答案有用/无用的反馈按钮
场景二:前端技术知识库
需求:搭建团队内部的技术文档问答系统(React/Vue 官方文档、组件库文档、最佳实践沉淀)。
数据来源:
- 框架官方文档爬取
- 团队内部技术沉淀
- GitHub 仓库 README
前端适配优势:
- Markdown 友好,前端天然擅长渲染
- 可以集成到 IDE 插件或浏览器扩展
- 支持代码高亮和示例预览
场景三:代码库问答助手
需求:让 AI 理解公司代码库,新人可以问"这个函数怎么用?"
typescript
// 代码库 RAG 流程
// 1. 离线阶段:解析代码文件
const codeChunks = parseCodeFiles(repoPath, {
language: "typescript",
chunkBy: "function", // 按函数分割
});
// 2. 向量化存储
await vectorStore.addDocuments(codeChunks);
// 3. 在线问答
const answer = await ragChain.invoke({
question: "auth 模块的 login 函数怎么用?",
});
场景四:客服知识库
需求:搭建智能客服系统,回答常见问题。
优势:
- 无需训练,直接导入 FAQ 文档
- 更新即时,新增问题立即生效
- 可溯源,用户可查看原始文档
场景五:个人学习助手
需求:上传 PDF 文档(论文、书籍),随时提问。
前端实现要点:
- 拖拽上传 PDF
- 显示问答历史和文档来源
- 支持导出对话记录
学习 RAG 必备知识储备
技术栈概览
| 类别 | 技术 | 说明 |
|---|---|---|
| 框架 | LangChain.js | 链路编排,支持中文社区 |
| Embedding | text-embedding-v3 / qwen | 国内可用,阿里云百炼提供 |
| 向量数据库 | Chroma / Qdrant | 轻量级,适合前端项目 |
| LLM | 通义千问 / DeepSeek | RAG 生成阶段使用 |
知识储备要求
| 知识点 | 重要性 | 学习建议 |
|---|---|---|
| LangChain 基础 | ⭐⭐⭐⭐⭐ | 先完成基础教程 |
| 异步编程 | ⭐⭐⭐⭐ | 掌握 Promise、async/await |
| 向量基础概念 | ⭐⭐⭐ | 了解嵌入和相似度即可 |
| 提示词工程 | ⭐⭐⭐⭐ | 掌握 prompt 编写技巧 |
向量基础速成
对于前端开发者来说,理解以下三个核心概念就足够入门:
1. 什么是向量?
- 向量就是一组数字,例如
[0.1, -0.5, 0.8, ...](通常几百到几千维) - 每个文本(句子、段落)都可以转换成一个向量
- 关键:语义相似的文本,它们的向量在空间中也彼此靠近
2. 什么是相似度检索?
- 比较两个向量的距离(余弦相似度)
- 距离越近,文本越相关
- 找到与用户问题向量距离最近的那些文档块
3. 常用的 Embedding 模型
- OpenAI:
text-embedding-3-small(1536 维) - 阿里云百炼:
text-embedding-v3(1024 维) - BGE(国产开源)
国内模型适配 RAG 的注意事项
Embedding 模型选择
| 模型 | 维度 | 中文效果 | 推荐指数 |
|---|---|---|---|
| 阿里云 text-embedding-v3 | 1024 | ⭐⭐⭐⭐⭐ | 首选 |
| OpenAI text-embedding-3-small | 1536 | ⭐⭐⭐⭐ | 需代理 |
| BGE-large-zh | 1024 | ⭐⭐⭐⭐ | 开源免费 |
阿里云百炼配置
typescript
// 阿里云百炼 Embedding 配置
import { OpenAIEmbeddings } from "@langchain/openai";
const embeddings = new OpenAIEmbeddings({
apiKey: process.env.DASHSCOPE_API_KEY,
configuration: {
baseURL: process.env.DASHSCOPE_API_URL,
},
model: "text-embedding-v3", // 阿里云百炼模型
});
前端适配注意事项
| 注意事项 | 说明 |
|---|---|
| Embedding 模型一致性 | 索引和查询必须使用相同模型 |
| 中文支持 | 选择中文效果好的 Embedding 模型 |
| 分段策略 | 中文按语义切分(段落/句子),避免截断 |
| 上下文长度 | 检索结果过多会超 LLM 窗口,建议 3-5 条 |
结语
通过这篇教程,我们系统了解了 RAG 的核心原理、选型对比和落地场景。
对于文章中错误的地方或有任何疑问,欢迎在评论区留言讨论!