大模型很聪明,但它有个致命弱点------它只知道训练截止日期之前的信息,而且不知道你公司内部的任何东西。
RAG(Retrieval-Augmented Generation,检索增强生成)就是解决这个问题的标准方案:先从你的私有文档里检索出相关内容,再把这些内容拼进 prompt 让大模型回答。
本文带你从零实现一个完整的 RAG 系统,代码可以直接跑。
整体架构
RAG 的流程分两个阶段:
建库阶段(离线):
- 加载文档(PDF、txt、网页等)
- 切分成小块(chunk)
- 向量化每个 chunk
- 存入向量数据库
查询阶段(在线):
- 用户提问
- 把问题向量化
- 从向量库里找最相似的 chunk
- 把 chunk + 问题拼成 prompt
- 大模型生成回答
环境准备
bash
pip install langchain langchain-community chromadb openai tiktoken
本文用 ChromaDB 做向量库,用 OpenAI Embedding 做向量化,大模型调 GPT-3.5。如果想换成国内模型(DeepSeek、通义等),只需换掉 LLM 和 Embedding 的初始化部分。
第一步:加载并切分文档
python
from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 加载文档
loader = TextLoader("knowledge.txt", encoding="utf-8")
documents = loader.load()
# 切分:每块 500 字符,重叠 50 字符
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
)
chunks = splitter.split_documents(documents)
print(f"切分出 {len(chunks)} 个 chunk")
chunk_overlap 是关键参数------让相邻 chunk 有一定重叠,避免一句话被切断后上下文丢失。
第二步:向量化并存入 ChromaDB
python
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
embeddings = OpenAIEmbeddings()
# 建库(第一次运行会调 Embedding API,有费用)
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db" # 持久化到本地
)
vectorstore.persist()
print("向量库建立完成")
第三步:检索 + 生成
python
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
# 加载已有的向量库
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=OpenAIEmbeddings()
)
llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=vectorstore.as_retriever(search_kwargs={"k": 3}), # 取最相关的3个chunk
return_source_documents=True # 同时返回来源,方便溯源
)
# 开始提问
question = "我们产品的退款政策是什么?"
result = qa_chain({"query": question})
print("回答:", result["result"])
print("\n来源文档:")
for doc in result["source_documents"]:
print("-", doc.page_content[:100])
几个实际踩坑点
1. chunk 大小要根据内容调
技术文档、合同这类信息密度高的,chunk_size 设 300-500 比较合适;FAQ 问答类可以更小,100-200 就够。太大会引入噪音,太小会丢失上下文。
2. Embedding 模型和查询要用同一个
建库和查询必须用同一个 Embedding 模型,不然向量空间不一致,检索结果会很差。
3. k 值不是越大越好
retriever 里的 k(返回几个 chunk)建议从 3 开始。k 太大会把不相关内容塞进 prompt,反而干扰模型回答。
4. 中文切分要注意
RecursiveCharacterTextSplitter 对中文支持还行,但如果文档是纯中文,建议加上中文标点作为切分分隔符:
python
splitter = RecursiveCharacterTextSplitter(
chunk_size=300,
chunk_overlap=30,
separators=["\n\n", "\n", "。", "!", "?", ";", " ", ""]
)
替换成本地/国产模型
不想用 OpenAI 的话,换成 DeepSeek 只需改两行:
python
from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(
model_name="deepseek-chat",
openai_api_key="your-deepseek-key",
openai_api_base="https://api.deepseek.com/v1"
)
Embedding 可以换成 HuggingFace 的免费模型:
python
from langchain.embeddings import HuggingFaceEmbeddings
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-small-zh-v1.5" # 中文效果不错
)
下一步
这是最基础的 RAG 实现。实际项目里还会遇到:
- 文档更新怎么增量入库
- 多路召回(关键词 + 向量混合检索)
- Rerank 对召回结果重排序
- 对话历史管理(多轮问答)
这些进阶内容在 gufacode.com 的 RAG 教程里有完整讲解,从原理到工程落地都有覆盖,可以继续深入。
跑通这个基础版本之后,你会发现 RAG 本身并不复杂------复杂的是让它在真实数据上表现稳定。慢慢调,多踩坑,才是正经路子。