一:背景介绍
1.1 什么是 RAG
检索增qiang生成(Retrieval-augmented generation)是指对大型语言模型输出进行优化,使其能够在生成响应之前引用训练数据来源之外的权威知识库。大型语言模型(LLM)用海量数据进行训练,使用数十亿个参数为回答问题、翻译语言和完成句子等任务生成原始输出。在 LLM 本就qiang大的功能基础上,RAG 将其扩展为能访问特定领域或组织的内部知识库,所有这些都无需重新训练模型。这是一种经济高效地改进 LLM 输出的方法,让它在各种情境下都能保持相关性、准确性和实用性。
1.2 为什么要使用 RAG
大模型在知识、逻辑、推理能力上的取得了显著进展,但是仍然面临许多挑战,
- LLM基于概率生成文本,在没有答案的情况下容易胡说八道,即产生的幻觉信息
- 由于LLM 训练数据是静态的,所以大模型回复的信息是过期的信息。
- 大模型的知识来源于训练数据,这些数据主要来自公开的互联网和开源数据集,因此没办法回答特定领域内的专业提问。
为解决这些问题,RAG技术应运而生,RAG技术主要将非参数化的外部知识库、文档与大模型相结合,使用LLM在生成内容之前,先根据用户query检索相关外部知识库和文档,将检索结果与用户查询一起发送给大模型。不仅能够避mianLLM 回答问题时效性问题,还可以降低由于将所有知识库数据交给大模型处理带来的处理成本的问题。
1.3 RAG的应用场景
RAG技术凭借其将检索与生成相结合的优势,可广泛应用于多个领域和场景,满足了在大模型应用中实时性、高准确性和领域专有知识获取的需求。
- 企业知识管理问答或者客服系统中,RAG能够实时从企业知识库中检索出相关信息,确保给出的回答是准确且是最新的企业内部知识。
- 在新闻与金融领域,RAG可以实时检索出最新的时事动态与研究成果,确保信息的准确、时效性。
在上述场景中,RAG技术有效弥补了LLM存在的缺陷,为LLM在多场景应用提供有力支撑。
二:RAG 技术工作流程
RAG 技术 工作流程主要由 1、构建知识库索引,2、在向量量检索用户query ,3、检索结果与用户问题构成prompt,输入到大模型中,生成准确实时的结果。
2.1 索引构建
对知识库中多种不同格式的文档(常见格式有 PDF、Word、Excel、MarkDown 等)进行解析,提取其中的文本数据,按照一定的语义规则或文档结构来切分成标准的文本块(chunk),并使用embedding model 对chunk 进行嵌入向量化(embedding),转换为计算机可理解的高维向量,向量存储在向量数据库(vector database)中,完成知识库的构建,因此向量数量库中有了chunk与向量的映射关联关系了。
2.2 向量检索
用户输入的问题不会直接发送给LLM, 而是同样先使用embedding model 转化为向量,接着再通过相似度匹配从向量数据库中检索出最相关的K个文本块,为下一阶段的prompt生成提供支持。
2.3 prompt生成
生成阶段,即将检索到的相关文本与用户初始提问一起拼接成提示词(Prompt),输入大语言模型(LLM),让LLM在相关文本中生成准确又有时效的回答。
三:RAG 技术原理
3.1 索引构建
索引构建是RAG系统的最基础阶段,包括各种格式文档的解析、Chunk切分、文本块向量化及向量存储四个细分步骤。
3.1.1 基于 LangChain Document Loaders 文档loading器
常见的企业知识库文档有PDF、TXT、Word、PPT、Excel、CSV、Markdown、XML、HTML 等多种格式,需要提取上述文档中目录、标题、段落、公式、表格等各个区块中的文本数据。LangChain 是一个用于 构建基于大语言模型(LLM)的应用 的开源框架, 提供了 Document Loaders(文档loading器) 来支持不同格式的文档解析,主要有下列文档loading器
TextLoader:loadingtxt 文件
CSVLoader:loading csv 文件
JSONLoader:loading json 文件
PDFPlumberLoader:loading pdf 文件(基于 pdfplumber 库)
UnstructuredPDFLoader:更qiang大的 PDF 解析(基于 unstructured 库)
UnstructuredWordDocumentLoader:loadingdoc和docx文件
UnstructuredExcelLoader:loading.xlsx 文档
UnstructuredMarkdownLoader: loadingmd文档
3.1.2 基于 LangChain Text Splitting
各种格式的知识文档数据(Documents)经过解析成对应的文本后,通过分块技术将上述文本划分为适当大小的文档片段(chunks),使得RAG能够精准和高效的检索这些片段信息。LangChain 提供了**文档分割(Text Splitting)**的类库,主要用于将长文档拆分成较小的片段,以便更好地进行索引。
LangChain中 提供以下常见的分块类库:
- CharacterTextSplitter :按字符长度拆分 ,基于字符的分割(默认按
\n\n
作为段落分隔符),适用于短文本 - RecursiveCharacterTextSplitter:按不同的分隔符递归拆分,适用于HTML、JSON、PDF**(文本层次结构复杂);分层处理,先按段落,再按句子
- TokenTextSplitter :按 LLM Token 进行拆分,防止超过模型的最大 Token 限制
- MarkdownTextSplitter:按 Markdown 结构拆分,适用于 Markdown 文档(按标题、列表、代码块拆分)和技术API文档
- SemanticChunker :按照语意来拆分,它使用**文本嵌入(Embeddings)**计算相邻句子的相似度,以保证语义完整性,适用于长文本(书籍、文章)
3.1.3 Embedding嵌入
Embedding(嵌入)是一种将文本、图像、音频转换为向量空间表示的技术。它的目的是在保留原始数据重要特征的同时,将数据映射到一个更适合计算机处理和分析的向量空间中 。在向量空间中,可以方便地计算向量之间的相似度(如余弦相似度、欧式距离等),从而用于衡量数据之间的相似性或相关性,在向量空间中,语义相近的对象在向量空间中彼此邻近,而语义相异的对象则相距较远。
简单来说就是: 计算机无法直接理解普通文本,却可以对Embedding 向量 计算相似性。**向量检索(Vector Retrieval)**是一种基于向量表示的搜索技术,通过计算用户输入的查询向量与存储在向量数据库中文本向量的相似度来识别最相关的文本数据。
举例说明:
句子 | 3D 向量(简化示例) |
---|---|
"人工智能很有趣" | [0.8, 0.3, 0.5] |
"机器学习是未来" | [0.9, 0.4, 0.6] |
"天气很好" | [0.2, 0.7, 0.1] |
计算三组向量的 余弦相似度结果如下:
- "人工智能很有趣" vs "机器学习是未来" → 0.998(高度相似)
- "人工智能很有趣" vs "天气很好" → 0.577(相似度较低)
- "机器学习是未来" vs "天气很好" → 0.614(相似度较低)
从结果可以看出:
- “人工智能很有趣” 和 “机器学习是未来” 语义接近,因此余弦相似度接近 1。
- “天气很好” 与前两个句子无明显关联,因此相似度较低
在RAG中,Embedding 主要用于 "知识检索",即把问题和文档进行相似度匹配,Embedding Model 将输入的**知识库文档分割之后的片段(Chunks)和用户的查询文本(Query)转换为嵌入向量(Vectors)**,这些向量表达了文本的语义信息,并可在向量空间中与其他嵌入向量进行比较。
使用 bge-base-zh-v1.5
(北京智源人工智能研究 发布的一款中文文本嵌入Embedding模型) Embedding Model 将文本转化为向量
from sentence_transformers import SentenceTransformer
# loading bge-base-zh-v1.5 远程模型
embedding_model = SentenceTransformer("BAAI/bge-base-zh-v1.5")
# 计算文本向量
text = "人工智能是未来的方向"
embedding = embedding_model.encode(text)
# 输出: (768,) 代表 768 维向量
print(embedding.shape)
3.1.3 向量数据库
3.1.3.1 非关系类型数据库与向量数据库区别
传统数据库主要有关系类型(SQL) 和非关系类型数据库(NoSQL),其中非关系类型数据库主要用于存储结构或者半结构化的数据,用于大规模数据存储与高并发的应用场景,有以下几类
- 键值数据库:适用于高速缓存、KV 存储。
- 文档数据库:适用于JSON 数据存储
- 列存数据库:适用于数据分析
- 图数据库:适用于关系网络分析
而向量数据库:专门用于存储和检索 高维向量(Embedding),主要用于AI 语义搜索、RAG(检索增qiang生成)、推荐系统等任务。
3.1.3.2 向量数据库的主要优势
- 支持高维数据存储:能够存储 128 维、768 维、1024 维甚至更高维的向量
- 高效的相似度检索 :相对比于传统数据库的关键字或者索引查询,向量数据库可以通过 余弦相似度、欧几里得距离、点积 等方法查找最相似的向量。向量数据库采用近似最近邻(ANN)算法,提高搜索效率。
- 采用高效索引结构,如HNSW(分层小世界图):支持高维数据索引,查询速度快。IVF(倒排文件索引):适合大规模向量数据,检索速度远超 MySQL 的全表scan。
- 易于扩展,支持大规模数据 :向量数据库支持分布式架构,可处理数十亿级别向量,Milvus、Weaviate、Pinecone 都支持分布式存储和查询,可以水ping扩展
3.1.3.3 向量数据库的检索优化
- 存储优化
- 乘积量化(PQ)把向量空间划分为多个子空间,在每个子空间内对向量进行聚类,用聚类中心的索引来表示向量。
- 数据分区: 按时间、地域或其他业务规则进行分区,在查询时只需搜索相关分区,减少搜索范围
- 索引优化:
- 基于树索引:KD树:通过递归地将 k 维空间划分为两个半空间,每个内部节点对应一个垂直于坐标轴的超ping面,将空间划分为两部分,从而构建出一个二叉树,适用于低维向量数据
- 基于图索引:HNSW索引:构建一个分层的图结构,节点表示向量,边表示向量之间的相似性关系。在不同的层次上,节点的连接密度不同,通过图的导航来快速找到相似向量,适用于高维向量数据的相似性搜索
- 基于哈希索引:局部敏感哈希:设计一系列哈希函数,将相似的向量映射到相同或相近的哈希桶中。在查询时,只需要在对应的哈希桶中进行搜索,从而大大减少了搜索范围,适用于大规模数据的近似搜索,对搜索精度要求不是很高。
- 基于量化索引:乘积量化:将高维向量空间划分为多个低维子空间,在每个子空间内对向量进行聚类,用聚类中心的索引来表示向量。通过这种方式,将高维向量的存储和搜索问题转化为多个低维向量的问题
- 混合索引:充分发挥不同索引的优势,向量数据库采用混合索引的方式。先使用基于哈希的索引进行快速筛选,缩小搜索范围,然后再使用基于图或树的索引进行更精确的搜索
3.2 向量检索
RAG 检索流程 即将用户输入的Query 转化为向量后,通过语义来在向量库中找到相似文本块。主要流程如下图所示:
3.2.1 Query预处理
RAG 检索流程中,用户向量查询只能与向量数据库一部分数据做相似度匹配,如果查询向量不包括向量数据库中关键信息,那么检索结果会存在一定的偏差,导致丢失相关语义。因此我们可以通过大模型从用户最初的查询语句生成多个语义近似的查询语句,使用生成后的多个查询语句去向量库中检索,从而扩大查询向量在向量库中空间覆盖的区域,提高检索结果的准确性。比如可以生成下列类似的相同语义的句子:
为这个句子生成3个同语义的句子,“天翼云云产品有哪些优势”
当然可以,以下是三个与原句同语义的句子:
天翼云的产品具备哪些优势?
使用天翼云的云服务有哪些好处?
天翼云提供的云解决方案有何特别之处?
这些句子都旨在询问天翼云云产品或服务所具有的优点和特点
通过生成类似同语义的子查询,每个子查询都会返回相应的检索结果,我们可以把最终的检索结果进行汇总和重新排序,从而保护用户检索过程中的全面性。
Query预处理中的标准化主要是对用户问题中部分专有名词或者是缩写与简称进行替换,比如说:
天翼云的ZOS 有哪些功能 ? 可以替换为
天翼云的对象存储有哪些功能?
3.2.2 结果召回
结果召回做的事情就是从向量库中获取相似度高的N个Chunk,常用的方案是**向量检索(Vector Search)**根据文本语义来查询匹配文本chunk,但在特殊场景下,比如说查询用户或者产品名称、商品订单、具体人名信息时,向量检索效果没有按照名称或者订单ID 的 关键字检索的效果好。
RAG 使用 混合检索(Hybrid Search) 即keyword检索和语义匹配相结合的方式提升检索的准确性。首先利用keyword检索精确定位到用户名称等信息,然后通过语义匹配扩展与该用户名称相关的上下信息。不仅可以获取用户名称的详细信息,还可以获取与用户名称相关的其它信息。
3.2.2 结果重排序
3.2.2.2.1 为什么要做重排序
需要对RAG检索结果做重排序,主要是出于以下原因
- embedding 模型的本身的局限性,自然语言中的语义具有高度复杂性和多义性, “苹果” 一词,既可以指水果,也可能指苹果公司,Embedding 模型在学习过程中无法表达句子在不同环境的真实语义;训练数据限制:Embedding 模型的训练依赖于大量的文本数据。如果训练数据存在偏差、不完整或者覆盖的语义场景有限,那么模型学习到的语义表示就会存在缺陷,特别是一些专业领域的词汇或者表达。
- 优化检索结果:检索结果通常来自于向量搜索或基于keyword的检索方法。然而,这些初始检索结果可能包含大量的冗余信息或与查询不完全相关的文档。因此需要对结果进行筛选和排序,将最相关文档放在最前面
3.2.2.2.2 使用重排序模型
重排序模型是将 用户的查询(Query)和初始的检索结果文档块作为输入,直接输出相似度评分,按照评分高低取出前topK个文档档。常见的排序思路是使用 Learning to Rank 中的 Pointwise 、Pairwise以及Listwise 等方法,腾讯向量数据库针对ListWist (时间复杂库高,有n!种排列方式)提出了使用NDCG 作为损失函数(loss)来优化排序模型的方案。常见的开源重排序模型有 BGE-Reranker、Sentence-Reranker 以及**T5-Reranker **
3.3 RAG 生成
RAG生成流程:首先需要组合Prompt指令,Prompt指令由查询问题和在向量数据库中检索到的相关信息组成,将组合的Prompt指令发送到大模型中,由大模型理解并生成最终的回复,从而完成整个应用过程。
3.3.1 提示词介绍
Prompt Engineering 是在人工智能(尤其是自然语言处理领域中),随着大型语言模型的发展起来的新的技术,主要涉及到如何设计和优化提示(Prompts),以引导人工智能模型,如大型语言模型(LLMs),生成准确、有用和符合特定要求的输出。
3.3.2 提示词主要元素
- 任务描述:指明模型要执行的特定任务或操作。
- role设定:指定模型扮演的role或视角,例如 “假设你是一位资深的人工智能专家,谈谈你对人工智能发展趋势的看法”,这样可以让模型从特定的role出发,生成更具专业性和权威性的内容
- 上下文:提供相关的背景信息或上下文语境,帮助模型更好地理解任务和生成连贯的结果。
- 输入数据:我们希望模型回答的问题或感兴趣的输入内容。
- 输出指示符:指定模型的输出格式或者要求,例如“使用Java语言生成代码样例”
四:代码实践
def load_embedding_model():
print(f"loadingEmbedding模型中")
embedding_model = SentenceTransformer(os.path.abspath('./bge-small-zh-v1.5'))
print(f"bge-small-zh-v1.5模型最大输入长度: {embedding_model.max_seq_length}")
return embedding_model
def create_index(pdf_file, embedding_model):
pdf_loader = PyPDFLoader(pdf_file, extract_images=False)
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=200, chunk_overlap=100
)
pdf_content_list = pdf_loader.load()
pdf_text = "\n".join([page.page_content for page in pdf_content_list])
print(f"PDF文档的总字符数: {len(pdf_text)}")
chunks = text_splitter.split_text(pdf_text)
print(f"分割的文本Chunk数量: {len(chunks)}")
embeddings = []
for chunk in chunks:
embedding = embedding_model.encode(chunk, normalize_embeddings=True)
embeddings.append(embedding)
print("文本块Chunk转化为嵌入向量完成")
dimension = embeddings_np.shape[1]
print(f"嵌入向量的维度 = {dimension}")
# 使用余弦相似度创建FAISS索引
index = faiss.IndexFlatIP(dimension)
# 将所有的嵌入向量添加到FAISS索引中,后续可以用来进行相似性检索
index.add(embeddings_np)
print("索引过程完成.")
return index, chunks
def search(query, index, chunks, embedding_model, top_k=3):
query_embedding = embedding_model.encode(query, normalize_embeddings=True)
query_embedding = np.array([query_embedding])
distances, indices = index.search(query_embedding, top_k)
print(f"查询语句: {query}")
print(f"最相似的前{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
def main():
print(" RAG start")
query="DeepSeek典型应用场景有哪些?"
embedding_model = load_embedding_model()
index, chunks = create_index('./test5.pdf', embedding_model)
retrieval_chunks = search(query, index, chunks, embedding_model)
generate_process(query,retrieval_chunks )
print(" RAG() end")
def buildPrompt(query,chunks):
context = ""
for i, chunk in enumerate(chunks):
context += f"参考文档{i+1}: \n{chunk}\n\n"
prompt = f"根据参考文档回答问题:{query}\n\n{context}"
print(f"生成模型的Prompt: {prompt}")
return prompt
def generate_process(query, chunks):
prompt = buildPrompt(query,chunks)
messages = [{'role': 'user', 'content': prompt}]
# 调用大模型API云服务生成响应
try:
generated_response = invoke_model(messages)
return generated_response
except Exception as e:
print(f"大模型生成过程中发生错误: {e}")
return None
def invoke_model(messages):
conn = http.client.HTTPSConnection("wishub-x1.ctyun.cn", 80,timeout=100)
payload = json.dumps({
"model": "xx",
"top_p": 1,
"top_k": 5,
"stream": False,
"stream_options": {
"include_usage": True
},
"temperature": 0.8,
"seed": 1,
"messages": messages
})
headers = {
'Authorization': 'Bearer xxxx',
'Cookie': 'vid=xxx',
'Content-Type': 'application/json',
'Accept': '*/*',
'Connection': 'keep-alive'
}
conn.request("POST", "/v1/chat/completions", payload, headers)
res = conn.getresponse()
data = res.read()
print(data.decode("utf-8"))
return data.decode("utf-8")
if __name__ == "__main__":
main()
输出结果
- load_embedding_model() : loading本地embedding模型,将文本转化为向量
- create_index(): 传入文件与embedding模型,生成索引和chunk
- search() :按照相似度,找到top3 的chunk
- generate_process(): 调用buildPrompt()拼装新的prompt,接着调用invoke_model()
五:问题
- chunk大小和相邻文本块之间的重叠大小设定影响着RAG系统的生成质量
- pdf文件中有些特殊符号需要过滤,另外针对某些pdf格式,PyPDFLoader存在不能解析的问题
六:总结
本文简要分析了下RAG技术工作流程,并基于langchain框架编写RAG代码示例。RAG有效弥补了大模型在知识时效性、准确性以及特定领域适应性等方面的不足,在实际应用中,在问答系统、文档生成、知识密集型任务以及实时信息获取等多个领域展现出qiang大的优势。