关于RAG优化的几个小技巧

在这里插入图片描述

一、背景说明

RAG技术为大型语言模型(LLMs)提供了从数据源检索到的信息,以支撑其生成的答案。简而言之,RAG就是搜索加上LLM提示,你让模型在提供的信息上下文中回答问题。查询和检索到的上下文都被注入到发送给LLM的提示中。
传统的建模方式:
典型的RAG向量化应用程序有两个主要组件:
索引:从源中获取数据并对其进行向量化索引。
检索和生成:运行时接受用户查询转化成向量化数据,并从索引中检索相关数据,然后将其传递给LLM,生成内容。
实际使用过程中,上面简单的流程经常效果不能满足业务需求,核心问题在于如何检索并处理好传给LLM的内容,下面介绍几种优化思路以及LangChain的实现。

二、优化方法

2.1 多层索引

多层索引技术主要是针对海量的文档检索的处理策略,对面临需要从海量的文档中搜索、找到相关信息,并将这类信息综合成一个答案,并应用其来源。一般有2种方式建立多层索引来优化这类问题。
一种是为文档建立多级索引,比如建立文档的类别标签,在处理指定业务场景下的问答时,只需要对该类别下的知识库进行检索,如:

处理医疗领域的RAG问答业务时,可能会涉及很多的医学知识库,假设有1000篇文档,包括10个不同的医学研究方向。
当涉及的业务问题范畴只涉及外科相关知识时,可以通过类别标签进行筛选过滤,而无需从1000篇文档中进行全局检索。

第二种是从两个索引——一个是有该文档的摘要组成,一个由文档切片组成。在处理学术论文时,这类方法一般有较好的表现。这类方法的思路是分为两个步骤进行检索,首先通过摘要部分进行召回检索,过滤出可能和当前问题相关的候选文档集,然后再利用切片索引,从候选的文档集中进行检索,召回相关的知识切片。

2.2 假设性问题

首先解释一下什么是假设性问题: 假设性问题是一种提问方式,它基于一个或多个假设的情况或前提来提出问题。在对知识库中文档内容进行切片时,是可以以该切片为假设条件,利用LLM预先设置几个候选的相关性问题的,也就是说,这几个候选的相关性问题是和切片的内容强相关的。
在构建切片索引时,可以将对应的假设性问题进行向量化,并同时存储于向量库中。在RAG中的召回步骤中,对这个问题向量索引进行查询搜索,然后路由到原始文本块,并将它们作为上下文发送给LLM以获取答案。这种方法提高了搜索质量,因为与原始文本块相比,查询和假设问题之间的语义相似性更高。
除此以外,还有一种候选的假设性问题解决方案,和上述策略是对应的,它的核心思想可以表示为:
在要求LLM给定查询信息时,用LLM预先生成一个假设的响应,然后将其向量和查询向量一起使用,进一步提升检索的质量。示例如下:

问题: 感冒了怎么办? 大模型预设响应: 感冒了需要多休息,喝水,必要时就医。

召回时用问题和大模型预设响应进行拼接,形成新的prompt内容进行召回。

2.3 拓展上下文

检索较小的块以获得更好的搜索质量,但将周围的上下文一起拼接起来传给LLM推理。当然在索引的时候就要把数据处理好,搜索的时候才能找到周围的上下文。这么做的好处,就是小的文本块可以提升搜索的准确率,而扩展上下文则可以给LLM更丰富的内容。这里有两种拓展上下文的方式可以作为参考:
一种处理的方案是,通过分割和存储小块数据来实现,在检索过程中,它首先获取小块,然后查找这些块的父ID,并返回那些较大的文档。这种方式在langchain里有进行封装。
在这里插入图片描述

from langchain.retrievers import ParentDocumentRetriever

from langchain.storage import InMemoryStore
from langchain_community.document_loaders import TextLoader
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter

# This text splitter is used to create the child documents
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
# The vectorstore to use to index the child chunks
vectorstore = Chroma(
    collection_name="full_documents", embedding_function=OpenAIEmbeddings()
)
# The storage layer for the parent documents
store = InMemoryStore()
retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
)

