RAG 完全指南:从概念到生产实践

大家好,我是双越。wangEditor 作者,前百度 滴滴 资深前端工程师,慕课网金牌讲师,PMP,前端面试派 作者。

我正致力于两个项目的开发和升级,感兴趣的可以私信我,加入项目小组。

  • 划水AI Node 全栈 AIGC 知识库,包括 AI 写作、多人协同编辑。复杂业务,真实上线。
  • 智语 AI Agent 智能体项目。一个智能面试官,可以优化简历、模拟面试、解答题目等。

本文是一篇面向 Node.js 开发者的 RAG 学习笔记,涵盖核心概念、技术细节、工程实现与 Agent 应用场景。

RAG 是什么

RAG(Retrieval-Augmented Generation,检索增强生成)是一种将信息检索大语言模型生成相结合的技术架构。

背景与动机

大语言模型(LLM)在实际应用中存在几个核心痛点:

  • 知识截止日期(Knowledge Cutoff) :模型训练数据有时效性,无法获取最新信息
  • 无法访问私有数据:企业内部文档、数据库等私有知识对模型不可见
  • 幻觉问题(Hallucination) :模型会自信地编造并不存在的信息

RAG 的核心思路是:先查资料,再回答问题。就像一个员工在回答老板问题之前,先去查阅相关文档一样。通过在生成答案前动态检索相关知识,RAG 让 LLM 的回答有据可查、可验证。


核心技术架构

整体流程

csharp 复制代码
用户提问
   ↓
[Retrieval 检索阶段]
将问题向量化 → 在向量数据库中搜索 → 返回相关文档片段
   ↓
[Augmentation 增强阶段]
将检索到的内容拼入 Prompt(作为上下文)
   ↓
[Generation 生成阶段]
LLM 基于上下文生成最终答案

RAG 系统分为两条流水线:索引流水线 (离线处理文档)和查询流水线(在线响应用户)。

处理阶段(Indexing Pipeline)

这是离线阶段,负责将原始文档转化为可检索的向量索引:

scss 复制代码
原始文档(PDF / Word / 网页 / 数据库)
   ↓ 解析(提取纯文本)
纯文本
   ↓ Chunking(分块)
文本片段(通常 200~1000 tokens)
   ↓ Embedding 模型(如 text-embedding-3-small)
向量(float[] 数组)
   ↓ 存入
向量数据库(Pinecone / Weaviate / pgvector / Chroma)

查询阶段(Query Pipeline)

这是在线阶段,负责实时响应用户提问:

javascript 复制代码
async function ragQuery(userQuestion) {
  // Step 1: 将问题转为向量
  const queryEmbedding = await embeddings.embed(userQuestion);

  // Step 2: 向量相似度搜索
  const relevantChunks = await vectorDB.similaritySearch(queryEmbedding, topK = 5);

  // Step 3: 构建增强 Prompt
  const context = relevantChunks.map(c => c.text).join('\n\n');
  const prompt = `
    根据以下资料回答问题,如果资料中没有相关信息请说明。

    【参考资料】
    ${context}

    【问题】
    ${userQuestion}
  `;

  // Step 4: LLM 生成答案
  return await llm.generate(prompt);
}

深入技术细节

Chunking 策略

分块方式直接影响检索质量,是 RAG 系统中最容易被忽视、却影响最大的环节之一。

策略 说明 适用场景
Fixed-size 按固定 token 数切割 简单场景快速验证
Sliding Window 带重叠的滑动窗口 防止关键信息在分块边界断裂
Semantic 按语义边界切割(句子/段落) 通用场景,推荐默认选择
Recursive 递归按标题层级切割 结构化文档(Markdown / HTML)
Document-specific 针对特定格式专门处理 代码文件、表格等特殊内容

核心原则:Chunk 不能太大(引入噪音)也不能太小(缺失上下文),通常 512 tokens 左右是比较合适的起点,需要根据实际效果调整。

检索增强主流程

生产级 RAG 系统的完整检索流程如下:

css 复制代码
用户输入模糊问题
   ↓ Query Rewriting    (扩展查询,提升召回率)
多个查询并行检索
   ↓ Hybrid Search      (混合检索,粗筛 Top 50)
Top 50 候选文档
   ↓ Re-ranking         (精准排序,筛出 Top 5)
Top 5 最相关文档
   ↓
注入 Prompt → LLM 回答

