1.RAG简介 #

RAG(Retrieval-Augmented Generation,检索式增强生成)是一种结合了“检索”与“生成”两种能力的自然语言处理技术,常用于问答系统、智能助手等场景。

1.1 大模型的局限性 #

1.2 为什么选择RAG? #

  1. 提升答案准确性
    RAG通过实时检索外部知识库中的相关信息,有效增强生成内容的准确性和权威性,避免模型“胡编乱造”。

  2. 显著降低训练与维护成本
    相较于完全依赖大规模数据训练的生成模型,RAG只需较少的训练数据即可实现高质量输出,大幅减少算力和数据投入。

  3. 灵活应对新知识与变化
    RAG具备极强的适应性,能够动态检索和利用最新的知识库内容,面对新领域、新事件时无需重新训练模型即可快速响应和生成相关答案。

1.3 RAG工作流程 #

1.3.1 步骤一:知识库构建 #

1.3.2 步骤二:检索(Retrieval) #

1.3.3 步骤三:生成(Generation) #

1.4 优势 #

1.5 应用场景 #

1.6 长上下文和RAG #

1.6.1 Long Context(长上下文) #

直接将大量上下文(如长文档、历史对话等)整体输入到大模型,让模型在“记住”全部内容的基础上直接生成答案。

维度 RAG(检索增强生成) Long Context(长上下文)
知识容量 理论上无限(依赖外部知识库) 受限于模型最大上下文窗口(如32K、128K)
实时性 检索+生成,检索速度快,生成速度取决于模型 直接生成,长文本输入会显著拖慢推理速度
可扩展性 知识库易于扩展和更新,无需重新训练模型 上下文窗口有限,超长内容需截断或摘要
准确性 检索相关性决定答案质量,依赖检索效果 只要内容在窗口内,模型可直接引用
幻觉风险 检索内容可控,幻觉概率较低 长上下文内仍可能出现幻觉
实现难度 需搭建检索系统和知识库,流程较复杂 只需支持大窗口模型,流程简单
成本 检索和生成分开,资源消耗可控 长上下文推理消耗显著增加

1.6.2 适用场景对比 #

2.RAG工作流 #

2.1 管理员部分:知识入库流程 #

这部分主要是将本地文档转化为可检索的向量,供后续问答使用。

  1. Local Documents(本地文档)
    管理员准备好需要导入的知识文档,格式可以多样(如PDF、Word、txt等)。

  2. Unstructured Loader(非结构化加载器)
    使用加载器将各种格式的文档解析为纯文本,便于后续处理。

  3. Text(文本)
    得到的纯文本内容。

  4. Text Splitter(文本切分器)
    将长文本按照一定规则(如段落、句子、字数等)切分成较小的文本块。

  5. Text Chunks(文本块)
    切分后得到的多个小文本片段。

  6. Embedding(向量化)
    利用嵌入模型(如BERT、SentenceTransformer等)将每个文本块转化为高维向量。

  7. VectorStore(向量存储)
    所有文本块的向量被存入向量数据库(如Milvus等),用于后续的高效检索。

2.2 用户部分:问答检索流程 #

这部分是用户实际提问并获得答案的过程。

  1. Query(查询)
    用户输入一个问题或查询。

  2. Embedding(向量化)
    将用户的查询同样转化为向量表示。

  3. Query Vector(查询向量)
    得到用户问题的向量。

  4. Vector Similarity(向量相似度)
    计算查询向量与知识库中所有文本块向量的相似度,找出最相关的若干文本块。

  5. Related Text Chunks(相关文本块)
    检索到与用户问题最相关的文本片段。

  6. Prompt Template(提示词模板)
    将用户问题和相关文本块填充到预设的提示词模板中,构建最终的Prompt。

  7. Prompt(提示词)
    生成用于大语言模型的完整输入。

  8. LLM(大语言模型)
    将Prompt输入到大语言模型,生成最终的答案。

  9. Answer(答案)
    返回给用户的最终回答。

2.3 工作流 #

