本文详解RAG(检索增强生成)如何解决大模型知识截止与幻觉难题。涵盖架构原理、文档分块、向量化、混合检索及重排序等核心技术,并提供完整代码实现与生产环境最佳实践,助你打造私有知识库。

前排提示,文末有大模型AGI-CSDN独家资料包哦!

  1. 开篇:为什么 LLM 需要 RAG?

「用户问:我们公司的报销流程是什么?」

你把这个问题丢给 ChatGPT,它会一本正经地编造一套通用流程。问题是——它根本不知道你们公司的报销制度。

这就是大语言模型的先天缺陷:

知识截止:训练数据有时间边界,不知道最新信息•无法访问私有数据:公司文档、内部知识库它看不到•幻觉问题:不知道就瞎编,而且编得很像真的

怎么办?两条路:

方案 做法 代价
微调 (Fine-tuning) 在预训练模型基础上用私有数据继续训练 成本高、周期长、数据更新要重新训练
RAG 检索相关文档,拼到 Prompt 里 成本低、实时更新、无需重新训练

RAG(Retrieval-Augmented Generation,检索增强生成)的思路很直接:先找资料,再回答问题

就像你写报告时会先查文档一样,RAG 让 LLM 在回答前先「查阅」相关资料,基于真实信息生成答案。

读完这篇文章,你将掌握:

•RAG 系统的完整架构和工作流程•文档解析、分块、向量化的核心技术•检索、重排序、生成的实现方案•生产环境的最佳实践和避坑指南


  1. 核心概念:RAG 的两个阶段

RAG 系统分为两个阶段:索引阶段(离线)和 检索生成阶段(在线)。

2.1 整体架构

┌─────────────────────────────────────────────────────────────┐│                     索引阶段(离线)                          │├─────────────────────────────────────────────────────────────┤│  文档 → 解析 → 分块 → 向量化 → 存入向量数据库                  │└─────────────────────────────────────────────────────────────┘                              ↓┌─────────────────────────────────────────────────────────────┐│                  检索生成阶段(在线)                         │├─────────────────────────────────────────────────────────────┤│  用户问题 → 向量化 → 检索 → 重排序 → 构造Prompt → LLM生成     │└─────────────────────────────────────────────────────────────┘

2.2 索引阶段

把文档「翻译」成向量,存到数据库里,方便后续检索。

直觉理解:就像给图书馆的每本书做索引卡片。

传统索引是按书名、作者分类。向量索引是按「内容语义」分类——讲同一个主题的书,不管标题怎么写,都会被放在一起。

索引阶段的核心步骤:

原始文档(PDF/Word/Markdown)    ↓文档解析(提取文本)    ↓文本分块(切成小段)    ↓向量化(转成数字)    ↓存入向量数据库

2.3 检索生成阶段

用户提问时,找到相关文档,让 LLM 基于这些文档回答。

直觉理解:就像开卷考试。

LLM 本来是「闭卷」答题,只能靠记忆(训练数据)。RAG 让它变成「开卷」——可以翻阅参考资料再作答。

检索生成阶段的核心步骤:

用户问题:"报销流程是什么?"    ↓向量化(把问题转成向量)    ↓向量检索(找语义相近的文档块)    ↓可选:重排序(精细化排序)    ↓构造 Prompt(问题 + 相关文档)    ↓LLM 生成答案

2.4 术语对照表

英文 中文 说明
Embedding 向量化/嵌入 把文本转成数字向量
Chunk 文本块/分块 文档切分后的小段
Vector Store 向量数据库 存储和检索向量的数据库
Retriever 检索器 负责找相关文档的组件
Reranker 重排序器 对检索结果精细化排序
Context 上下文 提供给 LLM 的参考信息

  1. 原理:每个环节在做什么?

了解了整体流程,接下来深入每个环节的原理。

3.1 文档解析

不同格式的文档需要不同的解析方式:

格式 解析难度 常用工具 注意事项
TXT/Markdown 直接读取 注意编码
PDF ★★★ PyMuPDF, pdfplumber 扫描件需要 OCR
Word ★★ python-docx 注意表格和图片
Excel ★★ openpyxl, pandas 保留表格结构

关键点:解析质量直接影响后续效果。垃圾进,垃圾出。

