RAG 完全教程(小白友好版)
这份 RAG 教程我从"为什么需要它"讲起,每个概念都用生活类比拆解,再从手写版(懂原理)到框架版(能上手),最后给完整实战。
RAG 完全教程(小白友好版)
一、先搞懂:RAG 到底是什么
RAG = Retrieval-Augmented Generation = 检索增强生成
拆成三个词理解:
- 检索(Retrieval):先去资料库里"查资料"
- 增强(Augmented):把查到的资料"塞给"AI
- 生成(Generation):AI 基于资料"生成"答案
为什么需要 RAG?(用生活类比)
想象大模型是一个博学但健忘、还会瞎编的学霸:
问题:大模型直接回答有三个毛病
├─ 不知道你公司内部的事(训练时没见过你的资料)
├─ 不知道最新消息(训练数据有截止日期)
└─ 不确定时会"一本正经地胡说"(幻觉)
RAG 的解决思路——开卷考试:
闭卷考试(纯大模型):
你问 → AI 凭记忆答 → 可能记错/不知道/瞎编
开卷考试(RAG):
你问 → 先翻书找到相关章节 → AI 看着书回答 → 准确、有依据
一句话:RAG 就是给 AI 配了一个"可以随时查阅的资料库",让它先查资料再回答,而不是凭记忆瞎答。
对照你的 Java 项目:
RagAnswerAdvisor+pgvector干的就是这个事。
二、RAG 完整流程全景图
RAG 分两个阶段,先理解这张图(最重要):
═══════ 阶段一:建库(离线,准备资料)═══════
你的文档 切成小块 变成向量 存入向量库
(PDF/Word/txt) → (chunking) → (embedding) → (vector store)
一本厚书 撕成一页页 每页编个"坐标" 放进带索引的柜子
═══════ 阶段二:问答(在线,实时回答)═══════
用户提问 问题也变向量 去库里找最像的 资料+问题给AI AI生成答案
"公司年假几天" → (embedding) → (检索 top-k) → (拼提示词) → (LLM生成)
找到"年假政策"那几页 "根据以下资料回答" "公司年假5天"
记住这个顺序:加载 → 切分 → 向量化 → 存储 ||| 提问 → 检索 → 增强 → 生成
下面逐个拆解每一步。
三、核心概念逐个拆解
3.1 文档加载(Loading)
把各种格式的文件读成纯文本。
# 不同格式用不同加载器
from langchain_community.document_loaders import (
TextLoader, # txt
PyPDFLoader, # pdf
Docx2txtLoader, # word
WebBaseLoader, # 网页
)
# 加载 txt
loader = TextLoader("公司制度.txt", encoding="utf-8")
docs = loader.load()
print(docs[0].page_content) # 文本内容
print(docs[0].metadata) # 元数据(来源、页码等)
小白要点:Document 对象有两部分——page_content(正文)和 metadata(出处信息,后面检索时用来标注"答案来自哪")。
3.2 文档切分(Chunking)——小白难点,重点讲
为什么要切?(这是 RAG 最关键的概念之一)
原因1:模型上下文有限
一本300页的书全塞给AI?放不下,也太贵
原因2:检索要精准
用户问"年假几天",你应该只给他"年假政策"那一段
而不是把整本员工手册都给他
用类比理解:切分就像把一本书拆成一张张知识卡片,这样查的时候能精准抽出最相关的几张卡片。
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每块最多500字符
chunk_overlap=50, # 相邻块重叠50字符
separators=["\n\n", "\n", "。", "!", "?", " ", ""], # 优先按这些断开
)
chunks = splitter.split_documents(docs)
print(f"切成了 {len(chunks)} 块")
两个关键参数(小白必懂):
① chunk_size(块大小)
太大(如2000):检索不精准,一块里混了好几个主题,还浪费token
太小(如100):上下文被切碎,一句话的意思可能断在两块里
经验值:300-800,中文偏小,英文偏大
② chunk_overlap(块重叠)
为什么要重叠?防止把完整意思切断
不重叠的问题:
块1:...员工入职满一年,
块2:可享受5天年假... ← "享受5天年假"和"满一年"被切散了
重叠后:
块1:...员工入职满一年,可享受
块2:满一年,可享受5天年假... ← 重叠部分保住了完整语义
经验值:chunk_size 的 10%-20%
类比:切香肠时每片留一点点连着,免得关键信息正好被切在两片中间。
3.3 Embedding 向量化——小白难点,重点讲
什么是向量(Embedding)?
这是 RAG 最玄但最核心的概念。用大白话讲:
向量 = 把一段文字变成一串数字(坐标)
让"意思相近"的文字,数字也"相近"
用地图坐标类比(最好理解):
想象一张"语义地图",每个词/句子是地图上的一个点:
猫 (1.2, 3.4)
狗 (1.3, 3.5) ← 猫和狗离得近(都是宠物)
汽车 (8.9, 0.2) ← 汽车离得远(不相关)
"宠物"相关的词聚在一起,"交通"相关的词聚在另一边
实际上不是2维,而是几百上千维,但道理一样:相似的内容,向量距离近。
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(
model="text-embedding-3-small",
api_key="sk-xxx",
)
# 把文字变成向量
vector = embeddings.embed_query("猫喜欢吃鱼")
print(len(vector)) # 1536(1536个数字组成的坐标)
print(vector[:5]) # [0.012, -0.034, 0.056, ...]
为什么这玩意儿能用来检索?
用户问:"喵星人吃什么?"
→ 变成向量 (1.21, 3.41, ...)
→ 和库里"猫喜欢吃鱼"的向量 (1.20, 3.40, ...) 距离很近!
→ 即使没有一个字相同,也能匹配上(因为"喵星人"≈"猫")
这就是 RAG 厉害的地方:理解语义,不是简单关键词匹配
对照传统搜索:
传统关键词搜索:搜"喵星人" → 找不到"猫"的文章(字面不匹配)
向量语义搜索: 搜"喵星人" → 能找到"猫"的文章(意思相近)
3.4 向量数据库(Vector Store)
存放向量,并能快速找出最相似的。
from langchain_community.vectorstores import Chroma
# 把切好的块向量化并存入
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
)
常见向量库对比(小白选型):
| 向量库 | 特点 | 适合 |
|---|---|---|
| Chroma | 轻量,本地,零配置 | 学习/小项目 |
| FAISS | Facebook出品,快,本地 | 中等规模 |
| pgvector | PostgreSQL插件 | 已有PG数据库(你Java项目用这个) |
| Milvus | 分布式,海量 | 大规模生产 |
| Pinecone | 云服务,免运维 | 不想自己搭 |
3.5 检索(Retrieval)
从向量库里找出和问题最相关的几块。
# 创建检索器
retriever = vectorstore.as_retriever(
search_kwargs={"k": 4} # 返回最相关的4块(top-k)
)
# 检索
results = retriever.invoke("公司年假有几天?")
for doc in results:
print(doc.page_content) # 最相关的4个文本块
k 怎么选(小白):
k太小(如1):可能漏掉相关信息
k太大(如20):塞太多无关内容,干扰AI,浪费token
经验值:3-5
3.6 生成(Generation)
把检索到的资料 + 用户问题,拼成提示词给 AI。
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="gpt-4o-mini", api_key="sk-xxx")
# 提示词模板(关键:告诉AI"根据资料回答")
prompt = ChatPromptTemplate.from_template("""
请根据以下参考资料回答用户问题。
如果资料中没有相关信息,就说"我不知道",不要编造。
参考资料:
{context}
用户问题:{question}
回答:
""")
# 拼接资料
context = "\n\n".join(doc.page_content for doc in results)
# 生成答案
chain = prompt | llm
answer = chain.invoke({"context": context, "question": "年假几天?"})
print(answer.content)
提示词里"没有就说不知道"这句很重要——这是抑制 AI 瞎编(幻觉)的关键。
四、从零手写一个最简 RAG(理解原理)
不用任何框架,用最朴素的方式实现,让你看清 RAG 的"骨架":
import numpy as np
from openai import OpenAI
client = OpenAI(api_key="sk-xxx")
# ========== 阶段一:建库 ==========
# 1. 准备知识(这里直接用文本,省略加载和切分)
documents = [
"公司年假政策:入职满一年的员工享有5天年假。",
"公司病假政策:员工每年享有10天带薪病假。",
"公司报销流程:发票需在月底前提交给财务部。",
"公司工作时间:早9点到晚6点,午休一小时。",
]
# 2. 把每个文档变成向量
def get_embedding(text):
resp = client.embeddings.create(model="text-embedding-3-small", input=text)
return resp.data[0].embedding
doc_vectors = [get_embedding(doc) for doc in documents] # 提前算好所有文档的向量
# ========== 阶段二:问答 ==========
# 3. 计算相似度(余弦相似度:越接近1越相似)
def cosine_similarity(a, b):
a, b = np.array(a), np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
def rag_answer(question, top_k=2):
# 4. 问题也变向量
q_vector = get_embedding(question)
# 5. 算问题和每个文档的相似度,排序取最高的几个
similarities = [cosine_similarity(q_vector, dv) for dv in doc_vectors]
top_indices = np.argsort(similarities)[::-1][:top_k] # 取相似度最高的top_k个
# 6. 取出最相关的文档
context = "\n".join(documents[i] for i in top_indices)
print(f"[检索到的资料]\n{context}\n")
# 7. 拼提示词,让AI回答
prompt = f"""根据以下资料回答问题,没有就说不知道。
资料:
{context}
问题:{question}
回答:"""
resp = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
# 测试
print(rag_answer("年假有几天?"))
# [检索到的资料] 公司年假政策:入职满一年的员工享有5天年假。...
# 回答:入职满一年的员工享有5天年假。
这 40 行就是 RAG 的全部本质! 框架做的事一模一样,只是帮你处理了加载、切分、向量库索引等工程细节。
五、用 LangChain 实现(生产级,简洁)
理解原理后,实际项目用框架,几行搞定:
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
# 1. 加载 + 切分
docs = TextLoader("knowledge.txt", encoding="utf-8").load()
chunks = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=50
).split_documents(docs)
# 2. 向量化 + 入库
vectorstore = Chroma.from_documents(chunks, OpenAIEmbeddings(api_key="sk-xxx"))
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
# 3. 构建 RAG 链(LCEL 管道)
prompt = ChatPromptTemplate.from_template("""
根据以下资料回答问题,没有就说不知道。
资料:{context}
问题:{question}
""")
def format_docs(docs):
return "\n\n".join(d.page_content for d in docs)
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| ChatOpenAI(model="gpt-4o-mini", api_key="sk-xxx")
| StrOutputParser()
)
# 4. 提问
print(rag_chain.invoke("公司年假几天?"))
六、RAG 进阶优化(从"能用"到"好用")
基础 RAG 效果一般,真实项目要靠这些技巧提升:
6.1 检索优化
① 混合检索(关键词 + 向量)
纯向量:理解语义好,但精确匹配差(如产品型号"X-200")
纯关键词:精确匹配好,但不懂语义
混合:两者结合,效果最好
LangChain 用 EnsembleRetriever 实现
② 重排(Rerank)
# 先粗检索20个,再用专门的重排模型精排出最相关的4个
# 类比:海选20人 → 精选4人
# 用 Cohere Rerank 或 BGE-Reranker
③ 查询改写(Query Rewriting)
用户问得口语化/模糊 → 先让LLM改写成更适合检索的query
"那个假期咋回事" → 改写成 → "公司年假政策是什么"
6.2 切分优化
# 按语义/结构切,而非死板按字数
# - Markdown 按标题切
# - 代码按函数切
# - 加上"父子分段":检索用小块(精准),喂给AI用大块(完整上下文)
6.3 加入元数据过滤
# 给每块打标签,检索时先过滤
# 对照你Java项目的 knowledge_tag!
retriever = vectorstore.as_retriever(
search_kwargs={
"k": 4,
"filter": {"category": "HR政策"} # 只在HR类文档里搜
}
)
这正是你 Java 项目里
RagAnswer的filterExpression: "knowledge == '知识库名称'"在做的事!
6.4 防幻觉
# 1. 提示词强调"只根据资料,没有就说不知道"
# 2. 让AI标注答案来源(引用哪段资料)
# 3. 返回检索到的原文,让用户可核对
七、完整实战:知识库问答系统
整合所有知识,做一个可复用的 RAG 类:
from langchain_community.document_loaders import TextLoader, PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
class KnowledgeBase:
"""知识库问答系统(对应你Java项目的 RagService)"""
def __init__(self, api_key: str):
self.embeddings = OpenAIEmbeddings(api_key=api_key)
self.llm = ChatOpenAI(model="gpt-4o-mini", api_key=api_key, temperature=0)
self.vectorstore = None
self.splitter = RecursiveCharacterTextSplitter(
chunk_size=500, chunk_overlap=50
)
def add_documents(self, file_paths: list[str]):
"""添加文档建立知识库(阶段一:建库)"""
all_chunks = []
for path in file_paths:
# 按扩展名选加载器
if path.endswith(".pdf"):
loader = PyPDFLoader(path)
else:
loader = TextLoader(path, encoding="utf-8")
docs = loader.load()
chunks = self.splitter.split_documents(docs)
all_chunks.extend(chunks)
# 入库
if self.vectorstore is None:
self.vectorstore = Chroma.from_documents(all_chunks, self.embeddings)
else:
self.vectorstore.add_documents(all_chunks)
print(f"✅ 已添加 {len(all_chunks)} 个知识块")
def ask(self, question: str, k: int = 4) -> dict:
"""提问(阶段二:问答)"""
if self.vectorstore is None:
return {"answer": "知识库为空,请先添加文档", "sources": []}
# 1. 检索
docs = self.vectorstore.similarity_search(question, k=k)
context = "\n\n".join(d.page_content for d in docs)
# 2. 生成
prompt = ChatPromptTemplate.from_template("""
你是一个专业的知识库助手。请严格根据以下参考资料回答问题。
如果资料中没有相关信息,请回答"抱歉,知识库中没有相关信息",不要编造。
参考资料:
{context}
问题:{question}
回答:""")
chain = prompt | self.llm | StrOutputParser()
answer = chain.invoke({"context": context, "question": question})
# 3. 返回答案 + 来源(可溯源,防幻觉)
return {
"answer": answer,
"sources": [d.page_content[:50] + "..." for d in docs],
}
# ========== 使用 ==========
kb = KnowledgeBase(api_key="sk-xxx")
# 建库
kb.add_documents(["公司制度.txt", "产品手册.pdf"])
# 问答
result = kb.ask("公司年假有几天?")
print("答案:", result["answer"])
print("来源:", result["sources"])
八、常见问题排查(小白避坑)
| 问题 | 原因 | 解决 |
|---|---|---|
| 检索不到相关内容 | chunk 太大/太小,或 embedding 模型差 | 调 chunk_size,换更好的 embedding |
| AI 还是瞎编 | 提示词没强调"根据资料" | 加"没有就说不知道" |
| 答案不完整 | k 太小,或切分把内容切断 | 增大 k,增加 overlap |
| 中文效果差 | 用了英文 embedding 模型 | 换中文友好的(如 bge-zh、m3e) |
| 精确词(型号)搜不到 | 纯向量语义检索的弱点 | 加混合检索(关键词+向量) |
| 速度慢 | 每次都重新建库 | 向量库持久化,只建一次 |
九、RAG 知识地图总结
RAG = 给AI配资料库,先查再答
阶段一 建库(离线):
加载 → 切分(chunk_size/overlap) → 向量化(embedding) → 存储(向量库)
阶段二 问答(在线):
提问 → 向量化 → 检索(top-k) → 拼提示词 → LLM生成 → 答案
核心概念:
Embedding = 文字变坐标,相似的靠得近
Chunking = 把文档切成知识卡片
向量库 = 能快速找最相似的柜子
检索 = 找最相关的k块
生成 = 资料+问题给AI,强调"按资料答"
进阶优化:
混合检索 / 重排 / 查询改写 / 元数据过滤 / 防幻觉
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)