RAG 大模型实战:手把手搭建智能知识库
RAG(Retrieval Augmented Generation,检索增强生成)这个技术方向,我最近花了不少时间研究。简单说就是让大模型在回答问题前先查资料,这样回答就不会胡编乱造了。这篇文章记录了我从零搭建一个智能知识库的完整过程,代码和踩坑经验都在这。
什么是 RAG,为什么前端人该关注
大模型有个老毛病:不知道的事情会编。你问它一个公司内部的政策,它根本没见过你的内部文档,但会编一个看起来合理的答案。
RAG 的思路很简单:回答之前,先从你的私有资料库里检索相关信息,把这些信息和问题一起交给大模型。这样模型回答的依据是真实数据,不是训练记忆。
前端工程师做 RAG 有几个优势:
- 前端天然做用户界面,知识库的交互体验需要前端
- 信息架构是前端的强项,RAG 本质就是把信息组织好、检索准
- 不需要训练模型,用现成的 Embedding API + 向量库就能跑起来
项目目标
能用的智能知识库,需要这几件事:
- 上传 PDF/Markdown 文档作为知识来源
- 自动将文档切块、向量化、存入数据库
- 用户提问时,先检索相关文档片段,再生成回答
- 回答附带引用来源,方便核实
技术选型
试了几种方案,这是目前觉得适合前端开发者的组合:
| 组件 | 选择 | 理由 |
|---|---|---|
| 运行时 | Node.js + TypeScript | 前端友好 |
| Embedding | OpenAI text-embedding-3-small | 便宜、快、效果好 |
| 向量库 | Chroma(本地)/ Pinecone(云端) | Chroma 零配置本地跑 |
| 框架 | LangChain.js | 生态成熟,文档全 |
| 前端 | Next.js + Tailwind | 快速出界面 |
| 文档解析 | pdf-parse + marked | 处理 PDF 和 Markdown |
环境准备
1
2
3
4
5
mkdir
rag-knowledge-base &&
cd
rag-knowledge-base
npm init -y
npm install langchain @langchain/openai @langchain/chroma
npm install pdf-parse marked
npm install -D typescript @types/node
配置 TypeScript:
1
2
3
4
5
6
7
8
9
10
{
"compilerOptions"
:
{
"target"
:
"ES2020"
,
"module"
:
"ESNext"
,
"moduleResolution"
:
"node"
,
"outDir"
:
"./dist"
,
"rootDir"
:
"./src"
,
"esModuleInterop"
:
true
}
}
文档处理
知识库的第一步是把文档变成可以检索的小片段。流程:读取文件 → 切块 → 向量化。
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
import
fs
from
"fs"
;
import
pdfParse
from
"pdf-parse"
;
import
{
RecursiveCharacterTextSplitter
}
from
"langchain/text_splitter"
;
// 读取文件(支持 PDF 和 Markdown)
async
function
readFile
(
filePath: string
):
Promise
<
string
> {
if
(filePath.
endsWith
(
".pdf"
)) {
const
dataBuffer = fs.
readFileSync
(filePath);
const
result =
await
pdfParse
(dataBuffer);
return
result.
text
;
}
return
fs.
readFileSync
(filePath,
"utf-8"
);
}
// 切块
async
function
chunkText
(
text: string
) {
const
splitter =
new
RecursiveCharacterTextSplitter
({
chunkSize
:
500
,
chunkOverlap
:
50
,
});
return
await
splitter.
splitText
(text);
}
// 测试
const
text =
await
readFile
(
"./docs/my-document.pdf"
);
const
chunks =
await
chunkText
(text);
console
.
log
(
`文档被切分为 ${chunks.length} 个片段`
);
切块这一步很关键。块太大检索不准,块太小上下文不够。500 字符是我试下来比较好的默认值,你可以根据实际文档类型调整。
向量存储
切好的片段通过 Embedding 变成向量,存入向量数据库。
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
import
{
Chroma
}
from
"@langchain/chroma"
;
import
{
OpenAIEmbeddings
}
from
"@langchain/openai"
;
import
{
Document
}
from
"@langchain/core/documents"
;
const
embeddings =
new
OpenAIEmbeddings
({
modelName
:
"text-embedding-3-small"
,
apiKey
: process.
env
.
OPENAI_API_KEY
,
});
// 将切块转为 Document 对象
function
chunksToDocuments
(
chunks: string[], sourceFile: string
) {
return
chunks.
map
(
(chunk, i) =>
new
Document
({
pageContent
: chunk,
metadata
: {
source
: sourceFile,
chunkIndex
: i },
})
);
}
// 存入 Chroma
async
function
storeDocuments
(
documents: Document[], collectionName: string
) {
const
vectorStore =
new
Chroma
(embeddings, {
collectionName,
url
:
"http://localhost:8000"
,
});
await
vectorStore.
addDocuments
(documents);
console
.
log
(
`已存储 ${documents.length} 个文档到 ${collectionName}`
);
}
// 完整流程
const
chunks =
await
chunkText
(
await
readFile
(
"./docs/product-guide.pdf"
));
const
docs =
chunksToDocuments
(chunks,
"product-guide.pdf"
);
await
storeDocuments
(docs,
"my-knowledge-base"
);
启动 Chroma 本地服务:
1
2
pip install chromadb
chroma run --path ./chroma-data
检索 + 生成回答
知识库建好了,接下来做核心的检索和回答功能。
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
import
{
ChatOpenAI
}
from
"@langchain/openai"
;
import
{
ChatPromptTemplate
}
from
"@langchain/core/prompts"
;
import
{
StringOutputParser
}
from
"@langchain/core/output_parsers"
;
import
{
Chroma
}
from
"@langchain/chroma"
;
const
llm =
new
ChatOpenAI
({
modelName
:
"gpt-4o"
,
temperature
:
0.3
,
});
// 连接已有的知识库
const
vectorStore =
new
Chroma
(embeddings, {
collectionName
:
"my-knowledge-base"
,
url
:
"http://localhost:8000"
,
});
// RAG 提示词模板
const
RAG_PROMPT
=
ChatPromptTemplate
.
fromMessages
([
[
"system"
,
`你是一个知识库助手,根据检索到的文档片段回答问题。
如果文档中有相关信息,请基于文档内容回答,并在末尾标注引用来源。
如果文档中没有相关信息,请如实告知用户
"知识库中没有找到相关信息"
。
不要编造答案。
`],
[
"human"
,
`检索到的文档:
{context}
用户问题:{question}
`],
]);
// 完整的 RAG 查询流程
async
function
queryKnowledge
(
question: string
) {
// 1. 检索相关文档
const
results =
await
vectorStore.
similaritySearch
(question,
3
);
if
(results.
length
===
0
) {
return
{
answer
:
"知识库中没有找到相关内容"
,
sources
: [] };
}
// 2. 组装上下文
const
context = results.
map
(
(doc, i) =>
`[文档${i + 1}] ${doc.pageContent}\n来源: ${doc.metadata.source}`
).
join
(
"\n\n"
);
// 3. 生成回答
const
chain =
RAG_PROMPT
.
pipe
(llm).
pipe
(
new
StringOutputParser
());
const
answer =
await
chain.
invoke
({ context, question });
// 4. 返回回答 + 引用来源
return
{
answer,
sources
: results.
map
(
doc =>
doc.
metadata
.
source
),
};
}
// 测试
const
result =
await
queryKnowledge
(
"产品的退款政策是什么?"
);
console
.
log
(
"回答:"
, result.
answer
);
console
.
log
(
"来源:"
, result.
sources
);
前端界面
前端开发者最关心的还是界面。用 Next.js 做一个简单的知识库问答界面:
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// app/page.tsx
"use client"
;
import
{ useState }
from
"react"
;
interface
Message
{
role
:
"user"
|
"assistant"
;
content
:
string
;
sources?:
string
[];
}
export
default
function
Home
() {
const
[messages, setMessages] = useState<
Message
[]>([]);
const
[input, setInput] =
useState
(
""
);
const
[loading, setLoading] =
useState
(
false
);
const
sendMessage
=
async
() => {
if
(!input.
trim
())
return
;
const
userMessage
:
Message
= {
role
:
"user"
,
content
: input };
setMessages
(
prev =>
[...prev, userMessage]);
setInput
(
""
);
setLoading
(
true
);
const
res =
await
fetch
(
"/api/query"
, {
method
:
"POST"
,
headers
: {
"Content-Type"
:
"application/json"
},
body
:
JSON
.
stringify
({
question
: input }),
});
const
data =
await
res.
json
();
const
assistantMessage
:
Message
= {
role
:
"assistant"
,
content
: data.
answer
,
sources
: data.
sources
,
};
setMessages
(
prev =>
[...prev, assistantMessage]);
setLoading
(
false
);
};
return
(
<div className=
"min-h-screen bg-gray-50 flex flex-col"
>
<header className=
"bg-white shadow p-4"
>
<h1 className=
"text-xl font-bold"
>智能知识库</h1>
</header>
<main className=
"flex-1 overflow-y-auto p-4 space-y-4"
>
{messages.
map
(
(msg, i) =>
(
<div
key={i}
className={
`max-w-2xl p-4 rounded-lg ${
msg.
role
===
"user"
?
"bg-blue-100 ml-auto"
:
"bg-white border"
}
`}
>
<p className=
"whitespace-pre-wrap"
>{msg.
content
}</p>
{msg.
sources
&& msg.
sources
.
length
>
0
&& (
<div className=
"mt-2 text-xs text-gray-500"
>
来源: {msg.
sources
.
join
(
", "
)}
</div>
)}
</div>
))}
{loading && (
<div className=
"max-w-2xl p-4 rounded-lg bg-white border"
>
<p className=
"text-gray-400"
>正在检索知识库...</p>
</div>
)}
</main>
<footer className=
"bg-white border-t p-4"
>
<div className=
"max-w-2xl mx-auto flex gap-2"
>
<input
value={input}
onChange={
(e) =>
setInput
(e.
target
.
value
)}
onKeyDown={
(e) =>
e.
key
===
"Enter"
&&
sendMessage
()}
placeholder=
"输入你的问题..."
className=
"flex-1 border rounded-lg px-4 py-2"
/>
<button
onClick={sendMessage}
disabled={loading}
className=
"bg-blue-600 text-white px-6 py-2 rounded-lg disabled:opacity-50"
>
提问
</button>
</div>
</footer>
</div>
);
}
API 路由:
1
2
3
4
5
6
7
8
9
// app/api/query/route.ts
import
{
NextResponse
}
from
"next/server"
;
import
{ queryKnowledge }
from
"@/lib/rag"
;
export
async
function
POST
(
request: Request
) {
const
{ question } =
await
request.
json
();
const
result =
await
queryKnowledge
(question);
return
NextResponse
.
json
(result);
}
批量导入文档
实际使用中不可能一条条录入。写一个批量导入脚本:
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
import
glob
from
"glob"
;
import
{
RecursiveCharacterTextSplitter
}
from
"langchain/text_splitter"
;
import
{
Chroma
}
from
"@langchain/chroma"
;
import
{
OpenAIEmbeddings
}
from
"@langchain/openai"
;
import
{
Document
}
from
"@langchain/core/documents"
;
import
fs
from
"fs"
;
import
pdfParse
from
"pdf-parse"
;
async
function
importAllDocuments
() {
const
files = glob.
sync
(
"./docs/**/*.{pdf,md}"
);
console
.
log
(
`找到 ${files.length} 个文档`
);
const
allDocuments
:
Document
[] = [];
for
(
const
file
of
files) {
let
text
:
string
;
if
(file.
endsWith
(
".pdf"
)) {
const
dataBuffer = fs.
readFileSync
(file);
const
result =
await
pdfParse
(dataBuffer);
text = result.
text
;
}
else
{
text = fs.
readFileSync
(file,
"utf-8"
);
}
const
splitter =
new
RecursiveCharacterTextSplitter
({
chunkSize
:
500
,
chunkOverlap
:
50
,
});
const
chunks =
await
splitter.
splitText
(text);
const
docs = chunks.
map
(
(chunk, i) =>
new
Document
({
pageContent
: chunk,
metadata
: {
source
: file,
chunkIndex
: i },
})
);
allDocuments.
push
(...docs);
console
.
log
(
`处理完成: ${file} → ${chunks.length} 个片段`
);
}
const
embeddings =
new
OpenAIEmbeddings
({
modelName
:
"text-embedding-3-small"
,
});
const
vectorStore =
new
Chroma
(embeddings, {
collectionName
:
"my-knowledge-base"
,
url
:
"http://localhost:8000"
,
});
await
vectorStore.
addDocuments
(allDocuments);
console
.
log
(
`全部导入完成,共 ${allDocuments.length} 个文档片段`
);
}
importAllDocuments();
踩坑记录
1. 中文检索效果不好
一开始用 text-embedding-ada-002,中文文档的检索准确率只有 60% 左右。换成 text-embedding-3-small 后提升到 85%。如果你的知识库以中文为主,建议直接用 3-small。
2. 切块大小影响很大
块太小(200 字符)检索出的内容缺乏上下文;块太大(1000 字符)又会混入不相关信息。500 字符是我试下来比较合适的值,具体看你的文档类型。技术文档可以偏大一些,FAQ 类偏小。
3. 回答引用的来源不对
有时候回答引用的文档和实际内容对不上。原因是 metadata 没有正确传递。确保每个 Document 对象都有 source 字段,检索结果按源文件过滤。
4. Chroma 本地存储的坑
Chroma 默认数据存在内存里,重启就没了。启动时加上 --path ./chroma-data 参数才能持久化。
进阶优化方向
| 方向 | 说明 | 难度 |
|---|---|---|
| 混合检索 | 关键词检索 + 向量检索结合 | ⭐⭐ |
| 重排序 | 用 Cross-Encoder 对检索结果重新排序 | ⭐⭐⭐ |
| 多路召回 | 多个知识库并行检索,合并结果 | ⭐⭐⭐ |
| 流式输出 | SSE 实现打字机效果 | ⭐⭐ |
| 问答评估 | 自动化评估回答准确率和忠实度 | ⭐⭐⭐ |
RAG 的入门门槛不高,但要做好需要反复调参。文档切块、检索策略、提示词设计这三个环节最值得花时间优化。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋

📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

更多推荐




所有评论(0)