RAG全流程实战:从文档到精准问答
检索增强生成(RAG)是一种将外部知识库与大型语言模型(LLM)能力结合的技术范式。其核心是通过向量检索技术,从海量非结构化数据中精准找到与用户问题相关的内容,并将其作为“证据”或“上下文”提供给LLM,从而生成更准确、实时且可追溯的答案。这有效解决了LLM知识截止、产生幻觉(编造事实)以及无法处理私有数据的问题。其应用场景广泛,涵盖智能客服、企业内部知识库、代码助手及多模态分析。
RAG系统流程可划分为离线索引构建和在线查询响应两大阶段。下面,我将结合关键技术与代码案例进行详细阐述。
阶段一:离线索引构建(文档到向量库)
该阶段的目标是将非结构化的原始数据(如PDF、网页、文档)处理并存储到向量数据库中,以供后续快速检索。
1. 数据加载与预处理:
原始数据需经清洗与分割。例如,PDF可能包含页眉页脚需要移除,网页存在HTML标签需要剥离。核心步骤是文本分块(Chunking),目的是将长文档分割成语义连贯、大小适中的片段。常见策略有:
| 分块策略 | 描述 | 适用场景 |
|---|---|---|
| 字符分块 | 按固定字符数(如512字)分割 | 简单快速,通用性强 |
| 递归分块 | 按优先级(如段落>句子)递归分割 | 保持语义单元完整性 |
| 语义分块 | 利用嵌入模型进行语义边界检测 | 追求最高语义连贯性 |
| 滑动窗口 | 分割时保留一定重叠字符(overlap) | 避免关键信息被硬边界切断 |
# 示例:使用 LangChain 进行递归分块
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import PyPDFLoader
# 加载PDF文档
loader = PyPDFLoader("企业员工手册.pdf")
documents = loader.load()
# 配置递归分块器,设置块大小和重叠区域
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500, # 每个块的字符数
chunk_overlap=50, # 块与块之间重叠的字符数,防止语义中断
separators=["
", "
", "。", "!", "?", ";", ",", " ", ""] # 分隔符优先级
)
chunks = text_splitter.split_documents(documents)
print(f"原始文档被分割为 {len(chunks)} 个文本块。")
# 输出示例:原始文档被分割为 120 个文本块。
2. 向量化(Embedding)与存储:
分块后的文本需通过嵌入模型转换为向量。选择与语言和应用场景匹配的模型至关重要。针对中文场景,BGE (BAAI/bge-large-zh-v1.5) 是优秀的选择;对于英文或通用场景,OpenAI 或 Sentence Transformers 模型是主流。
# 示例:使用 Sentence Transformers 库加载 BGE 模型生成向量
from sentence_transformers import SentenceTransformer
import chromadb # 以轻量级向量数据库 Chroma 为例
from chromadb.config import Settings
# 1. 加载嵌入模型
model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
# 2. 为文本块生成向量
texts = [chunk.page_content for chunk in chunks]
embeddings = model.encode(texts, normalize_embeddings=True) # 生成归一化的向量
# 3. 连接并存储到 Chroma 向量数据库
client = chromadb.PersistentClient(path="./vector_store")
collection = client.create_collection(name="company_handbook")
# 批量添加文档(元数据可包含来源、页码等信息)
for i, (text, embedding) in enumerate(zip(texts, embeddings)):
collection.add(
embeddings=[embedding.tolist()],
documents=[text],
metadatas=[{"source": "员工手册", "page": int(i/10) + 1}], # 示例元数据
ids=[f"chunk_{i}"]
)
print("向量索引构建完成。")
对于生产环境,Milvus或Pinecone是更强大的选择。它们专为大规模向量操作优化,支持复杂的近似最近邻(ANN)索引算法,如HNSW (Hierarchical Navigable Small World) 或IVF-PQ (Inverted File System with Product Quantization),能在毫秒级从百万级向量中检索出最相似的Top-K结果。
阶段二:在线查询响应(问题到答案)
当用户提出问题时,系统实时执行检索并生成回答。
1. 查询向量化与语义检索:
用户的原始问题首先被转换成向量(使用与索引阶段相同的模型),然后在向量数据库中进行相似度搜索。余弦相似度是衡量向量间语义相关性的常用指标。
# 示例:在 Chroma 中执行相似性检索
def retrieve_context(question, collection, model, top_k=3):
# 将问题转换为向量
question_embedding = model.encode(question, normalize_embeddings=True)
# 在集合中进行相似性搜索
results = collection.query(
query_embeddings=[question_embedding.tolist()],
n_results=top_k
)
# 返回检索到的文本列表
retrieved_docs = results['documents'][0]
return retrieved_docs
user_query = "公司年假政策是怎样的?"
relevant_chunks = retrieve_context(user_query, collection, model, top_k=3)
print("检索到的相关上下文:")
for i, chunk in enumerate(relevant_chunks):
print(f"[{i+1}] {chunk[:100]}...") # 打印前100字符预览
2. 检索后处理与Prompt构建:
简单的Top-K检索可能返回冗余或相关性不一的结果。高级RAG系统会引入重排序(Re-ranking) 模块,例如使用 Cohere Rerank 或 BGE Reranker 模型对初步检索结果进行精排,显著提升最终答案的准确性。随后,将精排后的上下文与用户问题组合成Prompt。
# 示例:构建包含上下文和指令的Prompt模板
def construct_prompt(query, contexts):
context_str = "
".join([f"[出处 {i+1}]: {ctx}" for i, ctx in enumerate(contexts)])
prompt_template = f"""
你是一个专业的企业知识助手。请严格根据以下提供的上下文信息来回答问题。如果上下文信息不足以回答问题,请明确说明“根据现有信息无法回答”。
上下文信息:
{context_str}
用户问题:{query}
请根据上下文给出准确、完整的回答,并在必要时指出信息的具体出处。
"""
return prompt_template
prompt_for_llm = construct_prompt(user_query, relevant_chunks)
print("构造的Prompt预览:
", prompt_for_llm[:500], "...")
3. 大模型(LLM)生成与优化:
将构造好的Prompt发送给LLM生成最终答案。为了获得更好的控制与性能,可以采用 LangChain、LlamaIndex 等框架,或直接调用 vLLM、GPT 等推理接口。Prompt工程是优化生成质量的关键,通过明确指令可以约束LLM,减少幻觉,使其基于检索到的上下文作答。
# 示例:使用 LangChain 的 LCEL 链式调用本地 vLLM 服务
from langchain.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI # 将vLLM服务模拟为OpenAI兼容端点
from langchain_core.output_parsers import StrOutputParser
# 定义Prompt模板
template = """基于以下上下文回答问题:
{context}
问题:{question}
"""
prompt = ChatPromptTemplate.from_template(template)
# 配置模型(假设本地vLLM服务运行在 http://localhost:8000/v1)
model = ChatOpenAI(
base_url="http://localhost:8000/v1",
api_key="no-key",
model="Qwen2.5-7B-Instruct",
temperature=0.1 # 低温度使输出更确定
)
# 构建检索-生成链
chain = prompt | model | StrOutputParser()
# 执行链
answer = chain.invoke({"context": "
".join(relevant_chunks), "question": user_query})
print("生成的答案:
", answer)
企业级实践与选型建议
对于不同规模的项目,技术选型有所不同:
| 考量维度 | 轻量级/原型验证 | 生产级/企业应用 |
|---|---|---|
| 向量数据库 | Chroma (轻量,易部署) | Milvus (分布式,高性能) / Pinecone (全托管云服务) |
| 嵌入模型 | text-embedding-ada-002 (OpenAI API) / 开箱即用的 BGE |
基于领域数据微调的专用Embedding模型 |
| 检索策略 | 简单向量相似度检索 | 混合检索(结合关键词BM25与向量语义)+ 重排序 |
| 框架与部署 | LangChain + 脚本 | 基于 Spring AI 或专用开源项目(如 AnythingLLM)的容器化、可观测系统 |
| 核心优化点 | 调优分块大小和重叠 | 实现多路召回、分级缓存、Agent工具调用和持续数据更新管道 |
综上所述,构建一个健壮的RAG系统需要对每个环节进行精细设计与调优。从数据预处理的质量,到嵌入模型与向量数据库的选型,再到检索策略与Prompt工程的协同,每一步都直接影响最终应用的准确性与用户体验。通过结合上述代码案例和选型建议,可以构建出从原型到生产的完整RAG解决方案。
参考来源
- 向量数据库与RAG技术
- RAG实战(一)构建QA系统
- 大模型系统开发能力体系
- 开源大模型新选择:anything-llm助力高效知识管理
- Java面试:基于Spring AI与RAG的智慧社区智能客服系统深度解析
- 第三章:RAG知识库开发之【RAG系统工作流程详细解析:从数据源到智能问答的全链路实战指南】
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)