3.2 文本分块(Chunking)

为什么要分块?两个原因:

1.LLM 上下文有限:不能把整本书塞进去2.检索需要粒度:整篇文档太粗,找不准

分块策略对比:

策略 做法 适用场景
固定长度 按字符数切分 简单场景
递归分割 按段落→句子→字符逐级切分 通用场景(推荐)
语义分块 按语义边界切分 高质量要求
按标题分块 按文档结构切分 结构化文档

最佳实践

Chunk 大小:500-1000 字符(中文)重叠大小:100-200 字符(保持上下文连贯)

为什么要重叠?避免关键信息被切断。

原文:「...报销需要提交发票。发票必须是正规发票...」  
无重叠切分:  Chunk1: 「...报销需要提交发票。」  Chunk2: 「发票必须是正规发票...」  → 问"发票要求"可能只找到 Chunk2,丢失了"需要提交"的信息  
有重叠切分:  Chunk1: 「...报销需要提交发票。发票必须...」  Chunk2: 「...提交发票。发票必须是正规发票...」  → 关键信息在两个 Chunk 都有

3.3 向量化(Embedding)

把文本转成高维向量,让计算机能计算「语义相似度」。

直觉理解:给每段文字一个「语义坐标」。

「报销流程」和「费用报销步骤」虽然用词不同,但语义坐标很接近。检索时,找坐标相近的文档块。

主流 Embedding 模型:

模型 提供商 维度 特点
text-embedding-3-small OpenAI 1536 性价比高,推荐
text-embedding-3-large OpenAI 3072 效果更好,成本更高
voyage-3 Voyage AI 1024 多语言效果好
bge-large-zh BAAI 1024 中文效果好,开源

3.4 向量检索

向量数据库的核心能力:快速找到最相似的向量

如果有 100 万个文档块,逐个计算相似度太慢。向量数据库用 ANN(近似最近邻)算法加速:

算法 原理 特点
HNSW 分层图索引 速度快,内存占用大
IVF 聚类+倒排 内存友好,需要训练
PQ 向量压缩 节省存储,精度有损

主流向量数据库:

数据库 类型 特点
Qdrant 开源/云服务 功能全面,推荐
Weaviate 开源/云服务 支持混合检索
Pinecone 云服务 全托管,易上手
Milvus 开源 大规模场景
pgvector PostgreSQL 扩展 已有 PG 可直接用

3.5 混合检索

单纯的向量检索有局限:对精确关键词不敏感。

比如用户问「ERR-4012 怎么解决」,向量检索可能找到一堆「错误处理」相关的文档,但不一定包含 ERR-4012。

混合检索的思路:向量检索 + 关键词检索,两手都要抓

用户问题    ↓┌───────────────────────────────────────┐│         并行执行两路检索               │├─────────────────┬─────────────────────┤│   向量检索       │      BM25检索       ││   (语义匹配)     │     (关键词匹配)    │├─────────────────┴─────────────────────┤│           RRF 融合算法                 │└───────────────────────────────────────┘    ↓融合后的结果

3.6 重排序(Reranking)

检索返回的结果可能不够精准。重排序器对候选结果做精细化排序。

检索结果 Top-20 → Reranker → 最终 Top-5

为什么不直接用 Reranker 检索?因为 Reranker 需要对每个候选单独计算,太慢。所以先用检索器快速召回,再用 Reranker 精排。

主流 Reranker:

•Cohere Rerank•Jina Reranker•BGE Reranker(开源)

3.7 Prompt 构造与生成

把检索到的文档和用户问题组合成 Prompt:

你是一个专业的助手。请根据以下参考资料回答用户问题。  
参考资料:{检索到的文档块}  
用户问题:{用户的问题}  
请基于参考资料回答,如果资料中没有相关信息,请说明无法回答。

关键点

•明确指示 LLM 基于资料回答•处理「找不到」的情况•控制上下文长度,避免超出限制


  1. 实践:生产级 RAG 系统实现

理论讲完,来看代码如何实现。

4.1 技术栈选型

