一、RAG 核心组件

image-20260508155807936

1、知识库

  • 知识库(Knowledge Base / 数据资产层):知识库不仅是存储,更是数据的 ETL(提取、转化、加载)过程

  • 原始数据处理:

    • 多模态解析: 不只是读取,更涉及 PDF 中的表格解析(如使用 Unstructured 或 Camelot)和图片 OCR
    • 清洗: 去除 HTML 标签、乱码、重复信息
  • 分片策略 (Chunking Strategy):关键优化点

    • 固定大小切分: 如 512 tokens,简单但容易切断语义
    • 语义切分: 识别段落、标题、层级结构(Recursive Character Text Splitter)
    • 滑动窗口: 增加 Overlap(重叠度),例如 512 长度 + 50 重叠,保证上下文不丢失
  • 向量索引与存储:

    • 本地轻量级: FAISS (向量搜索库)、ChromaDB (嵌入式数据库)
    • 云原生/分布式: Milvus (高性能分布式)、Pinecone (托管 SaaS)、Elasticsearch (支持向量搜索的传统巨头)

1.1 加载器

  • 文档解析是知识库构建的首要步骤,旨在将各种格式的文档转换为机器可读的文本格式
  • 加载器的作用:
  • 把 不同格式的文件 → 转成 Document 对象
  • Document 包含两个核心属性:
    • page_content(文本内容)
    • metadata(元数据,如文件名、页码、来源等)
  • 不会做向量化,只是“读文件 + 提取文本”
  • 向量化需要使用 Embedding 模型:负责“Document → 向量”
  • 此刻开始,了解实现流程,可 AI 辅助编程

1.1.1 环境准备

  • 数据准备:【F:\workspace\AI\stu_nlp\rag\datasets】

  • 安装依赖:

pip install langchain-community langchain-unstructured
pip install unstructured
pip install "unstructured[pptx]" "unstructured[image]" "unstructured[md]" "unstructured[pdf]"
pip install python-magic
# Tesseract-OCR -- 安装在D:\Program Files\tesseract-ocr
# 配置环境变量:D:\Program Files\tesseract-ocr
# 下面两个内容放在D:\Program Files\tesseract-ocr\tessdata
chi_sim.traineddata :用于简体中文字符识别
chi_tra.traineddata :用于繁体中文字符识别
# 代码中
import unstructured_pytesseract.pytesseract as pytesseract
pytesseract.tesseract_cmd = r"D:\Program Files\tesseract-ocr\tesseract.exe"
  • 在 LangChain 中,各种文本类型通常会用不同的加载器(Loader)来进行处理。下面是一些常见的文本文件类型及其对应的加载器的表格
文件类型 推荐加载器 底层依赖库 核心特点与使用建议
纯文本 (.txt) TextLoader 无 最基础。直接读取,速度最快,不带格式。
PDF (.pdf) PyPDFLoader pypdf 工程首选。支持分页,读取速度均衡,支持远程 URL。
PDF (高精度) PyMuPDFLoader fitz 解析速度极快,对布局复杂的 PDF 提取效果更好。
Word (.docx) UnstructuredWordDocumentLoader unstructured 比 DocxLoader 更强大,能自动识别标题和列表结构。
CSV (.csv) CSVLoader pandas (可选) 将每一行转为一个 Document,可用 source_column 指定来源列。
JSON (.json) JSONLoader jq 需配合 jq 语法。能精准提取特定字段(如只需 content 字段)。
HTML (.html) BSHTMLLoader beautifulsoup4 自动去除 HTML 标签,只保留纯文本,适合清理网页杂质。
Markdown (.md) UnstructuredMarkdownLoader unstructured 保持 MD 的层级结构,对后续基于标题的切片(Splitter)很有用。
Web 网页 SeleniumURLLoader selenium 相比 WebBaseLoader,它能处理 JavaScript 渲染的动态网页。
邮件 (.emil) UnstructuredEmailLoader unstructured 能够自动解析发件人、收件人、主题等元数据(Metadata)。
数据库 (SQL) SQLDatabaseLoader sqlalchemy 支持从 MySQL、PostgreSQL 等多种数据库通过 SQL 语句提取数据。
微信/钉钉 NotionDirectoryLoader 等 各种 SDK 针对企业级应用,LangChain 提供特定生态的专属加载器。
  • 在 Unstructured 系列加载器中,mode 参数决定了 Document 对象的生成粒度
