RAG(检索增强生成)通过将外部知识库接入大语言模型,有效缓解模型幻觉和知识时效性不足的问题。生产环境中搭建一套完整的RAG系统,涉及向量数据库选型、文档分块、Embedding模型部署、检索召回优化及Prompt模板设计等多个环节。本文以实际部署流程为主线,逐步拆解各组件配置要点。
RAG系统架构设计与核心组件选型
典型RAG系统的数据流分为离线索引和在线检索两个阶段。离线阶段完成文档加载、分块、向量化及入库;在线阶段接收用户查询,经过向量化后在向量库中检索Top-K相关片段,拼接到Prompt中提交给LLM生成回答。
向量数据库选型需要考量召回精度、写入吞吐、运维成本三个维度。Milvus适合大规模生产场景,支持IVF、HNSW等多种索引类型;Qdrant在中等数据量下性能优异,Rust实现保证了低延迟;Weaviate内置模块化向量化能力,对新手友好。以下是基于Milvus的Docker部署配置:
# docker-compose.yml
services:
milvus-standalone:
image: milvusdb/milvus:v2.4.0
command: ["milvus", "run", "standalone"]
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
ports:
- "19530:19530"
depends_on:
- etcd
- minio
etcd:
image: quay.io/coreos/etcd:v3.5.5
environment:
ETCD_AUTO_COMPACTION_MODE: revision
ETCD_AUTO_COMPACTION_RETENTION: "1000"
command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379
minio:
image: minio/minio:RELEASE.2023-03-24T21-41-23Z
command: minio server /minio_data
文档分块策略对检索质量的影响
分块是RAG系统中影响召回率的关键环节。固定长度分块(如每500字一段)实现简单但容易截断语义完整性;按段落和标题分块能保留文档结构信息;重叠分块在相邻块间保留一定重叠区域,减少边界信息丢失。
实际项目中推荐使用递归字符文本分割器,结合自定义分隔符层级逐级拆分:
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=64,
separators=["\n## ", "\n### ", "\n\n", "\n", "。", ",", " "]
)
chunks = splitter.split_text(document_text)
chunk_overlap设为chunk_size的10%到15%之间,能在边界信息保留和存储冗余之间取得平衡。对于技术文档类内容,按Markdown标题层级分割的效果显著优于纯长度分割,因为同一个小节内的内容通常语义聚焦。
Embedding模型本地部署与批量向量化
中文场景下bge-large-zh-v1.5是效果较好的开源Embedding模型。通过SentenceTransformer库加载,批量编码文档片段:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
def embed_texts(texts, batch_size=32):
embeddings = model.encode(
texts,
batch_size=batch_size,
normalize_embeddings=True,
show_progress_bar=True
)
return embeddings
vectors = embed_texts(chunks)
for i, vec in enumerate(vectors):
collection.insert({
"id": i,
"vector": vec.tolist(),
"text": chunks[i],
"source": "doc_001"
})
normalize_embeddings=True对后续余弦相似度检索至关重要,归一化后点积等价于余弦相似度,计算更快。批量编码时batch_size控制在32到64之间,GPU显存不足时适当调小。
向量检索召回率优化与混合检索
纯向量检索在某些场景下召回率不足,尤其是查询中包含精确关键词时。混合检索(Hybrid Search)结合BM25关键词检索和向量语义检索,能显著提升召回效果。Milvus 2.4版本支持稀疏向量字段,可在同一Collection中同时存储稠密和稀疏向量:
# 混合检索参数配置
search_params = {
"data": [query_dense_vector],
"anns_field": "dense_vector",
"param": {"metric_type": "COSINE", "params": {"nprobe": 16}},
"limit": 20
}
results = collection.search(**search_params)
# 使用重排序模型精排Top-20
from FlagEmbedding import FlagReranker
reranker = FlagReranker('BAAI/bge-reranker-v2-m3')
candidates = [r.entity.get('text') for r in results[0]]
scores = reranker.compute_score([[query, doc] for doc in candidates])
top5 = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)[:5]
nprobe参数控制搜索的簇数量,增大可提升召回率但增加查询延迟。生产环境中nprobe取值16到32是常见配置。重排序模型将召回的Top-20精排为Top-5,对最终回答质量的提升非常明显,代价是增加约50ms的推理延迟。
Prompt工程模板设计与防幻觉策略
检索到的上下文如何拼接到Prompt中,直接影响LLM输出的准确性。以下是一个经过生产验证的Prompt模板:
prompt_template = """你是一个技术问答助手。请严格根据以下检索到的上下文回答问题。
上下文信息:
{context}
问题:{question}
要求:
1. 答案必须基于上下文内容,不得编造信息
2. 如果上下文中没有相关信息,直接回答"根据现有资料无法回答该问题"
3. 引用上下文时标注来源文档编号
4. 回答保持简洁,避免无关扩展
"""
第二条约束是防幻觉的关键——明确指示模型在信息不足时拒绝回答,而非凭预训练知识编造。将context按相关度从高到低排列,LLM对前置内容的关注度更高。Token预算控制在4096以内,超出时优先截断相关度低的片段。temperature参数设为0.1到0.3之间,降低输出随机性。
多轮对话场景中,将历史检索结果缓存到会话上下文,避免重复检索带来的延迟。对于答案中需要引用原文的场景,在分块时记录每个chunk在原文中的位置偏移,生成回答时可回溯原始段落。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/rag-jian-suo-zeng-qiang-sheng-cheng-jia-gou-da-jian-xiang/