跳转到内容

RAG 教程:从零搭建知识库问答系统(50 行代码)

学完这一节,你能:

  • 50 行代码搭一个最小可用的 RAG(从零写,不用框架,而且分四步讲清楚)
  • 理解 RAG 四步流程:加载 → 分块 → Embedding → 检索生成,且每一步都能对上“开卷考试”
  • 说清楚为什么 RAG 能治幻觉(这是面试高频题)

RAG(检索增强生成)在国内 FDE JD 出现率 52%,是腾讯/蚂蚁/用友的硬性要求。这是本教程最重要的一节之一。

RAG = 开卷考试。

想象你进考场,有两种考法:

  • 闭卷(普通大模型):只准带脑子。题目问“光合作用”,你只能凭记忆写,记不清就编一个看起来像的——这就是幻觉
  • 开卷(RAG):可以带书。题目问“光合作用”,你先翻到讲光合作用那页,照着书上写——答得又准又能引经据典。

RAG 干的就是这件事,只不过“翻书”这一步交给程序自动完成。把开卷考试的每一步对应过来:

开卷考试 RAG 里的说法 在干嘛
把课本、讲义、笔记搬进考场 加载(Load) 把 PDF/Word/网页变成文字
把整本书按页拆开 分块(Chunk) 把长文档切成一小段一小段
给每一页贴“意思标签” Embedding(向量化) 让程序能按“意思”找页,不是按字面
看到题目,翻到对应那几页 检索(Retrieve) 程序算“意思有多像”,挑出最相关的几段
把书摊开,照着答题 生成(Generate) 大模型看着找到的资料写答案

记住这张表,下面四步讲解全在重复它。看不懂术语时,回来瞄一眼“在干嘛”那一列。

RAG 开卷考试四步流程

把 PDF、Word、网页、数据库……全部变成纯文本。难点是扫描件 PDF 要先 OCR(就是用程序把图片里的字“认”出来),表格要单独处理,不然数字会乱。

回到类比:这步就像考试前一天,你把老师的讲义、课本、同学的笔记,统统装进书包。

2. 分块(Chunk)= 把整本书按页拆开

Section titled “2. 分块(Chunk)= 把整本书按页拆开”

为什么不能整本塞?因为模型一次看不了那么多字(有“上下文窗口”限制),而且塞太多它反而会“忘”前面的、抓不住重点。所以要把长文档切成小段,通常 400-600 字一段。

回到类比:考试时你不会一页页从头翻到尾(太慢),你会按问题找对应那几页。前提是书得先按页拆开,你才能精准翻。

分块大小有讲究(这是 RAG 效果的第一杀手):

  • 技术文档:400-600 字。一个知识点短,小切更精准。
  • 法律条文:1200-1500 字。条款之间互相引用,大切才不会割断上下文。
  • FAQ 问答:200-400 字。一条问答天然就是一小块,直接切。

3. Embedding(意思标签)= 给每页贴标签

Section titled “3. Embedding(意思标签)= 给每页贴标签”

这是 RAG 最“玄”的一步,但用类比一说就懂:给拆好的每一段文字,贴一个**“意思标签”。这个标签其实是一串数字(向量),但你可以把它想成——“这页讲的是光合作用、植物、阳光”**这种意思描述。

为什么要贴标签?因为程序不会像人一样“读懂”文字,但它会比数字。意思相近的两段,标签里的数字也接近。这样以后问“光合作用”,程序就能算出“问题标签”和“哪页标签”最接近,直接定位。

回到类比:你给每页都贴了张便利贴,写着“这页讲啥”。考试时不用翻,只要扫一眼便利贴,就知道答案在哪几页。Embedding 就是那张自动生成的便利贴。

4. 检索 + 生成 = 翻到对应页,照着答题

Section titled “4. 检索 + 生成 = 翻到对应页,照着答题”

用户提问 → 程序把问题也贴个“意思标签” → 拿这个标签去和所有页的标签比 → 挑出最像的几段 → 把这几段连同问题一起递给大模型 → 大模型看着资料写答案。

回到类比:你翻到 3 页相关内容,摊在桌上,看着它们答题。这就是 RAG 的最后一步。

普通大模型答题是凭记忆——它的“记忆”是训练时看过的海量文本,但记不清的细节,它会“编”出一个看起来合理的答案,这就是幻觉。

RAG 治幻觉的原理一句话:不让他凭记忆,强制他看着资料答。

具体怎么做?在 Prompt(给模型的指令)里把找到的资料塞进去,并要求“只基于资料回答,找不到就说不知道”。模型从“闭卷脑补”变成“开卷抄书”,编造的概率大幅下降。

当然,这不是 100% 治愈——如果检索召回的资料本身就错或者不相关,模型还是会答错。所以后面的进阶课要讲混合检索 + Rerank(把召回准确率从 60% 拉到 90%)。

