1.RAG简介 #
- paraphrase-multilingual-MiniLM-L12-v2:多语言文本嵌入模型,将文本编码为向量用于相似度检索。
- openai:调用 OpenAI 兼容接口,完成大语言模型问答生成。
- ChromaDB:本地向量数据库,持久化存储文档块嵌入并支持相似度检索。
- langchain-text-splitters:按字符/语义规则切分长文档,生成适合检索的文本块。
- pymupdf:解析 PDF,提取正文文本用于入库。
- python-docx:解析 Word(
.docx)文档内容。 - python-pptx:解析 PowerPoint(
.pptx)幻灯片文本。 - openpyxl:读取 Excel(
.xlsx)工作表数据。 - beautifulsoup4:解析 HTML/XML,清洗网页类文档中的标签与噪声。
- rich:增强终端输出,支持彩色、表格与富文本展示。
RAG(Retrieval-Augmented Generation,检索式增强生成)是一种结合了“检索”与“生成”两种能力的自然语言处理技术,常用于问答系统、智能助手等场景。
1.1 大模型的局限性 #
- 知识更新滞后:大模型的训练数据具有时效性,难以及时反映最新的事实和动态信息。
- 缺乏专有领域知识:对于企业内部、行业专属等私有知识,大模型通常无法覆盖,难以满足专业化需求。
- 存在内容幻觉:大模型有时会生成看似合理但实际错误的信息,容易造成误导和错误决策。
1.2 为什么选择RAG? #
提升答案准确性
RAG通过实时检索外部知识库中的相关信息,有效增强生成内容的准确性和权威性,避免模型“胡编乱造”。显著降低训练与维护成本
相较于完全依赖大规模数据训练的生成模型,RAG只需较少的训练数据即可实现高质量输出,大幅减少算力和数据投入。灵活应对新知识与变化
RAG具备极强的适应性,能够动态检索和利用最新的知识库内容,面对新领域、新事件时无需重新训练模型即可快速响应和生成相关答案。
1.3 RAG工作流程 #
1.3.1 步骤一:知识库构建 #
- 管理员将本地文档转化为可检索的向量,供后续问答使用。
1.3.2 步骤二:检索(Retrieval) #
- 用户输入一个问题(Query)。
- 系统将问题转化为向量(embedding),在知识库中检索与问题最相关的若干条文档或片段。
1.3.3 步骤三:生成(Generation) #
- 将检索到的内容与原始问题一起输入到生成式模型中。
- 生成式模型根据这些信息,生成更准确、更有依据的答案。
1.4 优势 #
- 知识可扩展:模型不需要记住所有知识,只需检索外部知识库,便于更新和扩展。
- 答案更有依据:生成的答案可以引用检索到的具体内容,提升可信度。
- 减少幻觉:降低生成模型“胡编乱造”的概率。
1.5 应用场景 #
- 智能问答(如企业知识库问答、法律咨询等)
- 智能客服
- 文档摘要与分析
- 代码检索与生成
1.6 长上下文和RAG #
1.6.1 Long Context(长上下文) #
直接将大量上下文(如长文档、历史对话等)整体输入到大模型,让模型在“记住”全部内容的基础上直接生成答案。
| 维度 | RAG(检索增强生成) | Long Context(长上下文) |
|---|---|---|
| 知识容量 | 理论上无限(依赖外部知识库) | 受限于模型最大上下文窗口(如32K、128K) |
| 实时性 | 检索+生成,检索速度快,生成速度取决于模型 | 直接生成,长文本输入会显著拖慢推理速度 |
| 可扩展性 | 知识库易于扩展和更新,无需重新训练模型 | 上下文窗口有限,超长内容需截断或摘要 |
| 准确性 | 检索相关性决定答案质量,依赖检索效果 | 只要内容在窗口内,模型可直接引用 |
| 幻觉风险 | 检索内容可控,幻觉概率较低 | 长上下文内仍可能出现幻觉 |
| 实现难度 | 需搭建检索系统和知识库,流程较复杂 | 只需支持大窗口模型,流程简单 |
| 成本 | 检索和生成分开,资源消耗可控 | 长上下文推理消耗显著增加 |
1.6.2 适用场景对比 #
RAG适合:
- 企业知识库问答、法律/医疗/金融等专业领域
- 知识库大、内容经常更新的场景
- 需要可追溯、可解释答案的场景
Long Context适合:
- 需要处理长文档、长对话、论文、小说等场景
- 上下文内容有限且全部相关时
- 不方便搭建知识库或检索系统时
2.RAG工作流 #