模式 (Mode) 核心逻辑 形象类比 结果形态
single 整个文件不论长短,最终只生成 一个 Document 对象。 把一本书压成一张超长的卷轴。 [Document(page_content="全文...", metadata={...})]
paged 严格按照文件的物理分页,每一页生成 一个 Document 对象。 把书拆开,一页一页叠放。 [Document(Page 1), Document(Page 2), ...]
elements 识别文档结构(标题、段落、列表、表格),每一种元素生成 一个 Document。 把书里的标题、段落、表格全剪下来,分类存放。 [Document(Title), Document(Narrative), Document(Table)...]
  • 场景化案例与 mode 选型建议
模式 (Mode) 核心逻辑 适用文件类型 典型使用案例 选型理由 (IT 视角) 提取 & 检索重点
single 全文模式:不分段,将整个文件视为一个单一的 Document 对象。 短文本 (.txt)、单页简历 (.pdf/.docx)、即时通讯记录。 自动化简历筛选系统 简历篇幅短(1-2页),核心信息高度关联。拆分过细会导致 LLM 丢失候选人的整体画像。 逻辑完整性:方便模型一站式提取姓名、学历、技能等综合维度。
paged 分页模式:严格遵循物理页码,每一页生成一个独立的 Document。 法律条文、政府公告、学术论文、长篇书籍。 法律法规查询助手 法律文件常在页眉页脚包含版本或章节标识,按页切分符合人类查阅法律手册的习惯。 引用溯源:能够精准返回“该条文位于第 45 页”,增强答案的可信度与可查证性。
elements 元素模式:智能识别文档结构,将标题、段落、表格、代码块拆分为独立对象。 企业年报、产品技术手册、Markdown 架构文档、带复杂表格的报告。 技术文档 RAG 机器人 复杂文档中代码和表格是核心。该模式能识别 category="Table",避免语义混杂,提高精准召回。 结构化检索:可以针对性检索“表格”或“标题”,有效过滤噪音(如页码、无意义的页脚)。

1.1.2 txt

  • 案例代码:
from langchain_community.document_loaders import TextLoader


if __name__ == "__main__":
    # 创建加载器对象
    loader = TextLoader("./datasets/华清远见.txt", encoding="utf-8")
    # 加载数据
    data = loader.load()
    # 打印数据
    print(data)
  • 运行结果:
[Document(metadata={'source': './datasets/华清远见.txt'}, page_content='华清远见2004年成立于北京中关村,12年来始终专注于嵌入式及移动开发专业人才培养,“做良心教育,做专业教育,做受人尊敬的职业教育”是华清远见一直坚持的核心发展理念。培训内容主要包括嵌入式系统开发,Android开发,物联网开发、HTML5开发、UI设计。从短期高端到长期就业课程培训,再到产品研发,凭借专业的课程内容、强大的师资团队以及先进的研发技术受到客户和学员的好评。迄今已有超过10万名学员从华清远见走出。\n华清远见研发实力强大。拥有10余种实训套件与智能产品,100余套自主研发实验设备。同时与众多高校进行实验室共建合作,目前已有超过1100所高校1100多个实验室选择了我们。华清远见拥有众多高端培训课程,每类课程均有独特的教学模式。不同于其他机构,华清远见的培训课程不仅限于理论知识,还有丰富多样的实战项目贯穿其中。从研发到培训,全面提升学员技术水平。\n华清远见拥有一支非常强大的师资团队。近200余名讲师投身于研发当中。每位讲师均是从事IT行业多年的资深人士,并有着丰富的项目开发经验。在学员培训过程中实时答疑解惑。')]

1.1.3 csv

  • 案例代码:
from langchain_community.document_loaders import CSVLoader


if __name__ == "__main__":
    # 创建加载器对象
    loader = CSVLoader("./datasets/guazi.csv", encoding="utf-8", csv_args={"delimiter": "\t"})
    # 加载数据
    data = loader.load()
    # 打印数据
    print(data)

