检索增强生成(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) 是优秀的选择;对于英文或通用场景,OpenAISentence 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("向量索引构建完成。")

对于生产环境,MilvusPinecone是更强大的选择。它们专为大规模向量操作优化,支持复杂的近似最近邻(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 RerankBGE 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生成最终答案。为了获得更好的控制与性能,可以采用 LangChainLlamaIndex 等框架,或直接调用 vLLMGPT 等推理接口。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解决方案。


参考来源

 

Logo

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

更多推荐