你问 ChatGPT 公司内部文档,它答不上来。因为它不知道你们公司的东西。
RAG(Retrieval-Augmented Generation,检索增强生成)就是来解决这个问题的。它的核心思路很简单:
不让模型记住一切,而是让它知道去哪找答案。
2026 年了,RAG 已经从论文变成了标配。几乎所有企业级 AI 应用都在用它。今天手把手教你搭一个。
一、RAG 的基本原理
RAG 的工作流程分三步:
1. 切分(Chunking)
把长文档切成小块。比如一本 200 页的手册,切成 500 字一段的小片段。
2. 向量化(Embedding)
用 Embedding 模型把每段文字转成向量。向量是什么?就是一串数字,代表这段文字的语义。意思相近的文字,向量距离很近。
3. 检索 + 生成(Retrieve + Generate)
用户提问时,先把问题也转成向量,然后在向量库里找最相似的文档片段,把这些片段和原始问题一起丢给大模型,让它基于这些信息回答问题。
就这么简单。下面开始写代码。
二、环境准备
需要安装的包:
pip install langchain langchain-community langchain-openai chromadb tiktoken openai
设置 API Key:
import os
os.environ["OPENAI_API_KEY"] = "your-key-here"
三、文档加载与切分
先加载文档。支持 PDF、TXT、Markdown、HTML 等多种格式。
from langchain_community.document_loaders import DirectoryLoader
loader = DirectoryLoader(
"./knowledge_base",
glob="**/*.md",
show_progress=True
)
documents = loader.load()
然后把文档切分成小块。关键参数是 chunk_size(每块多少字符)和 chunk_overlap(相邻块的重叠字符数,防止边界信息丢失)。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每块500字符
chunk_overlap=50, # 重叠50字符
length_function=len,
)
chunks = splitter.split_documents(documents)
print(f"切分了 {len(chunks)} 个文档块")
实测建议:chunk_size 设在 300-800 之间效果最好。太小会丢失上下文,太大检索精度下降。
四、向量化与存储
用 OpenAI 的 embedding 模型把文本转成向量,存入 ChromaDB(一个轻量级向量数据库)。
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
text-embedding-3-small 是 OpenAI 最新的 embedding 模型,维度 1536,每千 token 只要 $0.02。比之前的 ada-002 便宜一半,效果更好。
ChromaDB 的好处是持久化存储。第一次向量化完成后,后续启动直接加载已有的向量库,不用再重新处理文档。
五、构建检索器
检索器负责从向量库中找出与问题最相关的文档片段。
retriever = vectorstore.as_retriever(
search_type="similarity", # 相似度搜索
search_kwargs={"k": 3} # 返回最相关的3个片段
)
k=3 表示每次检索返回 3 个最相关的文档块。一般 3-5 个是最佳范围。太少信息不足,太多会干扰模型判断。
还可以用 similarity_score_threshold 设置最低分数阈值,过滤掉不相关的结果:
retriever = vectorstore.as_retriever(
search_type="similarity_score_threshold",
search_kwargs={"k": 5, "score_threshold": 0.7}
)
六、构建问答链
把检索到的文档和原始问题一起传给大模型,让它基于这些材料回答问题。
from langchain_openai import ChatOpenAI
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
# 定义系统提示词
system_prompt = (
"你是一个智能问答助手。请根据以下参考资料回答问题。"
"如果资料中没有相关信息,请诚实回答'我不知道',不要编造答案。"
"\n\n{context}"
)
prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
("human", "{input}"),
])
# 创建文档组合链
question_answer_chain = create_stuff_documents_chain(
ChatOpenAI(model="gpt-4o", temperature=0),
prompt
)
# 创建检索链
rag_chain = create_retrieval_chain(retriever, question_answer_chain)
# 执行问答
response = rag_chain.invoke({"input": "你们公司的请假政策是什么?"})
print(response["answer"])
关键点:
- temperature 设为 0:RAG 场景需要确定性输出,不要随机性。
- 系统提示词很重要:明确告诉模型"不知道就说不知道",能有效减少幻觉。
- context 变量:检索到的文档片段会自动注入到这里。
七、完整代码
把所有步骤串起来,一个最小可用的 RAG 系统:
import os
from langchain_community.document_loaders import DirectoryLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain_chroma import Chroma
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
# 1. 加载文档
loader = DirectoryLoader("./knowledge_base", glob="**/*.md")
documents = loader.load()
# 2. 切分文档
splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(documents)
# 3. 构建向量库(首次运行)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(chunks, embeddings, persist_directory="./chroma_db")
# 4. 或者加载已有向量库
# vectorstore = Chroma(persist_directory="./chroma_db", embedding_function=embeddings)
# 5. 创建检索器
retriever = vectorstore.as_retriever(search_type="similarity", search_kwargs={"k": 3})
# 6. 构建问答链
system_prompt = (
"你是一个智能问答助手。请根据以下参考资料回答问题。"
"如果资料中没有相关信息,请回答'抱歉,我没有找到相关内容'。"
"\n\n{context}"
)
prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
("human", "{input}"),
])
qa_chain = create_stuff_documents_chain(ChatOpenAI(model="gpt-4o", temperature=0), prompt)
rag_chain = create_retrieval_chain(retriever, qa_chain)
# 7. 开始问答
while True:
query = input("你问: ")
if query == "退出":
break
result = rag_chain.invoke({"input": query})
print(f"回答: {result['answer']}")
八、进阶优化
基础版跑通后,还有几个方向可以继续优化:
1. 混合检索(Hybrid Search)
向量搜索擅长语义匹配,但对精确关键词搜索不够好。可以把向量搜索和关键词搜索(BM25)结合起来,取两者的加权得分。LangChain 内置了支持:
retriever = vectorstore.as_retriever(
search_type="mmr", # 最大边际相关性
search_kwargs={"k": 5, "fetch_k": 20}
)
2. 文档预处理
PDF 解析经常出问题。建议先用 UnstructuredLoader 做预处理,它支持更多格式和更智能的解析:
from langchain_community.document_loaders import UnstructuredPDFLoader
loader = UnstructuredPDFLoader("document.pdf")
3. 缓存层
频繁的问题没必要每次都走完整的 RAG 流程。加一层 Redis 缓存,相同问题直接返回之前答案:
import redis
r = redis.Redis(host='localhost', port=6379, db=0)
cached = r.get(f"query:{hash(query)}")
if cached:
return cached
# 否则走 RAG 流程...
4. 评估质量
用 RAGAS 框架评估你的 RAG 系统。它提供三个核心指标:
- Faithfulness:回答是否与检索到的文档一致
- Answer Relevance:回答是否与问题相关
- Context Precision:检索到的文档是否精准
pip install ragas
九、常见坑
坑1:文档切分不当
切得太碎,每块丢失上下文。切得太大,检索精度下降。500 字符 + 50 重叠是一个不错的起点,根据实际效果微调。
坑2:没有处理"不知道"的情况
模型倾向于编造答案。系统提示词里一定要加上"不知道就说不知道"。实测这能让幻觉率降低 60% 以上。
坑3:向量库不持久化
每次重启都重新向量化,浪费时间还烧钱。ChromaDB、FAISS、Pinecone 都支持持久化,务必开启。
坑4:embedding 模型选错
OpenAI 的 text-embedding-3-small 目前综合性价比最高。如果用国内模型,智谱的 embedding 效果也不错,而且不需要翻墙。
十、总结
RAG 的核心公式就一句话:
私有知识 + 向量检索 + 大模型生成 = 能回答你问题的 AI
不需要微调模型,不需要训练数据,只需要把你的文档喂进去,它就能基于这些文档回答问题。
对于中小团队来说,这是让 AI 落地最快、成本最低的方案。
搭建一个基础 RAG 系统,半天时间就够了。剩下的优化工作,可以根据实际需求逐步推进。
你的知识库准备好了么?