RAG 教程:从零搭建检索增强生成系统
RAG 教程:从零开始搭建检索增强生成系统。本文使用 LangChain 和 Chroma 向量数据库,手把手教你实现一个基于私有知识库的 AI 问答系统。
这篇教程适合谁
你会 Python,调过 LLM API,现在想让模型基于你自己的文档回答问题——而不是靠模型的通用知识瞎猜。RAG(检索增强生成)就是干这个的。
不适合:零编程基础。不想写代码的,用 Dify 可以零代码搭 RAG。 最终产物:一个能加载文档、分块、向量化、检索、生成答案的完整 RAG 系统。环境假设
- Python 3.9+
- OpenAI API Key(用于 Embedding 和生成,换成其他模型改两行代码)
- 以当前常见版本为准
RAG 的完整流程
文档 → 分块 → 向量化(Embedding)→ 存入向量数据库
↓
用户问题 → 向量化 → 相似度检索 → 取出相关片段
↓
相关片段 + 用户问题 → LLM → 生成答案
核心思想:先从你的文档里找到最相关的几段,和问题一起喂给 LLM,让它基于这些内容回答。
安装依赖
pip install langchain langchain-openai chromadb tiktoken
import os
os.environ["OPENAI_API_KEY"] = "your-api-key"
文档加载与分块
from langchain_community.document_loaders import TextLoader, DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 加载单个文件
loader = TextLoader("knowledge.txt", encoding="utf-8")
documents = loader.load()
# 或加载整个目录
loader = DirectoryLoader("./docs", glob="*/.txt", loader_cls=TextLoader)
documents = loader.load()
# 分块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ",", " "]
)
chunks = text_splitter.split_documents(documents)
print(f"文档被分为 {len(chunks)} 个块")
分块是最容易被忽视但影响最大的环节。chunk_size 太大,检索出来的片段不精准;太小,片段没有完整语义。中文文档建议 300-800 字符。chunk_overlap 是防止一句话被切成两半,设为 chunk_size 的 10%-20%。
separators 里的中文标点很重要——不加的话,RecursiveCharacterTextSplitter 按英文标点切,中文文档会被切成奇怪的片段。
向量化与存储
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db" # 持久化,下次不用重新索引
)
print("向量数据库创建完成!")
Embedding 模型怎么选:OpenAI 的 text-embedding-3-small 性价比高,但对中文的语义理解不如专门的中文 Embedding 模型(如 BGE 系列)。如果你的文档全是中文,可以考虑用 HuggingFace 上的 BGE 模型。
测试检索
results = vectorstore.similarity_search("什么是 RAG?", k=3)
for i, doc in enumerate(results):
print(f"结果 {i+1}: {doc.page_content[:100]}...")
检索结果不好怎么办:先检查分块质量——手动看切出来的片段是否语义完整。如果分块没问题但检索不精准,换 Embedding 模型或调 k 值。
构建 RAG Chain
from langchain_openai import ChatOpenAI
from langchain.prompts import ChatPromptTemplate
from langchain.schema.runnable import RunnablePassthrough
from langchain.schema.output_parser import StrOutputParser
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 3}
)
template = """你是一个知识库助手。请根据以下参考内容回答用户的问题。
如果参考内容中没有相关信息,请如实告知用户,不要编造答案。
参考内容:
{context}
用户问题:{question}
回答:"""
prompt = ChatPromptTemplate.from_template(template)
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
rag_chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
answer = rag_chain.invoke("RAG 和微调有什么区别?")
print(answer)
为什么 temperature=0:RAG 场景要求答案忠实于文档内容,不需要创造性。高温度会让模型"发挥",可能编造文档里没有的信息。
Prompt 里的"不要编造答案"为什么重要:不加这句,LLM 在检索不到相关内容时会用自己的通用知识回答,用户以为是文档里写的——这是 RAG 系统最常见的问题。
完整问答系统
def ask_question(question: str) -> dict:
"""RAG 问答函数,返回答案和来源文档。"""
relevant_docs = retriever.invoke(question)
answer = rag_chain.invoke(question)
return {
"question": question,
"answer": answer,
"sources": [doc.page_content[:200] for doc in relevant_docs]
}
result = ask_question("向量数据库的作用是什么?")
print(f"问题:{result['question']}")
print(f"答案:{result['answer']}")
print(f"来源:{result['sources']}")
返回来源文档很重要——让用户知道答案是从哪段文档来的,方便验证。
最容易踩的坑
- 分块质量差:这是 RAG 效果不好的头号原因。分块太大检索不精准,太小丢失上下文。手动检查分块结果。
- Embedding 模型不匹配:用英文 Embedding 模型处理中文,语义相似度计算不准。中文文档用中文 Embedding。
- Prompt 没约束:不告诉 LLM "基于参考内容回答",它就自由发挥。
- Top-K 太小:
k=3可能漏掉相关信息。复杂问题可以试k=5。 - 没有处理"找不到"的情况:检索到的内容和问题不相关时,系统应该说"我不知道"而不是硬答。
什么时候用 RAG,什么时候用微调
| 场景 | 推荐方案 |
|---|---|
| 知识经常更新 | RAG(更新文档即可) |
| 需要引用来源 | RAG(可以返回原文) |
| 改变模型的输出风格 | 微调 |
| 改变模型的领域能力 | 微调 |
| 数据量小(<100 条) | RAG |
| 数据量大(>10 万条) | 微调 + RAG |
进阶学习
- RAG 的原理:RAG 入门
- 向量数据库:向量数据库是什么
- 低代码搭 RAG:Dify 教程
- LangChain 深入:LangChain 教程
---
本文最后更新于 2026-07-08。代码示例基于 LangChain 0.2+,请根据最新版本调整。保障 API 调用稳定性
调用 OpenAI、Anthropic 等海外 API 时,网络波动可能导致请求超时或失败。稳定的网络环境有助于提升开发效率。
⚠️ 请遵守所在地的法律法规和服务条款。使用 AI API 时请遵守各平台的使用政策。
了解安全上网方案 →常见问题
相关推荐
获取更多 AI 内容
订阅更新,第一时间获取新教程和工具推荐。