3.文档解析 #

3.1 数据格式的多样性 #

在实际开展文档处理工作前,我们首先要对企业内部的数据类型和特点有一个全面的了解。只有充分认识到数据的多样性和复杂性,才能为后续的数据解析和知识库建设打下坚实基础。

企业在日常运营中会积累大量数据,这些数据因行业、业务流程和管理方式的不同而呈现出极大的多样性。部分数据甚至是企业独有的,外部很难见到。因此,数据处理往往需要根据实际情况定制脚本和工具,灵活应对各种数据格式和内容。

企业数据的复杂性主要体现在两个方面:数据格式的多样性和数据内容的复杂性。

3.2 数据内容的复杂性 #

企业数据大致可以分为两大类:结构化数据和非结构化/半结构化数据。

除了格式多样,企业数据在内容层面也极具挑战性,尤其是非结构化文档。以 PDF 文件为例,单个文档中可能同时包含标题、段落、表格、图片、公式等多种元素。不同文档的排版和布局也各不相同,有的采用单栏,有的为双栏,内容组织方式千变万化。文本、标题、列表、表格、图片等元素可能交错出现,给自动化解析带来很大难度。

正因为企业数据在格式和内容上的复杂性,构建高质量的知识库时,往往需要针对不同类型的数据进行专门的预处理和解析策略。只有这样,才能确保后续知识抽取和检索的准确性和有效性。

总之,企业数据的多样性和复杂性是知识管理和智能文档处理中的一大挑战。理解这些特点,有助于我们选择合适的技术方案和工具,提升数据处理的效率和质量。

3.3 GIGO #

在数据处理和智能系统开发领域,有一个非常重要的理念——“垃圾输入,必然导致垃圾输出”(Garbage In, Garbage Out,简称GIGO)。这个原则广泛应用于计算机科学和信息系统建设中,强调了数据质量对最终结果的决定性作用。无论你的算法多么先进、流程多么完善,如果最初输入的数据存在问题,最终的输出也难以令人满意。

在构建RAG(检索增强生成)知识库的过程中,数据质量同样是成败的关键。整个流程大致包括:业务数据的选取、数据解析、内容分块、向量化处理,以及将向量存入数据库。每一个环节都可能成为“垃圾数据”产生的源头。

数据源选择

首先,数据源的甄别至关重要。只有与业务需求高度相关的数据,才能为系统提供有价值的支撑。如果引入了无关或低质量的信息,不仅会影响检索效果,还可能导致系统输出错误或无意义的答案。此外,数据本身的准确性和一致性也必须得到保证。如果原始数据中存在矛盾、错误或遗漏,后续所有处理都无法弥补这些缺陷。最后,数据的覆盖面也要足够广泛,避免因信息不全而影响系统的整体表现。

数据解析环节

企业数据格式多样,内容复杂,解析过程中极易出现问题。例如,文本内容可能被错误识别,表格结构解析混乱,或者某些关键信息被遗漏。这些解析失误都会直接影响后续的知识抽取和检索效果。因此,数据解析工具和脚本的选择与调优同样重要。

内容分块策略

文档分块是将大文本拆分为更小的片段,以便后续向量化和检索。但如果分块策略不合理,比如将本应连贯的语义拆散,或者把无关内容混在一起,都会导致上下文丢失或检索噪声增加。这不仅影响检索的相关性,还可能让生成模型输出不准确的答案。

全流程质量把控

综上所述,RAG系统的每一步都可能成为“垃圾数据”的入口。只有在数据源筛选、解析、分块等各个环节都严格把控质量,才能确保最终系统的有效性和可靠性。数据质量的保障,是构建高效智能知识系统的基石。始终牢记:只有高质量的输入,才能带来高价值的输出。

3.4 文档解析 #

文档解析是 RAG 知识库建设中的关键前置步骤:把 PDF、Word、PPT、Excel、HTML 等原始文件,转换为可分块、可向量化的纯文本(及必要的结构化信息)。解析质量直接决定后续检索与生成的上限——漏提、乱序、表格错位、版式噪声混入,都会在入库后放大为“检索不准、答案跑偏”。