三个技术各司其职,组合使用才能达到生产级效果。


为什么需要混合检索

单一检索方式各有缺陷:

向量检索(Vector Search) 将文字转换成数字向量,语义相近的内容向量距离就近。它能理解同义词、近义词,但对精确关键词不敏感------搜索 "GPT-4o" 可能找不到含有 "GPT-4o" 字样的文档。

关键词检索(BM25) 基于词频统计打分,是传统搜索引擎(Elasticsearch)的核心算法。它精确命中专有名词、代码、型号,但完全不懂语义------搜"汽车"找不到只写了"轿车"的文档。

arduino 复制代码
用户搜索: "苹果手机拍照虚化效果怎么弄"

向量检索:能找到"iPhone 人像模式使用教程"(语义相关)✅
          但可能漏掉含 "f1.8光圈" 的专业文档

BM25:    精确命中含"虚化"关键词的文档 ✅
          但找不到只写了 "Bokeh效果" 的文档

混合检索:两者结果都要,然后合并排序 🎯

RRF 融合算法

两个检索系统各自返回一个排序列表,通过 RRF(Reciprocal Rank Fusion) 合并:

scss 复制代码
RRF得分 = Σ  1 / (k + rank)    (k 通常取 60)

举例说明:

less 复制代码
向量检索结果:    BM25检索结果:
  第1名: 文档A     第1名: 文档C
  第2名: 文档B     第2名: 文档A
  第3名: 文档C     第3名: 文档D

RRF 计算:
文档A: 1/(60+1) + 1/(60+2) = 0.03252  ← 综合最高
文档C: 1/(60+3) + 1/(60+1) = 0.03226
文档B: 1/(60+2) + 1/(60+4) = 0.03176

最终排序:A → C → B → D

文档 A 在两个列表中都靠前,综合得分最高。RRF 的精髓是奖励在多个系统中都表现好的文档

BM25 的输入注意事项

BM25 基于词频统计,不适合输入长句子,会适得其反:

  • 关键词被"在"、"中"、"通常"等无意义词稀释
  • 停用词干扰打分
  • 词越多,结果越发散

两种检索的最佳输入策略截然相反:

向量检索 BM25
最佳输入 完整的句子/段落 精炼的关键词
原因 Embedding 能压缩整体语义 词频统计,词越精准越好
类比 理解文意的人 Ctrl+F 全文搜索

Node.js 实现

ini 复制代码
import { EnsembleRetriever } from "langchain/retrievers/ensemble";
import { BM25Retriever } from "@langchain/community/retrievers/bm25";

const vectorRetriever = vectorStore.asRetriever({ k: 10 });
const bm25Retriever = BM25Retriever.fromDocuments(docs, { k: 10 });

const hybridRetriever = new EnsembleRetriever({
  retrievers: [vectorRetriever, bm25Retriever],
  weights: [0.6, 0.4],  // 向量检索权重更高
});

const results = await hybridRetriever.invoke("Node.js fs模块怎么用");

Re-ranking 重排序

核心原理:Bi-encoder vs Cross-encoder

初始检索用 Bi-encoder(双塔模型) :Query 和文档各自独立编码,提前存好向量,查询时只计算距离,速度极快,但两者从未"见过对方",理解不够深。

Re-ranking 用 Cross-encoder(交叉模型) :Query 和文档拼在一起让模型读,能看到两者完整关系,理解深度远超 Bi-encoder,但无法提前计算,只能实时处理少量文档。

为什么两阶段缺一不可

sql 复制代码
假设有 100 万份文档:

Cross-encoder 直接检索:100万次模型推理 → 慢到无法接受 ❌
Bi-encoder 检索:毫秒级返回,但理解粗糙 ✅(用于粗筛)

最优方案:粗筛缩小范围 → 精排提升质量

完整两阶段流程

css 复制代码
100万文档
   ↓ 向量/混合检索(毫秒级,粗筛)
Top 20~50 候选文档
   ↓ Cross-encoder Re-ranking(精排,只处理少量文档)
Top 5 高质量文档
   ↓ 注入 Prompt → LLM 生成答案

Node.js 实现

javascript 复制代码
import { CohereRerank } from "@langchain/cohere";
import { ContextualCompressionRetriever } from "langchain/retrievers/contextual_compression";

const baseRetriever = vectorStore.asRetriever({ k: 20 });

