RAG(Retrieval-Augmented Generation,检索增强生成)技术:通过引入外部知识库,利用检索模块(Retriever)从大量文档中提取相关信息,并将这些信息传递给生成模块(Generator,一般是大模型),从而生成更准确且有用的回答。
核心思想在于通过检索与生成的有机结合,弥补大模型在处理领域问题和实时任务时的不足
解决的问题:大模型在知识、理解、推理方面展现了卓越的能力,然而又很多无法忽视的局限性:
RAG:通过将非参数化的外部数据库、文档与大模型结合,使大模型在生成内容前,检索相关信息,以弥补模型在知识专业性和时效性上的不足,减少生成不确定性,在确保数据安全的同时,充分利用领域知识和私有数据。
非参数化的理解:训练模型时,训练数据在训练过程中转化为模型参数,而非参数化,就是不将外部文档等作为训练数据,而是保持独立,让大模型检索
为什么要使用 rag,直接将所有的知识库交给大模型处理不行吗(一股脑全扔给大模型):模型能够处理的 token 数有限,输入过多 token 会增加成本,而且提供少量相关的关键信息能带来更优质的回答
rag 和微调的选择:RAG 更适用于需要动态响应、频繁更新外部知识的场景,而微调则适合固定领域内的深度优化与推理。当应用场景中既需要利用最新的外部知识,又需要保持高水平的领域推理能力时,可以考虑结合使用 RAG 和微调,以实现最优的性能和效果。

RAG 的标准流程由三个节点组成:
索引包含四个关键步骤:
检索是连接用户查询与知识库的核心环节。
将检索到的相关文本块与用户的原始查询整合为增强提示词(Prompt),并输入到大语言模型(LLM)中。LLM 基于这些输入生成最终的回答,确保生成内容既符合用户的查询意图,又充分利用了检索到的上下文信息,使得回答更加准确和相关,充分使用到知识库中的知识。通过这一过程,RAG 实现了具备领域知识和私有信息的精确内容生成。
技术选型如下所示:
| 具体内容 | 技术选型 | 描述 |
|---|---|---|
| 操作系统 | Linux | |
| 编程语言 | Python | python >= 3.8 |
| RAG 技术框架 | LangChain | LLM 开发框架 |
| 索引 - 文档解析模块 | pypdf | 用于处理 pdf 文档 |
| 索引 - 文档分块模块 | RecursiveCharacterTextSplitter | ClangChain 默认的文本分割器,该分割器通过层次化的分隔符(从双换行符到单字符)拆分文本,旨在保持文本的结构和连贯性,优先考虑自然边界如段落和句子。 |
| 索引/检索 - 向量库 | Faiss | 全称 Facebook AI Similarity Search,由 Facebook AI Research 团队开源的向量库,因其稳定性和高效性在向量检索领域广受欢迎。 |
| 大模型 | Qwen | 阿里旗下大模型 |
技术选型流程图:

这里用到了 bge 模型,用于向量嵌入,[[bge 模型]]
python3 -m venv rag_env # 创建名为 rag_env 的虚拟环境
source rag_env/bin/activate # 激活虚拟环境
pip install langchain langchain_community pypdf sentence-transformers faiss-cpu dashscope
git clone https://gitee.com/techleadcy/rag_app.git
仅仅简单的走通了从索引、检索、由千问大模型生成回答的核心流程
from langchain_community.document_loaders import PyPDFLoader # PDF 文档提取
from langchain_text_splitters import RecursiveCharacterTextSplitter # 文档拆分 chunk
from sentence_transformers import SentenceTransformer # 加载和使用 Embedding 模型
import faiss # Faiss 向量库
import numpy as np # 处理嵌入向量数据,用于 Faiss 向量检索
import dashscope #调用 Qwen 大模型
from http import HTTPStatus #检查与 Qwen 模型 HTTP 请求状态
import os # 引入操作系统库,后续配置环境变量与获得当前文件路径使用
os.environ["TOKENIZERS_PARALLELISM"] = "false" # 不使用分词并行化操作, 避免多线程或多进程环境中运行多个模型引发冲突或死锁
# 设置 Qwen 系列具体模型及对应的调用 API 密钥,从阿里云百炼大模型服务平台获得
qwen_model = "qwen-turbo"
qwen_api_key = "sk-6fa20792379a46faa269a17d5015a4c8"
def load_embedding_model():
"""
加载 bge-small-zh-v1.5 模型
:return: 返回加载的 bge-small-zh-v1.5 模型
"""
print(f"加载 Embedding 模型中")
# SentenceTransformer 读取绝对路径下的 bge-small-zh-v1.5 模型,非下载
embedding_model = SentenceTransformer(os.path.abspath('rag_app/bge-small-zh-v1.5'))
print(f"bge-small-zh-v1.5 模型最大输入长度: {embedding_model.max_seq_length}")
return embedding_model
以上代码导入了我们在 RAG 流程中需要使用的核心模块及大模型参数等配置,这些模块和配置将在后续的索引、检索和生成流程中调用使用。
def indexing_process(pdf_file,embedding_model):
"""
索引流程:加载 PDF 文件,并将其内容分割成小块,计算这些小块的嵌入向量并将其存储在 FAISS 向量数据库中。
:param pdf_file:PDF 文件路径
:param embedding_model: 预加载的嵌入模型
:return: 返回 FAISS 嵌入向量索引和分割后的文本块原始内容列表
"""
# PyPDFLoader 加载 PDF 文件,忽略图片提取
pdf_loader = PyPDFLoader(pdf_file,extract_images=False)
# 配置 RecursiveCharacterTextSplitter 分割文本块库参数,每个文本块的大小为 768 字符(非 token),相邻文本块之间的重叠 256 字符(非 token)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=512,chunk_overlap=128
)
# 加载 PDF 文档,提取所有页的文本内容
pdf_content_list = pdf_loader.load()
# 将每页的文本内容用换行符连接,合并为 PDF 文档的完整文本
pdf_text = "\n".join([page.page_content for page in pdf_content_list])
print(f"PDF 文档的总字符数: {len(pdf_text)}")
# 将 PDF 文档文本分割成文本块 Chunk
chunks = text_splitter.split_text(pdf_text)
print(f"分割的文本 Chunk 数量: {len(chunks)}")
# 文本块转化为嵌入向量列表,normalize_embeddings 表示对嵌入向量进行归一化,用于准确计算相似度
embeddings = []
for chunk in chunks:
embedding = embedding_model.encode(chunk,normalize_embeddings=True)
embeddings.append(embedding)
print("文本块 Chunk 转化为嵌入向量完成")
# 将嵌入向量列表转化为 numpy 数组,FAISS 索引操作需要 numpy 数组输入
embeddings_np = np.array(embeddings)
# 获取嵌入向量的维度(每个向量的长度)
dimension = embeddings_np.shape[1]
# 使用余弦相似度创建 FAISS 索引
index = faiss.IndexFlatIP(dimension)
# 将所有的嵌入向量添加到 FAISS 索引中,后续可以用来进行相似性检索
index.add(embeddings_np)
print("索引过程完成.")
return index,chunks
上述代码实现了 RAG 技术中的索引流程,首先使用 PyPDFLoader 加载并预处理 PDF 文档,将其内容提取并合并为完整文本。接着,利用 RecursiveCharacterTextSplitter 将文本分割为每块 512 字符(非 token)、重叠 128 字符(非 token)的文本块,并通过预加载的 bge-small-zh-v1.5 嵌入模型将这些文本块转化为归一化的嵌入向量。最终,这些嵌入向量被存储在基于余弦相似度的 Faiss 向量库中,以支持后续的相似性检索和生成任务。
为更清晰地展示 RAG 流程的各个细节,当前代码未涉及多文档处理、嵌入模型的效率优化与并行处理。此外,Faiss 向量目前仅在内存中存储,未考虑持久化存储问题。以及文档解析、文本分块、嵌入模型与向量库的技术选型,后续课程将逐步深入探讨,并以上述代码作为基础,持续优化。
def retrieval_process(query,index,chunks,embedding_model,top_k=3):
"""
检索流程:将用户查询 Query 转化为嵌入向量,并在 Faiss 索引中检索最相似的前 k 个文本块。
:param query: 用户查询语句
:param index: 已建立的 Faiss 向量索引
:param chunks: 原始文本块内容列表
:param embedding_model: 预加载的嵌入模型
:param top_k: 返回最相似的前 K 个结果
:return: 返回最相似的文本块及其相似度得分
"""
# 将查询转化为嵌入向量,normalize_embeddings 表示对嵌入向量进行归一化
query_embedding = embedding_model.encode(query,normalize_embeddings=True)
# 将嵌入向量转化为 numpy 数组,Faiss 索引操作需要 numpy 数组输入
query_embedding = np.array([query_embedding])
# 在 Faiss 索引中使用 query_embedding 进行搜索,检索出最相似的前 top_k 个结果。
# 返回查询向量与每个返回结果之间的相似度得分(在使用余弦相似度时,值越大越相似)排名列表 distances,最相似的 top_k 个文本块在原始 chunks 列表中的索引 indices。
distances,indices = index.search(query_embedding,top_k)
print(f"查询语句: {query}")
print(f"最相似的前{top_k}个文本块:")
# 输出查询出的 top_k 个文本块及其相似度得分
results = []
for i in range(top_k):
# 获取相似文本块的原始内容
result_chunk = chunks[indices[0][i]]
print(f"文本块 {i}:\n{result_chunk}")
# 获取相似文本块的相似度得分
result_distance = distances[0][i]
print(f"相似度得分: {result_distance}\n")
# 将相似文本块存储在结果列表中
results.append(result_chunk)
print("检索过程完成.")
return results
上述代码实现了 RAG 技术中的检索流程。首先,用户的查询(Query)被预加载的 bge-small-zh-v1.5 嵌入模型转化为归一化的嵌入向量,进一步转换为 numpy 数组以适配 Faiss 向量库的输入格式。然后,利用 Faiss 向量库中的向量检索功能,计算查询向量与存储向量之间的余弦相似度,从而筛选出与查询最相似的前 top_k 个文本块。这些文本块及其相应的相似度得分被逐一输出,相似文本块存储在结果列表中,最终返回供后续生成过程使用。
def generate_process(query,chunks):
"""
生成流程:调用 Qwen 大模型云端 API,根据查询和文本块生成最终回复。
:param query: 用户查询语句
:param chunks: 从检索过程中获得的相关文本块上下文 chunks
:return: 返回生成的响应内容
"""
# 设置 Qwen 系列具体模型及对应的调用 API 密钥,从阿里云大模型服务平台百炼获得
llm_model = qwen_model
dashscope.api_key = qwen_api_key
# 构建参考文档内容,格式为“参考文档 1: \n 参考文档 2: \n ...”等
context = ""
for i,chunk in enumerate(chunks):
context += f"参考文档{i+1}: \n{chunk}\n\n"
# 构建生成模型所需的 Prompt,包含用户查询和检索到的上下文
prompt = f"根据参考文档回答问题:{query}\n\n{context}"
print("###############################################################")
print(f"生成模型的 Prompt: {prompt}")
print("###############################################################")
# 准备请求消息,将 prompt 作为输入
messages = [{'role': 'user', 'content':prompt}]
# 调用大模型 API 云服务生成响应
try:
responses = dashscope.Generation.call(
model = llm_model,
messages=messages,
result_format='message', # 设置返回格式为"message"
stream=True, # 启用流式输出
incremental_output=True # 获取流式增量输出
)
# 初始化变量以存储生成的响应内容
generated_response = ""
print("生成过程开始:")
# 逐步获取和处理模型的增量输出
for response in responses:
if response.status_code == HTTPStatus.OK:
content = response.output.choices[0]['message']['content']
generated_response += content
print(content,end='') # 实时输出模型生成的内容
else:
print(f"请求失败: {response.status_code} - {response.message}")
return None # 请求失败时返回 None
print("\n 生成过程完成.")
return generated_response
except Exception as e:
print(f"大模型生成过程中发生错误: {e}")
return None
上述代码实现了 RAG 流程中的生成过程。首先,结合用户查询与检索到的文本块内容组织成大模型提示词(Prompt)。随后,代码通过调用 Qwen 大模型云端 API,将构建好的 Prompt 发送给大模型,并利用流式输出的方式逐步获取模型生成的响应内容,实时输出并汇总为最终的生成结果。
这里相当于是把通过 RAG 检索出来的与用户问题相似度高的 chunk,拼接到了 prompt 中
def main():
print("RAG 过程开始.")
query="下面报告中涉及了哪几个行业的案例以及总结各自面临的挑战?"
embedding_model = load_embedding_model()
# 索引流程:加载 PDF 文件,分割文本块,计算嵌入向量,存储在 FAISS 索引中(内存)
index,chunks = indexing_process('rag_app/test_lesson2.pdf',embedding_model)
# 检索流程:将用户查询转化为嵌入向量,检索最相似的文本块
retrieval_chunks = retrieval_process(query,index,chunks,embedding_model)
# 生成流程:调用 Qwen 大模型生成响应
generate_process(query,retrieval_chunks)
print("RAG 过程结束.")
if __name__ == "__main__":
main()
source rag_env/bin/activate # 激活虚拟环境
python rag_app/rag_app_lesson2.py # **执行 RAG 应用脚本**
测试代码通过 main() 函数串联各个步骤,从索引到生成,确保 RAG 的各个环节顺畅执行,准确完成“下面报告中涉及了哪几个行业的案例以及总结各自面临的挑战?”的 RAG 问答任务。
运行结果:
RAG 过程开始.
加载 Embedding 模型中
bge-small-zh-v1.5 模型最大输入长度 token:512
PDF 文档的总字符数:9135
分割的文本 Chunk 数量:24
文本块 Chunk 转化为嵌入向量完成
索引过程完成.
查询语句: 下面报告中涉及了哪几个行业的案例以及总结各自面临的挑战?
最相似的前 3 个文本块:
文本块 0: 面的数字化转型。2.3.2 面临的挑战:......相似度得分:0.5915016531944275
文本块 1: ...... 相似度得分:0.5728524327278137
文本块 2: ...... 相似度得分:0.5637902617454529
检索过程完成.
生成模型的 Prompt: 根据参考文档回答问题:下面报告中涉及了哪几个行业的案例以及总结各自面临的挑战?
参考文档 1: 面的数字化转型。2.3.2 面临的挑战...
参考文档 2: ......
参考文档 3: ......
生成过程开始:
参考文档中涉及了三个行业的案例及其面临的挑战:
1.### 制造业...... 2. ### 零售业...... 3. ### 金融业......
### 数字化转型解决方案概述
-**制造业**:...... -**零售业**:...... -**金融业**:......
这些案例强调了不同行业在数字化转型过程中面临的独特挑战......
生成过程完成.
RAG 过程结束.
选用适合业务场景的支持多格式、多版式、高精度、高效率的文档解析技术,是构建成功 RAG 系统的基础。

