细数RAG的12个痛点,英伟达高级架构师亲授解决方案-人工智能

首页 2024-07-06 14:54:07

检索增强式生成(RAG)是一种使用检索提升语言模型的技术。具体来说,就是在语言模型生成答案之前,先从广泛的文档数据库中检索相关信息,然后利用这些信息来引导生成过程。这种技术能极大提升内容的准确性和相关性,并能有效缓解幻觉问题,提高知识更新的速度,并增强内容生成的可追溯性。RAG 无疑是最激动人心的人工智能研究领域之一。有关 RAG 的更多详情请参阅本站专栏文章《专补大模型短板的RAG有哪些新进展?这篇综述讲明白了》。

但 RAG 也并非完美,用户在使用时也常会遭遇一些「痛点」。近日,英伟达生成式AI高级解决方案架构师Wenqi Glantz 在 Towards Data Science 发布了一篇文章,梳理了 12 个 RAG 的痛点并给出了相应的解决方案。

文章目录如下:

痛点 1:内容缺失
痛点 2:错过排名靠前的文档
痛点 3:不在上下文中——合并策略的局限
痛点 4:未提取出来
痛点 5:格式错误
痛点 6:不正确的具体说明
痛点 7:不完备
痛点 8:数据摄取的可扩展性
痛点 9:结构化数据问答
痛点 10:从复杂 PDF 提取数据
痛点 11:后备模型
痛点 12:LLM 安全

其中 7 个痛点(见下图)来自 Barnett et al. 的论文《Seven Failure Points When Engineering a Retrieval Augmented Generation System》,此外还另外增加了 5 个常见痛点。

这些痛点对应的解决方案如下:?

痛点 1:内容缺失

知识库中缺失上下文。当知识库中没有答案时,RAG 系统会提供一个看似可信但并不正确的答案,而不会承认它不知道。用户会收到错误信息,遭遇挫折。

人们提出了两种解决方案:

清洁数据

输入垃圾,那也必定输出垃圾。如果你的源数据质量低劣,比如包含互相冲突的信息,那不管你的 RAG 工作构建得多么好,它都不可能用你输入的垃圾神奇地输出高质量结果。这个解决方案不仅适用于这个痛点,而且适用于本文列出的所有痛点。任何 RAG 工作流程想要获得优良表现,都必须先清洁数据。

下面列出了几个清洁数据的常用策略:

  • 移除噪声和不相关信息:这包括移除特殊字符、停用词(stop words,如 the 和 a)、HTML 标签。
  • 识别和纠正错误:包括拼写错误、错别字和语法错误。可以使用拼写检查器和语言模型等工具来解决这个问题。
  • 去重:移除重复数据记录或可能导致检索过程出现偏差的相似记录。

unstructured.io 的核心软件库提供了一整套清洁工具可以帮助解决这些数据清洁需求。值得一试。

更好的提词设计

对于因为信息缺乏而导致系统给出看似可信却不正确结果的问题,更好的提词设计能提供很大帮助。通过为系统给出「如果你不确定答案是什么,就告诉我你不知道」这样的指示,就能鼓励模型承认自己的局限,并更透明地向用户传达它的不确定。虽然不能保证 100% 准确度,但在清洁数据之后,精心设计 prompt 是最好的做法之一。

痛点 2:错过排名靠前的文档

初始检索过程中缺失上下文。在系统的检索组件返回的结果中,关键性的文档可能并不靠前。正确的答案被忽视了,这会导致系统无法给出准确响应。上述论文中写道:「问题的答案就在文档中,但排名不够高,就没有返回给用户。」

研究者提出了两种解决方案:

对 chunk_size 和 similarity_top_k 进行超参数微调

chunk_size 和 similarity_top_k 这两个参数可用于管理 RAG 模型的数据检索过程的效率和效果。调整这两个参数会影响被检索信息的计算效率和质量之间的权衡。作者在之前一篇文章中探索了对 chunk_size 和 similarity_top_k 进行超参数微调的细节:

请访问:https://medium.com/gitconnected/automating-hyperparameter-tuning-with-llamaindex-72fdd68e3b90?

下面给出了示例代码:
param_tuner = ParamTuner(param_fn=objective_function_semantic_similarity,param_dict=param_dict,fixed_param_dict=fixed_param_dict,show_progress=True,)results = param_tuner.tune()