别怕,我把 50 行拆成 4 个小步骤,每步单独一个代码块,每步都对应“开卷考试”的一步。最后再给完整版。

先准备环境:把上一节封装用的智谱 GLM 客户端建好。

# === 第 0 步:搭好工具台(就两行,跟上一节一模一样)===
import os, numpy as np
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("GLM_API_KEY"), # 你的智谱 key
base_url="https://open.bigmodel.cn/api/paas/v4/" # 智谱的接口地址
)

Step 1:准备几条“文档”(加载 + 分块)

Section titled “Step 1:准备几条“文档”(加载 + 分块)”

先用最简单的列表模拟“知识库”,别一上来就读 PDF,那样你会被文件操作绕晕。先看清“知识库”长什么样——就是几段文字而已。

对应开卷考试:这步就是“把资料搬进考场,并按页码摆好”。

# === Step 1:知识库(就是几段文字,模拟分块后的样子)===
documents = [
"FDE 是前沿部署工程师,Palantir 首创,2026 年因大模型爆发而火。",
"FDE 的核心是填补产品与客户真实需求的鸿沟,既要写代码也要见客户。",
"RAG 是检索增强生成,让模型基于你的资料回答,能治幻觉。",
"评估是 FDE 面试分水岭,49% 的 JD 要求,但最没人教。",
]
print(f"知识库搬好了,一共 {len(documents)} 段")

Step 2:把文档变成“意思标签”(Embedding)

Section titled “Step 2:把文档变成“意思标签”(Embedding)”

这步就像给每一页贴便利贴。我们调一次智谱的 embedding-3 模型,它会返回一串数字(就是那个“意思标签”)。

对应开卷考试:给每页贴上“这页讲啥”的便利贴。

# === Step 2:给每段文字贴"意思标签"(Embedding)===
def embed(text):
"""把一段文字变成一串数字(意思标签)"""
resp = client.embeddings.create(model="embedding-3", input=text)
return np.array(resp.data[0].embedding) # 拿到一串数字,存成 numpy 数组方便后面算
doc_vectors = [embed(d) for d in documents] # 给每段都贴上标签
print(f"贴好了,每段标签是 {len(doc_vectors[0])} 个数字")

Step 3:问问题时,先找最相关的几条(检索)

Section titled “Step 3:问问题时,先找最相关的几条(检索)”

用户提问时,我们先把问题也贴个标签,再和所有文档的标签比一比——“意思有多像”。比的方法叫余弦相似度,你不用记名字,记住它就是“算两个标签意思有多接近”就行:结果越接近 1,越像。

对应开卷考试:扫一眼便利贴,翻到跟问题最对得上的那几页。

# === Step 3:检索——算"意思有多像",挑最相关的 top_k 段 ===
def retrieve(query, top_k=2):
q_vec = embed(query) # 给问题也贴个标签
scores = [np.dot(q_vec, dv) for dv in doc_vectors] # 算问题跟每段"意思有多像"(点积≈余弦相似度)
top_idx = np.argsort(scores)[-top_k:][::-1] # 按像不像排序,取最像的前 top_k 段
return [documents[i] for i in top_idx] # 把这几段原文拿出来
# 试试看:问"FDE 是干嘛的",它能不能翻到讲 FDE 的那两段
print("翻到这几段:", retrieve("FDE 是干嘛的?"))

Step 4:把找到的塞给大模型答题(生成)

Section titled “Step 4:把找到的塞给大模型答题(生成)”

最后一步,把检索到的那几段连同问题一起丢给 glm-4-flash,让它看着资料答。这里调的就是上一节我们封装过的 chat() 那种接口。

对应开卷考试:把翻到的几页摊开,照着答题。

# === Step 4:生成——把找到的资料塞进 Prompt,让模型看着答 ===
def rag_answer(query):
context = "\n".join(retrieve(query)) # 把翻到的几段拼起来
prompt = f"基于以下资料回答问题,找不到就说不知道。\n资料:\n{context}\n\n问题:{query}"
resp = client.chat.completions.create(
model="glm-4-flash",
messages=[{"role": "user", "content": prompt}]
)
return resp.choices[0].message.content
print(rag_answer("FDE 是干嘛的?"))
print(rag_answer("怎么治幻觉?"))

把上面 4 步合起来,就是一个能跑的最小 RAG。下面是完整版,你可以直接复制运行。