RAG 系统的应用场景主要集中在专业领域和企业场景。这些场景中,除了关系型和非关系型数据库,更多的数据以 PDF、TXT、Word、PPT、Excel、CSV、Markdown、XML、HTML 等多种格式存储。尤其是 PDF 文件,凭借其统一的排版和多样化的结构形式,成为了最为常见的文档数据存储与交换格式。文档解析技术不仅需要支持上述所有常见格式,还需要特别强化对于 PDF 的解析能力,包括对电子档和扫描档的处理,支持多种版面形式的解析、不同类型版面元素的识别,并能够还原正确的阅读顺序。
此外,由于 PDF 文档往往篇幅巨大、页数众多,且企业及专业领域 PDF 文件数据量庞大,因此文档解析技术还需具备极高的处理性能,以确保知识库的高效构建和实时更新。
LangChain 提供了一套功能强大的文档加载器(Document Loaders),涵盖 PDF、TXT、Word、PPT、Excel、CSV、Markdown、XML 和 HTML 格式,帮助开发者轻松地将数据源中的内容加载为文档对象。
LangChain 定义了 BaseLoader 类和 Document 类,其中 BaseLoader 类负责定义如何从不同数据源加载文档,而 Document 类则统一描述了不同文档类型的元数据。
开发者可以基于 BaseLoader 类为特定数据源创建自定义加载器,并将其内容加载为 Document 对象。使用预构建的加载器比自行编写更加便捷。例如,PyPDF 加载器能够处理 PDF 文件,将多页文档分解为独立的、可分析的单元,并附带内容及诸如源信息、页码等重要元数据。
langchain_community 是 LangChain 与常用第三方库相结合的拓展库。各类开源库和企业库基于 BaseLoader 类在 langchain_community 库中扩展了不同文档类型的加载器,这些加载器被归类于 langchain_community.document_loaders 模块中。每个加载器都可以输入对应的参数,如指定文档解析编码、解析特定元素等,以及对 Document 类进行提取或检索等操作。目前,已有超过 160 种数据加载器,覆盖了本地文件、云端文件、数据库、互联网平台、Web 服务等多种数据源。详情可以在 LangChain 官网查看。

Document Loader 模块是封装好的各种文档解析库集成 SDK,项目中使用还需要安装对应的文档解析库。例如,当我们项目中使用 from langchain_community.document_loaders import PDFPlumberLoader 时,需要先通过命令行 pip install pdfplumber 安装 pdfplumber 库。某些特殊情况下,还需要额外的依赖库,比如使用 UnstructuredMarkdownLoader 时,需要安装 unstructured 库来提供底层文档解析,还需要 markdown 库来支持 Markdown 文档格式更多能力。此外,对于像 .doc 这种早期的文档类型,还需要安装 libreoffice 软件库才能进行解析。
实际研发场景中,使用 Document Loader 文档加载器模块时,需要根据具体的业务需求编写自定义的文档后处理逻辑。针对业务需求,开发者可以自行编写和实现对不同文档内容的解析,例如对标题、段落、表格、图片等元素的特殊处理。在本课程的案例中,我们将从 Document 类中提取所有文本内容,进行下一步的文档分块处理。

按文档加载器的使用:
source rag_env/bin/activate # 激活虚拟环境
pip install unstructured pdfplumber python-docx python-pptx markdown openpyxl pandas
安装 .doc 文件的支持软件 LibreOffice:
sudo apt-get install libreoffice # Linux 系统执行这条指令
from langchain_community.document_loaders import(
PDFPlumberLoader,
TextLoader,
UnstructuredWordDocumentLoader,
UnstructuredPowerPointLoader,
UnstructuredExcelLoader,
CSVLoader,
UnstructuredMarkdownLoader,
UnstructuredXMLLoader,
UnstructuredHTMLLoader,
) # 从 langchain_community.document_loaders 模块中导入各种类型文档加载器类
def load_document(file_path):
"""
解析各种文档格式的文件,返回文档内容字符串
:param file_path: 文档文件路径
:return: 返回文档内容的字符串
"""
# 定义文档解析加载器字典,根据文档类型选择对应的文档解析加载器类和输入参数
DOCUMENT_LOADER_MAPPING = {
".pdf": (PDFPlumberLoader, {}),
".txt": (TextLoader, {"encoding": "utf8"}),
".doc": (UnstructuredWordDocumentLoader, {}),
".docx": (UnstructuredWordDocumentLoader, {}),
".ppt": (UnstructuredPowerPointLoader, {}),
".pptx": (UnstructuredPowerPointLoader, {}),
".xlsx": (UnstructuredExcelLoader, {}),
".csv": (CSVLoader, {}),
".md": (UnstructuredMarkdownLoader, {}),
".xml": (UnstructuredXMLLoader, {}),
".html": (UnstructuredHTMLLoader, {}),
}
ext = os.path.splitext(file_path)[1] # 获取文件扩展名,确定文档类型
loader_tuple = DOCUMENT_LOADER_MAPPING.get(ext) # 获取文档对应的文档解析加载器类和参数元组
if loader_tuple: # 判断文档格式是否在加载器支持范围
loader_class,loader_args = loader_tuple # 解包元组,获取文档解析加载器类和参数
loader = loader_class(file_path, **loader_args) # 创建文档解析加载器实例,并传入文档文件路径
documents = loader.load() # 加载文档
content = "\n".join([doc.page_content for doc in documents]) # 多页文档内容组合为字符串
print(f"文档 {file_path} 的部分内容为: {content[:100]}...") # 仅用来展示文档内容的前 100 个字符
return content # 返回文档内容的多页拼合字符串
print(file_path+f",不支持的文档类型: '{ext}'") # 若文件格式不支持,输出信息,返回空字符串。
return""
上述代码实现了解析多种文档格式并返回文档内容的字符串的方法 load_document。函数通过检查文件的扩展名 ext,动态选择合适的文档加载器 Document Loader,使用相应的加载器调用对应库读取文档内容 documents。支持的文档格式与对应的加载器类和参数在字典 DOCUMENT_LOADER_MAPPING 中进行了映射。根据文件的扩展名,函数会实例化对应的加载器,并将文档内容加载为字符串 content,支持多页文档的合并处理。
def indexing_process(folder_path,embedding_model):
"""
索引流程:加载文件夹中的所有文档文件,并将其内容分割成文档块,计算这些小块的嵌入向量并将其存储在 Faiss 向量数据库中。
:param folder_path: 文档文件夹路径
:param embedding_model: 预加载的嵌入模型
:return: 返回 Faiss 嵌入向量索引和分割后的文本块原始内容列表
"""
# 初始化空的 chunks 列表,用于存储所有文档文件的文本块
all_chunks = []
# 遍历文件夹中的所有文档文件
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path,filename)
# 检查是否为文件
if os.path.isfile(file_path):
# 解析文档文件,获得文档字符串内容
document_text = load_document(file_path)
print(f"文档 {filename} 的总字符数: {len(document_text)}")
# 配置 RecursiveCharacterTextSplitter 分割文本块库参数,每个文本块的大小为 512 字符(非 token),相邻文本块之间的重叠 128 字符(非 token)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=512,chunk_overlap=128
)
# 将文档文本分割成文本块 Chunk
chunks = text_splitter.split_text(document_text)
print(f"文档 {filename} 分割的文本 Chunk 数量: {len(chunks)}")
# 将分割的文本块添加到总 chunks 列表中
all_chunks.extend(chunks)
# 文本块转化为嵌入向量列表,normalize_embeddings 表示对嵌入向量进行归一化,用于准确计算相似度
embeddings = []
for chunk in all_chunks:
embedding = embedding_model.encode(chunk,normalize_embeddings=True)
embeddings.append(embedding)
print("所有文本块 Chunk 转化为嵌入向量完成")
# 将嵌入向量列表转化为 numpy 数组,FAISS 索引操作需要 numpy 数组输入
embeddings_np = np.array(embeddings)
# 获取嵌入向量的维度(每个向量的长度)
dimension = embeddings_np.shape[1]
# 使用余弦相似度创建 FAISS 索引
index = faiss.IndexFlatIP(dimension)
# 将所有的嵌入向量添加到 FAISS 索引中,后续可以用来进行相似性检索
index.add(embeddings_np)
print("索引过程完成.")
return index,all_chunks
上述代码是在上一讲的 indexing_process 函数基础上进行了迭代,新增了批量处理多种格式文档文件的功能。调整部分为函数遍历文件夹中的所有文档文件,通过调用 load_document 获取文档的字符串内容,并将其切分为文本块 chunks,然后将所有文档的 chunks 汇总到一个总列表 all_chunks 中。当前的实现主要聚焦于文档解析技术,chunks 和对应的嵌入向量 index 暂时存储在内存中,持久化存储的部分将在后续的向量库课程中详细讲解。
关于 pdf 的解析