objective_function_semantic_similarity 函数的定义如下,其中 param_dict 包含了参数 chunk_size 和 top_k 以及它们对应的值:
import osfrom llama_index.postprocessor.cohere_rerank import CohereRerankapi_key = os.environ["COHERE_API_KEY"]cohere_rerank = CohereRerank(api_key=api_key, top_n=2) # return top 2 nodes from rerankerquery_engine = index.as_query_engine(similarity_top_k=10, # we can set a high top_k here to ensure maximum relevant retrievalnode_postprocessors=[cohere_rerank], # pass the reranker to node_postprocessors)response = query_engine.query("What did Sam Altman do in this essay?",)

另外,还可以使用多种嵌入和重新排名工具评估和提升检索器的性能。

参阅:https://blog.llamaindex.ai/boosting-rag-picking-the-best-embedding-reranker-models-42d079022e83

此外,为了得到更好的检索性能,还能微调一个定制版的重新排名工具,其实现细节可访问:

博客链接:https://blog.llamaindex.ai/improving-retrieval-performance-by-fine-tuning-cohere-reranker-with-llamaindex-16c0c1f9b33b

痛点 3:不在上下文中——合并策略的局限

重新排名之后缺乏上下文。对于这个痛点,上述论文的定义为:「已经从数据库检索到了带答案的文档,但该文档没能成为生成答案的上下文。发生这种情况的原因是数据库返回了许多文档,之后采用了一种合并过程来检索答案。」

除了前文提到的增加重新排名工具和微调重新排名工具之外,我们还可以探索以下解决方案:

调整检索策略

LlamaIndex 提供了一系列从基础到高级的检索策略,可帮助研究者在 RAG 工作流程中实现准确的检索。

这里可以看到已分成不同类别的检索策略列表:https://docs.llamaindex.ai/en/stable/module_guides/querying/retriever/retrievers.html

  • 基于每个索引进行基本的检索
  • 高级检索和搜索
  • 自动检索
  • 知识图谱检索器
  • 组合/分层检索器

对嵌入进行微调

如果你使用开源的嵌入模型,那么为了实现更准确的检索,可以对嵌入模型进行微调。LlamaIndex 有一个微调开源嵌入模型的逐步教程,其中证明微调嵌入模型确实可以提升在多个评估指标上的表现:

教程链接:https://docs.llamaindex.ai/en/stable/examples/finetuning/embeddings/finetune_embedding.html

下面是创建微调引擎、运行微调、得到已微调模型的样本代码:
finetune_engine = SentenceTransformersFinetuneEngine(train_dataset,model_id="BAAI/bge-small-en",model_output_path="test_model",val_dataset=val_dataset,)finetune_engine.finetune()embed_model = finetune_engine.get_finetuned_model()

痛点 4:未提取出来

未正确提取上下文。系统难以从所提供的上下文提取出正确答案,尤其是当信息过载时。这会导致关键细节缺失,损害响应的质量。上述论文写道:「当上下文中有太多噪声或互相矛盾的信息时,就会出现这种情况。」

下面来看三种解决方案:

清洁数据

这个痛点的一个典型原因就是数据质量差。清洁数据的重要性值得一再强调!在责备你的 RAG 流程之前,请务必清洁你的数据。

prompt 压缩

LongLLMLingua 研究项目/论文针对长上下文情况提出了 prompt 压缩。通过将其整合进 LlamaIndex,我们可以将 LongLLMLingua 实现成一个节点后处理器,其可在检索步骤之后对上下文进行压缩,之后再将其传输给 LLM。LongLLMLingua 压缩的 prompt 能以远远更低的成本得到更高的性能。此外,整个系统会有更快的运行速度。

下面的代码设置了 LongLLMLinguaPostprocessor,其中使用了 longllmlingua 软件包来运行 prompt 压缩。

更多细节请访问这个笔记:
https://docs.llamaindex.ai/en/stable/examples/node_postprocessor/LongLLMLingua.html#longllmlingua
from llama_index.core.query_engine import RetrieverQueryEnginefrom llama_index.core.response_synthesizers import CompactAndRefinefrom llama_index.postprocessor.longllmlingua import LongLLMLinguaPostprocessorfrom llama_index.core import QueryBundlenode_postprocessor = LongLLMLinguaPostprocessor(instruction_str="Given the context, please answer the final question",target_token=300,rank_method="longllmlingua",additional_compress_kwargs={"condition_compare": True,"condition_in_question": "after","context_budget": "+100","reorder_context": "sort",? # enable document reorder},)retrieved_nodes = retriever.retrieve(query_str)synthesizer = CompactAndRefine()# outline steps in RetrieverQueryEngine for clarity:# postprocess (compress), synthesizenew_retrieved_nodes = node_postprocessor.postprocess_nodes(retrieved_nodes, query_bundle=QueryBundle(query_str=query_str))print("\n\n".join([n.get_content() for n in new_retrieved_nodes]))response = synthesizer.synthesize(query_str, new_retrieved_nodes)

LongContextReorder

论文《Lost in the Middle: How Language Models Use Long Contexts》观察到:当关键信息位于输入上下文的开头或末尾时,通常能获得最佳性能。为了解决这种「中部丢失」问题,研究者设计了 LongContextReorder,其做法是重新调整被检索节点的顺序,这对需要较大 top-k 的情况很有用。

下面的代码展示了如何在查询引擎构建期间将 LongContextReorder 定义成你的节点后处理器。更多细节,请参看这份笔记:
https://docs.llamaindex.ai/en/stable/examples/node_postprocessor/LongContextReorder.html
from llama_index.core.postprocessor import LongContextReorderreorder = LongContextReorder()reorder_engine = index.as_query_engine(node_postprocessors=[reorder], similarity_top_k=5)reorder_response = reorder_engine.query("Did the author meet Sam Altman?")

痛点 5:格式错误

输出的格式有误。当 LLM 忽视了提取特定格式的信息(如表格或列表)的指令时,就会出现这个问题,对此的解决方案有四个:

更好的提词设计

针对这个问题,可使用多种策略来提升 prompt:

  • 清晰地说明指令
  • 简化请求并使用关键词
  • 给出示例
  • 使用迭代式的 prompt 并询问后续问题

输出解析

为了确保得到所需结果,可以使用以下方式输出解析:

  • 为任意 prompt/查询提供格式说明
  • 为 LLM 输出提供「解析」

LlamaIndex 支持整合 Guardrails 和 LangChain 等其它框架提供的输出解析模块。

下面是可在 LlamaIndex 中使用的 LangChain 的输出解析模块的代码。更多细节请访问这份有关输出解析模块的文档:
https://docs.llamaindex.ai/en/stable/module_guides/querying/structured_outputs/output_parser.html
from llama_index.core import VectorStoreIndex, SimpleDirectoryReaderfrom llama_index.core.output_parsers import LangchainOutputParserfrom llama_index.llms.openai import OpenAIfrom langchain.output_parsers import StructuredOutputParser, ResponseSchema# load documents, build indexdocuments = SimpleDirectoryReader("../paul_graham_essay/data").load_data()index = VectorStoreIndex.from_documents(documents)# define output schemaresponse_schemas = [ResponseSchema(name="Education",description="Describes the author's educational experience/background.",),ResponseSchema( ? ? ? ?name="Work",description="Describes the author's work experience/background.",),]# define output parserlc_output_parser = StructuredOutputParser.from_response_schemas(response_schemas)output_parser = LangchainOutputParser(lc_output_parser)# Attach output parser to LLMllm = OpenAI(output_parser=output_parser)# obtain a structured responsequery_engine = index.as_query_engine(llm=llm)response = query_engine.query("What are a few things the author did growing up?",)print(str(response))

Pydantic 程序

Pydantic 程序是一个多功能框架,可将输入字符串转换为结构化的 Pydantic 对象。LlamaIndex 提供几类 Pydantic 程序:

  • LLM 文本补全 Pydantic 程序:这些程序使用文本补全 API 加上输出解析,可将输入文本转换成用户定义的结构化对象。
  • LLM 函数调用 Pydantic 程序:通过利用 LLM 函数调用 API,这些程序可将输入文本转换成用户指定的结构化对象。
  • 预封装 Pydantic 程序:其设计目标是将输入文本转换成预定义的结构化对象。

下面是来自 OpenAI pydantic 程序的代码。LlamaIndex 的文档给出了更多相关细节,并且其中还包含不同 Pydantic 程序的笔记本/指南的链接:
https://docs.llamaindex.ai/en/stable/module_guides/querying/structured_outputs/pydantic_program.html

OpenAI JSON 模式

OpenAI JSON 模式可让我们通过将 response_format 设置成 { "type": "json_object" } 来启用 JSON 模式的响应。当启用了 JSON 模式时,模型就只会生成能解析成有效 JSON 对象的字符串。虽然 JSON 模式会强制设定输出格式,但它无助于针对指定架构进行验证。

更多细节请访问这个文档:https://docs.llamaindex.ai/en/stable/examples/llm/openai_json_vs_function_calling.html

痛点 6:不正确的具体说明