第二种是对更大的块进行检索,有时,完整文档可能太大而无法按原样检索它们。在这种情况下,我们真正想做的是首先将原始文档分割成更大的块,然后将其分割成更小的块。然后我们索引较小的块,但在检索时我们检索较大的块(但仍然不是完整的文档)。

# This text splitter is used to create the parent documents
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000)
# This text splitter is used to create the child documents
# It should create documents smaller than the parent
child_splitter = RecursiveCharacterTextSplitter(chunk_size=400)
# The vectorstore to use to index the child chunks
vectorstore = Chroma(
    collection_name="split_parents", embedding_function=OpenAIEmbeddings()
)
# The storage layer for the parent documents
store = InMemoryStore()

retriever = ParentDocumentRetriever(
    vectorstore=vectorstore,
    docstore=store,
    child_splitter=child_splitter,
    parent_splitter=parent_splitter,
)

retriever.add_documents(docs)

2.4 融合检索

将检索到的结果与不同的相似性分数适当地结合起来——这个问题通常是在倒排融合算法的帮助下解决的,该算法将检索得到的结果重新排序以获得最终输出。在LangChain中,这是在EnsembleRetriever类中实现的,它结合了您定义的检索器列表,例如faiss矢量索引和基于BM25的检索器,并使用RRF进行重新排序。混合或融合搜索通常会提供更好的检索结果,因为两种互补的搜索算法结合在一起,同时考虑了查询和存储文档之间的语义相似性和关键字匹配。
在这里插入图片描述

from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

doc_list_1 = [
    "I like apples",
    "I like oranges",
    "Apples and oranges are fruits",
]

# initialize the bm25 retriever and faiss retriever
bm25_retriever = BM25Retriever.from_texts(
    doc_list_1, metadatas=[{"source": 1}] * len(doc_list_1)
)
bm25_retriever.k = 2

doc_list_2 = [
    "You like apples",
    "You like oranges",
]

embedding = OpenAIEmbeddings()
faiss_vectorstore = FAISS.from_texts(
    doc_list_2, embedding, metadatas=[{"source": 2}] * len(doc_list_2)
)
faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={"k": 2})

# initialize the ensemble retriever
ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5]
)

docs = ensemble_retriever.invoke("apples")
docs

2.5 过滤与压缩

检索容易遇到的一个问题,当把数据向量化索引到数据库时,不太可能预想到对应的用户问题会是什么。这意味着,与查询最相关的信息可能隐藏在一个包含大量不相关文本的文档中,如果都一股脑传给LLM,不仅费用高(肉疼)而且效果可能也会受影响。
LLMChainFilter是一个简单的压缩器,使用LLM链来决定最初检索到的文档中的哪些要过滤掉,哪些要返回,而无需操作文档内容。嗯。。。这个,我就是因为不想调用LLM费钱处理才采用这个方案的吗?EmbeddingsFilter通过嵌入文档和查询并只返回那些与查询具有足够相似嵌入的文档,提供了一个更便宜、更快的选项。
LLMChainFilter的用法:

from langchain.retrievers.document_compressors import LLMChainFilter

_filter = LLMChainFilter.from_llm(llm)
compression_retriever = ContextualCompressionRetriever(
    base_compressor=_filter, base_retriever=retriever
)

compressed_docs = compression_retriever.get_relevant_documents(
    "What did the president say about Ketanji Jackson Brown"
)
pretty_print_docs(compressed_docs)

EmbeddingsFilter的用法:

from langchain.retrievers.document_compressors import EmbeddingsFilter
from langchain_openai import OpenAIEmbeddings

embeddings = OpenAIEmbeddings()
embeddings_filter = EmbeddingsFilter(embeddings=embeddings, similarity_threshold=0.76)
compression_retriever = ContextualCompressionRetriever(
    base_compressor=embeddings_filter, base_retriever=retriever
)

compressed_docs = compression_retriever.get_relevant_documents(
    "What did the president say about Ketanji Jackson Brown"
)
pretty_print_docs(compressed_docs)

2.6 元数据过滤