实际工程中,文档解析通常要解决三类问题:

  1. 格式适配:不同后缀对应不同二进制结构与 API,需要选用专用库分别处理。
  2. 内容还原:尽量保留标题层级、段落顺序、表格语义与列表结构,避免把版式信息全部压扁成无意义字符串。
  3. 噪声清洗:去除页眉页脚、水印、重复目录、无意义空白与 HTML 标签等干扰项,为分块提供干净输入。

下面列出本项目常用的解析依赖及其用途。入库时会按文件类型自动路由到对应解析器;若某类格式解析失败或效果不佳,应优先在这一层排查与增强,而不是直接依赖大模型“猜”原文。

库名 用途说明
pymupdf 高性能 PDF 解析库,可提取文本、图片、表格等页面内容
python-docx 读取和解析 Word 文档(.docx),支持段落、表格、样式等
openpyxl 处理 Excel 电子表格(.xlsx),支持工作表、单元格与公式
python-pptx 解析 PowerPoint 演示文稿(.pptx),提取幻灯片文本与结构
beautifulsoup4 解析 HTML / 网页内容,便于从半结构化页面中抽取文本
lxml 高效的 XML/HTML 解析引擎,常作为 BeautifulSoup 的底层解析器

4 文档分割 #

文本分块(Text Chunking)是一种将长文档分解为更小、更易处理的文本片段的技术。这个过程类似于将一本厚重的书籍拆分成多个章节,每个章节都包含相对独立且完整的信息单元。

4.1 文本分块的核心价值 #

在构建检索增强生成(RAG)系统时,文本分块技术具有以下重要意义:

  1. 语义纯度保证:长文档往往包含多个不同的主题和语义信息,直接处理会导致语义混淆,影响检索精度。

  2. 检索精度提升:通过分块,系统能够更精确地匹配用户查询,避免返回无关信息,从而提高回答质量。

  3. 模型限制适配:现代语言模型都有输入长度限制,分块技术确保每个文本片段都能在模型的处理范围内。

4.2. 文本分块的基本原则 #

4.2.1 语义完整性原则 #

每个文本块应该包含一个完整且独立的语义单元。这就像拼图游戏中的每一块,都应该能够独立表达一个完整的概念。

4.2.2 大小平衡原则 #

4.3 递归分割策略 #

RecursiveCharacterTextSplitter 是 LangChain 中最常用的文本分割器,它实现了基于文本结构的分割策略。对于大多数应用场景,这是推荐的默认选择。

4.3.1 为什么推荐? #

4.3.2 工作原理 #

4.3.3 参数说明 #

RecursiveCharacterTextSplitter 的主要参数:

chunk_size(块大小):

chunk_overlap(块重叠):

length_function(长度函数):

is_separator_regex(分隔符是否为正则表达式):

separators(分隔符列表):

4.3.4 实现 #

# 从 langchain_text_splitters 模块导入递归字符文本分割器类
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 创建递归字符文本分割器对象,指定分块参数
# chunk_size 表示每块最大允许的字符数为 10
# chunk_overlap 表示块与块之间没有重叠(重叠字符数为 0)
text_splitter = RecursiveCharacterTextSplitter(chunk_size=10, chunk_overlap=0)

# 构造待分割的文本,包含多段相同字符以及换行符
document = f"""{"1"*10}\n{"2"*9}\n\n{"3"*9}\n{"4"*9}"""

# 使用文本分割器的 split_text 方法将 document 分割为多个字符串块
texts = text_splitter.split_text(document)

# 打印一共分割出的块数
print(f"共分割为 {len(texts)} 个块:")

# 从 1 开始枚举每个文本块,依次取出序号 i 和块内容 text
for i, text in enumerate(texts, 1):
    # 打印当前块的序号,并用 repr 格式输出块内容以便查看特殊字符
    print(i, repr(text))

5. 向量化 #

5.1 使用云服务 #

