一、 开场:一个让我彻夜难眠的问题
大家好,我是老金。
上周有个客户找到我,问了一个让我头疼的问题:
“老金,我们公司有20年的技术文档、产品手册、客服话术,加起来几百万字。能不能做个AI,让员工直接问问题,AI自动从这些文档里找答案?”
我当时的第一反应是:这不就是RAG吗?
但当我真正深入这个需求后,我发现——
RAG系统,远没有网上那些”5分钟搭建RAG”教程说的那么简单。
今天这篇文章,我想把我在这个项目中踩过的坑、积累的经验,系统地分享出来。
目标是:让你看完这篇文章,真的能从0到1搭建一套生产级的RAG系统。
二、 什么是RAG?为什么需要RAG?
2.1 RAG的核心概念
RAG = Retrieval-Augmented Generation,检索增强生成。
说白了就是:先检索,再生成。
传统的大模型(如GPT-4)有两个致命问题:
- 知识有截止日期:GPT-4的知识库截止到2023年,2024年发生的事它不知道
- 没有你的私有数据:公司的内部文档、产品信息,GPT-4从来没见过
RAG的解决方案是:
- 把你的私有文档切成小块,存入向量数据库
- 用户提问时,先从向量数据库检索相关内容
- 把检索到的内容作为”上下文”,喂给大模型
- 大模型基于这些上下文,生成准确的回答
2.2 RAG vs 微调 vs 长上下文
很多团队会纠结:我是用RAG,还是微调模型,还是等长上下文模型成熟?
我的建议:
| 方案 | 适用场景 | 成本 | 时效性 |
|---|---|---|---|
| RAG | 知识频繁更新、需要引用来源 | 中 | 实时 |
| 微调 | 特定任务、知识相对稳定 | 高 | 更新慢 |
| 长上下文 | 文档量小(<10万字) | 高(Token费) | 实时 |
对于企业知识库这种场景,RAG是最佳选择。
三、 RAG系统的核心架构
一个完整的RAG系统,包含以下模块:
用户提问 → Embedding → 向量检索 → 重排序 → 上下文构建 → LLM生成 → 回答
↑
文档入库流程:
文档 → 分块 → Embedding → 向量数据库
让我逐一拆解每个环节的技术选型和踩坑经验。
四、 第一步:文档处理与分块
4.1 分块策略的选择
这是RAG系统最容易被忽视的环节,但直接影响检索质量。
常见的分块策略:
- 固定长度分块:每512个token一块(最简单,但效果一般)
- 语义分块:按段落、章节分割(效果好,但需要解析文档结构)
- 滑动窗口:有重叠的分块(解决信息被切断的问题)
- 父文档检索:小块检索,大块返回(LangChain推荐方案)
4.2 我的踩坑经验
在客户项目中,我一开始用固定长度分块,结果:
- 一段完整的技术说明被切成了两块
- 检索时只找到了前半部分,答案不完整
后来我改用语义分块 + 滑动窗口:
from langchain.text_splitter import RecursiveCharacterTextSplitter
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每块500字符
chunk_overlap=100, # 重叠100字符
separators=["nn", "n", "。", "!", "?", ";", ",", " ", ""]
)
chunks = text_splitter.split_text(document)
效果提升明显:检索准确率从65%提升到82%。
五、 第二步:Embedding模型选择
Embedding模型的选择,直接影响检索质量。
5.1 主流Embedding模型对比
| 模型 | 维度 | 中文效果 | 成本 |
|---|---|---|---|
| OpenAI text-embedding-3-small | 1536 | 中等 | $0.02/1M tokens |
| OpenAI text-embedding-3-large | 3072 | 较好 | $0.13/1M tokens |
| BGE-large-zh | 1024 | 优秀 | 免费(开源) |
| M3E-large | 1024 | 优秀 | 免费(开源) |
5.2 我的推荐
中文场景,首选BGE系列。
原因:
- 中文效果好,在C-MTEB榜单上名列前茅
- 开源免费,无Token成本
- 可以本地部署,数据不出域
from sentence_transformers import SentenceTransformer
加载BGE模型
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
生成向量
embeddings = model.encode(chunks)
六、 第三步:向量数据库选型
6.1 主流向量数据库对比
| 数据库 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Chroma | 原型开发、小规模 | 轻量、易上手 | 性能有限 |
| Milvus | 生产环境、大规模 | 高性能、分布式 | 部署复杂 |
| Weaviate | 企业级、混合检索 | 支持多种检索 | 资源占用大 |
| Pinecone | SaaS、快速上线 | 全托管 | 贵、数据出境 |
6.2 我的选择
对于客户项目,我选择了Milvus:
- 数据量大(百万级文档)
- 需要高性能(QPS > 1000)
- 要求数据不出域
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType
连接Milvus
connections.connect("default", host="localhost", port="19530")
创建集合
fields = [
FieldSchema(name="id", dtype=DataType.INT64, is_primary=True, auto_id=True),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=1024),
FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=2000),
FieldSchema(name="metadata", dtype=DataType.JSON)
]
schema = CollectionSchema(fields, "knowledge_base")
collection = Collection("docs", schema)
创建向量索引
index_params = {
"metric_type": "COSINE",
"index_type": "IVF_FLAT",
"params": {"nlist": 1024}
}
collection.create_index("embedding", index_params)
七、 第四步:检索与重排序
7.1 为什么需要重排序?
向量检索是语义相似,不等于问题相关。
举个例子:
- 用户问:”如何处理客户投诉?”
- 向量检索返回了10条结果,其中第1条是”客户投诉的常见类型”,第8条才是”客户投诉处理流程”
这时候,就需要重排序模型来重新打分。
7.2 重排序模型选择
推荐使用BGE-Reranker:
from sentence_transformers import CrossEncoder
加载重排序模型
reranker = CrossEncoder('BAAI/bge-reranker-large')
对检索结果重排序
query = "如何处理客户投诉?"
candidates = [doc1, doc2, doc3, ...] # 向量检索返回的文档
计算相关性分数
scores = reranker.predict([(query, doc) for doc in candidates])
按分数排序
ranked_results = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)
7.3 混合检索
更高级的做法是向量检索 + 关键词检索混合:
- 向量检索:语义匹配,召回率高
- 关键词检索:精确匹配,准确率高
两者结合,效果最佳。
八、 第五步:Prompt设计与上下文构建
8.1 Prompt模板
一个标准的RAG Prompt:
你是一个专业的企业知识库助手。请根据以下上下文回答用户问题。
上下文:
{context}
用户问题:
{question}
要求:
- 只基于上下文回答,不要编造信息
- 如果上下文中没有相关信息,请明确告知用户
- 回答要简洁、准确、有条理
- 如果引用上下文内容,请标注来源
8.2 上下文构建策略
核心问题:上下文窗口有限,如何选择最相关的文档?
我的策略:
- 检索Top 20文档
- 重排序后取Top 5
- 按相关性分数加权组合
- 总Token数控制在3000以内
九、 完整代码示例
from langchain.vectorstores import Milvus
from langchain.embeddings import HuggingFaceEmbeddings
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
1. 初始化Embedding模型
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-large-zh-v1.5"
)
2. 连接向量数据库
vectorstore = Milvus(
embedding_function=embeddings,
collection_name="knowledge_base",
connection_args={"host": "localhost", "port": "19530"}
)
3. 创建检索器
base_retriever = vectorstore.as_retriever(
search_kwargs={"k": 20} # 检索Top 20
)
4. 添加重排序
reranker = CrossEncoderReranker(
model_name="BAAI/bge-reranker-large",
top_n=5 # 重排序后取Top 5
)
retriever = ContextualCompressionRetriever(
base_compressor=reranker,
base_retriever=base_retriever
)
5. 构建RAG链
from langchain.chains import RetrievalQA
from langchain.llms import OpenAI
qa_chain = RetrievalQA.from_chain_type(
llm=OpenAI(temperature=0),
retriever=retriever,
return_source_documents=True
)
6. 提问
result = qa_chain({"query": "客户投诉的标准处理流程是什么?"})
print(result["result"])
print(result["source_documents"])
十、 生产环境优化建议
10.1 性能优化
- 向量索引优化:使用HNSW索引,检索速度提升10倍
- 缓存机制:相同问题缓存答案,减少LLM调用
- 批处理:Embedding批量计算,提升吞吐量
10.2 质量优化
- 文档清洗:去除无关内容、格式化
- 分块调优:根据文档类型选择最佳分块策略
- 评估体系:建立RAG评估指标(准确率、召回率、相关性)
10.3 运维监控
- 检索延迟监控:P99 < 200ms
- 答案质量监控:用户满意度 > 85%
- 成本监控:Token消耗告警
十一、 写在最后
RAG系统搭建,不是简单的”调API”,而是一个需要精细调优的系统工程。
从文档处理、Embedding选择、向量数据库、重排序、Prompt设计——每个环节都有坑。
但一旦搭建好,它的价值是巨大的:
- 员工再也不用在几十万字的文档里大海捞针
- 新员工入职,AI就是最耐心的老师
- 知识沉淀,变成企业的核心竞争力
如果你也在搭建RAG系统,欢迎在评论区分享你的经验或问题。
我是技术老金,我们下期见!
📌 往期精彩回顾