const reranker = new CohereRerank({
  apiKey: process.env.COHERE_API_KEY,
  topN: 5,
  model: "rerank-multilingual-v3.0",  // 支持中文
});

const retriever = new ContextualCompressionRetriever({
  base_compressor: reranker,
  base_retriever: baseRetriever,
});

const results = await retriever.invoke("Node.js 如何处理文件上传");
// 自动完成:粗筛20条 → rerank精排 → 返回最相关的5条

类比理解:Re-ranking 就像招聘时的两轮筛选------简历筛选 (检索)快速从 1000 份中选出 20 个,再安排面试(Re-ranking)深度评估,最终选出最合适的 5 个。


Query Rewriting 查询改写

为什么需要改写

用户输入的问题往往口语化、模糊、信息量不足,直接检索效果很差:

arduino 复制代码
用户输入: "nodejs怎么连数据库"

可能会漏掉这些相关文档:
  ❌ "使用 Prisma ORM 操作 PostgreSQL"
  ❌ "Sequelize 配置连接池最佳实践"
  ❌ "mysql2 驱动安装与初始化"

根本原因:用户问的方式文档写的方式之间存在表达鸿沟。

三种常见做法

做法一:同义扩展(生成多个查询)

arduino 复制代码
// 让 LLM 将一个问题改写成多个不同角度的查询
const queries = [
  "Node.js 数据库连接方法",
  "Prisma Sequelize 使用教程",
  "mysql2 pg 驱动配置",
  "Node.js connection pool 连接池"
];
// 用这4个查询分别检索,合并结果 → 召回率大幅提升

做法二:HyDE(假设性文档生成)

不改写问题,而是让 LLM 先假装回答一遍,再拿这个"假答案"去检索:

arduino 复制代码
原始问题: "nodejs怎么连数据库"
   ↓ LLM 生成假设答案
"在 Node.js 中连接数据库通常使用 mysql2 或 pg 这类驱动,
 也可以使用 Prisma、Sequelize 等 ORM 框架..."
   ↓
拿这段话做向量检索(效果更好,因为更接近真实文档的表达方式)

原理:假答案的向量短问题的向量更接近真实文档的向量。

⚠️ 注意:HyDE 生成的长文本适合用于向量检索,用于 BM25 时需要先提取关键词:

ini 复制代码
// 向量检索:直接用假设答案的完整语义
const vectorResults = await vectorStore.similaritySearch(hypotheticalAnswer, 20);

// BM25检索:先提取关键词再检索
const keywords = ["mysql2", "Prisma", "连接池", "ORM", "pg"];
const bm25Results = await bm25.search(keywords.join(" "), 20);

做法三:问题分解(复杂问题)

arduino 复制代码
原始问题: "Prisma 和 Sequelize 哪个更适合 Node.js 新项目"
   ↓ LLM 拆解
[
  "Prisma ORM 的优缺点",
  "Sequelize ORM 的优缺点",
  "Prisma 和 Sequelize 性能对比",
  "2024年 Node.js ORM 选型建议"
]
每个子问题单独检索 → 汇总结果 → LLM 综合回答

在 AI Agent 中的应用场景

在 Agent 架构中,RAG 不再是简单的"查一次",而是作为 Tool(工具) 被 Agent 按需调用,赋予 Agent 动态访问外部知识的能力。

场景一:企业知识库问答

最经典的场景,RAG 作为 Agent 的知识外挂:

css 复制代码
用户: "我们公司的年假政策是什么?"
Agent: 调用 search_knowledge_base("年假政策")
    → 检索 HR 内部文档
    → 生成准确答案(而非 LLM 瞎猜)

场景二:代码库智能助手

代码向量化后,Agent 能理解整个代码仓库的语义:

css 复制代码
用户: "帮我找到处理用户登录的函数"
Agent: 调用 search_codebase("用户登录认证")
    → 返回相关代码片段及文件位置
    → 分析并解释代码逻辑

场景三:Multi-step Research Agent

Agent 自主决定多次检索,综合多个来源:

css 复制代码
用户: "帮我分析竞争对手的产品策略"
Agent:
  1. search_web("competitor A product 2024") → 检索网页内容
  2. search_internal_reports("市场分析")    → 检索内部报告
  3. 综合两次检索结果 → 生成完整分析报告

场景四:长期记忆(Long-term Memory)

Agent 将历史对话存入向量库,实现真正的"记住用户":