# 导入 os 模块,用于读取环境变量
import os

# 导入 requests 库,用于发送 HTTP 请求
import requests
# 从 dotenv 导入 load_dotenv,用于从 .env 文件加载环境变量
from dotenv import load_dotenv

# 加载 .env 中的环境变量;override=True 表示覆盖已存在的同名环境变量
load_dotenv(override=True)

# 拼接 DashScope Embeddings API 的完整请求地址
API_URL = (
    # 从环境变量读取 API 基础地址,若不存在则使用默认国内兼容模式地址
    os.getenv(
        # 环境变量名:DashScope API 基础地址
        "DASHSCOPE_API_BASE",
        # 默认的 DashScope 兼容模式 API 基础地址
        "https://dashscope.aliyuncs.com/compatible-mode/v1",
    # 去掉末尾多余的斜杠,避免拼接时出现双斜杠
    ).rstrip("/")
    # 拼接 embeddings 接口路径
    + "/embeddings"
)
# 从环境变量读取 DashScope API 密钥
API_KEY = os.getenv("DASHSCOPE_API_KEY")


# 定义获取文本向量嵌入的函数,接收文档内容字符串,返回浮点数列表
def get_embedding(doc_content: str) -> list[float]:
    # 构造 HTTP 请求头
    headers = {
        # 声明请求体为 JSON 格式
        "Content-Type": "application/json",
        # 使用 Bearer Token 方式进行身份认证
        "Authorization": f"Bearer {API_KEY}",
    }
    # 构造请求体载荷
    payload = {
        # 指定使用的嵌入模型为 text-embedding-v4
        "model": "text-embedding-v4",
        # 待向量化的输入文本内容
        "input": doc_content,
    }
    # 向 Embeddings API 发送 POST 请求并获取响应
    response = requests.post(API_URL, json=payload, headers=headers)
    # 判断请求是否成功(HTTP 状态码为 200)
    if response.status_code == 200:
        # 从响应 JSON 中提取第一条结果的 embedding 向量并返回
        return response.json()["data"][0]["embedding"]
    # 请求失败时抛出异常,附带错误响应文本
    raise Exception(f"Embedding API error: {response.text}")


# 定义待向量化的示例文档内容
doc_content = "这是一个示例文档"
# 调用 get_embedding 函数获取文档的向量嵌入
embedding = get_embedding(doc_content)
# 打印向量的维度(长度)
print(f"维度: {len(embedding)}")
# 打印向量的前 8 个元素,并以省略号表示后续内容
print(embedding[:8], "...")

.env

DASHSCOPE_API_KEY=sk-ws-H.ERHHILP.EwfH.MEUCIG4MABNcWTX6Ab0hxo2mH0h_LZhK54LD5mh-Ud_Fy9byAiEA_Ke0gA9kPvpxK57xEEXtEijinbGbJZGUCNsknqSDnuA

5.2 本地向量化 #

# 从sentence_transformers库中导入SentenceTransformer类
from sentence_transformers import SentenceTransformer

# 加载预训练的句子嵌入模型"all-MiniLM-L6-v2"
model = SentenceTransformer("all-MiniLM-L6-v2")


# 定义一个函数,用于获取输入文档内容的向量表示
def get_sentence_embedding(doc_content):
    # 使用模型对文档内容进行编码,得到嵌入向量
    embedding = model.encode(doc_content)
    # 返回嵌入向量
    return embedding


# 定义一个示例文档内容
doc_content = "这是一个示例文档"
# 获取示例文档的嵌入向量
embedding = get_sentence_embedding(doc_content)
# 打印嵌入向量
print(embedding)

5.3 向量数据库 #

ChromaDB 是一个轻量级的开源向量数据库,专门设计用于存储和检索向量数据。想象一下,你有一个包含数千篇文档的知识库,当用户提问"如何学习机器学习?"时,ChromaDB 能快速找到与这个问题最相关的文档,即使用户的提问和文档中的文字不完全一样。

向量数据库的作用:

ChromaDB 的优势: