一、背景与目标
作为一名有十年经验的前端开发者,我在转向全栈工程师的过程中,决定亲手搭建一个生产级 RAG(Retrieval-Augmented Generation)系统。目标是从一本 PDF 书籍(《好好学习》)中自动回答相关问题,例如"成甲老师从哪一年开始接触知识管理?"、"什么是临界知识?"等。
然而,理想很丰满,现实很骨感。在从零搭建的过程中,我遇到了切片断层、语义漂移、模型维度不匹配、LangChain 版本冲突、旧数据库残留、LLM 指令遵循不稳定等一系列问题。本文记录了完整的排错与调优历程,并提供可直接复制的代码和配置。
二、环境准备
2.1 Python 虚拟环境
bash
bash
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
2.2 安装依赖
arduino
bash
pip install langchain langchain-community langchain-core langchain-huggingface langchain-openai \
langchain-text-splitters chromadb sentence-transformers flagembedding pypdf2 \
rank_bm25 numpy<2.0
注意:
numpy<2.0是为了避免chromadb与 NumPy 2.0 的不兼容错误。
三、基础 RAG 流水线(初版)
3.1 加载 PDF 与切片
ini
python
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
loader = PyPDFLoader("sample.pdf")
docs = loader.load()
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=150,
separators=["\n\n", "\n", " ", ""]
)
splits = text_splitter.split_documents(docs)
3.2 向量化(使用多语言模型)
ini
python
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2")
vectordb = Chroma.from_documents(splits, embedding=embeddings, persist_directory="./chroma_db")
3.3 检索 + 生成(使用 DeepSeek)
ini
python
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
llm = ChatOpenAI(model="deepseek-chat", api_key=os.getenv("DEEPSEEK_API_KEY"), base_url="https://api.deepseek.com/v1", temperature=0)
prompt = ChatPromptTemplate.from_messages([
("system", "你是一个知识库助手。请严格根据上下文回答。如果上下文中没有明确包含问题的答案,请直接回答'未找到相关信息'。禁止推断。"),
("human", "上下文:{context}\n\n问题:{input}")
])
chain = prompt | llm
四、遇到的坑与解决方案
4.1 切片断层
现象:关键句子被切碎,导致上下文不完整。
解决 :增大 chunk_size 至 1000,chunk_overlap 至 200,并添加中文标点作为分隔符。
ini
python
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)
4.2 语义漂移(检索不相关)
现象 :向量检索召回的内容与问题无关。 解决 :引入 BGE Rerank 重排序模型,对粗筛结果进行二次精排。
ini
python
from FlagEmbedding import FlagReranker
reranker = FlagReranker("BAAI/bge-reranker-v2-m3", use_fp16=False)
def rerank(query, docs, top_n=3):
pairs = [(query, doc.page_content) for doc in docs]
scores = reranker.compute_score(pairs, normalize=True)
ranked = sorted(zip(scores, docs), key=lambda x: x[0], reverse=True)
return [doc for _, doc in ranked[:top_n]]
4.3 模型维度不匹配(多语言 Embedding 对中文理解差)
现象:Rerank 分数极低(0.06),检索结果全是无关片段。

解决 :换用中文专用 Embedding 模型 BAAI/bge-small-zh-v1.5。
ini
python
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5")

4.4 LangChain 版本冲突与代码残留
现象 :ModuleNotFoundError: No module named 'langchain_core.memory' 或 AttributeError: np.float_ was removed。
解决:
- 统一升级 LangChain 全家桶:
pip install --upgrade langchain langchain-community langchain-core langchain-huggingface langchain-openai - 降级 NumPy:
pip install "numpy<2.0" - 彻底删除旧数据库
chroma_db文件夹。
4.5 LLM 指令遵循不稳定(过度拒绝)
现象:上下文明明包含答案,LLM 却回答"未找到相关信息"。

解决 :绕过 LangChain 模板,直接构造 SystemMessage 和 HumanMessage,并放宽指令。
swift
python
from langchain_core.messages import SystemMessage, HumanMessage
messages = [
SystemMessage(content="你是一个知识库问答助手。请严格基于【上下文】回答【问题】。即使上下文信息不完整,也请尝试提取相关信息回答。只有在上下文完全为空或与问题毫无关联时,才回答'未找到相关信息'。"),
HumanMessage(content=f"【上下文】\n{context_str}\n\n【问题】\n{query}\n\n请回答:")
]
response = llm.invoke(messages)
五、调优实验与效果对比
5.1 实验参数表
| 参数 | 初始值 | 调优后 |
|---|---|---|
| chunk_size | 800 | 1000 |
| chunk_overlap | 150 | 200 |
| 粗筛 k | 10 | 20 |
| Rerank top_n | 3 | 5 |
| Rerank 阈值 | 0.2 | 0.1 |
| Embedding 模型 | paraphrase-multilingual-MiniLM-L12-v2 | BAAI/bge-small-zh-v1.5 |