- 管理员负责知识的整理、切分、向量化和入库,保证知识库的丰富和可检索性。
- 用户只需输入问题,系统会自动完成向量化、检索、生成答案等一系列操作,最终返回高质量的答案。
2.1 管理员部分:知识入库流程 #
这部分主要是将本地文档转化为可检索的向量,供后续问答使用。
Local Documents(本地文档)
管理员准备好需要导入的知识文档,格式可以多样(如PDF、Word、txt等)。Unstructured Loader(非结构化加载器)
使用加载器将各种格式的文档解析为纯文本,便于后续处理。Text(文本)
得到的纯文本内容。Text Splitter(文本切分器)
将长文本按照一定规则(如段落、句子、字数等)切分成较小的文本块。Text Chunks(文本块)
切分后得到的多个小文本片段。Embedding(向量化)
利用嵌入模型(如BERT、SentenceTransformer等)将每个文本块转化为高维向量。VectorStore(向量存储)
所有文本块的向量被存入向量数据库(如Milvus等),用于后续的高效检索。
2.2 用户部分:问答检索流程 #
这部分是用户实际提问并获得答案的过程。
Query(查询)
用户输入一个问题或查询。Embedding(向量化)
将用户的查询同样转化为向量表示。Query Vector(查询向量)
得到用户问题的向量。Vector Similarity(向量相似度)
计算查询向量与知识库中所有文本块向量的相似度,找出最相关的若干文本块。Related Text Chunks(相关文本块)
检索到与用户问题最相关的文本片段。Prompt Template(提示词模板)
将用户问题和相关文本块填充到预设的提示词模板中,构建最终的Prompt。Prompt(提示词)
生成用于大语言模型的完整输入。LLM(大语言模型)
将Prompt输入到大语言模型,生成最终的答案。Answer(答案)
返回给用户的最终回答。
2.3 工作流 #

