RAG: 从核心原理到Next.js实战演练

在大模型(LLM)开发中,如何让 AI 拥有“最新记忆”和“私有知识”?答案就是 RAG(Retrieval-Augmented Generation,检索增强生成)

本文将带你揭开 RAG 的神秘面纱,从底层向量原理出发,并结合真实的 Next.js + Pinecone + DeepSeek 项目代码,拆解一个基于私有知识库的 AI 对话接口是如何跑通的。

🧠 核心灵魂:Embedding(向量化)与高维空间

RAG 的魔法基石在于数据结构的转化,也就是让机器“懂”人类的语义。

  • 什么是 Embedding: 它是通过顶级深度神经网络(如 Transformer 架构)“压榨”出来的超级翻译官,能把人类的自然语言,压缩提取成机器能懂的“语义特征”。

  • 高维坐标: 一个文本块通常会被转化为包含数百到上千个(例如 1024 个)小数的数组,这就是它在高维空间中的“坐标点”。

  • 空间距离法则: 在这个高维空间里,意思越相近的文本,它们的坐标距离就越近

  • 分工明确的存储原则: 提取出的向量(数字)专门给向量数据库(如 Pinecone)用来做海量数学比对和搜索;而源文本(Metadata)则用来在搜到结果后,提取出来拿给大模型“阅读”,因为大模型看不懂坐标,只认文字。

向量的底层本质是什么?

在底层,经过 Embedding 转换后的文本没有任何神秘感,它就是一个由浮点数组成的高维数组。Pinecone 这样的向量数据库在底层就是拿着用户问题转化出的坐标,与数据库里海量的坐标进行余弦相似度 (Cosine Similarity) 计算。它通过疯狂地做浮点数点乘运算,找出最接近 1(夹角最小,语义最相近)的几个数据块。


🏗️ RAG 的两大生命周期

完整的 RAG 应用通常分为离线灌库和在线生成两个独立阶段。

第一阶段:离线知识灌库 (Data Ingestion)

这是把外部资料变成大模型“外挂记忆”的流水线,经典的 4 步如下:

  1. 爬虫 (Loader): 抓取目标数据(如 React 官网文档),转化为纯文本。

  2. 切块 (Chunking/Splitter): 受限于大模型上下文窗口和精确提取坐标的需要,使用工具将长文档切分成若干个小文本块。

  3. 向量化 (Embedding): 将这些文本块发送给 Embedding 模型换取对应的数字数组。

  4. 入库 (Vector DB): 将数组作为索引,原文存放在 Metadata 中,一起永久锁定在 Pinecone 等向量数据库中。

第二阶段:在线问答与生成 (Query & Augmented Generation)

当用户发起对话时,后端的完整业务流分为三步:

  1. 问题向量化: 首先将“用户的提问”发送给 Embedding 模型,转化为高维坐标。

  2. 空间搜索 (Retrieval): 拿着问题坐标去数据库比对,找出最相关的几个背景知识数据块。

  3. 喂给大模型 (Augmented Generation): 将搜到的背景知识和用户提问拼装成提示词(Prompt),打包发给大模型进行开卷考试。


💻 核心实战:代码级拆解在线检索流

接下来,我们来看看以上逻辑在 Next.js 服务端路由中是如何优雅落地的。以下代码展示了接收用户提问、检索向量库并调用 DeepSeek 回答的完整流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// src/app/api/chat/route.ts
import { NextResponse } from "next/server";
import { Pinecone } from "@pinecone-database/pinecone";

export async function POST(req: Request) {
try {
const { question } = await req.json();
console.log(`🗣️ 用户提问: ${question}`);

// 【步骤 1:问题向量化】调用 OpenAI 将用户问题转化为 1024 维度的向量
const embedRes = await fetch("https://api.gptsapi.net/v1/embeddings", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "text-embedding-3-large",
input: question,
dimensions: 1024 // 🚨 必须与灌库时的维度完全一致
})
});
const embedData = await embedRes.json();
const vector = embedData.data[0].embedding;

// 【步骤 2:空间搜索】连接 Pinecone,在 react-docs 索引中寻找最相似的 Top 3 背景文本
const pc = new Pinecone({ apiKey: process.env.PINECONE_API_KEY! });
const index = pc.Index("react-docs");
const queryResponse = await index.query({
vector,
topK: 3,
includeMetadata: true // 必须携带 metadata,里面存着人类可读的原文
});

// 提取背景文本进行拼接
const context = queryResponse.matches
.map(match => match.metadata?.text)
.join("\n\n");

// 【步骤 3:增强生成】组装底层的 Messages 数组,携带背景知识请求 DeepSeek
const dsRes = await fetch("https://api.deepseek.com/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.DEEPSEEK_API_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "deepseek-chat",
messages: [
{
// 通过 system 角色强制大模型读取设定的“剧本”内容
role: "system",
content: `你是一个 React 专家。请严格基于以下参考资料回答。
资料库中没有的内容请直说不知道。

--- 参考资料 ---
${context}`
},
{ role: "user", content: question }
],
temperature: 0.1 // 降低温度以保持专业和严谨
})
});

const dsData = await dsRes.json();
return NextResponse.json({ answer: dsData.choices[0].message.content });

} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}

💡 实战避坑与进阶:Messages 数组设计

在上述步骤 3 中,我们把获取到的上下文文本 context 强行塞进了 role: "system" 中。

在底层通信协议中,大模型是没有记忆的,它每次回答都会根据传入的 Messages 数组上下文重新计算概率。

  • system (系统指令/人设):权重最高的系统级配置,这就好比我们给大模型发的一个“剧本”。我们把检索到的资料放在 system 里,让模型在回答前先充分“脑补”这些设定。

  • user (用户提问):代表终端人类用户的输入,原封不动地传递。

源码已托管至🫱 GitHub,期待你的 Star🌟。


RAG: 从核心原理到Next.js实战演练
http://example.com/2026/05/02/RAG-从核心原理到Next-js实战演练/
作者
Lingkai Shi
发布于
2026年5月2日
许可协议