PDF 文件的显示效果不受设备、软件或系统的影响,但对计算机而言,它是一种非数据结构化的格式,储存的信息无法直接被理解。此外,大模型的训练数据中不包含直接的 PDF 文件,无法直接理解。
PDF 文件分为电子版和扫描版,电子版基于规则来解析,而扫描版通过深度学习来解析
文档数据(Documents)经过解析后,通过分块技术将信息内容划分为适当大小的文档片段(chunks),从而使 RAG 系统能够高效处理和精准检索这些片段信息。分块的本质在于依据一定逻辑或语义原则,将较长文本拆解为更小的单元。分块策略有多种,各有侧重,选择适合特定场景的分块策略是提升 RAG 系统召回率的关键。
文档通常包含丰富的上下文信息和复杂的语义结构,通过将文档分块,模型可以更有效地提取关键信息,并减少不相关内容的干扰。分块的目标在于确保每个片段在保留核心语义的同时,具备相对独立的语义完整性,从而使模型在处理时不必依赖广泛的上下文信息,增强检索召回的准确性。
分块的重要性在于它直接影响 RAG 系统的生成质量。首先,合理的分块能够确保检索到的片段与用户查询信息高度匹配,避免信息冗余或丢失。其次,分块有助于提升生成内容的连贯性,精心设计的独立语义片段可以降低模型对上下文的依赖,从而增强生成的逻辑性与一致性。最后,分块策略的选择还会影响系统的响应速度与效率,模型能够更快、更准确地处理和生成内容。
嵌入模型(Embedding Model)负责将文本数据映射到高维向量空间中,将输入的文档片段转换为对应的嵌入向量(embedding vectors)。这些向量捕捉了文本的语义信息,并被存储在向量库(VectorStore)中,以便后续检索使用。用户查询(Query)同样通过嵌入模型的处理生成查询嵌入向量,这些向量用于在向量数据库中通过向量检索(Vector Retrieval)匹配最相似的文档片段。根据不同的场景需求,评估并选择最优的嵌入模型,以确保 RAG 的检索性能符合要求。

分块策略最大的挑战在于确定分块的大小。如果片段过大,可能导致向量无法精确捕捉内容的特定细节并且计算成本增加;若片段过小,则可能丢失上下文信息,导致句子碎片化和语义不连贯。较小的块适用于需要细粒度分析的任务,例如情感分析,能够精确捕捉特定短语或句子的细节。更大的块则更为合适需要保留更广泛上下文的场景,例如文档摘要或主题检测。因此,块大小的确定必须在计算效率和上下文信息之间取得平衡。
分块策略应该按照应用场景具体决定
多种分块策略从本质上来看,由以下三个关键组成部分构成:

分块策略:
langchain 提供了多种分块方法,在 langchain_text_splitters 库中对应的具体方法类如下:

分块策略具体实现:
SpacyTextSplitter 和 NLTKTextSplitter 需要额外安装 Python 依赖库,其中 SpacyTextSplitter 还需要按照文档的语言对应安装额外的语言模型。
source rag_env/bin/activate # 激活虚拟环境
pip install spacy nltk -i https://pypi.tuna.tsinghua.edu.cn/simple
python -m spacy download zh_core_web_sm # 如果需要进行中文分块,安装 spacy 中文语言模型
python -m spacy download en_core_web_sm # 如果需要进行英文分块,安装 spacy 英文语言模型
导入 langchain.text_splitter 中各种文档分块类代码:
from langchain.text_splitter import(
CharacterTextSplitter,
RecursiveCharacterTextSplitter,
MarkdownTextSplitter,
PythonCodeTextSplitter,
LatexTextSplitter,
SpacyTextSplitter,
NLTKTextSplitter
) # 从 langchain.text_splitter 模块中导入各种文档分块类
indexing_process 方法中切分文本块库代码:
# 配置 SpacyTextSplitter 分割文本块库
#text_splitter = SpacyTextSplitter(
# chunk_size=512,chunk_overlap=128,pipeline="zh_core_web_sm")
# 配置 RecursiveCharacterTextSplitter 分割文本块
# 可以更换为 CharacterTextSplitter、MarkdownTextSplitter、PythonCodeTextSplitter、LatexTextSplitter、NLTKTextSplitter 等
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=512,chunk_overlap=128)
CharacterTextSplitter、RecursiveCharacterTextSplitter、MarkdownTextSplitter、PythonCodeTextSplitter、LatexTextSplitter、NLTKTextSplitter 替换原有 text_splitter 参数的赋值类即可。需要额外处理的是 SpacyTextSplitter,需要参数 pipeline 指定具体的语言模型才可以运行。
Embedding 嵌入是指将文本、图像、音频、视频等形式的信息映射为高维空间中的密集向量表示。这些向量在语义空间中起到坐标的作用,捕捉对象之间的语义关系和隐含的意义。通过在向量空间中进行计算(例如余弦相似度),可以量化和衡量这些对象之间的语义相似性。

向量检索(Vector Retrieval)是一种基于向量表示的搜索技术,通过计算查询向量与已知文本向量的相似度来识别最相关的文本数据。向量检索的高效性在于,它能在大规模数据集中快速、准确地找到与查询最相关的内容,这得益于向量表示中蕴含的丰富语义信息。
嵌入模型
自 2013 年以来,word2vec、GloVe、fastText 等嵌入模型通过分析大量文本数据,学习得出单词的嵌入向量。近年来,随着 transformer 模型的突破,嵌入技术以惊人的速度发展。BERT、RoBERTa、ELECTRA 等模型将词嵌入推进到上下文敏感的阶段。这些模型在为文本中的每个单词生成嵌入时,会充分考虑其上下文环境,因此同一个单词在不同语境下的嵌入向量可以有所不同,从而大大提升了模型理解复杂语言结构的能力。

在 RAG 流程中,文档首先被分割成多个片段,每个片段随后通过 Embedding Model 进行嵌入处理。生成的文档嵌入向量被存储在 VectorStore 中,供后续检索使用。用户查询会通过 Embedding Model 转换为查询嵌入向量,这些向量用于在向量数据库中匹配最相似的文档片段,最终组合生成指令(Prompt),大模型生成回答。
嵌入模型如何选择
嵌入模型评估:

在选择适合的嵌入模型时,需要综合考虑多个因素,包括特定领域的适用性、检索精度、支持的语言、文本块长度、模型大小以及检索效率等因素。同时以广泛受到认可的 MTEB(Massive Text Embedding Benchmark)和 C-MTEB(Chinese Massive Text Embedding Benchmark)榜单作为参考,通过涵盖分类、聚类、语义文本相似性、重排序和检索等多个数据集的评测,开发者可以根据不同任务的需求,评估并选择最优的向量模型,以确保在特定应用场景中的最佳性能。
榜单如下:

榜单每日更新,切换语言为 Chinese,可以看到中文嵌入模型的排名。由于 RAG 是一项检索任务,我们需要按“Retrieval Average”(检索平均值)列对排行榜进行排序,图中显示的就是检索任务效果排序后的结果。在检索任务中,我们需要在榜单顶部看到最佳的检索模型,并且专注于以下几个关键列:
嵌入模型代码实战
这里使用 SentenceTransformers 作为加载嵌入模型的 Python 模块。
SentenceTransformers(又名 SBERT)是一个用于训练和推理文本嵌入模型的 Python 模块,可以在 RAG 系统中计算嵌入向量。使用 SentenceTransformers 进行文本嵌入转换非常简单:只需导入模块库、加载模型,并调用 encode 方法即可。执行时,SentenceTransformers 会自动下载相应的模型库,当然也可以手动下载并指定模型库的路径。所有可用的模型都可以在 SentenceTransformers 模型库 查看,超过 8000 个发布在 Hugging Face 上的嵌入模型库可以被使用。
在中文领域,智源研究院的 BGE 系列模型 是较为知名的开源嵌入模型,在 C-MTEB 上表现出色。BGE 系列目前包含 23 个嵌入模型,涵盖多种维度、多种最大 Token 数和模型大小,用户可以根据需求进行测试和使用。
load_embedding_model 方法中使用 SentenceTransformer 加载嵌入模型代码:
# 绝对路径:SentenceTransformer 读取绝对路径下的 bge-small-zh-v1.5 模型,如需使用其他模型,下载其他模型,并且更换绝对路径即可
embedding_model = SentenceTransformer(os.path.abspath('rag_app/bge-small-zh-v1.5'))
# 自动下载:SentenceTransformer 库自动下载 BAAI/bge-large-zh-v1.5 模型,如需下载其他模型,输入其他模型名称即可
# embedding_model = SentenceTransformer('BAAI/bge-large-zh-v1.5')
indexing_process 方法中将文本转化为嵌入向量代码:
# 文本块转化为嵌入向量列表,normalize_embeddings 表示对嵌入向量进行归一化,用于后续流程准确计算向量相似度
embeddings = []
for chunk in all_chunks:
embedding = embedding_model.encode(chunk,normalize_embeddings=True)
embeddings.append(embedding)
用于存储嵌入向量及文档元数据,并高效地进行相似性检索
根据是否开源以及是否为专用向量库,分为:

向量数据库的核心在于其能够基于向量之间的相似性,快速、精确地定位和检索数据。这类数据库不仅为向量嵌入提供了优化的存储和查询功能,同时也继承了传统数据库的诸多优势,如性能、可扩展性和灵活性,满足了充分利用大规模数据的需求。相比之下,传统的基于标量的数据库由于无法应对数据复杂性和规模化处理的挑战,难以有效提取洞察并实现实时分析。
存储:
向量数据库的存储通过建立索引,来加速查找,常用算法:
搜索:
向量数据库的搜索机制不是追求精确匹配,而是通过近似最近邻(ANN)算法在速度与准确性之间找到最佳平衡。ANN 算法通过允许一定程度的误差,在显著提高搜索速度的同时,依然能够找到与查询相似度较高的向量。这种策略对于需要实时、高精度响应的应用场景尤为重要。
常见的向量搜索方法:
向量数据库的工作流程涵盖了从数据处理、向量化、向量存储、向量索引到最终检索的全链条操作,确保在复杂的数据环境中实现高效的存储、索引和相似性搜索。
在 RAG 中,向量数据库主要用于存储向量、建立索引以及检索的过程
常用向量数据库:

根据上面所示特点,
向量数据库代码实战——以 Chroma 为例:
Chroma 是一种简单且易于持久化的向量数据库,它以轻量级、开箱即用的特性著称。Chroma 支持内存中操作和磁盘持久化,能够高效地管理和查询向量数据,非常适合快速集成和开发。其设计简洁且不需要复杂的配置,使开发者能够专注于核心功能的实现而无需担心底层存储的复杂性。
安装依赖:
pip install -U pip chromadb langchain langchain_community sentence-transformers dashscope unstructured pdfplumber python-docx python-pptx markdown openpyxl pandas -i https://pypi.tuna.tsinghua.edu.cn/simple
有部分依赖已经安装
代码主要改动内容:
具体改动:
import chromadb # 引入 Chroma 向量数据库
import uuid # 生成唯一 ID
import shutil # 文件操作模块,为了避免既往数据的干扰,在每次启动时清空 ChromaDB 存储目录中的文件
def main():
print("RAG 过程开始.")
# 为了避免既往数据的干扰,在每次启动时清空 ChromaDB 存储目录中的文件
chroma_db_path = os.path.abspath("rag_app/chroma_db")
if os.path.exists(chroma_db_path):
shutil.rmtree(chroma_db_path)
# 创建 ChromaDB 本地存储实例和 collection
client = chromadb.PersistentClient(chroma_db_path)
collection = client.get_or_create_collection(name="documents")
embedding_model = load_embedding_model()
indexing_process('rag_app/data_lesson5',embedding_model,collection)
query = "下面报告中涉及了哪几个行业的案例以及总结各自面临的挑战?"
retrieval_chunks = retrieval_process(query,collection,embedding_model)
generate_process(query,retrieval_chunks)
print("RAG 过程结束.")
def indexing_process(folder_path,embedding_model,collection):
all_chunks = []
all_ids = []
for filename in os.listdir(folder_path):
file_path = os.path.join(folder_path,filename)
if os.path.isfile(file_path):
document_text = load_document(file_path)
if document_text:
print(f"文档 {filename} 的总字符数: {len(document_text)}")
text_splitter = RecursiveCharacterTextSplitter(chunk_size=512,chunk_overlap=128)
chunks = text_splitter.split_text(document_text)
print(f"文档 {filename} 分割的文本 Chunk 数量: {len(chunks)}")
all_chunks.extend(chunks)
# 生成每个文本块对应的唯一 ID
all_ids.extend([str(uuid.uuid4())for _ in range(len(chunks))])
embeddings = [embedding_model.encode(chunk,normalize_embeddings=True).tolist()for chunk in all_chunks]
# 将文本块的 ID、嵌入向量和原始文本块内容添加到 ChromaDB 的 collection 中
collection.add(ids=all_ids,embeddings=embeddings,documents=all_chunks)
print("嵌入生成完成,向量数据库存储完成.")
print("索引过程完成.")
print("********************************************************")
retrieval_process 方法:
def retrieval_process(query,collection,embedding_model=None,top_k=6):
query_embedding = embedding_model.encode(query,normalize_embeddings=True).tolist()
# 使用向量数据库检索与 query 最相似的 top_k 个文本块
results = collection.query(query_embeddings=[query_embedding],n_results=top_k)
print(f"查询语句: {query}")
print(f"最相似的前{top_k}个文本块:")
retrieved_chunks = []
# 打印检索到的文本块 ID、相似度和文本块信息
for doc_id,doc,score in zip(results['ids'][0],results['documents'][0],results['distances'][0]):
print(f"文本块 ID: {doc_id}")
print(f"相似度: {score}")
print(f"文本块信息:\n{doc}\n")
retrieved_chunks.append(doc)
print("检索过程完成.")
print("********************************************************")
return retrieved_chunks
当前主流的 RAG 检索方式主要采用向量检索(Vector Search),通过语义相似度来匹配文本切块,然而,向量检索并非万能,某些场景下无法替代传统关键词检索的优势,如需要精确搜索某个订单 id、品牌名称等等
传统关键词检索的优势场景:

在上述案例中,虽然依靠关键词检索可以精确找到与“订单 12345”匹配的特定信息,但它无法提供与订单相关的更广泛上下文。另一方面,语义匹配虽然能够识别“订单”和“配送”等相关概念,但在处理具体的订单 ID 时,往往容易出错。
混合检索(Hybrid Search,也成为多路召回):通过结合关键词检索和语义匹配的优势,可以首先利用关键词检索精确定位到“订单 12345”的信息,然后通过语义匹配扩展与该订单相关的其他上下文或客户操作的信息,例如“12 开头的订单、包装破损严重”等。这样不仅能够获取精确的订单详情,还能获得与之相关的额外有用信息。
混合检索不限于关键词检索和向量匹配,还可以混合更多的检索方式,如下图:langchain 提供了检索器模块
langchain_community.retrievers,参考:retrievers — 🦜🔗 LangChain documentation

在 RAG 检索场景中,首要目标是确保最相关的结果能够出现在候选列表中。混合检索提供了一种更加全面精准的搜索方案。
仅具备混合检索的能力还不足以满足需求,检索到的见过还需要经过重排序,目的是将混合检索的结果进行整合,并将与用户问题语义最契合的结果排在前列。确保最符合用户意图和查询语义的结果优先展示,从而提升用户的搜索体验和结果的准确性。
RAG 流程有两个概念,粗排和精排。粗排检索效率较快,但是召回的内容并不一定强相关。而精排效率较低,因此适合在粗排的基础上进行进一步优化。精排的代表就是重排序(Reranking)。

在这个案例中,我们通过重排序技术成功找到了与问题语义最契合的结果。系统评分显示,“订单 12345 于 2023 年 8 月 15 日在上海,客户不满意。”与“该 12 开头的订单客户不满意的地方在于包装破损严重。”这两个文档块的相关性分别为 0.9 和 0.8,排序为第一和第二位。
重排序模型大多是基于双塔或交叉编码架构的模型,在此基础上进一步计算更精确的相关性分数,能够捕捉查询词与文档块之间更细致的相关性,从而在细节层面上提高检索精度。因此,尽管向量检索提供了有效的初步筛选,重排序模型则通过更深入的分析和排序,确保最终结果在语义和内容层面上更紧密地契合查询意图,实现了检索质量的提升。
总的来说,重排序技术的作用:
重排序模型:将查询与每个文档块之间计算对应的相关性分数,并根据这些分数对文档进行重新排序,确保文档按照从最相关到最不相关的顺序排列,并返回前 top-k 个结果。

与嵌入模型不同,重排序模型将用户的查询(Query)和文档块作为输入,直接输出相似度评分,而非生成嵌入向量。目前,市面上可用的重排序模型并不多,商用的有 Cohere,开源的有 BGE、Sentence、Mixedbread、T5-Reranker 等,甚至可以使用指令(Prompt)让大模型(GPT、Claude、通义千问、文心一言等)进行重排
在生产环境中使用重排序模型会面临资源和效率问题,包括计算资源消耗高、推理速度慢以及模型参数量大等问题。这些问题主要源于重排序模型在对候选项进行精细排序时,因其较大参数量而导致的高计算需求和复杂耗时的推理过程,从而对 RAG 系统的响应时间和整体效率产生负面影响。因此,在实际应用中,需要根据实际资源情况,在精度与效率之间进行平衡。
使用 rank_bm25 作为 RAG 项目的关键词搜索技术。BM25 是一种强大的关键词搜索算法,通过分析词频(TF)和逆向文档频率(IDF)来评估文档与查询的相关性。具体来说,BM25 检查查询词在文档中的出现频率,以及该词在所有文档中出现的稀有程度。如果一个词在特定文档中频繁出现,但在其他文档中较少见,那么 BM25 会将该文档评为高度相关。
此外,BM25 还通过调整文档长度的影响,防止因文档长度不同而导致的词频偏差。正是这种结合了词频和文档长度平衡的机制,使得 BM25 在关键词搜索中能够提供精准的检索结果,在 RAG 项目中尤为有效。
安装依赖库:
pip install -U pip jieba rank_bm25 chromadb langchain langchain_community sentence-transformers dashscope unstructured pdfplumber python-docx python-pptx markdown openpyxl pandas -i https://pypi.tuna.tsinghua.edu.cn/**simple**
代码中新增内容:
在此代码实现中,没有使用混合检索的 RRF(递归折减融合)排名,课程下半部分会对检索结果进行进一步的重排序,所以这节课直接返回了向量检索和 BM25 检索的结果,并按顺序合并,集中展示 BM25 关键词检索的代码实战。
具体代码改动:
from rank_bm25 import BM25Okapi # 从 rank_bm25 库中导入 BM25Okapi 类,用于实现 BM25 算法的检索功能
import jieba # 导入 jieba 库,用于对中文文本进行分词处理
def retrieval_process(query,collection,embedding_model=None,top_k=3):
query_embedding = embedding_model.encode(query,normalize_embeddings=True).tolist()
vector_results = collection.query(query_embeddings=[query_embedding],n_results=top_k)
# 从 Chroma collection 中提取所有文档
all_docs = collection.get()['documents']
# 对所有文档进行中文分词
tokenized_corpus = [list(jieba.cut(doc))for doc in all_docs]
# 使用分词后的文档集合实例化 BM25Okapi,对这些文档进行 BM25 检索的准备工作
bm25 = BM25Okapi(tokenized_corpus)
# 对查询语句进行分词处理,将分词结果存储为列表
tokenized_query = list(jieba.cut(query))
# 计算查询语句与每个文档的 BM25 得分,返回每个文档的相关性分数
bm25_scores = bm25.get_scores(tokenized_query)
# 获取 BM25 检索得分最高的前 top_k 个文档的索引
bm25_top_k_indices = sorted(range(len(bm25_scores)),key=lambda i:bm25_scores[i],reverse=True)[:top_k]
# 根据索引提取对应的文档内容
bm25_chunks = [all_docs[i] for i in bm25_top_k_indices]
# 打印 向量 检索结果
print(f"查询语句: {query}")
print(f"向量检索最相似的前 {top_k} 个文本块:")
vector_chunks = []
for rank, (doc_id,doc)in enumerate(zip(vector_results['ids'][0],vector_results['documents'][0])):
print(f"向量检索排名: {rank + 1}")
print(f"文本块 ID: {doc_id}")
print(f"文本块信息:\n{doc}\n")
vector_chunks.append(doc)
# 打印 BM25 检索结果
print(f"BM25 检索最相似的前 {top_k} 个文本块:")
for rank,doc in enumerate(bm25_chunks):
print(f"BM25 检索排名: {rank + 1}")
print(f"文档内容:\n{doc}\n")
# 合并结果,将 向量 检索的结果放在前面,然后是 BM25 检索的结果
combined_results = vector_chunks + bm25_chunks
print("检索过程完成.")
print("********************************************************")
# 返回合并后的全部结果,共 2*top_k 个文档块
return combined_results
在实战中,我们使用来自北京人工智能研究院 BGE 的 bge-reranker-v2-m3 作为 RAG 项目的重排序模型,这是一种轻量级的开源和多语言的重排序模型。更多模型相关信息参考huggingface.co
依赖安装:
pip install -U pip FlagEmbedding Peft jieba rank_bm25 chromadb langchain langchain_community sentence-transformers dashscope unstructured pdfplumber python-docx python-pptx markdown openpyxl pandas -i https://pypi.tuna.tsinghua.edu.cn/simple
主要增加内容:
具体代码改动:
from FlagEmbedding import FlagReranker # 用于对嵌入结果进行重新排序的工具类
def reranking(query,chunks,top_k=3):
# 初始化重排序模型,使用 BAAI/bge-reranker-v2-m3
reranker = FlagReranker('BAAI/bge-reranker-v2-m3',use_fp16=True)
# 构造输入对,每个 query 与 chunk 形成一对
input_pairs = [[query,chunk] for chunk in chunks]
# 计算每个 chunk 与 query 的语义相似性得分
scores = reranker.compute_score(input_pairs,normalize=True)
print("文档块重排序得分:",scores)
# 对得分进行排序并获取排名前 top_k 的 chunks
sorted_indices = sorted(range(len(scores)),key=lambda i:scores[i],reverse=True)
reranking_chunks = [chunks[i] for i in sorted_indices[:top_k]]
# 打印前三个 score 对应的文档块
for i in range(top_k):
print(f"重排序文档块{i+1}: 相似度得分:{scores[sorted_indices[i]]},文档块信息:{reranking_chunks[i]}\n")
return reranking_chunks
retrieval_process 方法:
# 使用重排序模型对检索结果进行重新排序,输出重排序后的前 top_k 文档块
reranking_chunks = reranking(query,vector_chunks + bm25_chunks,top_k)
print("检索过程完成.")
print("********************************************************")
# 返回重排序后的前 top_k 个文档块
return reranking_chunks
def retrieval_process(query,collection,embedding_model=None,top_k=6):
query_embedding = embedding_model.encode(query,normalize_embeddings=True).tolist()
vector_results = collection.query(query_embeddings=[query_embedding],n_results=top_k)
all_docs = collection.get()['documents']
tokenized_corpus = [list(jieba.cut(doc))for doc in all_docs]
bm25 = BM25Okapi(tokenized_corpus)
tokenized_query = list(jieba.cut(query))
bm25_scores = bm25.get_scores(tokenized_query)
bm25_top_k_indices = sorted(range(len(bm25_scores)),key=lambda i:bm25_scores[i],reverse=True)[:top_k]
bm25_chunks = [all_docs[i] for i in bm25_top_k_indices]
print(f"查询语句: {query}")
print(f"向量检索最相似的前 {top_k} 个文本块:")
vector_chunks = []
for rank, (doc_id,doc)in enumerate(zip(vector_results['ids'][0],vector_results['documents'][0])):
print(f"向量检索排名: {rank + 1}")
print(f"文本块 ID: {doc_id}")
print(f"文本块信息:\n{doc}\n")
vector_chunks.append(doc)
print(f"BM25 检索最相似的前 {top_k} 个文本块:")
for rank,doc in enumerate(bm25_chunks):
print(f"BM25 检索排名: {rank + 1}")
print(f"文档内容:\n{doc}\n")
# 使用重排序模型对检索结果进行重新排序,输出重排序后的前 top_k 文档块
reranking_chunks = reranking(query,vector_chunks + bm25_chunks,top_k)
print("检索过程完成.")
print("********************************************************")
# 返回重排序后的前 top_k 个文档块
return reranking_chunks
RAG 的本质是通过为大模型提供外部知识来增强其理解和回答领域问题的能力,类似于为大语言模型配备插件,使其能够结合外部知识作出更为精准和符合上下文的回答。大模型在 RAG 系统中起到大脑中枢的作用,尤其在面对复杂且多样化的 RAG 任务时,大模型的性能直接决定了整个系统的效果和响应质量,可以说大模型是整个系统的大脑。
一个 prompt 通常包含以下内容:

如何提升 prompt 的质量:
请根据上传的银行业报告,简洁总结当前的市场趋势,重点分析政策变化对行业的影响,输出为以下 Markdown 格式:
- **市场趋势**
- **政策影响**
- **竞争风险**
以下是两个关于银行业的分析示例,请按照这种格式对新的报告进行分析:
- 示例 1:**市场趋势**:由于政策放宽,银行贷款增长迅速。
- 示例 2:**政策影响**:新的利率政策可能会对中小企业贷款产生负面影响。
请对下面报告进行同样的分析。
如果文档中没有足够的事实回答问题,请返回{无法从文档中获得相关内容},而不是进行推测。
你的角色: 知识库专家
- 背景:分析银行业市场数据
- 目标:生成一份详细的行业趋势分析
- 限制:仅根据报告中的数据生成分析
请生成一份简明扼要的银行业报告摘要,不要逐字重复段落内容。原因:读者可以访问完整文档,如果需要可以详细阅读全文。
以下是关于银行业政策变化的相关规则,它们将用于回答有关政策对银行业影响的问题。
在提示工程中,过于具体的指令可能会限制模型的创造性,过于宽泛的提示则可能导致生成偏差。如何在提示设计中找到合适的权衡点,既能够引导模型生成高质量结果,又不过度限制模型的灵活性,是提示工程的重中之重
需要不断调试优化提示词才能找到合适的平衡点,引导模型生成高质量结果,而又不过度限制模型的灵活性
上述内容的讲解实际上已经涵盖了一些能够提升 RAG 检索效果的关键技术。这些技术包括:处理多种文档格式、版面布局及阅读顺序还原的高精度、高效率文档解析技术,适用于特定场景的多样化分块策略,综合考虑特定领域精度、效率和文本块长度的嵌入模型,支持高效索引、检索和存储的向量数据库,结合多种检索技术的混合检索方法,以及能够捕捉查询词与文档块相关性的重排序技术。每个技术的细节优化都可以进一步提升整体检索精度。
在 RAG 索引流程中,文档解析之后、文本块切分之前,进行数据清洗和预处理能够有效减少脏数据和噪声,提升文本的整体质量和信息密度。通过清除冗余信息、统一格式、处理异常字符等手段,数据清洗和预处理过程确保文档更加规范和高质量,从而提高 RAG 系统的检索效果和信息准确性。
如
文档中出现大量的重复段落或内容,特别是在合同、报告中,这类重复内容并没有重复的意义,只会增加存储负担、影响检索效率
文档中的多余的空行、缩进或其他格式不一致的地方,这些多余格式可能会影响文本块的切分和向量化过程,如递归分块时,若根据空行,则就会影响
在文档解析时,可能会从网页或 PDF 中提取出脚注、版权声明、页眉页脚等无关信息。这些内容会增加数据的噪声,影响向量生成的精度。
在 RAG 系统的典型检索步骤中,用户的查询会转化为向量后进行检索,但单个向量查询只能覆盖向量空间中的一个有限区域。如果查询中的嵌入向量未能包含所有关键信息,那么检索到的文档块可能不相关或缺乏必要的上下文。因此,单点查询的局限性会限制系统在庞大文档库中的搜索范围,导致错失与查询语义相关的内容。
查询扩展策略:借助大模型,从原始的查询语句(即用户输入)生成多个语义相关的查询,可以覆盖向量空间中的不同区域,从而提高检索的全面性和准确性。这些查询在嵌入后能够击中不同的语义区域,确保系统能够从更广泛的文档中检索到与用户需求相关的有用信息。
如下查询扩展的 prompt:
你是一个 AI 语言模型助手。
你的任务是生成五个不同版本的用户问题,以便从向量数据库中检索相关文档。
通过从多个角度生成用户问题,你的目标是帮助用户克服基于距离的相似性搜索的一些局限性。
请将这些替代问题用换行符分隔。原始问题:{查询原文}
原始查询问题: 下面报告中涉及了哪几个行业的案例以及总结各自面临的挑战?
查询扩展后:
请问报告中提到的案例涉及了哪些行业?这些行业各自面临的挑战有哪些?
报告中有哪些行业的案例被讨论?每个行业在报告中描述的挑战是什么?
这个报告中具体提到了哪些行业的案例?能否总结一下这些行业当前面临的主要挑战?
该报告中涵盖了哪些行业案例,并对各行业的挑战进行了哪些讨论?
在报告中提到的行业案例有哪些?这些行业分别遇到的主要问题和挑战是什么?
通过这种查询扩展策略,原始问题被分解为多个子查询,每个子查询独立检索相关文档并生成相应的结果。随后,系统将所有子查询的检索结果进行合并和重新排序。此方法能够有效扩展用户的查询意图,确保在复杂信息库中进行更全面的文档检索,从而避免遗漏与查询语义密切相关的重要内容。
自查询策略通过大语言模型自动提取查询中对业务场景至关重要的元数据字段(如标签、作者 ID、评论数量等关键信息),并将这些信息结合到嵌入检索过程中。通过这种方式,可以确保嵌入向量中包含这些关键信息,从而提高检索的全面性与精确性。
自查询 prompt 如下:
你是一个 AI 语言模型助手。
你的任务是从用户问题中提取关键信息,你的回复应仅包含提取的关键信息。
用户问题:{查询原文}
原始查询问题为:“下面报告中涉及了哪几个行业的案例以及总结各自面临的挑战?”
通过自查询指令生成如下内容:
行业,案例,挑战
通过这种自查询策略,系统能够精准提取查询中的关键信息,结合关键词检索及向量检索,确保这些元数据在向量检索中得以充分利用,从而提高检索结果的相关性和准确性。
提示压缩旨在减少上下文中的噪声,并突出最相关的信息,从而提高检索精度和生成质量。在 RAG 系统中,检索到的文档通常包含大量无关的文本,这些无关内容可能会掩盖与查询高度相关的信息,导致生成结果的相关性下降。提示压缩通过精简上下文、过滤掉不相关的信息,确保系统只处理与查询最相关、最重要的内容。
提示压缩 prompt 如下:
你是一个 AI 语言模型助手,负责对检索到的文档进行上下文压缩。
你的目标是从文档中提取与用户查询高度相关的段落,并删除与查询无关或噪声较大的部分。
你应确保保留所有能够直接回答用户查询的问题核心信息。
输入:
用户查询:{用户的原始查询}
检索到的文档:{检索到的文档内容}
输出要求:
提取与用户查询最相关的段落和信息。
删除所有与查询无关的内容,包括噪声、背景信息或扩展讨论。
压缩后的内容应简洁清晰,直指用户的核心问题。
输出格式:
{压缩段落 1}
{压缩段落 2}
{压缩段落 3}
通过提示压缩,系统能够准确提取出与查询高度相关的核心信息,去除冗余内容,并返回简洁的压缩结果。组合成为新的指令,输入大模型获得回复,提高 RAG 系统答案准确度。
评估方式:
评估指标:

RAG 效果评估是 RAG 系统完成搭建后的一个持续优化流程。通过设定打分标准和评估指标,综合评分能够准确反映 RAG 系统的整体性能。针对不同的应用场景,还可以引入更多评估指标,如 Top5 召回率、Top3 召回率、Top1 召回率以及其他常用的 NLP 评估指标。通过灵活组合这些评估方式与指标,可以更加精确地衡量 RAG 系统在特定场景中的表现,并为后续优化提供方向。
有部分内容和之前的重叠,主要作为参考,面试过程中有内容可以扯就行
按照理论来说,这叫做 ** advanced RAG**范式。
按照检索前、检索、检索后的策略分类
检索前优化通过索引、分块、查询优化以及内容向量化等技术手段,提高检索内容的精确性和生成内容的相关性。
检索优化是 RAG 系统中直接影响检索效果和质量的核心环节。通过增强向量搜索、动态嵌入模型、混合检索等技术手段,系统能够高效、精准地找到与用户查询最相关的内容。
检索后优化目的是对已经检索到的内容进行进一步的处理和筛选,常用的技术包括重排序、提示压缩等,以确保最终生成的答案具有高度的相关性和准确性。