RAG 搭建教程:从文档向量化到上下文注入,构建可生产部署的完整管道
很多人第一次搭 RAG 系统,卡在"跑通了但效果差"的阶段——召回的文档不对,模型答非所问,或者一放到生产环境就崩。这篇文章把一个完整的 RAG 搭建教程拆成四个关键环节,每个环节给出具体参数和取舍逻辑,目标是搭出能实际上线的管道,而不是 demo。
Chunking:切错了,后面全白费
文档切分是整条链路里最被低估的步骤。chunk 切得太大,embedding 语义模糊;切得太小,上下文碎片化,检索出来的片段没有足够信息让模型回答。
**固定长度切分(Fixed-size chunking)**适合结构化不强的长文本,推荐 chunk size 设 512 token,overlap 设 64~128 token。overlap 的作用是防止关键信息恰好落在两个 chunk 的边界被截断。
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=128,
length_function=len,
)
chunks = splitter.split_text(raw_text)
**语义切分(Semantic chunking)**更适合技术文档或有明确段落结构的内容:按标题层级、段落边界切分,同一语义单元不被拆散。代价是实现复杂度高一些,适合文档质量稳定的场景。
实际工程里的建议:先用固定切分跑基线,评估召回质量后再决定是否引入语义切分。不要一上来就过度工程化。
向量化:Embedding 模型的选型与部署
Embedding 是把 chunk 映射到高维向量空间,检索时计算 query 和 chunk 向量的余弦相似度。
主流选择:
| 模型 | 维度 | 适用场景 | 接入方式 |
|---|---|---|---|
| text-embedding-3-large | 3072 | 英文/多语言高精度 | OpenAI API |
| text-embedding-3-small | 1536 | 成本敏感场景 | OpenAI API |
| BAAI/bge-m3 | 1024 | 中文强,支持离线 | HuggingFace / 本地部署 |
对于中文文档,bge-m3 的效果通常比 OpenAI 的通用 embedding 更稳定,且可以本地部署,规避数据出境合规风险。
向量存储选 Qdrant 或 Weaviate 都可以。如果是快速原型,FAISS + 内存索引也够用,但不支持实时更新。生产环境推荐 Qdrant,支持 payload 过滤、持久化和水平扩展。
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams
client = QdrantClient(url="http://localhost:6333")
client.create_collection(
collection_name="docs",
vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
)
检索 + 重排序:两阶段让召回质量上一个台阶
单靠向量检索(ANN 近似近邻搜索)召回 top-k,精度往往不够。原因是 embedding 空间对语义的压缩是有损的,尤其遇到专业术语或短查询时,相似度排名靠前的 chunk 未必是最相关的。
标准做法是两阶段检索:
- 粗检索:用向量检索捞出 top-20~50 个候选 chunk
- 精排(Reranking):用 Cross-Encoder 模型对 query 和每个候选 chunk 做精细打分,取 top-3~5 送入上下文
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("BAAI/bge-reranker-v2-m3")
pairs = [(query, chunk) for chunk in candidates]
scores = reranker.predict(pairs)
# 取 top-5
ranked = sorted(zip(scores, candidates), reverse=True)[:5]
final_chunks = [chunk for _, chunk in ranked]
bge-reranker-v2-m3 在中文场景下表现稳定,推理延迟在 GPU 上约 50~100ms(批量 20 条),CPU 部署约 300~500ms,可以接受。
**Hybrid Search(混合检索)**是另一个值得加的优化:把向量检索和 BM25 关键词检索的结果用 RRF(Reciprocal Rank Fusion)融合。关键词检索对精确匹配(产品型号、人名、代码片段)的召回更稳,两者互补。
上下文注入与 Prompt 工程
检索完成后,把 final_chunks 组装进 prompt,这一步的细节决定最终生成质量。
上下文注入的几个工程细节:
- 顺序很重要:把相关度最高的 chunk 放在 prompt 靠近结尾的位置(紧邻问题之前),模型对位置靠后的内容注意力更集中
- 显式标注来源:给每个 chunk 加
[来源: 文件名, 第X节]标注,方便模型在回答时引用,也方便用户溯源 - 设定拒答逻辑:明确告诉模型"如果提供的文档中没有足够信息,请直接说不知道",防止幻觉
system_prompt = """你是一个文档问答助手。
只根据以下提供的文档内容回答问题。
如果文档中没有足够信息,请直接说"文档中未找到相关内容",不要猜测。"""
context = "\n\n".join([
f"[来源: {meta['source']}]\n{chunk}"
for chunk, meta in final_chunks
])
user_prompt = f"文档内容:\n{context}\n\n问题:{query}"
调用模型时,Claude Sonnet 4.6 和 GPT-5.4 在长上下文理解和指令遵循上都表现稳定,适合生产场景。温度建议设 0~0.2,确保回答可复现。
生产部署的几个关键检查项
RAG 管道跑通之后,上生产前需要过几个检查点:
延迟预算:端到端 P95 延迟目标控制在 3s 以内。向量检索通常 <50ms,reranking 约 100~500ms,LLM 生成是大头(1~2s)。如果 reranking 拖慢响应,可以异步预计算或缩减候选数量。
索引更新策略:文档更新时,增量 upsert 而不是全量重建。Qdrant 支持按 document_id 做向量更新,配合文档 hash 做变更检测,可以做到分钟级同步。
评估体系:上线前跑一轮离线评估,核心指标看 Recall@5(top-5 是否包含正确 chunk)和 Answer Faithfulness(生成答案是否有幻觉)。用 RAGAS 框架可以快速建起评估流水线。
监控:生产中记录每条 query 的检索结果 ID 和 rerank 分数,方便排查召回失败的 case。
搭完这条管道之后,你会发现 API 调用成本是持续运营的主要变量——尤其是 embedding 批量写入和 LLM 生成这两块。我自己用 XycAi 词元平台(https://www.xyc.ai)接入主力模型,GPT-5.4 和 Claude Sonnet 4.6 的价格低至官方价 1.4 折起,OpenAI 兼容接口,换一行 base_url 就能切换,Claude Code 和 Codex 也支持直连。对于要跑大量文档写入或多租户 RAG 服务的项目,成本差距相当可观,感兴趣可以看看。
一个 API 接入 200+ 全球 AI 模型
GPT · Claude · Gemini 官方模型低至 1.4 折起,持大模型算法备案号,CN2 直连低至 5ms,可开全球合规发票。
立即体验 XycAi →