1.1.4 图片

  • 案例代码:
import unstructured_pytesseract.pytesseract as pytesseract
from langchain_community.document_loaders import UnstructuredImageLoader
pytesseract.tesseract_cmd = r"D:\Program Files\tesseract-ocr\tesseract.exe"


if __name__ == "__main__":
    loader = UnstructuredImageLoader("./datasets/黑神话.png", mode="elements", languages=["chi_sim"])
    data = loader.load()
    for item in data:
        print(item.page_content, end="")

1.1.5 md

  • 使用TextLoader能够保留 md 的原始格式
from langchain_community.document_loaders import UnstructuredMarkdownLoader

if __name__ == "__main__":
    loader = UnstructuredMarkdownLoader(
        "./datasets/09.算力租赁.md",
        mode="elements",
    )
    data = loader.load()
    for item in data:
        print(item.page_content, end="")

1.1.6 json

  • 需要使用 jq 包,jq_schema 的作用是“数据过滤器 + 文本重构器”。它决定了 JSONLoader 如何从复杂的 JSON 结构中精准地提取你想要的内容,并将其转换成大模型(LLM)最容易理解的格式

  • 需要依赖包:

pip install jq
  • 参考代码:
from langchain_community.document_loaders import JSONLoader

if __name__ == "__main__":
    loader = JSONLoader(
        "./datasets/Chinese.json",
        jq_schema='.[] | "instruction:" +  .instruction  + "\\n" +  "input:" +  .input + "\\n" +  "output:" +  .output',
    )
    documents = loader.load()
    for item in documents:
        print(item.page_content)
        print("======================")

1.1.7 网页文档

  • 基本的网页获取:
from langchain_community.document_loaders import WebBaseLoader
from bs4 import SoupStrainer

if __name__ == "__main__":
    loader = WebBaseLoader(
        "https://hqyj.com/",
        # 只解析主题部分
        # bs_kwargs={"parse_only": SoupStrainer("body")},
        # bs_kwargs={"parse_only": SoupStrainer("title")},
        bs_kwargs={"parse_only": SoupStrainer(("title", "body"))},
    )
    documents = loader.load()
    print(documents)
  • 也可以指定具体的标签:
SoupStrainer("div", {"class": "main-content"})

1.1.8 PDF

  • PyPDF2:用于处理 PDF 文件,支持提取文本、分割和合并 PDF 页面。适用于简单的 PDF 文本提取任务

  • PDF2Text:一种更专注于将 PDF 文档转换为纯文本的工具,强调转换质量和速度,适合大规模的 PDF 文本提取需求

  • GROBID:利用机器学习方法处理PDF和其他类型文档的高级解析工具。能够识别文档的结构和元数据,适用于需要高精度文档结构化信息的场景

  • OCR:

    • 文字:Paddle-OCR,Rapid-OCR

    • 表格:Camelot,Paddle-OCR,阿里追光,Table Transformer

    • 公式:Nougat,marker

  • 选择哪个工具取决于具体的需求:对于需要高精度结构化输出的应用,GROBID 是更好的选择;而对于大量 PDF 文本提取,PDF2Text 可能更为高效

PyPDF2 : https://github.com/py-pdf/pypdf.git
GROBID : https://github.com/kermitt2/grobid.git
Paddle-OCR : https://github.com/PaddlePaddle/PaddleOCR.git
Rapid-OCR : https://github.com/RapidAI/RapidOCR.git
  • 案例代码:
from pdfminer.high_level import extract_text

pdf_path = './datasets/09.算力租赁.pdf'

# 提取全文文本
text = extract_text(pdf_path)

# 打印全文
print(text)
  • 遇到下面的错误解决
ImportError: cannot import name 'HOCRConverter' from 'pdfminer.converter' (E:\Anaconda\envs\nlp_env\Lib\site-packages\pdfminer\converter.py). Did you mean: 'HTMLConverter'?

1.1.9 word

  • Document 对象可以了解一下:【G:\上课\人工智能\10-transformer\42.Document对象.md】

  • 安装库

pip install python-docx
  • 案例代码
from docx import Document


if __name__ == "__main__":
    path = r"./datasets/test.docx"
    doc = Document(path)
    data = []
    # 遍历表格
    for table in doc.tables:
        for row in table.rows:
            row_data = []
            for cell in row.cells:
                row_data.append(cell.text)
            data.append(row_data)
    print(data)

1.1.10 总结

  • 熟悉处理外部文件的方案有哪些,想要实现好的效果需要去查找对应的专业处理技术

1.2 文本分割

image-20260508155819548

1.2.1 为什么需要文本分割

  • 模型输入限制(Context Window): LLM 的输入窗口是有限的(如 8k, 32k, 128k Tokens),长文档无法一次性投喂
  • 计算成本(Cost Efficiency): Token 是要钱的,只输入最相关的片段能大幅节省开支
  • 检索精度(Retrieval Precision): 向量模型对“短小精悍”的语义块匹配度更高。如果一个块里讲了三个不同的主题,向量表征就会模糊

1.2.2 文本分割的核心术语

  • 在进行代码实现前,必须理解两个关键参数:

    • Chunk Size(块大小): 每个片段包含的最大字符数或 Token 数

    • Chunk Overlap(块重叠): 相邻两个片段之间重复的部分,作用是为了保持上下文连贯。防止一个核心观点(如人名、关键数据)刚好被切在两个块的中间而导致丢失语义

1.2.3 基础分割

  • 每段话遇到特定字符或满足一定长度就进行裁断

  • 基于 Langchain 的分割 :【 https://python.langchain.com/api_reference/text_splitters/index.html 】

  • 安装:【pip install langchain-text-splitters】

  • RecursiveCharacterTextSplitter 是 LangChain 里最常用的文本分割器之一,专门用于把长文档切分成适合向量化和检索的小块,参数如下:

参数 类型 默认值 说明 适用场景 / 备注
chunk_size int 1000 每个文本块的最大长度(字符或 token 数) 控制切分粒度,块太小上下文不足,块太大可能超长
chunk_overlap int 200 相邻文本块之间的重叠长度(字符或 token 数) 避免切分断裂语义
separators list[str] ["\n\n", "\n", " ", ""] 按优先级依次尝试的分隔符 中文场景可加入 "。", ",",先按段落 → 换行 → 空格 → 字符
length_function callable len 计算文本长度的方法 可用 token 计数函数,确保与 Embedding 模型输入长度一致
is_separator_regex bool False 是否将 separators 当作正则表达式处理 复杂文本分割时使用
keep_separator bool False 是否在生成的文本块中保留分隔符 保留分隔符有助于保持段落结构
add_start_index bool False 是否在文档中添加每个块的起始字符索引 用于追踪原始文档位置
  • 案例代码:
from langchain_text_splitters import RecursiveCharacterTextSplitter
import os

CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
document_path = os.path.join(CURRENT_DIR, "datasets", "糖尿病.txt")
output_path = os.path.join(CURRENT_DIR, "datasets", "糖尿病.seg.txt")


# 按照中文标点进行文本分割
def text_split_punc(text, chunk=300, chunk_overlap=50):
    text_splitter = RecursiveCharacterTextSplitter(
        separators=[
            "。", ",", "?", "、", ":", ";", "!", "“", "”", "(",
            ")", "《", "》", "……", "——", "‘", "’", "·", "【", "】",
        ],  # 分割符号
        chunk_size=chunk,  # 分割长度
        chunk_overlap=chunk_overlap,  # 重叠长度
        length_function=len,  # 计算长度的函数
        keep_separator=False,  # 不保留分割符号
        is_separator_regex=False,  # 不使用正则表达式
    )
    chunks = text_splitter.split_text(text)  # 分割文本
    return chunks  # 返回分割好的列表


if __name__ == "__main__":
    with open(document_path, "r", encoding="utf-8") as f:  # 打开文件
        data = f.read().replace("\n", "")  # 读取文件内容并去除换行符
        chunks = text_split_punc(data)  # 调用分割函数

    with open(output_path, "w", encoding="utf-8") as f:  # 打开文件
        for chunk in chunks:  # 遍历分割好的文本
            print(len(chunk))
            f.write(chunk + "\n")  # 写入文件
    print("文本分割完成!")

1.2.4 语义分割

pip install modelscope addict datasets simplejson sortedcontainers
  • 下载模型
from modelscope import snapshot_download

model_dir = snapshot_download(
    'iic/nlp_bert_document-segmentation_chinese-base',
    cache_dir='segmodel'
)


# 我自己模型保存在位置
modelscope download --model iic/nlp_bert_document-segmentation_chinese-base --local_dir D:\models
  • 案例代码:
from modelscope.outputs import OutputKeys
from modelscope.pipelines import pipeline
from modelscope.utils.constant import NLPTasks
import os

CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
document_path = os.path.join(CURRENT_DIR, "datasets", "华清远见.txt")
model_path = os.path.join(CURRENT_DIR, r"F:\workspace\AI\stu_nlp\rag\segmodel\iic\nlp_bert_document-segmentation_chinese-base")


# 按照语义进行分割
def text_seg_seq(text, model_path):
    # 加载文档语义分割模型
    p = pipeline(
        task=NLPTasks.document_segmentation,
        model=model_path,
        model_revision='master',
    )
    # 将整篇文本传入模型进行分割,输出是一个字典,chunks 是取出的分割后的文本列表
    chunks = p(documents=text)[OutputKeys.TEXT].split("\n")
    return chunks


if __name__ == "__main__":
    with open(document_path, "r", encoding="utf-8") as f:  # 打开文件
        data = f.read().replace("\n", "")  # 读取文件内容并去除换行符
        chunks = text_seg_seq(data, model_path=model_path)
    print(chunks)

1.3 Embedding 模型选择

1.3.1 选择参考指标

  • 任务

    • MTEB 提供 classification(文本分类)、clustering(文本聚类)、retrieval(信息检索)、summarization(摘要生成) 等不同任务的评测

    • RAG 重点关注 retrieval 任务

  • 语言支持:中文、英文、多语言

  • 评测分数:模型会提供排名和分数,用于对比性能

  • 模型大小和内存使用:取决于设备资源和执行性能

  • Embedding 维度:

    • 表示输出 embedding 向量的长度
    • 维度高:可捕捉更复杂语义
    • 维度低:执行效率更好,更易于存储
    • 长度一般不需要太长,权衡语义能力与性能
  • 最大 tokens 数:指 embedding 模型支持输入的最大 token 数,超过会截断

1.4 总结

  • 知识库的构建效果关系到后期检索的效果
  • 知识库的构建属于离线操作,即开发 RAG 项目之前就应该构建好

2、检索器

  • RAG(Retrieval-Augmented Generation)模型的检索器负责在一个大型文档库中查找与输入查询相关的文档片段,提供给生成模型用于回答问题或生成相关文本

  • 检索器 (Retriever / 召回层):核心目标是提高“召回率”和“准确率”

  • 多维检索技术:

    • Dense Retrieval (向量检索): 捕获语义,适合“意思相近”的查找
    • Sparse Retrieval (关键词检索): BM25 算法,适合精准匹配(如人名、产品型号、错误代码)
    • Hybrid Search (混合检索):结合两者打分,解决向量检索在“专有名词”上的无力【rrf 算法】
  • 检索增强技术 (Advanced Retrieval):

    • Query Rewrite (查询重写): LLM 将用户口语化问题转为更适合检索的关键词
    • HyDE (假设性文档嵌入): 先让 LLM 生成一个伪答案,用伪答案去检索,效果往往比直接检索问题好
    • Rerank (重排序): [最强提质手段] 检索出 100 条,用精排模型(如 BGE-Reranker)筛选出最相关的 Top-5
      • 【召回、重排序】

2.1 核心任务

  • 检索相似文档:RAG 的检索器通过计算输入查询与文档库中每个文档的相似度,找到最相关的 top-k 个文档

  • 生成上下文:找到相关文档后,这些文档会作为上下文,与查询问题一起提供给生成模型,生成答案

