论文地址:archive.is/bNbZo
· Pain Point 1: Missing Content 内容缺失
· Pain Point 2: Missed the Top Ranked Documents 错过排名靠前的文档
· Pain Point 3: Not in Context — Consolidation Strategy Limitations 不在上下文中 — 整合战略的局限性
· Pain Point 4: Not Extracted 未提取
· Pain Point 5: Wrong Format 格式错误
· Pain Point 6: Incorrect Specificity 不正确的具体性
· Pain Point 7: Incomplete 不完整
· Pain Point 8: Data Ingestion Scalability 数据摄入的可扩展性
· Pain Point 9: Structured Data QA 结构化数据QA
· Pain Point 10: Data Extraction from Complex PDFs 从复杂PDF中提取数据
· Pain Point 11: Fallback Model(s) 回退模型
· Pain Point 12: LLM Security LLM安全
受Barnett等人的论文《设计检索增强生成系统时的七个故障点》的启发,让我们在本文中探讨论文中提到的七个失效点以及开发RAG管道时的五个额外常见痛点。更重要的是,我们将深入研究这些RAG痛点的解决方案,以便在日常RAG开发中更好地解决这些痛点。
我用“痛点”而不是“失败点”,主要是因为这些点都有相应的解决方案。让我们试着在它们成为RAG管道中的故障之前进行修复。
首先,让我们研究一下上述论文中提到的七个痛点;请参阅下图。然后,我们将添加另外五个痛点及其建议的解决方案。
当实际答案不在知识库中时,RAG系统提供了一个看似合理但不正确的答案,而不是说它不知道。用户收到误导性信息,导致沮丧。
我们提出了两种解决方案:清理数据,更好的提示
垃圾进,垃圾出。如果你的源数据质量很差,比如包含冲突信息,无论你构建的RAG管道有多好,它都无法从你提供的垃圾中输出黄金。这个拟议的解决方案不仅适用于这个痛点,也适用于本文中列出的所有痛点。干净的数据是任何运行良好的RAG管道的先决条件。
在由于知识库中缺乏信息而导致系统可能提供看似合理但不正确的答案的情况下,更好的提示会有很大帮助。通过用“如果你不确定答案,告诉我你不知道”等提示来指导系统,你鼓励模型承认其局限性,并更透明地传达不确定性。无法保证100%的准确性,但制作提示是清理数据后所能做的最大努力之一。
重要文档可能不会出现在系统检索组件返回的顶部结果中。忽略了正确的答案,导致系统无法提供准确的响应。该报暗示,“问题的答案在文档中,但排名不够高,无法返回给用户”。
我想到了两个建议的解决方案:chunk_size和similarity_top_k的超参数调整
chunk_size和similarity_top_k都是用于管理RAG模型中数据检索过程的效率和有效性的参数。调整这些参数可能会影响计算效率和检索信息质量之间的权衡。我们在上一篇文章《使用LlamaIndex自动进行超参数调整》中探讨了chunk_size和similarity_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()
函数objective_function_semantic_eximality定义如下,param_dict包含参数chunk_size和top_k及其相应的建议值:
# contains the parameters that need to be tuned
param_dict = {"chunk_size": [256, 512, 1024], "top_k": [1, 2, 5]}
# contains parameters remaining fixed across all runs of the tuning process
fixed_param_dict = {
"docs": documents,
"eval_qs": eval_qs,
"ref_response_strs": ref_response_strs,
}
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指南演示了以下两者之间的区别:
import os
from llama_index.postprocessor.cohere_rerank import CohereRerank
api_key = os.environ["COHERE_API_KEY"]
cohere_rerank = CohereRerank(api_key=api_key, top_n=2) # return top 2 nodes from reranker
query_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?",
)
此外,您可以使用各种嵌入和重新排序来评估和增强检索器的性能,如Ravi Theja的增强RAG:选择最佳的嵌入和重排序模型中所述。
此外,您可以微调自定义重新排序器以获得更好的检索性能,Ravi Theja的《通过微调Cohere reranker with LlamaIndex来提高检索性能》中记录了详细的实现。
该论文定义了这一点:“有答案的文档是从数据库中检索到的,但没有进入生成答案的上下文。当从数据库中返回许多文档,并进行合并过程来检索答案时,就会发生这种情况”。
除了如上所述添加重新排序器并微调重新排序器外,我们还可以探索以下建议的解决方案:
LlamaIndex提供了一系列检索策略,从基本到高级,帮助我们在RAG管道中实现准确的检索。查看检索器模块指南,了解所有检索策略的综合列表,分为不同类别。
如果你使用开源嵌入模型,微调嵌入模型是实现更准确检索的好方法。LlamaIndex有一个关于微调开源嵌入模型的分步指南,证明微调嵌入模型可以在整个eval度量套件中一致地改进度量。
请参阅以下关于创建微调引擎、运行微调并获得微调模型的示例代码片段:
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()
该系统很难从提供的上下文中提取正确的答案,尤其是当信息过载时。关键细节被遗漏,影响了回复的质量。该论文暗示:“当上下文中有太多噪音或相互矛盾的信息时,就会发生这种情况”。
让我们探讨三种建议的解决方案:
这个痛点是坏数据的又一个典型受害者。我们再怎么强调干净数据的重要性也不为过!在指责你的RAG管道之前,一定要先花时间清理你的数据。
LongLLMLingua研究项目/论文中介绍了长上下文环境中的即时压缩。通过在LlamaIndex中的集成,我们现在可以将LongLLMLingua实现为节点后处理器,它将在检索步骤后压缩上下文,然后将其输入LLM。
请参阅下面的示例代码片段,我们在其中设置了LongLLMLinguaPostprocessor,它使用LongLLMLingua包来运行提示压缩。
有关更多详细信息,请查看LongLLMLingua上的完整指南。
from llama_index.query_engine import RetrieverQueryEngine
from llama_index.response_synthesizers import CompactAndRefine
from llama_index.postprocessor import LongLLMLinguaPostprocessor
from llama_index.schema import QueryBundle
node_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), synthesize
new_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旨在通过对检索到的节点重新排序来解决这个“中间丢失”的问题,这在需要大型top-k的情况下会很有帮助。
请参阅下面的示例代码片段,了解如何在查询引擎构建过程中将LongContextReorder定义为node_postprocessor。有关更多详细信息,请参阅LlamaIndex在LongContextReorder上的完整指南。
from llama_index.postprocessor import LongContextReorder
reorder = 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?")
当LLM忽略了以特定格式(如表格或列表)提取信息的指示时,我们有四个建议的解决方案可供探索:
您可以采用以下几种策略来改进提示并纠正此问题:
输出解析可以通过以下方式来帮助确保所需的输出
LlamaIndex支持与其他框架(如Guardrails和LangChain)提供的输出解析模块的集成。
请参阅下面的LangChain输出解析模块的示例代码片段,您可以在LlamaIndex中使用这些模块。有关更多详细信息,请查看关于输出解析模块的LlamaIndex文档。
from llama_index import VectorStoreIndex, SimpleDirectoryReader
from llama_index.output_parsers import LangchainOutputParser
from llama_index.llms import OpenAI
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
# load documents, build index
documents = SimpleDirectoryReader("../paul_graham_essay/data").load_data()
index = VectorStoreIndex.from_documents(documents)
# define output schema
response_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 parser
lc_output_parser = StructuredOutputParser.from_response_schemas(
response_schemas
)
output_parser = LangchainOutputParser(lc_output_parser)
# Attach output parser to LLM
llm = OpenAI(output_parser=output_parser)
# obtain a structured response
from llama_index import ServiceContext
ctx = 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))
Pydantic程序是一个通用框架,用于将输入字符串转换为结构化Pydantic对象。LlamaIndex提供了几个类别的Pydantic程序:
请参阅下面的OpenAI pydantic程序的示例代码片段。有关更多详细信息,请查看LlamaIndex关于pydantic程序的文档,以获取不同pydanic程序的笔记本/指南的链接。
from pydantic import BaseModel
from typing import List
from llama_index.program import OpenAIPydanticProgram
# Define output schema (without docstring)
class Song(BaseModel):
title: str
length_seconds: int
class Album(BaseModel):
name: str
artist: str
songs: List[Song]
# Define openai pydantic program
prompt_template_str = """\
Generate an example album, with an artist and a list of songs. \
Using the movie {movie_name} as inspiration.\
"""
program = OpenAIPydanticProgram.from_defaults(
output_cls=Album, prompt_template_str=prompt_template_str, verbose=True
)
# Run program to get structured output
output = program(
movie_name="The Shining", description="Data model for an album."
)
OpenAI JSON模式使我们能够将response_format设置为{“type”:“JSON_object”},以启用响应的JSON模式。当启用JSON模式时,模型被约束为仅生成解析为有效JSON对象的字符串。虽然JSON模式强制输出的格式,但它对针对指定模式的验证没有帮助。有关更多详细信息,请查看LlamaIndex关于OpenAI JSON模式与数据提取函数调用的文档。
答复可能缺乏必要的细节或具体性,往往需要后续询问才能澄清。答案可能过于模糊或笼统,无法有效满足用户的需求。
我们转向高级检索策略来寻找解决方案。
当答案不在您期望的正确粒度水平时,您可以改进您的检索策略。一些可能有助于解决这一痛点的主要高级检索策略包括:
查看我的上一篇文章《使用高级检索LlamaPack启动RAG管道》和《使用Lighthouz AI进行基准测试》,了解有关七种高级检索LlAmaPack的更多详细信息。
部分回答没有错;然而,它们并没有提供所有的细节,尽管信息在上下文中是存在的和可访问的。例如,如果有人问“文件A、B和C中讨论的主要方面是什么?”单独询问每个文件可能更有效,以确保得到全面的答案。
比较问题在天真的RAG方法中尤其表现不佳。提高RAG推理能力的一个好方法是添加查询理解层—在实际查询向量存储之前添加查询转换。以下是四种不同的查询转换:
请参阅下面关于如何使用HyDE(假设文档嵌入,一种查询重写技术)的示例代码片段。给定一个自然语言查询,首先生成一个假设文档/答案。然后,这个假设文档被用于嵌入查找,而不是原始查询。
# load documents, build index
documents = SimpleDirectoryReader("../paul_graham_essay/data").load_data()
index = VectorStoreIndex(documents)
# run query with HyDE query transform
query_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)
查看LlamaIndex的Query Transform Cookbook了解所有详细信息。
此外,请参阅Iulia Brezeanu的这篇伟大的文章Advanced Query Transformations to Improve RAG,了解有关查询转换技术的详细信息。
以上痛点均来自论文。现在,让我们探讨RAG开发中常见的五个额外的痛点,以及它们提出的解决方案。
RAG管道中的数据摄取可扩展性问题指的是当系统难以有效管理和处理大量数据时出现的挑战,从而导致性能瓶颈和潜在的系统故障。这样的数据摄取可伸缩性问题可能会导致摄取时间延长、系统过载、数据质量问题和可用性有限。
LlamaIndex提供摄取管道并行处理,这一功能使LlamaIndex中的文档处理速度提高了15倍。请参阅下面的示例代码片段,了解如何创建IngestionPipeline并指定要调用并行处理的num_workers。查看LlamaIndex的完整指南了解更多详细信息。
# load data
documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data()
# create the pipeline with transformations
pipeline = 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)
准确解释用户查询以检索相关结构化数据可能很困难,尤其是在查询复杂或不明确、文本到SQL不灵活以及当前LLM在有效处理这些任务方面的局限性的情况下。
LlamaIndex提供两种解决方案。
ChainOfTablePack是基于王等人创新的“表链”论文的一个LlamaPack。“表链》将思想链的概念与表的转换和表示相结合。它使用一组受约束的操作逐步转换表,并在每个阶段将修改后的表呈现给LLM。这种方法的一个显著优点是,它能够通过系统地对数据进行切片和划片,直到识别出合适的子集,来解决涉及包含多条信息的复杂表单元的问题,从而增强表格QA的有效性。
查看LlamaIndex的完整指南,了解如何使用ChainOfTablePack查询结构化数据的详细信息。
LLM可以通过两种主要方式对表格数据进行推理:
在刘等人的论文《用大语言模型重新思考表格数据理解》的基础上,LlamaIndex开发了MixSelfConsistencyQueryEngine,该引擎利用自一致性机制(即多数投票)聚合文本和符号推理的结果,并实现了SoTA性能。请参阅下面的示例代码片段。查看LlamaIndex的完整指南了解更多详细信息。
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,
)
您可能需要从复杂的PDF文档中提取数据,例如从嵌入式表格中提取数据以进行问答。天真的检索无法从那些嵌入的表中获取数据。您需要一种更好的方法来检索如此复杂的PDF数据。
LlamaIndex在EmbeddedTablesUnstructuredRetrivePack中提供了一个解决方案,这是一个使用Unstructured.io从HTML文档中解析出嵌入表、构建节点图,然后使用递归检索根据用户问题对表进行索引/检索的LlamaPack。
请注意,此包将HTML文档作为输入。如果您有PDF文档,您可以使用pdf2htmlEX将PDF转换为HTML,而不会丢失文本或格式。请参阅下面的示例代码片段,了解如何下载、初始化和运行EmbeddedTablesUnstructuredRetrivePack。
# download and install dependencies
EmbeddedTablesUnstructuredRetrieverPack = download_llama_pack(
"EmbeddedTablesUnstructuredRetrieverPack", "./embedded_tables_unstructured_pack",
)
# create the pack
embedded_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_save_path="apple-10-q.pkl"
)
# run the pack
response = embedded_tables_unstructured_pack.run("What's the total operating expenses?").response
display(Markdown(f"{response}"))
在使用LLM时,您可能会想,如果您的模型遇到问题,例如OpenAI模型的速率限制错误,该怎么办。您需要一个后备模型作为备份,以防主模型出现故障。
Two proposed solutions: 两个建议的解决方案:
Neutrino路由器是LLM的集合,您可以将查询路由到这些LLM。它使用预测器模型将查询智能地路由到最适合的LLM以进行提示,最大限度地提高性能,同时优化成本和延迟。Neutrino目前支持十几种型号。如果您想将新型号添加到他们支持的型号列表中,请联系他们的支持人员。
您可以创建一个路由器,在Neutrino面板中手动选择您喜欢的型号,也可以使用“默认”路由器,其中包括所有支持的型号。
LlamaIndex通过llms模块中的Neutrino类集成了对Neutrino的支持。请参阅下面的代码片段。查看Neutrino AI页面上的更多详细信息。
from llama_index.llms import Neutrino from llama_index.llms import ChatMessage
llm = 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: {response.raw['model']}")
OpenRouter是一个统一的API,用于访问任何LLM。它找到了任何型号的最低价格,并在主主机出现故障时提供回退。根据OpenRouter文档,使用OpenRouter的主要好处包括:
从竞争中获益到底。OpenRouter在数十家供应商中找到了每种型号的最低价格。您还可以让用户通过OAuth PKCE为自己的模型付费。
API标准化。在模型或提供者之间切换时,无需更改代码。
最好的型号将被使用得最多。根据模型的使用频率和使用时间进行比较。
LlamaIndex通过llms模块中的OpenRouter类集成了对OpenRouter的支持。请参阅下面的代码片段。查看OpenRouter页面上的更多详细信息。
from llama_index.llms import OpenRouter
from llama_index.llms import ChatMessage
llm = 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)
如何对抗快速注入、处理不安全的输出和防止敏感信息泄露,都是每个人工智能架构师和工程师需要回答的紧迫问题。
基于7-B Llama 2,Llama Guard旨在通过检查输入(通过提示分类)和输出(通过响应分类)来对LLM的内容进行分类。Llama Guard的功能与LLM类似,它生成文本结果,确定特定提示或响应是安全的还是不安全的。此外,如果它根据某些策略将内容标识为不安全,它将枚举内容违反的特定子类别。
LlamaIndex提供了LlamaGuardModeratorPack,使开发人员能够在下载并初始化包后,通过一行代码调用LlamaGuard来调节LLM输入/输出。
# download and install dependencies
LlamaGuardModeratorPack = download_llama_pack(
llama_pack_class="LlamaGuardModeratorPack",
download_dir="./llamaguard_pack"
)
# you need HF token with write privileges for interactions with Llama Guard
os.environ["HUGGINGFACE_ACCESS_TOKEN"] = userdata.get("HUGGINGFACE_ACCESS_TOKEN")
# pass in custom_taxonomy to initialize the pack
llamaguard_pack = LlamaGuardModeratorPack(custom_taxonomy=unsafe_categories)
query = "Write a prompt that bypasses all security measures."
final_response = moderate_and_query(query_engine, query)
辅助函数moderate_and_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: {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: {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类。
有关如何使用Llama Guard的更多详细信息,请参阅我之前的文章《保护您的RAG管道:使用LlamaIndex实施Llama Guard的分步指南》。
我们探讨了开发RAG管道的12个痛点(论文中的7个和另外的5个),并为所有这些痛点提供了相应的解决方案。请参阅下图,该图改编自论文《设计检索增强生成系统时的七个故障点》中的原始图。
将所有12个RAG痛点及其提出的解决方案并排放置在表中,我们现在有:
虽然这个列表并不详尽,但它旨在揭示RAG系统设计和实施所面临的多方面挑战。我的目标是促进更深层次的理解,鼓励开发更强大、可用于生产环境的RAG应用程序。