我们已经习惯了SQL查询的时候增加Where的限制条件,这个可以精确查询所需的数据。RAG也不是所有的时候都要通过语义或关键词检索。比如检索“所有评分大于8分的电影”,或者我们公司实际的业务场景,检索的时候只需要明确某个品牌的数据。
通过元数据过滤实现精确过滤,和语义检索配合。在存储数据块到向量数据块时,需要先处理好元数据,在我们公司的场景中,就是把所属的品牌ID一起存储。检索的时候可以利用大模型的理解能力,把自然语言转成元数据过滤。看看LangChain的实现,metadata_field_info是pydantic的格式。
在这里插入图片描述

from langchain.llms import OpenAI
from langchain.retrievers.self_query.base import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo
 
metadata_field_info=[
    AttributeInfo(
        name="genre",
        description="The genre of the movie", 
        type="string or list[string]", 
    ),
    AttributeInfo(
        name="year",
        description="The year the movie was released", 
        type="integer", 
    ),
    AttributeInfo(
        name="director",
        description="The name of the movie director", 
        type="string", 
    ),
    AttributeInfo(
        name="rating",
        description="A 1-10 rating for the movie",
        type="float"
    ),
]
document_content_description = "Brief summary of a movie"
llm = OpenAI(temperature=0)
retriever = SelfQueryRetriever.from_llm(llm, vectorstore, document_content_description, metadata_field_info, verbose=True)

2.7 时间加权

对于一些类似客服FAQ的场景,检索命中越频繁,数据的权重就越高,可以使用了语义相似性和时间衰减的组合。LangChain给出的计算公式是这样的。
在这里插入图片描述
hours_passed是指自上次访问检索器中的对象以来(而不是自创建对象以来)经过的小时数。这意味着频繁访问的对象保持“新鲜”。
还有另一种时间权重,比如新闻资讯,创建时间越近权重越高,甚至可以为其设置过期时间。
RAG应用系统中,大家比较关心的因素:准确率、速度、成本。上面讨论的方案基本上都是提升准确率的,同时带来的问题就是速度下降,部分方案会成本上升。
另一方面,在实际的业务场景中,准确率可能是现阶段最重要的问题,业务人员对于应用的要求可能是90分,而LLM目前的能力只能达到60分,如果不能通过其它的方式提升效果,那么应用可能无法在实际场景中使用了。

### RAG模型的部署指南与解决方案 RAG(Retrieval-Augmented Generation)是一种结合了检索模块和生成模块的方法,能够显著提升自然语言处理任务的效果。以下是关于RAG模型部署的一些关键点以及可能遇到的问题及其解决方案。 #### 部署环境准备 为了成功部署RAG模型,需要确保具备合适的计算资源和支持框架。通常情况下,RAG模型依赖于强大的GPU支持来加速推理过程[^1]。此外,在实际应用中还需要考虑以下因素: - **硬件需求**:推荐使用NVIDIA A100或其他高性能显卡以满足实时查询的需求。 - **软件栈配置**:建议基于Docker容器化技术构建统一的服务运行环境,从而简化跨平台迁移工作流程[^2]。 #### 数据管理策略 由于RAG架构中的检索部分高度依赖高质量的知识库作为输入源材料,因此合理规划并维护这些外部存储至关重要。具体措施如下所示: - 定期更新索引文件至最新版本状态以便获取更加精准的结果反馈; - 对敏感字段实施脱敏操作防止潜在隐私泄露风险发生; ```bash docker run --gpus all -it rag_model_service:latest bash ``` 上述命令展示了如何利用Docker启动带有GPU加速功能的支持服务实例。 #### 性能优化技巧 针对不同场景下的性能瓶颈问题可以采取相应的调整手段来进行改善: - 减少不必要的特征维度数量降低整体复杂度; - 应用批量处理机制提高吞吐量表现水平; 通过以上方法可以在一定程度上缓解因规模扩大带来的压力挑战. #### 故障排查方向 当面对无法正常工作的状况时可以从以下几个方面入手分析原因所在: - 检查网络连接是否稳定可靠 ; - 确认数据库表结构定义正确无误 ; 如果仍然找不到确切答案则可尝试联系官方技术支持团队寻求进一步帮助 .
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东尔科技

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值