import os, numpy as np
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("GLM_API_KEY"),
base_url="https://open.bigmodel.cn/api/paas/v4/"
)
# 1. 知识库(模拟加载+分块后的结果)
documents = [
"FDE 是前沿部署工程师,Palantir 首创,2026 年因大模型爆发而火。",
"FDE 的核心是填补产品与客户真实需求的鸿沟,既要写代码也要见客户。",
"RAG 是检索增强生成,让模型基于你的资料回答,能治幻觉。",
"评估是 FDE 面试分水岭,49% 的 JD 要求,但最没人教。",
]
# 2. 贴"意思标签"(Embedding)
def embed(text):
resp = client.embeddings.create(model="embedding-3", input=text)
return np.array(resp.data[0].embedding)
doc_vectors = [embed(d) for d in documents]
# 3. 检索:算意思有多像,挑最像的前 top_k 段
def retrieve(query, top_k=2):
q_vec = embed(query)
scores = [np.dot(q_vec, dv) for dv in doc_vectors]
top_idx = np.argsort(scores)[-top_k:][::-1]
return [documents[i] for i in top_idx]
# 4. 生成:把资料塞进 Prompt,让模型看着答
def rag_answer(query):
context = "\n".join(retrieve(query))
prompt = f"基于以下资料回答问题,找不到就说不知道。\n资料:\n{context}\n\n问题:{query}"
resp = client.chat.completions.create(
model="glm-4-flash",
messages=[{"role": "user", "content": prompt}]
)
return resp.choices[0].message.content
if __name__ == "__main__":
print(rag_answer("FDE 是干嘛的?"))
print(rag_answer("怎么治幻觉?"))

✅ 跑通后,你会发现模型答案明确基于那几条文档,而不是它自己脑补——这就是 RAG 治幻觉的原理在代码层面的体现。模块 4 第 2 节我们把它工程化(混合检索 + Rerank)。

问题 原因 解决
检索不到相关内容 分块太大或太小 技术文档 400-600 字,法律 1200-1500,FAQ 200-400
回答不引用资料、还在脑补 Prompt 没强调 加“只基于资料回答,找不到就说不知道”
速度慢 每次都重新 Embedding 预计算标签存进向量库(Chroma / Milvus),别每次现算
检索到的是错的段 只看“意思像”,没看是否真的相关 进阶课讲 Rerank,给候选段重新打分
1. RAG 的四步流程是?为什么不能把整本书一次塞进模型?

加载 → 分块 → Embedding → 检索生成。不能整本塞是因为 ① 上下文窗口有长度限制 ② 塞太多模型会“忘”前面 ③ 检索精度下降。分块才能精准命中相关片段。

2. 用"开卷考试"的类比,解释为什么 RAG 能治幻觉。

普通大模型是“闭卷”,凭记忆答题,记不清就编(幻觉)。RAG 强制模型“开卷”——先把相关资料检索出来塞进 Prompt,要求“只基于资料答,找不到就说不知道”。模型从脑补变成照着抄,编造概率大幅下降。注意:如果检索召回的资料本身就错,模型还是会答错,所以还要靠 Rerank 提精度。

3. 为什么技术文档分块通常切 400-600 字,法律切 1200-1500?

技术文档一个知识点短,小切块更精准。法律条文相互关联、互相引用,大切块才不会割裂上下文。分块大小要匹配内容的语义颗粒度——这是 RAG 效果的第一杀手。

把上面的最小 RAG 改成一个能回答“本教程里 FDE 是什么”的小应用:

  1. module-0-fde-intro/01-what-is-fde.md 里的关键段落(比如“FDE 的官方定义”“FDE vs AI 工程师”两节)复制成 5-8 段,替换掉 documents 列表。
  2. documents 里的每段切成 300-500 字的小块(手动切就行,先别写自动分块)。
  3. 跑一遍 rag_answer("FDE 和 AI 工程师有啥区别?"),看它能不能引用到你塞进去的内容。
  4. 对比一下:同样的问题直接问 glm-4-flash(不塞资料),和 RAG 版的答案有啥区别——你会肉眼看到“治幻觉”的效果。

把你跑出来的两个答案截图存档,这是你简历项目里“RAG 演示”的第一手素材。

3 句话回顾:

  1. RAG 就是“开卷考试”:加载(搬资料)→ 分块(按页拆)→ Embedding(贴意思标签)→ 检索生成(翻页+照着答)。
  2. 它治幻觉的原理:不让他凭记忆,强制他看着资料答
  3. 分块大小是 RAG 效果第一杀手,要按内容颗粒度调。

下一节:工程化——混合检索(关键词 + 向量)+ Rerank,把召回准确率从 60% 拉到 90%。

  • LlamaIndex 5 天 RAG 教程 — 官方“理解 RAG”系列,从零搭一个(免费)
  • RAG From Scratch — LangChain 出品的 41 节 RAG 系列,配 YouTube 视频(免费)
  • 把本节的 50 行 RAG 跑通,换成你自己的文档(如一份产品说明书)

← 上一节:MCP 协议 | 下一节:RAG 进阶