csharp 复制代码
// 存储用户偏好
await memoryStore.save({
  userId: "user_123",
  content: "用户偏好用 TypeScript,不喜欢 callback 风格",
  embedding: await embed("用户偏好TypeScript...")
});

// 下次对话时检索相关记忆,实现个性化响应
const memories = await memoryStore.recall(userId, currentQuestion);

代表框架:mem0

场景五:动态工具文档检索(Tool RAG)

当 Agent 有数百个可用工具时,全部塞进 Prompt 会超出上下文长度限制:

复制代码
用户意图
   ↓ RAG 检索最相关的 10 个工具定义
注入 Prompt
   ↓ LLM 选择调用哪个工具

技术实现

框架(Node.js 生态)

LangChain.js

目前 Node.js 生态中最成熟的 RAG/Agent 框架,提供了完整的工具链:

javascript 复制代码
import { ChatOpenAI } from "@langchain/openai";
import { OpenAIEmbeddings } from "@langchain/openai";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { EnsembleRetriever } from "langchain/retrievers/ensemble";
  • 优点:文档丰富、社区活跃、集成全面(向量库、LLM、工具全覆盖)
  • 缺点:抽象层较多,调试复杂,学习曲线较陡

LlamaIndex.TS

专注于数据索引和检索的框架,对 RAG 场景优化更深:

  • 优点:RAG 专用设计,对复杂检索场景支持好
  • 缺点:社区比 LangChain 小,文档相对少

入门推荐 :先用 LangChain.js,生态最完整,遇到问题最容易找到答案。


向量数据库对比

Chroma --- 本地开发专用

javascript 复制代码
import { Chroma } from "@langchain/community/vectorstores/chroma";

// 零配置,像 SQLite 一样嵌入式运行
const vectorStore = await Chroma.fromDocuments(docs, embeddings, {
  collectionName: "my-docs",
  persistDirectory: "./chroma-data",
});
  • 定位:嵌入式数据库,直接跑在进程里,数据存本地文件
  • 优点 :零配置,npm install 即用
  • 缺点:无集群、无高可用,不适合生产
  • 适合:学习阶段、本地原型验证

pgvector --- 已有 PostgreSQL 时的最优解

sql 复制代码
-- 开启扩展
CREATE EXTENSION vector;

-- 普通 PG 表,多了一个向量列
CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  content TEXT,
  metadata JSONB,
  embedding vector(1536)
);

-- 向量查询和业务查询混用,支持 JOIN
SELECT d.content, u.username
FROM documents d
JOIN users u ON d.author_id = u.id
WHERE u.plan = 'premium'
ORDER BY d.embedding <=> $1
LIMIT 5;
  • 定位:PostgreSQL 插件,不是独立数据库
  • 优点:向量查询与业务数据同库,事务/JOIN/权限全部复用,运维成本低
  • 缺点 :没有原生 BM25,混合检索需自己实现(使用 ts_rank 近似替代)
  • 适合:团队已有 PG、数据量 < 500 万、不想引入新服务

Weaviate --- 功能最全的开源方案

csharp 复制代码
// 原生混合检索,一行搞定
const results = await weaviate.graphql.get()
  .withClassName("Document")
  .withHybrid({
    query: "Node.js 连接数据库",
    alpha: 0.6,  // 0=纯BM25, 1=纯向量
  })
  .withLimit(5)
  .do();
  • 定位:专为 RAG/AI 场景设计的向量数据库
  • 内置能力:向量检索 + BM25 + 混合检索 + RRF 融合 + Re-ranking + 多租户 + 多模态
  • 优点:功能最全,原生混合检索开箱即用,相比自己实现省大量代码
  • 缺点:需要自托管或使用 Weaviate Cloud,有一定运维成本
  • 适合:需要混合检索、对功能要求高、可以接受自托管

Pinecone --- 托管云服务,最省心

ini 复制代码
// 配置最简单,没有任何服务器要管
const pinecone = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const index = pinecone.index("my-index");

await index.upsert(vectors);
const results = await index.query({ vector: queryEmbedding, topK: 10 });
  • 定位:纯云托管 Serverless 向量数据库
  • 优点:零运维、自动扩容、按量付费
  • 缺点:不支持原生混合检索(需外挂 Elasticsearch)、数据存境外
  • 适合:不想管运维、快速上线、预算充足

选型决策