2.2 工作流程

  • 查询编码:首先,查询会通过一个编码器转化为一个高维向量表示(查询向量),这里的编码器和存储文本到向量数据库的编码器用同一个
  • 文档编码:文档库中的每个文档或文档片段也需要通过同样的编码器转化为向量。为了加速检索过程,通常会对文档库进行预编码,将每个文档转换为固定的向量并存储起来
  • 向量相似度计算:检索器会计算查询向量与文档库中每个文档向量之间的相似度,常用的计算方法有余弦相似度、内积、欧氏距离等
  • 返回相关文档:基于相似度得分,检索器返回最相关的文档或片段。这些文档会被传递给生成模型

2.3 检索方法

  • 基于稠密向量的检索:现代 RAG 的核心方案

    • 其基本思想是:将文本通过 Embedding Model 编码成高维向量,再通过向量相似度(如余弦相似度)查找最相关文档
  • 基于稀疏向量的检索:传统方法

    • BM25:基于词袋模型的经典检索方法,通过统计词频和反向文档频率(IDF)来评估文档与查询的相关性。虽然 BM25 相对于密集向量方法较为简单,但在某些情况下仍然能提供较好的性能
  • 混合检索方法:

    • RAG模型通常使用稠密向量和稀疏向量结合的方式

3、生成器

  • RAG(Retrieval-Augmented Generation)模型的生成器(Generator)是模型中负责生成最终输出的部分,通常是文本

  • 核心任务:在检索阶段获得的文档片段或信息的基础上,生成自然语言的回答、摘要或相关文本

  • 生成器 (Generator / 决策层):生成器负责将碎片化的信息转化为逻辑严密的语言

  • 生成模型选型:

    • 闭源: GPT、Claude
    • 开源/国产: DeepSeek、Qwen
  • Prompt Engineering (提示工程) :核心逻辑

    • 角色定义: “你是一名专业的 IT 技术支持...”
    • 约束条件: “只能根据给定的参考资料回答,不要编造。”
    • 结构化输出: 要求以 Markdown、JSON 或引用格式回复
  • 长上下文处理: 解决“迷失在中间 (Lost in the Middle)”问题,即模型容易忽略长文本中间的信息

3.1 工作流程

  • RAG(Retrieval-Augmented Generation)模型中的生成器的作用,确实就是将用户输入的问题与检索到的外部资源进行合并,然后传递给一个大语言模型进行最终的生成

  • 生成器的工作流程可以分为以下几个步骤:

    • 将查询与检索结果合并

      • 生成器接收到查询和检索结果之后,会将两者合并在一起。这时,生成器不仅有用户的问题信息,还获得了与该问题相关的外部知识

      • 这种合并的方式通常是将查询和文档片段作为输入并传递给生成模型。合并后,生成器会根据这些上下文信息来生成一个更准确和全面的输出

    • 通过生成模型生成回答

      • 生成器将合并后的信息传递给大语言模型(如T5、BART等)。该生成模型使用输入的查询和相关的文档片段来推理并生成最终的回答或文本

      • 生成模型的输出可能是:

        • 直接回答问题(例如,“量子计算是研究量子力学原理在计算机科学中的应用”)
        • 生成摘要(对于长文档或信息密集的输入)
        • 生成建议、推荐或其他类型的文本
    • 生成输出

      • 最终,生成模型会输出一个经过优化的回答或生成文本,用户就能看到最终结果
  • 案例助解:

  • 假设用户问:“什么是量子计算?”

    • 检索阶段:检索器会在知识库中找到相关的文档片段,比如从Wikipedia上检索到的“量子计算是一种基于量子力学原理的新型计算方式…”等内容

    • 生成阶段:生成器会将这个查询与检索到的文档片段合并,并交给大语言模型生成最终的回答

    • 输出:生成器最终可能输出:“量子计算是一种使用量子位(qubits)进行数据处理的计算方式,相较于传统计算机,量子计算机能够在某些任务上实现指数级的加速…”

3.2 作用

  • 提升回答的准确性:生成器可以利用外部文档中的最新和详细信息,从而提高回答的质量

  • 扩展知识范围:即使生成模型的预训练数据不包含某些信息,检索器仍然可以提供相关的外部资源,使得生成器能够回答更新的、特定领域的问题

  • 处理复杂问题:对于需要综合多方面信息的问题,生成器可以通过整合多个文档片段来生成更加全面和丰富的回答