组件 推荐方案 备选方案
框架 LangChain / LlamaIndex 自研
Embedding OpenAI text-embedding-3-small Voyage, BGE
向量数据库 Qdrant Weaviate, pgvector
LLM GPT-4o / Claude 国产模型
Reranker Cohere Rerank Jina, BGE

4.2 环境准备

# 核心依赖pip install langchain langchain-openai langchain-communitypip install qdrant-clientpip install rank-bm25      # BM25 检索pip install cohere         # 重排序(可选)  
# 文档解析pip install pymupdf        # PDFpip install python-docx    # Wordpip install openpyxl       # Excel

4.3 索引管道实现

"""RAG 索引管道 - 文档解析、分块、向量化、存储"""from typing import Optionalfrom pathlib import Path  
from langchain.text_splitter import RecursiveCharacterTextSplitterfrom langchain_openai import OpenAIEmbeddingsfrom langchain_community.vectorstores import Qdrantfrom qdrant_client import QdrantClient  
# === 第一步:文档解析 ===def load_document(file_path: str) -> str:    """    解析文档,提取文本  
    支持格式:txt, md, pdf, docx    """    path = Path(file_path)    suffix = path.suffix.lower()  
    if suffix in ['.txt', '.md']:        return path.read_text(encoding='utf-8')  
    elif suffix == '.pdf':        import fitz  # PyMuPDF        doc = fitz.open(file_path)        text = ""        for page in doc:            text += page.get_text()        return text  
    elif suffix == '.docx':        from docx import Document        doc = Document(file_path)        return "/n".join([p.text for p in doc.paragraphs])  
    else:        raise ValueError(f"不支持的文件格式: {suffix}")  
  
# === 第二步:文本分块 ===def split_text(    text: str,    chunk_size: int = 800,    chunk_overlap: int = 150,) -> list[str]:    """    递归分块 - 按段落→句子→字符逐级切分  
    参数:        text: 原始文本        chunk_size: 块大小(字符数)        chunk_overlap: 重叠大小    """    splitter = RecursiveCharacterTextSplitter(        chunk_size=chunk_size,        chunk_overlap=chunk_overlap,        separators=["/n/n", "/n", "。", "!", "?", ";", " ", ""],    )    return splitter.split_text(text)  
  
# === 第三步:向量化并存储 ===def create_vector_store(    texts: list[str],    collection_name: str = "documents",    qdrant_url: str = "http://localhost:6333",) -> Qdrant:    """    创建向量存储  
    参数:        texts: 文本块列表        collection_name: 集合名称        qdrant_url: Qdrant 服务地址    """    # 初始化 Embedding 模型    embeddings = OpenAIEmbeddings(        model="text-embedding-3-small",    )  
    # 创建向量存储    client = QdrantClient(url=qdrant_url)  
    vector_store = Qdrant.from_texts(        texts=texts,        embedding=embeddings,        collection_name=collection_name,        url=qdrant_url,    )  
    return vector_store  
  
# === 完整索引流程 ===def index_document(file_path: str, collection_name: str = "documents"):    """    索引单个文档的完整流程    """    # 1. 解析    text = load_document(file_path)    print(f"解析完成,文本长度: {len(text)}")  
    # 2. 分块    chunks = split_text(text)    print(f"分块完成,共 {len(chunks)} 个块")  
    # 3. 向量化并存储    vector_store = create_vector_store(chunks, collection_name)    print(f"索引完成,已存入 {collection_name}")  
    return vector_store

4.4 检索生成实现

"""RAG 检索生成 - 检索、重排序、生成答案"""from typing import Optionalfrom langchain_openai import ChatOpenAI, OpenAIEmbeddingsfrom langchain_community.vectorstores import Qdrantfrom qdrant_client import QdrantClient  
# === 第一步:初始化检索器 ===def get_retriever(    collection_name: str = "documents",    qdrant_url: str = "http://localhost:6333",    top_k: int = 5,):    """    获取向量检索器    """    embeddings = OpenAIEmbeddings(model="text-embedding-3-small")    client = QdrantClient(url=qdrant_url)  
    vector_store = Qdrant(        client=client,        collection_name=collection_name,        embeddings=embeddings,    )  
    return vector_store.as_retriever(search_kwargs={"k": top_k})  
  
# === 第二步:检索相关文档 ===def retrieve_documents(query: str, retriever) -> list[str]:    """    检索与查询相关的文档    """    docs = retriever.invoke(query)    return [doc.page_content for doc in docs]  
  
# === 第三步:可选 - 重排序 ===def rerank_documents(    query: str,    documents: list[str],    top_n: int = 3,) -> list[str]:    """    使用 Cohere 重排序    """    import cohere  
    co = cohere.Client()  # 需要设置 COHERE_API_KEY  
    response = co.rerank(        model="rerank-english-v3.0",        query=query,        documents=documents,        top_n=top_n,    )  
    return [documents[r.index] for r in response.results]  
  
# === 第四步:构造 Prompt 并生成 ===def generate_answer(    query: str,    context_docs: list[str],    model: str = "gpt-4o",) -> str:    """    基于检索到的文档生成答案    """    # 构造上下文    context = "/n/n---/n/n".join(context_docs)  
    # 构造 Prompt    prompt = f"""你是一个专业的助手。请根据以下参考资料回答用户问题。  
