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 装上了一个可以随时扩展、随时更新的"外挂大脑"。

相关推荐
一水鉴天2 小时前
“活结-活络-活扩”:一种面向数据价值升维与业务敏捷演进的系统架构元模型 20260310 之1(元宝)
人工智能·架构
小马_xiaoen2 小时前
RAG(检索增强生成)从原理到实战全解析
人工智能·ai·rag·检索增强生成
hans汉斯2 小时前
【数据挖掘】基于轻量化GE-GRU-VAE模型的多维时间序列异常检测
人工智能·深度学习·机器学习·数据挖掘·gru·汉斯出版社
STLearner2 小时前
ICLR 2026 | 时空数据(Spatial-Temporal)论文总结[上](交通与城市科学:交通预测,轨迹挖掘,自动驾驶等)
大数据·论文阅读·人工智能·深度学习·机器学习·数据挖掘·自动驾驶
yzx9910132 小时前
开源“龙虾”启示录:从OpenClaw看AI Agent的私有化、安全与未来
人工智能·安全·开源
Sagittarius_A*2 小时前
图像去雾:从直方图增强到暗通道先验【计算机视觉】
图像处理·人工智能·python·opencv·计算机视觉·图像去雾·暗通道先验
墨染天姬2 小时前
【AI】2026年只能基于分层记忆架构实现模型永久记忆
人工智能
Ada's2 小时前
《具身智能》机器人001
人工智能
G***技2 小时前
移动咖啡机器人上岗!杰和算力板卡成高效服务“隐形引擎”
人工智能·嵌入式硬件·机器人·gpu算力