复制代码
学习 / 做原型          →  Chroma(零成本零配置)
项目已用 PostgreSQL    →  pgvector(复用现有基础设施)
需要混合检索且能自托管  →  Weaviate(功能最全)
不想管运维且预算充足    →  Pinecone(最省心)
Chroma pgvector Weaviate Pinecone
部署方式 本地嵌入 PG 插件 自托管/云 纯云托管
原生混合检索
运维成本
适合数据量
费用 免费 免费 免费/付费 按量付费
推荐阶段 学习 生产 生产 生产

Embedding 模型

Embedding 模型负责将文本转换为向量,是 RAG 质量的基础。

模型 提供方 特点
text-embedding-3-small OpenAI 性价比高,速度快,推荐默认选择
text-embedding-3-large OpenAI 精度更高,价格更贵
embed-multilingual-v3.0 Cohere 多语言支持好,中文效果佳
BGE 系列 BAAI(智源) 开源,中文效果极佳,可本地部署
本地模型(via Ollama) --- 完全免费,数据不出本地,适合隐私要求高的场景

选型建议

  • 快速上手:OpenAI text-embedding-3-small,API 简单,效果稳定
  • 中文场景:优先考虑 Cohere 多语言模型BGE 中文模型
  • 数据隐私要求高:本地部署 BGE via Ollama

总结

技术全景图

sql 复制代码
用户提问
   ↓
Query Rewriting     将模糊问题扩展为多个精准查询
   ↓
Hybrid Search       向量检索(语义)+ BM25(关键词)→ RRF 融合
   ↓
Re-ranking          Cross-encoder 精准排序,淘汰低质量结果
   ↓
Prompt 增强         将 Top K 文档注入上下文
   ↓
LLM 生成            基于真实资料生成有据可查的答案

各技术作用一览

技术 解决的问题 何时引入
基础 RAG LLM 知识截止/幻觉 项目起步
Chunking 优化 检索到的内容质量差 发现答案不准时
Hybrid Search 专有名词/精确词匹配差 有代码、型号等精确词时
Re-ranking 检索结果排序不够精准 需要提升答案质量时
Query Rewriting 用户问题模糊导致漏检 召回率不足时
RAGAS 评估 无法量化系统好坏 需要持续优化时

Node.js 开发者学习路径

  1. 跑通最小 Demo:LangChain.js + Chroma + OpenAI,本地搭一个知识库问答
  2. 理解 Chunking:同一份文档用不同分块策略,观察检索质量差异
  3. 引入混合检索:用 Weaviate 体验原生 Hybrid Search
  4. 加入 Re-ranking:接入 Cohere Rerank API,对比前后效果
  5. Agentic RAG:将 RAG 封装成 Tool,让 Agent 自主决定何时检索
  6. 评估与迭代:建立测试集,用 RAGAS 量化每次改动的效果

RAG 是目前 AI Agent 落地最成熟的技术之一。掌握它,就是给你的 Agent 装上了一个可以随时扩展、随时更新的"外挂大脑"。

相关推荐
羊羊小栈3 分钟前
基于「YOLO目标检测 + 多模态AI分析」的水稻病害智能检测分析预警系统(LangChain框架)
人工智能·yolo·目标检测·毕业设计·创业创新·大作业
IT_陈寒6 分钟前
Vue的响应式让我熬到凌晨三点,原来漏了这个小细节
前端·人工智能·后端
lisw059 分钟前
提升社会科学领域的计算可重复性
人工智能·数字时代
HyperAI超神经13 分钟前
HyperAI 入选 36氪「East Forward 2026 出海全球化拓新企业」
人工智能·深度学习·全球化企业
9呀15 分钟前
VS Code Codex 多窗口使用与补丁记录
人工智能
七仔啊19 分钟前
安防通行人脸抓拍识别
人工智能
SoonITer23 分钟前
怎样构建一个 Agent-friendly 的网站
前端·agent
梦在远山后24 分钟前
从手写 Loop 到可恢复 Runtime:用 LangGraph、PostgreSQL Checkpoint 与 AG-UI 跑通中断恢复
python·langchain·agent
guoran_shini1 小时前
SVD矩阵分解
人工智能
烟锁池塘柳01 小时前
【Agent】如何从大模型构建真正的 Agent?以 Claude Code 为例,理解能构建智能体的真正的 Harness 工程
人工智能·agent