1.RAG背景与简介
1.1 背景
RAG(Retrieval Augmented Generation,检索增强生成)
技术最初源于2020年Facebook的一篇论文------《Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks》。是的,2020年就已经提出了这项技术。
这篇论文要解决的一个问题非常简单:如何让大语言模型使用外部知识进行生成。 通常,预训练模型(pre-trained models)
的知识是存储在参数中的,因此模型无法了解训练集之外的知识(例如搜索数据、行业知识)。之前的做法是通过在预训练模型上进行微调(fine-tuning)
来更新模型的知识。
这样的方式会有几个问题:
- 每当有新的知识时,模型都需要重新进行微调。
- 训练模型的成本是很高。
- 所有AI模型的底层原理都基于数学概率,大模型也不例外。因此,有时模型在缺乏某方面知识时,可能会生成不准确的内容(即"幻觉")。而识别这些幻觉问题对于用户来说是相当困难的,因为这需要用户具备相应领域的知识。
- 对于企业而言,数据安全至关重要。没有企业愿意承担数据泄露的风险,将其私域数据上传到第三方平台进行训练。因此,完全依赖通用大模型能力的应用方案在数据安全与效果之间存在权衡。
为了解决这些问题,这篇论文提出了RAG的方法。既然预训练模型能够理解新知识,那么我们可以通过提示(prompt)
的方式将新知识直接提供给模型。
1.2 简介
RAG(Retrieval Augmented Generation,检索增强生成)
是一种将大规模语言模型(LLM)与外部知识源的检索相结合,以改进问答能力的工程框架。它使用来自私有或专有数据源的信息来辅助文本生成,从而弥补LLM的局限性,特别是在解决幻觉问题和提升时效性方面。
原始 RAG 的流程包括索引、检索和生成三个步骤,既把问答内容输入到数据库中,给定query,可以直接去数据库中搜索,搜索完成后把查询结果和query拼接起来送给模型去生成内容。
实际上langchain
, llama-index
本质上就是做的这套 RAG 系统(当然还包括构建在 RAG 上的 agent)
在大语言模型的优化过程中,除了RAG,微调也很常用。
RAG与模型微调的对比:
微调模型 | RAG | |
---|---|---|
优点 | 针对特定任务调整预训练模型。优点是可针对特定任务优化; | 结合检索系统和生成模型。优点是能利用最新信息,提高答案质量,具有更好的可解释性和适应性: |
缺点 | 但缺点是更新成本高,对新信息适应性较差; | 是可能面临检索质量问题和曾加额外计算资源需求; |
两者的特性对比:
特性 | RAG技术 | 模型微调 |
---|---|---|
知识更新 | 实时更新检索库,适合动态数据,无需频繁重训 | 存储静态信息,更新知识需要重新训练 |
外部知识 | 高效利用外部资源,适合各类数据库 | 可对齐外部知识,但对动态数据源不够灵活 |
数据处理 | 数据处理需求低 | 需构建高质量数据集,数据限制可能影响性能 |
模型定制化 | 专注于信息检索和整合,定制化程度低 | 可定制行为,风格及领域知识 |
可解释性 | 答案可追溯,解释性高 | 解释性相对低 |
计算资源 | 需要支持检索的计算资源,维护外部数据源 | 需要训练数据集和微调资源 |
延迟要求 | 数据检索可能增加延迟 | 微调后的模型反应更快 |
减少幻觉 | 基于实际数据,幻觉减少 | 通过特定域训练可减少幻觉,但仍然有限 |
道德和隐私 | 处理外部文本数据时需要考虑隐私和道德问题 | 训练数据的敏感内容可能引发隐私问题 |
2.RAG技术原理与流程
接下来详细介绍一下RAG的技术原理与实现流程
在论文综述「Retrieval-Augmented Generation for Large Language Models: A Survey」中,作者将RAG技术按照复杂度继续划分为Naive RAG
,Advanced RAG
、Modular RAG
。
初级RAG主要涉及"索引-检索-生成"过程。
初级 RAG 在检索质量、响应生成质量以及增强过程中存在多个挑战,高级 RAG 范式随后被提出,高级RAG在数据索引、检索前和检索后都进行了额外处理。通过更精细的数据清洗、设计文档结构和添加元数据等方法提升文本的一致性、准确性和检索效率。
随着 RAG 技术的进一步发展和演变,新的技术突破了传统的 初级 RAG 检索 --- 生成框架,基于此我们提出模块化 RAG 的概念。在结构上它更加自由的和灵活,引入了更多的具体功能模块,例如查询搜索引擎、融合多个回答。技术上将检索与微调、强化学习等技术融合。流程上也对 RAG 模块之间进行设计和编排,出现了多种的 RAG 模式。
原始 RAG 的流程包括传统的索引、检索和生成步骤。核心是信息检索和大型语言模型调用两个关键过程。信息检索通过连接外部知识库,获取与问题相关的信息;而大语言模型调用则用于将这些信息整合到自然语言生成的过程中,以生成最终的回答。一般的 RAG 工作流程如下图所示:
2.1 索引
索引指的是在离线状态下,从数据来源处获取数据并建立索引的过程。具体而言,构建数据索引包括四个步骤:数据加载 、文本分块 、文本嵌入、创建索引。
-
数据加载
数据加载是指将外部数据进行清理和提取,将
CSV
、PDF
、HTML
、Word
、Markdown
等不同格式的文件转换成纯文本,这里可以借助LangChain
内置的加载器来实现。LangChain
内置的加载器是LangChain
中最有用的部分之一,例如加载CSV的CSVLoader,加载PDF的PyPDFLoader,加载HTML的UnstructuredHTMLLoader,加载Word的:UnstructuredWordDocumentLoader,加载MarkDown的:UnstructuredMarkdownLoader等 -
文本分块
通过加载器获取到纯文化后,接下来需要对文本进行分块。
**为什么需要对文本进行分块?**一方面Transformer模型有固定的输入序列长度,即使输入context很大,一个句子或几个句子的向量也比几页文本的平均向量更好地代表它们的语义含义,另一方面,我们将文档分割成适合搜索的小块,使其更适合进行嵌入搜索,从而提升片段召回的准确性。
另外,分块的大小是一个需要考虑的重要参数,如果文本分块太大,则无法很好地匹配查询,如果太小,则没有足够的有用上下文来生成答案。
常用的分块方法:
-
固定大小分块 :这是最常见、最直接的分块方法:我们只需决定块中的标记数量,以及可选地确定它们之间是否应该有重叠。**一般来说,我们希望在块之间保留一些重叠,以确保语义上下文不会在块之间丢失。在大多数常见情况下,固定大小的分块将是最佳路径。**与其他形式的分块相比,固定大小的分块计算成本低且易于使用,因为它不需要使用任何NLP库,例如:
pythontext = "..." # your text from langchain.text_splitter import CharacterTextSplitter text_splitter = CharacterTextSplitter( separator = "\n\n", chunk_size = 256, chunk_overlap = 20 ) docs = text_splitter.create_documents([text])
- Chunk Size(块大小):表示将文本划分为较小块的大小。这是分割后每个独立文本块的长度或容量。块大小的选择取决于应用的需求和对文本结构的理解。
- Overlap Size(重叠大小):指相邻两个文本块之间的重叠部分的大小。在切割文本时,通常希望保留一些上下文信息,重叠大小就是控制这种上下文保留的参数。
-
句子分块 :最直接的方法是按句点(".")和换行符分割句子
pythontext = "..." # your text docs = text.split(".")
-
NLP工具分块 : 例如借助spaCy,它是一个用于NLP任务的强大Python库,它提供了复杂的句子分割功能,可以有效地将文本分割成单独的句子,从而在生成的块中更好地保留上下文,帮助创建更有意义的块。例如,将spaCy与LangChain结合使用:
pythontext = "..." # your text from langchain.text_splitter import SpacyTextSplitter text_splitter = SpaCyTextSplitter() docs = text_splitter.split_text(text)
-
递归分块
递归分块使用一组分隔符以分层和迭代的方式将输入文本划分为更小的块。如果分割文本的初始尝试没有生成所需大小或结构的块,则该方法会使用不同的分隔符或标准在生成的块上递归调用自身,直到达到所需的块大小或结构。这意味着块的大小不会完全相同。LangChain中使用递归分块:
pythontext = "..." # your text from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( # Set a really small chunk size, just to show. chunk_size = 256, chunk_overlap = 20 ) docs = text_splitter.create_documents([text])
需要注意的是:没有一种万能的分块解决方案,也没有规定的分块大小,这需要你综合考虑处理文档的长度,使用那种嵌入模型,以及使用的大语言模型的上下文限制等因素。 文本分块并没有固定的最佳策略。选择哪种方式取决于具体的需求和场景,需要根据业务情况进行调整和优化。关键是找到适合当前应用的分块策略,而不是追求单一的完美方案。有时候,为了获得更准确的查询结果,我们甚至需要灵活地使用多种策略相结合。
另外,为了直观分析文本分割器是如何工作的,我们可以使用 ChunkViz 工具进行可视化,它会展示文本是如何被分割的,可以帮助我们调整分割参数。
-
-
文本嵌入(embedding)
什么是嵌入 : 嵌入也叫向量化,就是将文本内容通过embedding嵌入模型转化为多维向量的过程。虽然计算机并不能理解自然语言,但是借助LLM 技术的一个关键部分是翻译器,它可以从人类文字语言翻译成人工智能数字语言。我们称这种翻译机为 "嵌入机
(embedding machine)
",它输入人类语言,输出人工智能理解的数字语言。为什么要做嵌入:
上面是一个简单的示例:将不同的人类语言生成的向量绘制到二维坐标系中,发现在这个假设的语言空间中,两个点越接近,它们所表达的语义就越相似。例如,"Hello, how are you?" 和 "Hey, how's it going?" 几乎在同一个位置。另一个问候语"Good morning"也不太远。而"I like cupcakes"则完全与其他内容分开,它们在语言空间中的位置远离其他文本片段。
当然,不可能在一张二维图上表示全部人类语言,但理论是一样的。实际上,嵌入具有更多维的坐标。例如OpenAI当前使用的模型使用的嵌入向量是由1,536个数字组成的,也就1536个维度。你可以通过基本的数学运算来确定两个向量直接的接近程度。
有许多模型提供商提供嵌入模型:
OpenAI、Cohere、Hugging Face、智谱AI、百度
等,模型名称 描述 获取地址 ChatGPT-Embedding ChatGPT-Embedding由OpenAI公司提供,以接口形式调用。 platform.openai.com/docs/guides... ERNIE-Embedding V1 ERNIE-Embedding V1由百度公司提供,依赖于文心大模型能力,以接口形式调用。 cloud.baidu.com/doc/WENXINW... M3E M3E是一款功能强大的开源Embedding模型,包含m3e-small、m3e-base、m3e-large等多个版本,支持微调和本地部署。 huggingface.co/moka-ai/m3e... BGE BGE由北京智源人工智能研究院发布,同样是一款功能强大的开源Embedding模型,包含了支持中文和英文的多个版本,同样支持微调和本地部署。 huggingface.co/BAAI/bge-ba... 比如这里结合langchain,使用
bge-large-en-v1.5
嵌入模型进行文本向量化:pythonfrom langchain.embeddings import HuggingFaceBgeEmbeddings # 定义模型名称和参数 model_name = "BAAI/bge-large-en-v1.5" model_kwargs = {'device': 'cuda'} encode_kwargs = {'normalize_embeddings': True} # 初始化模型并设置 query_instruction model = HuggingFaceBgeEmbeddings( model_name=model_name, model_kwargs=model_kwargs, encode_kwargs=encode_kwargs, query_instruction="为这个句子生成表示以用于检索相关文章:" ) # 生成句子的嵌入表示 sentence = "我是文章分块内容示例" embedding = model.encode([sentence]) print("Embedding:", embedding[0])
面对这么多向量模型,我们如何衡量一种 Embedding 模型相对于其他模型的有效性呢?Hugging Face 推出了 MTEB(Massive Text Embedding Benchmark 大规模文本嵌入基准)测试框架,旨在评估文本 Embedding 模型在多种任务上的性能。
业务系统中,选择合适的 Embedding 模型是一个微妙的过程,受到多种因素的影响,比如知识库语言是中文、英文还是中英混合抑或是其他语言混合、切分文本块的长度、通用向量模型在垂类细分领域性能、模型的精度、硬件的限制、检索时间限制等等。
你可以参考MTEB排行榜以获取最新模型效果:huggingface.co/spaces/mteb...
-
创建索引
完成嵌入之后,下一步是创建索引,将原始语料块和嵌入以键值对形式存储,以便于未来进行快速且频繁的搜索。使用专业的向量数据库进行存储向量数据,常用的向量数据库有:
Chroma
、Weaviate
、FAISS
、ES
等,下面是使用Chroma
的示例:pythonimport chromadb from chromadb.config import Settings # 配置 Chroma 数据库客户端 chroma_client = chromadb.Client(Settings(chroma_api_impl="rest", chroma_server_host="localhost", chroma_server_http_port="8000")) # 确保 Chroma 数据库中存在相应的集合 collection_name = "text_embeddings" if collection_name not in chroma_client.list_collections(): chroma_client.create_collection(name=collection_name) collection = chroma_client.get_collection(name=collection_name) # 生成嵌入表示 embedding = model.encode([request.text])[0].tolist() # 存储到 Chroma 数据库 collection.add( embeddings=[embedding], metadatas=[{"text": request.text}], ids=[str(uuid.uuid4())] )
2.2 检索
数据检索是RAG框架中的重要组成部分,目标是根据用户的查询,快速检索到与之最相关的知识,并将其融入提示(Prompt)
中。这个过程一般分两步:
- 根据用户的输入,采用与索引创建相同的编码模型将查询内容转换为向量。
- 系统会计算问题向量与语料库中文档块向量之间的相似性,并根据相似度水平选出最相关的前 K 个文档块作为当前问题的补充背景信息。
为了检索出最相关的前K个文档,有多种检索方法,最简单的方式,直接通过上面创建的向量数据库来实现,向量数据库都根据数据是高维向量这一特性,在存储、索引、检索方面都做了大量的优化。代码示例:
python
# 初始化 HuggingFaceBgeEmbeddings 模型
model_name = "BAAI/bge-large-en-v1.5"
model_kwargs = {'device': 'cuda'}
encode_kwargs = {'normalize_embeddings': True}
model = HuggingFaceBgeEmbeddings(
model_name=model_name,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs,
query_instruction="为这个句子生成表示以用于检索相关文章:"
)
# 生成查询文本的嵌入表示
query_embedding = model.encode([request.text])[0].tolist()
# 从 Chroma 数据库中检索最相似的嵌入
results = collection.query(
query_embeddings=[query_embedding],
n_results=5 # 返回最相似的5个结果
)
当然为了提高检索的召回率,通常会选择多种检索方法结合使用。例如使用:分层索引检索
、混合检索
、HyDE方案
等,会在后面的RAG优化模块做详细介绍,这里不在涉及。
2.3 生成
生成是指将用户的问题与知识库被检索出的文本块相结合, 用prompt的形式传递给大语言模型的上下文,使大模型更好理解用户意图,生成用户想要的结果。接下来需要构建这个提示模板:
python
from langchain.prompts import ChatPromptTemplate
template = """您是一个问答机器人助手,接下来将给您一份知识库中提取的文档内容和一个相关的问题。请使用知识库中的信息回答问题,总是使用中文回答。
问题: {question}
可参考的上下文:
'''
{context}
'''
如果给定的上下文无法让你做出回答,请回答知识库中没有相关内容。
答案:"""
prompt = ChatPromptTemplate.from_template(template)
question
就是用户的问题,context
就是我们从知识库检索出的文本块。
以上就是RAG框架的整体流程,接下来将用代码实现一个完整的RAG Demo。
3.代码构建一个RAG系统
接下来大模型使用ZhiPuAI
,向量数据库使用Chroma
,嵌入模型使用ZhipuAIEmbeddings
,借助langchain
搭建一套专门处理外部数据为pdf类型的的RAG系统。
-
导入相关库
pythonimport streamlit as st from langchain_community.document_loaders import PyMuPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.vectorstores import Chroma from langchain_community.embeddings import ZhipuAIEmbeddings from langchain.chains import RetrievalQA from langchain_community.chat_models import ChatZhipuAI from langchain.prompts import ChatPromptTemplate
使用
streamlit
创建交互页面 -
设置
ZhiPu
的ApiKeyluaos.environ['ZHIPUAI_API_KEY'] = "***"
-
加载并分割pdf文档
inidef process_pdf(pdf_path): loader = PyMuPDFLoader(pdf_path) documents = loader.load() text_splitter = RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=50) texts = text_splitter.split_documents(documents) return texts
使用递归分割方式,文档分块大小为512,块与块之间的重复字符为50
-
向量化、创建索引
inidef setup_embeddings_and_vector_db(texts, persist_directory="./storage"): embeddings = ZhipuAIEmbeddings() vectordb = Chroma.from_documents(documents=texts, embedding=embeddings, persist_directory=persist_directory) vectordb.persist() return vectordb
-
创建检索器
initexts = process_pdf("temp_uploaded.pdf") # print("Texts:", texts) vectordb = setup_embeddings_and_vector_db(texts) retriever = vectordb.as_retriever(search_kwargs={"k": 3})
将向量数据库对象转换为一个检索器对象,这个检索器可以用来根据查询向量检索最相关的文档。指定了检索时的参数,这里
k
表示要返回的最相关文档的数量,即每次检索返回最相关的 3 个文档。 -
构造prompt模板,检索,生成
initemplate = """您是一个问答机器人助手,接下来将给您一份知识库中提取的文档内容和一个相关的问题。请使用知识库中的信息回答问题,总是使用中文回答。 问题: {question} 可参考的上下文: ''' {context} ''' 如果给定的上下文无法让你做出回答,请回答知识库中没有相关内容。 答案:""" prompt = ChatPromptTemplate.from_template(template) llm = ChatZhipuAI(model_name='glm-4-0520') qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=retriever) # Retrieve context using the retriever context_docs = retriever.get_relevant_documents(question) context = "\n".join([doc.page_content for doc in context_docs]) # Format the prompt with the question and context formatted_prompt = prompt.format(question=question, context=context) # Invoke the LLM with the formatted prompt llm_response = qa.invoke({"query": formatted_prompt}) answer = llm_response["result"]
大模型使用智谱的
glm-4-0520
,利用向量数据库Chroma的检索能力,完成向量检索。 运行streamlit run main.py
4.RAG系统效果评估
在了解到 RAG 是什么,RAG的基本技术原理,以及 RAG 怎么创建之后,我们进一步需要评估 RAG 的效果。
4.1 RAG评估的体系与指标
RAG 的过程涉及三个主要部分:输入、检索到的上下文、LLM 生成的回答。
所以,我们就可以通过衡量这三元组之间的相关性来评估 RAG 系统的有效性,评估体系架构图如下所示:
评估指标
通过上面的三元关系,可以抽象出 6个主要指标:
- 上下文相关性(Context Relevance):衡量检索出来的上下文与问题之间的相关性。
- 忠实度(Faithfulness):衡量生成的答案是否基于给定的上下文。
- 答案相关性(Answer Relevance):衡量生成的答案与问题之间的相关性。
- 上下文召回率(Context Recall) :衡量检索到的上下文与
ground truth
(真实数据) 之间的一致程度。 - 答案正确性(Answer Correctness):衡量生成答案的准确性。
- 答案完整性(Answer Integrity) :以
ground truth
(真实数据) 为依据,衡量生成答案的内容是否完整,或者说衡量生成答案的详细程度。
不同RAG评估系统的评估维度,大多数也是基于上面的6个主要指标的扩展或是变体。
4.2 对RAG评估的方法
-
人工评估 人工评估:邀请专家或人工评估员对RAG生成的结果进行评估。他们可以根据预先定义的标准对生成的答案进行质量评估,如准确性、连贯性、相关性等。这种评估方法可以提供高质量的反馈,但可能会消耗大量的时间和人力资源。
-
自动化评估 鉴于人工评估的耗时耗力,通过自动化的评估系统对RAG进行评估,这也是RAG评估的主流和发展方向。目前主流的RAG评估系统有:LangSmith、Langfuse、RAGAS、Trulens 等,其核心原理还是使用标注良好的数据集和大语言模型 (LLM) 来完成自动化评估。下面主要介绍如何使用
LangSmith
来构建自动化的评估系统。
LangSmith
是一个开发者平台,允许您调试、测试、评估和监控大语言模型 (LLM) 应用程序,并且能够与 LangChain
无缝集成。构建一个RAG系统进行评估,主要步骤如下:
- 创建一个测试用的数据集。
- 准备基本的RAG程序。
- 使用
LangSmith
运行评估。 - 查看指标,迭代改进系统。
4.3 LangSmith
评估
-
创建
LangChain
的API Key -
设置环境参数,
LangSmith
会自动进行跟踪管理pythonimport os os.environ['LANGCHAIN_TRACING_V2'] = 'true' os.environ['LANGCHAIN_ENDPOINT'] = 'https://api.smith.langchain.com' os.environ['LANGCHAIN_API_KEY'] = <your-api-key> os.environ['LANGCHAIN_PROJECT'] = "my project name"
-
上传测试测试数据集,导入CSV格式文件
-
集成评估代码
pythonfrom langchain_openai import ChatOpenAI from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langsmith.evaluation import evaluate, LangChainStringEvaluator # Target task definition prompt = ChatPromptTemplate.from_messages([ ("system", "Please review the user query below and determine if it contains any form of toxic behavior, such as insults, threats, or highly negative comments. Respond with 'Toxic' if it does, and 'Not toxic' if it doesn't."), ("user", "{问题}") ]) chat_model = ChatOpenAI() output_parser = StrOutputParser() chain = prompt | chat_model | output_parser # The name or UUID of the LangSmith dataset to evaluate on. # Alternatively, you can pass an iterator of examples data = "ds-back-snug-93" # A string to prefix the experiment name with. # If not provided, a random string will be generated. experiment_prefix = "ds-back-snug-93" # List of evaluators to score the outputs of target task evaluators = [ LangChainStringEvaluator("cot_qa") ] # Evaluate the target task results = evaluate( chain.invoke, data=data, evaluators=evaluators, experiment_prefix=experiment_prefix, )
-
根据生成指标调试RAG程序
5.如何优化RAG系统
有了RAG的评估系统,接下来看如何优化我们构建的RAG系统。
构建一个RAG 应用过程相对简单,但要将其推广到生产环境中则会面临多方面的挑战。这主要是因为 RAG 系统涉及多个不同的组件,每个组件都需要精心设计和优化,以确保整体性能达到令人满意的水平。在这一过程中,外部非结构化数据的清洗和处理、文本分块、Query 的预处理、是不是每次 Query 都要进行检索、上下文信息的检索和排序能力、知识缓存等环节都会影响系统的性能。首先我们来看针对Query 的优化。
5.1 Query 优化
Query 优化是RAG系统中提升检索结果准确性非常必要的阶段,通过这些手段,系统可以更好的理解用户意图并提供更准确的结果。下面介绍两种针对query优化的两种常用方法:
-
RAG Fusion -Query改写
工作流程如下:
1、多查询生成:直接使用用户输入的 query 进行查询,查询结果可能太窄导致无法产生较为全面的结果。通过使用 LLM 将原始查询扩展成多样化的查询,从而增加搜索的视野和深度。
2、逆向排名融合(RRF):RRF 是一种简单而有效的技术,用于融合多个检索结果的排名,以提高搜索结果的质量。它通过将多个系统的排名结果进行加权综合,生成一个统一的排名列表,使最相关的文档更有可能出现在结果的顶部。这种方法不需要训练数据,适用于多种信息检索任务,且在多个实验中表现优于其他融合方法。
3、生成性输出:将重新排名的文档和查询输入到 LLM ,生成结构化、富有洞见的答案或摘要。
-
HyDE-Query增强
HyDE (假设性文档嵌入,Hypothetical Document Embeddings) 是一种无监督的方法,它基于这样一个假设:与 query 相比,假设性回答(LLM 直接对 query 生成的答案)与文档有更相似的语义空间。
HyDE 的思路是:让大模型对query先生成一个答案,对答案做embedding相比对原query能更接近索引中的doc的embedding。
然而,因为 HyDE 强调问题的假设性回答和查找内容的相似性,因此也存在着不可避免的问题,即,假设性回答的质量取决于大型语言模型的生成能力,如果模型生成的回答不准确或不相关,会影响检索效果。
5.2 高级检索优化
最为关键和复杂的步骤------在向量数据库检索之上如何具体开发或改进整个系统的策略。这部分的内容足够写成一篇独立文章。为了保持简洁,我们只讨论一些常用或者新提出的策略。
-
句子窗口检索
在该方案中,文档中的每个句子都是单独嵌入的,这为上下文余弦距离搜索的查询提供了很高的准确性。**为了在获取最相关的单个句子后更好地对找到的上下文进行推理,我们将上下文窗口在检索到的句子之前和之后扩展了k个句子,然后将此扩展的上下文发送给LLM。**核心思想是当提问匹配好分块后,将该分块周围的块作为上下文一并交给LLM进行输出,来增加LLM对文档上下文的理解。文档文块太小会导致上下文的缺失该方法的
-
上下文压缩
当文档文块过大时,可能包含太多不相关的信息,传递这样的整个文档可能导致更昂贵的LLM调用和更差的响应。上下文压缩的思想就是通过LLM的帮助根据上下文对单个文档内容进行压缩,或者对返回结果进行一定程度的过滤仅返回相关信息。
-
分层索引检索
针对大型知识库的情况下,在创建索引的时候会创建两个索引,一个是由文档的摘要,一个由文档块组成,实现在搜索时的分步过滤。
在检索时,将query进行向量化表示,进行两次向量化搜索,首先,在文档摘要中进行向量化相似度匹配,然后获取到相似度高的摘要所在的文档,然后在指定的文档中进行相似度匹配,得到相似度最高的几个向量,最终输入到大模型中的上下文中。这检索方式可以提高大型数据库的检索效率。
-
混合检索
在检索时,将老式的关键字搜索---稀疏检索算法(搜索行业标准
BM25
)和向量搜索相结合,并将搜索内容组合到一个检索结果中,最后借助Reciprocal Rank Fusion (RRF)
算法来对检索到的结果进行重新排序以获得最终输出。混合搜索通常会提供更好的检索结果,因为结合了两种互补的搜索算法,同时考虑了查询与存储文档之间的语义相似性和关键字匹配。
pythonquery_engine = index.as_query_engine( vector_store_query_mode="hybrid", #Milvus 2.4开始支持, 在2.4版本之前使用 Default )
5.3 语义路由
基于语义路由来优化RAG系统
传统路由 :传统的路由机制通常依赖于预定义的规则或简单的条件判断(如 if/else 语句),根据输入的关键词或模式将请求分发到相应的模块。
语义路由基于自然语言来做出分支决策,本质上也是一种 if/else 分支逻辑。 只是它不再单纯依赖于显式的关键词或模式匹配,而是通过语义理解,识别用户意图和需求,将请求引导至最合适的服务、数据库或相关处理组件。
用户想进行交互的数据可能有多种来源, RAG 路由可以根据 user query 路由到不同的数据来源。看下上面的这幅图,这里的数据源有文档知识库、DB 知识库、API 查询。通过语义路由的分发,查询更加准确的数据,提供精确而高效的回答。
5.4 重排序(Re-ranking)
在完成语义搜索的优化步骤后,我们能够检索到语义上最相似的文档,但不知你是否注意到一个关键问题:语义最相似是否总代表最相关?答案是不一定。例如,当用户查询"最新上映的科幻电影推荐"时,可能得到的结果是"科幻电影的历史演变",虽然从语义上这与科幻电影相关,但并未直接回应用户关于最新电影的查询。
重排模型可以帮助我们缓解这个问题,重排模型通过对初始检索结果进行更深入的相关性评估和排序,确保最终展示给用户的结果更加符合其查询意图。
实现重排序除了可以提示LLM进行重排,更多的是使用了专门的重排序模型(例如闭源的有Cohere
,开源有BAAI
和IBM
发布的模型)。这些模型会考虑更多的特征,如查询意图、词汇的多重语义、用户的历史行为和上下文信息,从而保证最相关的文档排在结果列表的最前面。
在RAG 中添加一个重排器(Re-ranking
)仅需三个简单步骤:
-
首先,定义一个重排(
Re-ranking
)模型,使用Hugging Face
上的BAAI/bge-reranker-base
。 -
在查询引擎中,将重排模型添加到
node_postprocessors
列表中。 -
增加查询引擎中的
similarity_top_k
以检索更多的上下文片段,经过重排后可以减少到top_n
。pythonfrom llama_index.core.postprocessor import SentenceTransformerRerank rerank = SentenceTransformerRerank( top_n = 3, model = "BAAI/bge-reranker-base" ) ... query_engine = index.as_query_engine( similarity_top_k = 6, node_postprocessors = [rerank], ..., )
结语
在本文中,我们深入探讨了 RAG(Relevance-Generation)
系统的各个方面,从构建、效果评估到系统优化,力求为读者提供对 RAG 的全面且深入的理解。通过优化查询、灵活应用多级索引和路由技术,以及实施先进的检索策略,我们能够显著提升系统的效率和准确性,更好地满足用户需求。需要强调的是,**提升 RAG 系统性能是一个持续的过程,需不断评估、优化和迭代。**RAG 系统的未来发展前景广阔,随着技术的不断进步,它将在各行各业中发挥越来越重要的作用。