参考资料:{context}  
用户问题:{query}  
请基于参考资料回答。如果资料中没有相关信息,请明确说明"根据现有资料无法回答该问题"。"""  
    # 调用 LLM    llm = ChatOpenAI(model=model, temperature=0)    response = llm.invoke(prompt)  
    return response.content  
  
# === 完整 RAG 查询流程 ===def rag_query(    query: str,    collection_name: str = "documents",    use_rerank: bool = True,) -> dict:    """    完整的 RAG 查询流程  
    返回:        {            "answer": 生成的答案,            "sources": 参考的文档块,        }    """    # 1. 获取检索器    retriever = get_retriever(collection_name, top_k=10 if use_rerank else 5)  
    # 2. 检索    docs = retrieve_documents(query, retriever)  
    # 3. 重排序(可选)    if use_rerank and len(docs) > 0:        docs = rerank_documents(query, docs, top_n=3)  
    # 4. 生成答案    answer = generate_answer(query, docs)  
    return {        "answer": answer,        "sources": docs,    }  
  
# === 使用示例 ===if __name__ == "__main__":    # 索引文档    index_document("company_policy.pdf", "company_docs")  
    # 查询    result = rag_query(        "报销流程是什么?",        collection_name="company_docs",        use_rerank=True,    )  
    print("答案:", result["answer"])    print("/n参考来源:")    for i, source in enumerate(result["sources"], 1):        print(f"{i}. {source[:100]}...")

4.5 常见问题

Q: 检索结果不相关怎么办?

排查顺序:

1.检查分块是否合理(太大或太小都不好)2.检查 Embedding 模型是否适合你的数据(中文用中文模型)3.尝试混合检索(向量 + BM25)4.加入重排序

Q: 生成的答案有幻觉怎么办?

1.Prompt 中明确要求「基于资料回答」2.要求 LLM 引用来源3.降低 temperature(设为 0)4.检索更多文档,提供更充分的上下文

Q: 上下文太长超出限制怎么办?

1.减少检索数量(top/_k 调小)2.用重排序筛选最相关的3.对检索结果做摘要压缩4.使用支持长上下文的模型


  1. 最佳实践

5.1 分块策略

场景 推荐配置
通用文档 chunk/_size=800, overlap=150
技术文档 chunk/_size=1000, overlap=200
FAQ/问答 chunk/_size=300, overlap=50
法律合同 按条款分块

原则:一个 Chunk 应该包含完整的语义单元,能独立回答一个问题。

5.2 Embedding 选择

场景 推荐模型
英文为主 text-embedding-3-small
中文为主 bge-large-zh 或 text-embedding-3-small
多语言 voyage-3
成本敏感 text-embedding-3-small(性价比最高)

5.3 检索优化

混合检索权重

场景 向量权重 BM25权重
通用问答 0.7 0.3
技术文档 0.5 0.5
精确搜索 0.3 0.7

5.4 避坑指南

陷阱 1:Chunk 太大或太小

太大(>2000字符):  - 检索不精准,噪音多  - 浪费上下文空间  
太小(<200字符):  - 语义不完整  - 检索结果碎片化

陷阱 2:忽略文档结构

错误:把表格当普通文本切分  → 表格被切碎,信息丢失  
正确:识别表格,整体保留或转为描述性文本

陷阱 3:Prompt 没有边界约束

错误 Prompt:  "请回答用户问题:{query}"  → LLM 可能瞎编  
正确 Prompt:  "请基于以下资料回答,如果资料中没有相关信息,请说明无法回答。"  → 有边界约束

陷阱 4:没有处理空结果

# 错误:直接生成docs = retrieve(query)answer = generate(query, docs)  # docs 可能为空  
# 正确:检查结果docs = retrieve(query)if not docs:    return "抱歉,没有找到相关信息"answer = generate(query, docs)

  1. 进阶:生产环境优化

6.1 查询改写

用户的问题可能表述不清。查询改写可以提升检索效果:

def rewrite_query(original_query: str) -> list[str]:    """    将用户问题改写成多个检索友好的查询    """    prompt = f"""将以下问题改写成3个不同的表述,用于检索:  
原问题:{original_query}  
要求:1. 保持原意2. 使用不同的关键词3. 每行一个改写结果"""    # 调用 LLM 改写    ...

6.2 缓存策略

# 查询缓存 - 相同问题直接返回query_cache = {}  
def cached_rag_query(query: str):    cache_key = hash(query)    if cache_key in query_cache:        return query_cache[cache_key]  
    result = rag_query(query)    query_cache[cache_key] = result    return result  
# Embedding 缓存 - 避免重复计算# 使用 Redis 或本地缓存存储已计算的 embedding

6.3 监控指标

生产环境需要监控:

指标 说明 告警阈值
检索延迟 向量检索耗时 P99 > 500ms
生成延迟 LLM 生成耗时 P99 > 5s
空结果率 检索无结果的比例 > 20%
用户满意度 点赞/点踩比例 < 70%

6.4 评估指标

指标 说明 计算方式
Recall@K 相关文档被检索到的比例 相关文档数 / 总相关文档数
MRR 第一个相关结果的排名倒数 1 / 第一个相关结果排名
Faithfulness 答案是否基于检索内容 LLM 评估
Answer Relevancy 答案与问题的相关性 LLM 评估

6.5 架构演进

阶段1:单体架构  所有组件在一个服务中  适合:POC、小规模  
阶段2:服务拆分  索引服务 + 检索服务 + 生成服务  适合:中等规模  
阶段3:分布式架构  向量数据库集群 + LLM 网关 + 缓存层  适合:大规模生产

  1. 总结

RAG 的核心价值:让 LLM 基于真实数据回答问题,而不是靠「记忆」瞎编

关键要点:

两阶段架构:索引阶段(离线)准备数据,检索生成阶段(在线)回答问题

分块是关键:Chunk 大小影响检索精度,推荐 500-1000 字符,带 100-200 字符重叠

混合检索更稳健:向量检索擅长语义,BM25 擅长关键词,两者结合效果更好

重排序提升精度:检索后加一层 Reranker,能显著提升相关性

Prompt 要有边界:明确要求 LLM 基于资料回答,处理好「找不到」的情况

读者福利:倘若大家对大模型感兴趣,那么这套大模型学习资料一定对你有用。

针对0基础小白:

如果你是零基础小白,快速入门大模型是可行的。
大模型学习流程较短,学习内容全面,需要理论与实践结合
学习计划和方向能根据资料进行归纳总结

包括:大模型学习线路汇总、学习阶段,大模型实战案例,大模型学习视频,人工智能、机器学习、大模型书籍PDF。带你从零基础系统性的学好大模型!

😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓

请添加图片描述

👉AI大模型学习路线汇总👈

大模型学习路线图,整体分为7个大的阶段:(全套教程文末领取哈)

第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;

第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;

第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;

第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;

第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;

第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;

第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。

👉大模型实战案例👈

光学理论是没用的,要学会跟着一起做,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

在这里插入图片描述

👉大模型视频和PDF合集👈

这里我们能提供零基础学习书籍和视频。作为最快捷也是最有效的方式之一,跟着老师的思路,由浅入深,从理论到实操,其实大模型并不难

在这里插入图片描述

👉学会后的收获:👈

• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;

• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;

• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;

• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。

👉获取方式:

😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓

Logo

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

更多推荐