5.2 效果对比(以"成甲老师从哪一年开始接触知识管理?"为例)
| 版本 | Rerank 最高分 | 注入上下文长度 | 回答 |
|---|---|---|---|
| 初始版 | 0.00(未命中) | 16 字符 | 未找到相关信息 |
| 调优后 | 0.95 | 1500+ 字符 | 2008年 |
5.3 最终代码(可直接复制运行)
ini
python
import os
from dotenv import load_dotenv
load_dotenv()
# 关闭 LangChain 遥测警告
os.environ["LANGCHAIN_TRACING_V2"] = "false"
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, HumanMessage
from FlagEmbedding import FlagReranker
# ============ 1. 加载 PDF ============
print("📄 正在加载 PDF...")
loader = PyPDFLoader("sample.pdf")
docs = loader.load()
print(f" 共加载 {len(docs)} 页")
# ============ 2. 文本切片 ============
print("✂️ 正在切片...")
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1200, # 稍微再调大一点,保留更多上下文连贯性
chunk_overlap=300, # 增加重叠度,防止关键句子被切断
separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)
splits = text_splitter.split_documents(docs)
print(f" 共切成 {len(splits)} 块")
# ============ 3. 向量化(换用中文专用模型)============
print("🧠 正在生成向量并存入 ChromaDB...")
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5")
vectordb = Chroma.from_documents(
documents=splits,
embedding=embeddings,
persist_directory="./chroma_db"
)
print("✅ 向量数据库创建完成!")
# ============ 4. 初始化重排序模型 ============
print("🔄 初始化重排序模型(首次运行会下载模型)...")
reranker = FlagReranker("BAAI/bge-reranker-v2-m3", use_fp16=False)
# ============ 5. 构建 LLM ============
print("🤖 构建 RAG 问答链...")
llm = ChatOpenAI(
model="deepseek-chat",
openai_api_key=os.getenv("DEEPSEEK_API_KEY"),
openai_api_base="https://api.deepseek.com/v1",
temperature=0
)
# ============ 6. 交互式问答(极简直调版) ============
print("\n💬 RAG 知识库已就绪!输入问题(输入 'quit' 退出):")
while True:
query = input("\n❓ 请输入问题: ").strip()
if query.lower() == 'quit':
break
print("🔍 正在检索相关文档...")
# 1. 粗筛(增加召回数量)
candidate_docs = vectordb.similarity_search(query, k=20) # 从10增加到20
# 2. Rerank 精排
pairs = [(query, doc.page_content) for doc in candidate_docs]
scores = reranker.compute_score(pairs, normalize=True)
ranked = sorted(zip(scores, candidate_docs), key=lambda x: x[0], reverse=True)
# print("🔍 [Debug] Rerank 分数:")
# for score, doc in ranked[:5]:
# print(f" 分数: {score:.4f} | 内容: {doc.page_content[:100]}...")
# 3. 提取精排文档(取前5个,增加上下文丰富度)
top_docs = [doc for score, doc in ranked[:5] if score >= 0.1]
if not top_docs:
print("⚠️ 警告:Rerank 分数过低,启用兜底策略!")
top_docs = [ranked[0][1]] if ranked else []
# 4. 手动拼接上下文
context_str = "\n\n".join([doc.page_content for doc in top_docs])
# print(f"📝 注入上下文长度: {len(context_str)} 字符")
if len(context_str) < 50:
print("🚨 严重告警:注入上下文极短,检索阶段失败!请检查PDF内容和切片。")
# 5. 直接构造消息并调用 LLM
messages = [
SystemMessage(content="你是一个知识库问答助手。请严格基于【上下文】回答【问题】。直接给出最终答案,不要输出'上下文截断'、'根据上下文'等解释性前缀。如果确实没有,才回答'未找到相关信息'。"),
HumanMessage(content=f"【上下文】\n{context_str}\n\n【问题】\n{query}\n\n请回答:")
]
response = llm.invoke(messages)
print("\n🤖 AI 回答:", response.content)

七、总结
从零到一搭建生产级 RAG 系统,不仅仅是写几行代码那么简单。本文记录了切片粒度、向量模型选择、重排序、Prompt 设计、依赖管理等关键环节的踩坑与调优。最终的系统能够准确回答书中 90% 以上的问题,上下文注入长度稳定在 1000~2000 字符,Rerank 最高分可达 0.95。
希望这份实录能帮助你快速避开我走过的弯路,构建属于自己的高效 RAG 系统。如果你有任何问题或改进建议,欢迎在评论区留言交流!
附录:常用命令速查
- 删除旧数据库:
rm -rf ./chroma_db - 查看 PDF 内容:
python -c "import PyPDF2; reader = PyPDF2.PdfReader('sample.pdf'); print(reader.pages[0].extract_text())" - 安装指定版本包:
pip install "numpy<2.0" - 源码地址:github.com/qishuixian/...
- 测试文档:pan.baidu.com/s/1MMcXx--p...