3.文档解析 #
3.1 数据格式的多样性 #
在实际开展文档处理工作前,我们首先要对企业内部的数据类型和特点有一个全面的了解。只有充分认识到数据的多样性和复杂性,才能为后续的数据解析和知识库建设打下坚实基础。
企业在日常运营中会积累大量数据,这些数据因行业、业务流程和管理方式的不同而呈现出极大的多样性。部分数据甚至是企业独有的,外部很难见到。因此,数据处理往往需要根据实际情况定制脚本和工具,灵活应对各种数据格式和内容。
企业数据的复杂性主要体现在两个方面:数据格式的多样性和数据内容的复杂性。
3.2 数据内容的复杂性 #
企业数据大致可以分为两大类:结构化数据和非结构化/半结构化数据。
结构化数据
这类数据通常存储在关系型数据库(如 MySQL、Oracle)中,数据以表格形式组织,每个字段都有明确的定义和含义。除了传统的关系型数据库,还有文档型数据库(如 MongoDB)、全文检索数据库(如 Elasticsearch)、列式数据库(如 ClickHouse)、图数据库(如 Neo4j)以及分布式 NoSQL 数据库(如 Cassandra)等。结构化数据的优点是格式统一、易于查询和分析,通常通过 SQL 或类似的查询语言进行操作。非结构化与半结构化数据
这部分数据类型丰富,包括但不限于 PDF、Word、PPT、Excel 等常见办公文档,纯文本文件(如日志、txt),网页数据(HTML)、数据交换格式(JSON、XML)、Markdown 文档,以及图片、音频、视频等多媒体文件。非结构化数据没有固定的字段和格式,内容表现形式灵活多变,解析和处理难度较大。半结构化数据则介于两者之间,既有一定的结构性,又保留了内容的灵活性。
除了格式多样,企业数据在内容层面也极具挑战性,尤其是非结构化文档。以 PDF 文件为例,单个文档中可能同时包含标题、段落、表格、图片、公式等多种元素。不同文档的排版和布局也各不相同,有的采用单栏,有的为双栏,内容组织方式千变万化。文本、标题、列表、表格、图片等元素可能交错出现,给自动化解析带来很大难度。
正因为企业数据在格式和内容上的复杂性,构建高质量的知识库时,往往需要针对不同类型的数据进行专门的预处理和解析策略。只有这样,才能确保后续知识抽取和检索的准确性和有效性。
总之,企业数据的多样性和复杂性是知识管理和智能文档处理中的一大挑战。理解这些特点,有助于我们选择合适的技术方案和工具,提升数据处理的效率和质量。
3.3 GIGO #
在数据处理和智能系统开发领域,有一个非常重要的理念——“垃圾输入,必然导致垃圾输出”(Garbage In, Garbage Out,简称GIGO)。这个原则广泛应用于计算机科学和信息系统建设中,强调了数据质量对最终结果的决定性作用。无论你的算法多么先进、流程多么完善,如果最初输入的数据存在问题,最终的输出也难以令人满意。
在构建RAG(检索增强生成)知识库的过程中,数据质量同样是成败的关键。整个流程大致包括:业务数据的选取、数据解析、内容分块、向量化处理,以及将向量存入数据库。每一个环节都可能成为“垃圾数据”产生的源头。
数据源选择
首先,数据源的甄别至关重要。只有与业务需求高度相关的数据,才能为系统提供有价值的支撑。如果引入了无关或低质量的信息,不仅会影响检索效果,还可能导致系统输出错误或无意义的答案。此外,数据本身的准确性和一致性也必须得到保证。如果原始数据中存在矛盾、错误或遗漏,后续所有处理都无法弥补这些缺陷。最后,数据的覆盖面也要足够广泛,避免因信息不全而影响系统的整体表现。
数据解析环节
企业数据格式多样,内容复杂,解析过程中极易出现问题。例如,文本内容可能被错误识别,表格结构解析混乱,或者某些关键信息被遗漏。这些解析失误都会直接影响后续的知识抽取和检索效果。因此,数据解析工具和脚本的选择与调优同样重要。
内容分块策略
文档分块是将大文本拆分为更小的片段,以便后续向量化和检索。但如果分块策略不合理,比如将本应连贯的语义拆散,或者把无关内容混在一起,都会导致上下文丢失或检索噪声增加。这不仅影响检索的相关性,还可能让生成模型输出不准确的答案。
全流程质量把控
综上所述,RAG系统的每一步都可能成为“垃圾数据”的入口。只有在数据源筛选、解析、分块等各个环节都严格把控质量,才能确保最终系统的有效性和可靠性。数据质量的保障,是构建高效智能知识系统的基石。始终牢记:只有高质量的输入,才能带来高价值的输出。
3.4 文档解析 #
文档解析是 RAG 知识库建设中的关键前置步骤:把 PDF、Word、PPT、Excel、HTML 等原始文件,转换为可分块、可向量化的纯文本(及必要的结构化信息)。解析质量直接决定后续检索与生成的上限——漏提、乱序、表格错位、版式噪声混入,都会在入库后放大为“检索不准、答案跑偏”。
实际工程中,文档解析通常要解决三类问题:
- 格式适配:不同后缀对应不同二进制结构与 API,需要选用专用库分别处理。
- 内容还原:尽量保留标题层级、段落顺序、表格语义与列表结构,避免把版式信息全部压扁成无意义字符串。
- 噪声清洗:去除页眉页脚、水印、重复目录、无意义空白与 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)系统时,文本分块技术具有以下重要意义:
语义纯度保证:长文档往往包含多个不同的主题和语义信息,直接处理会导致语义混淆,影响检索精度。
检索精度提升:通过分块,系统能够更精确地匹配用户查询,避免返回无关信息,从而提高回答质量。
模型限制适配:现代语言模型都有输入长度限制,分块技术确保每个文本片段都能在模型的处理范围内。
4.2. 文本分块的基本原则 #
4.2.1 语义完整性原则 #
每个文本块应该包含一个完整且独立的语义单元。这就像拼图游戏中的每一块,都应该能够独立表达一个完整的概念。
4.2.2 大小平衡原则 #
- 过小的块:会破坏语义的完整性,导致上下文信息缺失
- 过大的块:可能包含多个不相关的主题,降低检索精度
4.3 递归分割策略 #
RecursiveCharacterTextSplitter 是 LangChain 中最常用的文本分割器,它实现了基于文本结构的分割策略。对于大多数应用场景,这是推荐的默认选择。
4.3.1 为什么推荐? #
- 在保持上下文完整性和管理块大小之间取得了良好的平衡
- 开箱即用,默认配置就能很好地工作
- 只有在需要针对特定应用进行微调时才需要调整参数
4.3.2 工作原理 #

4.3.3 参数说明 #
RecursiveCharacterTextSplitter 的主要参数:
chunk_size(块大小):
- 每个块的最大大小
- 大小由
length_function决定(默认是字符数) - 建议值:200-1000 字符,取决于你的模型限制
chunk_overlap(块重叠):
- 相邻块之间的重叠大小
- 有助于在上下文被分割到不同块时减轻信息丢失
- 建议值:chunk_size 的 10-20%
length_function(长度函数):
- 用于确定块大小的函数
- 默认是
len(字符数) - 也可以使用 token 计数函数
is_separator_regex(分隔符是否为正则表达式):
- 默认为
False - 如果设置为
True,分隔符列表会被解释为正则表达式
separators(分隔符列表):
- 默认是
["\n\n", "\n", " ", ""] - 按照从粗粒度到细粒度的顺序使用
- 可以自定义分隔符列表

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_Ke0gA9kPvpxK57xEEXtEijinbGbJZGUCNsknqSDnuA5.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 的优势:
- 简单易用:API 简洁,上手快
- 轻量级:可以本地运行,无需复杂的服务器配置
- 自动向量化:内置向量化模型,无需手动转换
- 快速检索:使用 HNSW 算法实现高效的相似度搜索