从零搭建 RAG 系统,我踩了 30 个坑后总结出这份指南

想做一个「上传 PDF → 智能问答」的系统,搜了一圈发现教程太浅、方案太重、踩坑太多。这篇文章把从零到生产的完整路径整理清楚了。

前言

最近想做一个 RAG(Retrieval-Augmented Generation)系统,需求很简单:上传 PDF,然后对文档提问。

搜了一圈教程,发现几个问题:

  • 教程太浅:只讲 langchain.load_qa_chain(),生产环境根本不能用

  • 方案太重:上来就要 Milvus + Kubernetes,个人项目扛不住

  • 缺少对比:向量检索 vs 关键词检索 vs 混合检索,到底选哪个?

  • 踩坑太多:embedding 维度不匹配、中文分词断裂、reranker 超时……

花了两周时间摸索后,我把所有经验整理成了一个开源仓库:rag-system-building,每一步都有代码、有对比、有踩坑记录。

一、架构设计

RAG 系统由两条管道组成:

Ingestion:  文档 → 解析 → 分块 → 向量化 → 存储
Query:      问题 → 向量化 → 检索 → 重排 → LLM 生成

按依赖顺序构建,每个模块只依赖前面的模块:

config.py → pdf_parser → text_splitter → embeddings → vector_store → retriever → reranker → llm → orchestrator → app

二、技术选型

向量数据库:FAISS 是最佳起点

数据库 适用规模 推荐场景
FAISS <50 万条 本地开发、小项目
ChromaDB <50 万条 需要元数据过滤
Milvus >100 万条 生产环境

FAISS 零依赖、速度最快,50 万条以内完全够用。不要一上来就上 Milvus。

Embedding 模型:bge-small-zh

中文场景推荐 BAAI/bge-small-zh-v1.5

  • 512 维,本地推理,CPU 也能跑

  • ~1000 条/秒的编码速度

  • 注意:需要加检索前缀 为这个句子生成表示以用于检索:

LLM:qwen-turbo 性价比最高

from openai import OpenAI
​
client = OpenAI(
    api_key="sk-xxx",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

通义千问 qwen-turbo 是国内最便宜的选择,RAG 场景下质量足够。

三、重排策略对比(核心干货)

向量检索返回的 top-K 结果不一定按相关性排序,需要重排(Reranking)。

我对比了三种方案的实际性能(10 个文档块,Apple M4):

方案 延迟 准确率提升 推荐场景
无重排 0ms 基线 延迟敏感
TF-IDF 3ms +12% 默认推荐
CrossEncoder 450ms +18% 精度优先
LLM Reranker 5.5s +22% 不推荐

结论:TF-IDF 是最佳默认选择。

3ms 延迟,+12% 准确率,零外部依赖。CrossEncoder 精度更高但慢 150 倍。LLM Reranker 性价比最低——每次调用 O(N) 次 API,DeepSeek 从国内调用还经常超时。

端到端优化效果:

优化前:CrossEncoder + qwen-plus + top_k=10 → 4558ms
优化后:TF-IDF + qwen-turbo + top_k=5       → 595ms(87% 提升)

四、关键词兜底检索(踩坑重点)

当向量检索分数过低(< 0.5)时,需要关键词兜底。这里有一个中文关键词提取的大坑

# ❌ 错误:提取整句话作为关键词
re.findall(r'[\u4e00-\u9fa5]{2,}', "武汉力源信息技术股份有限公司组织结构图")
# → ["武汉力源信息技术股份有限公司组织结构图"]  → 搜索时零匹配
​
# ✅ 正确:提取 2-4 字短词
re.findall(r'[\u4e00-\u9fa5]{2,4}', "武汉力源信息技术股份有限公司组织结构图")
# → ["武汉", "力源", "信息", "技术", "股份", "有限", "公司", "组织", "结构"]

正则 [\u4e00-\u9fa5]{2,} 会把整句话当做一个关键词,导致搜索时永远匹配不到。必须限制为 2-4 字。

五、常见踩坑记录

仓库里整理了 10+ 个踩坑记录,这里挑几个最典型的:

1. Embedding 维度不匹配

换了 embedding 模型但没有重建索引。FAISS 索引的维度是创建时固定的,换了模型必须删掉旧索引重建。

2. BGE 模型缺少检索前缀

# ❌ 错误
vectors = model.encode(["什么是RAG?"])
​
# ✅ 正确
query = "为这个句子生成表示以用于检索:什么是RAG?"
vectors = model.encode([query])

3. Streamlit 工作目录问题

Streamlit 的工作目录是 streamlit run 命令执行的目录,不是脚本所在目录。用 Path(__file__).parent 获取正确路径。

4. LLM 回答 "文档中未找到相关信息"

排查步骤:

  1. 确认 chunks 数量 > 0

  2. 确认检索有结果(分数 > 阈值)

  3. 确认 LLM 收到了上下文(打印 prompt)

常见原因:分数阈值设太高(> 0.8),实际最高分只有 0.6。

六、仓库里有什么

rag-system-building/
├── docs/
│   ├── architecture.md          # 架构设计 + 技术选型决策树
│   ├── document-parsing.md      # PDF/DOCX/TXT 解析方案
│   ├── vector-search.md         # 向量检索方案对比
│   ├── reranking.md             # 重排策略对比 + 性能基准
│   ├── llm-integration.md       # LLM 接入方案
│   ├── performance.md           # 性能优化指南
│   └── common-pitfalls.md       # 10+ 踩坑记录
├── templates/
│   ├── rag-pipeline.py          # 完整 RAG 管道(可直接运行)
│   ├── streamlit-ui.py          # Streamlit 前端
│   └── config.py                # 配置模板
└── benchmarks/
    └── reranking-comparison.md  # 重排性能基准测试

总结

搭建 RAG 系统,核心就几句话:

  1. FAISS 够用就别上 Milvus

  2. TF-IDF 重排是最佳默认选择

  3. qwen-turbo 性价比最高

  4. 中文关键词提取用 2-4 字短词

  5. embedding 模型换了必须重建索引

完整方案、代码和踩坑记录都在 GitHub 仓库里:

👉 rag-system-building

如果对你有帮助,给个 Star 吧 ⭐


本文同步发布于 GitHub

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