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

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

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

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

RAG 是什么

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

背景与动机

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

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

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


核心技术架构

整体流程

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

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

处理阶段(Indexing Pipeline)

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

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

查询阶段(Query Pipeline)

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

javascript
 体验AI代码助手
 代码解读
复制代码
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
 体验AI代码助手
 代码解读
复制代码
用户输入模糊问题
   ↓ Query Rewriting    (扩展查询,提升召回率)
多个查询并行检索
   ↓ Hybrid Search      (混合检索,粗筛 Top 50Top 50 候选文档
   ↓ Re-ranking         (精准排序,筛出 Top 5Top 5 最相关文档
   ↓
注入 Prompt → LLM 回答

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


Hybrid Search 混合检索

为什么需要混合检索

单一检索方式各有缺陷:

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

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

arduino
 体验AI代码助手
 代码解读
复制代码
用户搜索: "苹果手机拍照虚化效果怎么弄"

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

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

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

RRF 融合算法

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

scss
 体验AI代码助手
 代码解读
复制代码
RRF得分 = Σ  1 / (k + rank)    (k 通常取 60

举例说明:

less
 体验AI代码助手
 代码解读
复制代码
向量检索结果:    BM25检索结果:
  第1名: 文档A1名: 文档C2名: 文档B2名: 文档A3名: 文档C3名: 文档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

最终排序:ACBD

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

BM25 的输入注意事项

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

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

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

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

Node.js 实现

ini
 体验AI代码助手
 代码解读
复制代码
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
 体验AI代码助手
 代码解读
复制代码
假设有 100 万份文档:

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

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

完整两阶段流程

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

Node.js 实现

javascript
 体验AI代码助手
 代码解读
复制代码
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
 体验AI代码助手
 代码解读
复制代码
用户输入: "nodejs怎么连数据库"

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

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

三种常见做法

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

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

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

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

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

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

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

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

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

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

arduino
 体验AI代码助手
 代码解读
复制代码
原始问题: "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
 体验AI代码助手
 代码解读
复制代码
用户: "我们公司的年假政策是什么?"
Agent: 调用 search_knowledge_base("年假政策")
    → 检索 HR 内部文档
    → 生成准确答案(而非 LLM 瞎猜)

场景二:代码库智能助手

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

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

场景三:Multi-step Research Agent

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

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

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

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

csharp
 体验AI代码助手
 代码解读
复制代码
// 存储用户偏好
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 会超出上下文长度限制:

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

技术实现

框架(Node.js 生态)

LangChain.js

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

javascript
 体验AI代码助手
 代码解读
复制代码
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,生态最完整,遇到问题最容易找到答案。


向量数据库对比

http://bjypfp.mpmpc.cn/ http://tjypfp.mpmpc.cn/ http://shypfp.mpmpc.cn/ http://zqypfp.mpmpc.cn/ http://tyypfp.mpmpc.cn/ http://sjzypfp.mpmpc.cn/ http://hhhtypfp.mpmpc.cn/ http://syypfp.mpmpc.cn/ http://ccypfp.mpmpc.cn/ http://hebypfp.mpmpc.cn/ http://njypfp.mpmpc.cn/ http://szypfp.mpmpc.cn/ http://hzypfp.mpmpc.cn/ http://hfypfp.mpmpc.cn/ http://xmypfp.mpmpc.cn/ http://fzypfp.mpmpc.cn/ http://ncypfp.mpmpc.cn/ http://jnypfp.mpmpc.cn/ http://qdypfp.mpmpc.cn/ http://zzypfp.mpmpc.cn/ http://whypfp.mpmpc.cn/ http://csypfp.mpmpc.cn/ http://szkypfp.mpmpc.cn/ http://gzypfp.mpmpc.cn/ http://nnypfp.mpmpc.cn/ http://hkypfp.mpmpc.cn/ http://cdypfp.mpmpc.cn/ http://gyypfp.mpmpc.cn/ http://kmypfp.mpmpc.cn/ http://lsypfp.mpmpc.cn/ http://xaypfp.mpmpc.cn/ http://lzypfp.mpmpc.cn/ http://xnypfp.mpmpc.cn/ http://yckypfp.mpmpc.cn/ http://wlmqypfp.mpmpc.cn/

Chroma — 本地开发专用

javascript
 体验AI代码助手
 代码解读
复制代码
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
 体验AI代码助手
 代码解读
复制代码
-- 开启扩展
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
 体验AI代码助手
 代码解读
复制代码
// 原生混合检索,一行搞定
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
 体验AI代码助手
 代码解读
复制代码
// 配置最简单,没有任何服务器要管
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)、数据存境外
  • 适合:不想管运维、快速上线、预算充足

选型决策

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

Embedding 模型

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

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

选型建议

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

总结

技术全景图

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

各技术作用一览

技术解决的问题何时引入
基础 RAGLLM 知识截止/幻觉项目起步
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 装上了一个可以随时扩展、随时更新的"外挂大脑"。

0
0
0
0
评论
未登录
暂无评论