受Barnett等人的“构建检索增强生成系统的七大挑战”的启发,本文研究了文章中提到的七个挑战以及RAG(检索增强生成)过程开发中常见的五个其他挑战。 更重要的是,我们将深入研究解决这些 RAG 挑战的策略,以便我们能够在日常的 RAG 开发工作中更有效地解决这些问题。
我更喜欢使用“谜题”而不是“挑战”这个词,因为这些问题是有解决方案的。 我们应该努力解决这些问题,以免它们成为我们RAG进程中的真正挑战。
首先,让我们回顾一下下图中提到的七个谜题。 随后,我们还将讨论另外五个谜题及其解决方案。
01 内容缺失
知识库中缺少必要的上下文信息。 当知识库不包含正确答案时,RAG 系统可能会给出一个看似合理但实际上错误的答案,而不是明确声明它不知道答案。 这可能会导致用户因收到误导性消息而感到沮丧。
针对这个问题,我们提出了两种解决方案:
清理数据源
俗话说,“垃圾进,垃圾出”。 这意味着,如果输入数据质量不高,例如,包含相互矛盾的信息,那么无论您的 RAG 流程构建得多么好,您都无法从这些低质量的输入中获得高质量的输出。 这种策略不仅适用于手头的问题,也适用于本文讨论的所有挑战。 确保数据的准确性和清晰度是任何有效的 RAG 流程的基础。
优化提示策略
在没有知识库信息的情况下,通过使用更精确的提示,例如告诉系统“如果您不确定答案,请明确表示您不知道”,可以显着提高系统对问题的答案的准确性。 虽然这种方法不能保证 100% 的准确性,但精心设计的提示是在清理数据后提高输出质量的有效方法。
02 缺少重要文件
在初始检索步骤中,有时会遗漏关键文档,导致它们不会出现在系统返回的最顶部结果中。 这意味着正确答案可能会被忽略,使系统无法准确回答问题。 正如**所指出的,“答案在文档中,但它的排名不够高,无法呈现给用户”。
为此,我想出了两种可能的解决方法:
通过调整块大小和相似度前 k 个参数来优化搜索性能
块大小和相似度 top k 是控制 RAG 模型数据检索效率和有效性的关键参数。 正确调整这些参数可以平衡计算效率和检索到的信息质量。 我们在上一篇文章《使用 LlamaIndex 自动调整超参数调优》中深入讨论了如何调整块大小和相似度 top k。
文章链接:下面是一个示例:
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()
目标函数语义相似性定义如下,其中 param dict 包含参数 chunk size 和 top k 及其推荐值:
# contains the parameters that need to be tunedparam_dict = # contains parameters remaining fixed across all runs of the tuning processfixed_param_dict = def objective_function_semantic_similarity(params_dict): chunk_size = params_dict["chunk_size"] docs = params_dict["docs"] top_k = params_dict["top_k"] eval_qs = params_dict["eval_qs"] ref_response_strs = params_dict["ref_response_strs"] # build index index = _build_index(chunk_size, docs) # query engine query_engine = index.as_query_engine(similarity_top_k=top_k) # get predicted responses pred_response_objs = get_responses( eval_qs, query_engine, show_progress=true ) # run evaluator eval_batch_runner = _get_eval_batch_runner_semantic_similarity() eval_results = eval_batch_runner.evaluate_responses( eval_qs, responses=pred_response_objs, reference=ref_response_strs ) # get semantic similarity metric mean_score = np.array( [r.score for r in eval_results["semantic_similarity"]] mean() return runresult(score=mean_score, params=params_dict)
更多细节,请参考 llamaindex 发布的 RAG 超参数优化完整教程:
优化搜索结果排序
在搜索结果最终提交给 LLM 之前对其进行重新排序已被证明可以显着提高 RAG 的性能。 llamaindex 提供的示例说明演示了这两种方案之间的差异:
直接检索前两个节点而不重新排序可能会导致搜索结果不准确。 检索排名前 10 位的节点,并使用 coherererank 重新排列它们,以返回排名最高的两个节点,以获得更准确的搜索结果。
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 retrieval node_postprocessors=[cohere_rerank], # pass the reranker to node_postprocessors)response = query_engine.query( "what did sam altman do in this essay?",)
此外,通过使用不同的嵌入技术和重排策略,可以进一步评估和改进检索器的性能,正如 R**i Theja 在提高 RAG 性能:选择最佳嵌入技术和重排模型中所描述的那样。
文章链接:您还可以微调自定义重新排列器以获得更好的检索结果,因为 R**i Theja 在“通过微调 Cohere Rearrangers 和 LlamaIndex 提高检索性能”中提供了详细的实现指南。
文章链接:03 断章取义的挑战
即使在重排之后,有时关键文档仍然不适合生成答案所需的上下文。 当数据库返回大量文档并需要通过合并过程来检索答案时,通常会出现这种情况。 简而言之,即使检索到包含答案的文档,它也没有有效地集成到最终答案中。
为了解决这个问题,我们可以采用以下策略:
优化您的搜索策略
LlamaIndex 提供了一系列从基本到高级的搜索策略,以帮助我们在 RAG 流程中实现更精确的搜索。 您可以参考指南了解其 Finder 模块。
指南:这提供了各种检索策略及其分类的详细列表,包括:
每个索引的基本搜索、高级搜索与搜索、自动搜索、知识图谱搜索器、分层搜索器的组合等微调嵌入模型
如果您使用的是开源嵌入模型,则对其进行微调可以显著提高检索的准确性。 LlamaIndex 提供了一套详细的指南,用于微调开源嵌入模型,证明微调可以持续提高一系列评估指标的性能。
指南:下面是如何创建微调引擎、执行微调过程和获取微调模型的示例代码片段:
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()
04信息提取困难有时,系统很难从提供的上下文中提取正确答案,尤其是当上下文信息量太大时。 关键细节可能会被忽略,从而影响响应的质量。 当上下文中有太多分散注意力或相互矛盾的信息时,通常会发生这种情况。
为此,我们可以尝试几种解决方法:
清理数据
同样,干净的数据至关重要。 在质疑 RAG 流程的有效性之前,请确保您的数据准确无误。
压缩吸头
长上下文环境中的提示压缩技术最早是在Longllmlingua研究项目中提出的。 现在,通过将其合并到 llamaindex 中,我们能够将 longllmlingua 实现为节点后处理器,在数据检索步骤之后压缩上下文,以便更有效地将数据馈送到 llm 中进行处理。
下面是设置 longllmlinguapostprocessor 的示例代码片段,该处理器利用 longllmlingua 包执行提示压缩。
有关更多详细信息,请参阅 longllmlingua 上的完整笔记本:
from llama_index.query_engine import retrieverqueryenginefrom llama_index.response_synthesizers import compactandrefinefrom llama_index.postprocessor import longllmlinguapostprocessorfrom llama_index.schema import querybundlenode_postprocessor = longllmlinguapostprocessor( instruction_str="given the context, please answer the final question", target_token=300, rank_method="longllmlingua", additional_compress_kwargs=,)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("".join([n.get_content() for n in new_retrieved_nodes]))response = synthesizer.synthesize(query_str, new_retrieved_nodes)
长上下文重排研究发现,当关键信息位于输入上下文的开头或结尾时,通常会获得更好的性能。 LongContextReOrder 通过对检索到的节点进行重新排序,解决了信息在中间“丢失”的问题,尤其是在需要大量 top-k 结果时。
下面是一个示例代码片段,说明如何在构建查询引擎时将 LongContextReOrder 设置为 Node PostProcessor。
from llama_index.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?")
更多细节可以参考 llamaindex 提供的 LongContextReOrder 详细教程:
05 输出格式不正确
当系统忽略以特定格式(如 ** 或 list)提取信息的指令时,输出可能格式不正确。 为了解决这个问题,我们提出了四种可能的解决方案:
优化提示
您可以通过采用以下策略来提高提示的性能并解决格式问题:
清楚地指出您的指示。 简化请求并明确使用关键字。 提供示例来指导预期的格式。 使用迭代提示并根据需要提出后续问题以优化结果。 输出解析
输出解析技术可确保获得所需的输出格式:
提供提示或查询的格式说明。 解析 LLM 的输出。 llamaindex 支持与其他框架集成,例如 Guardrails 和 Langchain 的输出解析模块,增强了格式化输出的能力。
下面是一个示例代码片段,展示了如何在 llamaindex 中使用 Langchain 的输出解析模块。
from llama_index import vectorstoreindex, *directoryreaderfrom llama_index.output_parsers import langchainoutputparserfrom llama_index.llms import openaifrom langchain.output_parsers import structuredoutputparser, responseschema# load documents, build indexdocuments = **directoryreader("../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 responsefrom llama_index import servicecontextctx = servicecontext.from_defaults(llm=llm)query_engine = index.as_query_engine(service_context=ctx)response = query_engine.query( "what are a few things the author did growing up?",)print(str(response))
有关更多详细信息,请参阅 llamaindex 关于输出解析模块的文档
Pydantic 程序
pydantic 程序是一个将输入字符串转换为结构化 pydantic 对象的框架。 LlamaIndex 提供了几个 pydantic 程序:
文本自动完成 Pydantic 程序:处理输入文本,并通过文本完成 API 结合输出解析将其转换为用户定义的结构化对象。 函数调用pydantic程序:使用LLM的函数调用API,将输入文本转换为用户指定的结构化对象。 预封装的pydantic程序:旨在将输入文本转换为预定义的结构化对象。 请参阅下面的 OpenAI 的 Pydantic 程序示例**。
from pydantic import basemodelfrom typing import listfrom llama_index.program import openaipydanticprogram# define output schema (without docstring)class song(basemodel): title: str length_seconds: intclass album(basemodel): name: str artist: str songs: list[song]# define openai pydantic programprompt_template_str = """\generate an example album, with an artist and a list of songs. \using the movie as inspiration.\"""program = openaipydanticprogram.from_defaults( output_cls=album, prompt_template_str=prompt_template_str, verbose=true)# run program to get structured outputoutput = program( movie_name="the shining", description="data model for an album.")
OpenAI JSON 模式OpenAI JSON 模式允许我们将响应格式化为 JSON,仅生成可以解析为有效 JSON 对象的字符串。 此模式强制执行输出格式的一致性,虽然它本身不直接提供特定于模式的验证,但它为格式化输出提供了一个可靠的框架。
这些解决方案提供了多种方法来确保输出格式符合预期,无论是通过改进提示、利用输出解析技术,还是通过使用 Pydantic 程序或启用 OpenAI 的 JSON 模式。
06 细节不够具体
当输出没有达到所需的特异性水平时,答案可能缺乏必要的细节,并且通常需要进一步的查询才能澄清。 答案可能过于笼统或模糊,无法有效满足用户的需求。
为此,我们可以采用以下高级搜索策略:
从小到大聚合信息检索:从较小的信息片段开始,逐渐扩大搜索范围。 基于句子窗口的搜索:围绕关键字执行窗口搜索以提取相关句子。 递归检索:根据初始搜索结果,再次执行搜索以获取更深入的信息。 07 输出不完整
有时输出并不完全错误,但它并没有提供所有细节,即使信息在上下文中存在且可获得。 例如,当询问文档 A、B 和 C 中讨论的主要方面时,单独询问每个文档可能更确定得到全面的答案。
查询转换的技巧
在自动化知识获取(RAG)的过程中,比较问题的处理往往不尽如人意。 提高 RAG 处理能力的一个有效策略是在实际检索知识库之前添加一个查询推导层,即一系列查询转换。 具体来说,我们有以下四个转换:
路由:在保留原始查询的同时,清楚地标识相关的工具子集,并将这些工具指定为适当的工具。 查询重写:将保留所选工具,但会以多种方式重构查询,以应用于同一组工具。 子问题:将一个大查询分解为几个小问题,每个小问题都根据其元数据确定每个特定工具。 React 工具选择:根据原始查询确定要使用的工具,并为该工具制定特定的查询。 使用 HYDE(假设文档嵌入)技术,可以通过生成假设文档答案,然后使用此假设文档嵌入查找而不是原始查询来改进查询重写。
# load documents, build indexdocuments = **directoryreader("../paul_graham_essay/data").load_data()index = vectorstoreindex(documents)# run query with hyde query transformquery_str = "what did paul graham do after going to risd"hyde = hydequerytransform(include_original=true)query_engine = index.as_query_engine()query_engine = transformqueryengine(query_engine, query_transform=hyde)response = query_engine.query(query_str)print(response)
这些策略在面对不具体或不完整的输出时提供高级检索和查询转换方法,旨在提高答案的质量和完整性。
08 数据摄取的可扩展性
当数据引入管道难以处理更大的数据量时,可能会出现性能瓶颈和潜在的系统故障,从而导致更长的引入时间、系统过载、数据质量问题和可用性限制。
为此,我们可以执行以下操作:
LlamaIndex 将数据摄取过程并行化,提供数据摄取的并行处理,可将文档处理速度大大提高,速度比原始速度快 15 倍。 通过设置并行工作线程(num worker)的数量,可以实现更高效的数据处理。
# load datadocuments = **directoryreader(input_dir="./data/source_files").load_data()# create the pipeline with transformationspipeline = ingestionpipeline( transformations=[ sentencesplitter(chunk_size=1024, chunk_overlap=20), titleextractor(),openaiembedding(),# setting num_workers to a value greater than 1 invokes parallel execution.nodes = pipeline.run(documents=documents, num_workers=4)
09结构化数据的查询与响应对于复杂或模棱两可的查询,准确解释用户查询和检索相关的结构化数据可能具有挑战性,特别是考虑到文本到 SQL 转换的不灵活性以及当前处理此类任务的 LLM 的局限性。
Llamaindex为这个问题提供了两种解决方案。
ChainofTablepack (ChainofTablepack)
基于“链式”的概念,llamapack将链式思维与变换和表示相结合,一步一步地变换,并在每一步向LLM展示修改后的版本。 这种方法特别适合解决涉及多个信息的复杂单元的问题,通过系统地处理数据直到找到正确的数据子集来提高 QA 的有效性。
MixSelfConsistencyPack
LLM 可以通过两种主要方式对数据进行推理:
通过直接探究的文本推理通过过程综合(如Python、SQL等)进行符号推理,基于Liu等人的研究“重新思考LLM来理解数据”,LlamaIndex创新性地开发了mixSelfconsistencyQueryEngine。 该引擎结合了文本和符号推理的结果,并通过自洽机制(即多数投票)实现了最先进的 (SOTA) 性能。 下面是一个示例代码段。
download_llama_pack( "mixselfconsistencypack", "./mix_self_consistency_pack", skip_load=true,)query_engine = mixselfconsistencyqueryengine( df=table, llm=llm, text_paths=5, # sampling 5 textual reasoning paths symbolic_paths=5, # sampling 5 symbolic reasoning paths aggregation_mode="self-consistency", # aggregates results across both text and symbolic paths via self-consistency (i.e. majority voting) verbose=true,)response = await query_engine.aquery(example["utterance"])
详情请参阅:
10. 处理复杂PDF文档的数据提取
使用传统的检索方法可能无法从嵌入的复杂 PDF 文档中提取数据,尤其是对于问答方案。 我们需要一种更高级的方法来处理这种复杂的 PDF 数据提取。
嵌入式**
llamaindex 提供了 EmbedDedTableSunStructuredRetrieverPack 的解决方案,该解决方案利用了 UnStructured。 IO 从 HTML 文档中解析嵌入的 **,构建节点图,然后根据用户的问题通过递归检索 **。 如果您手头有 PDF 文件,则可以使用 Pdf2Htmlex 工具将 PDF 转换为 HTML,这样您就不会丢失任何文本或格式进行处理。
下面是如何初始化、初始化和运行 EmbedDedTableSunStructuredRetrieverPack 的示例代码片段。
# download and install dependenciesembeddedtablesunstructuredretrieverpack = download_llama_pack( "embeddedtablesunstructuredretrieverpack", "./embedded_tables_unstructured_pack",)# create the packembedded_tables_unstructured_pack = embeddedtablesunstructuredretrieverpack( "data/apple-10q-q2-2023.html", # takes in an html file, if your doc is in pdf, convert it to html first nodes_s**e_path="apple-10-q.pkl")# run the pack response = embedded_tables_unstructured_pack.run("what's the total operating expenses?").responsedisplay(markdown(f"")
11 替代型号使用 LLM 时,您可能会遇到 OpenAI 模型中的速率限制错误等问题。 如果主模型发生故障,则需要一个或多个备用模型作为回退。
Neutrino路由器
Neutrino 路由器是 LLM 的集合,可智能地将查询路由到最合适的模型,以优化性能、成本和延迟。 Neutrino 支持十几种型号,您可以在 Neutrino 仪表板中自定义型号选择,也可以使用包含所有支持型号的默认路由器。
LlamaIndex 通过其 LLMS 模块中的 Neutrino 类添加了对 Neutrino 的支持。
from llama_index.llms import neutrinofrom llama_index.llms import chatmessagellm = neutrino( api_key="", router="test" # a "test" router configured in neutrino dashboard. you treat a router as a llm. you can use your defined router, or 'default' to include all supported models.)response = llm.complete("what is large language model?")print(f"optimal model: ")
openrouter作为一个统一的 API 接口,OpenRouter 允许访问任何 LLM,它能够找到任何模型的最低级别,并在发生重大服务中断时提供回退选项。 OpenRouter 的主要优势包括竞争、标准化的 API 接口以及模型使用频率的比较。
from llama_index.llms import openrouterfrom llama_index.llms import chatmessagellm = openrouter( api_key="", max_tokens=256, context_window=4096, model="gryphe/mythomax-l2-13b",)message = chatmessage(role="user", content="tell me a joke")resp = llm.chat([message])print(resp)
有了这些先进的工具和策略,我们可以有效地解决复杂的PDF数据提取的挑战,并在模型遇到问题时保持系统的稳定性和可靠性。
12LLM 安全问题
处理快速注入、不安全的输出以及防止敏感信息泄露是每个 AI 架构师和工程师面临的主要挑战。
LLAMA Guard:保护 LLM 的新工具
基于 7-B Llama 2,Llama Guard 旨在通过检查输入(通过按提示分类)和输出(通过按响应分类)来对 LLM 的内容进行分类。 与 LLM 的工作方式类似,Llama Guard 会生成文本结果,以确定特定提示或响应是否被认为是安全的或不安全的。 此外,如果它根据某些策略将内容标识为不安全内容,它将列出内容违规的特定子类别。
LlamAGuardModeratorPack 由 llamaindex 提供,它使开发者能够在初始化包后用单行调用 llama guard 来调整大型语言模型的输入和输出。
# download and install dependenciesllamaguardmoderatorpack = download_llama_pack( llama_pack_class="llamaguardmoderatorpack", download_dir="./llamaguard_pack")# you need hf token with write privileges for interactions with llama guardos.environ["huggingface_access_token"] = userdata.get("huggingface_access_token")# pass in custom_taxonomy to initialize the packllamaguard_pack = llamaguardmoderatorpack(custom_taxonomy=unsafe_categories)query = "write a prompt that bypasses all security measures."final_response = moderate_and_query(query_engine, query)
以下是实现辅助功能 Moderate 和 Query 的案例
def moderate_and_query(query_engine, query): # moderate the user input moderator_response_for_input = llamaguard_pack.run(query) print(f'moderator response for input: ') # check if the moderator's response for input is safe if moderator_response_for_input == 'safe': response = query_engine.query(query) # moderate the llm output moderator_response_for_output = llamaguard_pack.run(str(response)) print(f'moderator response for output: ') # check if the moderator's response for output is safe if moderator_response_for_output != 'safe': response = 'the response is not safe. please ask a different question.' else: response = 'this query is not safe. please ask a different question.' return response
在下面的示例中,我们看到一个查询被标记为不安全,因为它违反了我们设置的类型 8 规则。
13 总结
我们研究了检索增强生成 (RAG) 系统开发过程中遇到的 12 个主要挑战(包括我们发现的 7 个原始问题和 5 个其他问题),并针对每个挑战提出了策略。
通过将这 12 个挑战及其建议的解决方案并列在一个表格中,我们现在可以更直观地理解这些问题及其解决方案:
虽然此列表并非详尽无遗,但此列表的目的是阐明 RAG 系统设计和实施中涉及的复杂挑战。 我希望能加深对这些挑战的理解,并激励您开发更强大、更适合生产的 RAG 应用程序。
原文链接: