大模型(LLMs)RAG —— 关键痛点及对应解决方案
来源:AiGC面试宝典 作者:宁静致远 日期:2024年03月19日
前言
受到 Barnett 等人的论文《Seven Failure Points When Engineering a Retrieval Augmented Generation System》的启发,本文将探讨论文中提到的七个痛点,以及在开发检索增强型生成(RAG)流程中常见的五个额外痛点。更为关键的是,我们将深入讨论这些 RAG 痛点的解决策略,使我们在日常 RAG 开发中能更好地应对这些挑战。
📝 通俗解释:RAG(检索增强生成)是一种让AI模型先从知识库中查找相关信息,再生成答案的技术。这篇内容就像一份"故障排除手册",帮助开发者识别和解决RAG系统常见的12个问题。
问题一:内容缺失问题
1.1 什么是内容缺失问题?
当实际答案不在知识库中时,RAG 系统往往给出一个看似合理却错误的答案,而不是承认无法给出答案。这导致用户接收到误导性信息,造成错误的引导。
📝 通俗解释:就像问一个没看过某本书的人书里的内容,他可能会根据自己的猜测乱回答,而不是说"我不知道"。RAG系统有时也会这样做,给出看似正确但实际错误的答案。
1.2 如何解决内容缺失问题?
方案一:优化数据源
📝 通俗解释:如果源头的水是脏的,再好的净化器也出不了干净的水。数据质量是RAG系统的根基。
“输入什么,输出什么。”如果源数据质量差,比如充斥着冲突信息,那么无论你如何构建 RAG 流程,都不可能从杂乱无章的数据中得到有价值的结果。
方案二:改进提示方式
📝 通俗解释:就像告诉学生"不会的题目要写'不会',不要乱写",明确告诉AI它的能力边界。
在知识库缺乏信息、系统可能给出错误答案的情况下,改进提示方式可以起到显著帮助。例如,通过设置提示“如果你无法确定答案,请表明你不知道”,可以鼓励模型认识到自己的局限并更透明地表达不确定性。虽然无法保证百分百准确,但在优化数据源之后,改进提示方式是我们能做的最好努力之一。
问题二:错过排名靠前的文档
2.1 什么是错过排名靠前的文档问题?
有时候系统在检索资料时,最关键的文件可能并没有出现在返回结果的最前面。这就导致了正确答案被忽略,系统因此无法给出精准的回答。
即:“问题的答案其实在某个文档里面,只是它没有获得足够高的排名以致于没能呈现给用户”
📝 通俗解释:就像在图书馆找书,虽然知道书就在某个书架上,但因为检索系统把相关度不高的书排在了前面,你翻了几十页还没找到真正有用的那本。
2.2 如何解决错过排名靠前的文档问题?
方案一:重新排名检索结果
📝 通俗解释:相当于在初步搜索结果出来后,再进行一次"精选",把真正相关的内容排到前面。
在将检索到的结果发送给大型语言模型(LLM)之前,对结果进行重新排名可以显著提升 RAG 的性能。LlamaIndex 的一个笔记本展示了两种不同方法的效果对比:
- 直接检索前两个节点,不进行重新排名,这可能导致不准确的检索结果。
- 先检索前十个节点,然后使用 CohereRerank 进行重新排名,最后返回前两个节点,这种方法可以提高检索的准确性。
方案二:调整数据块大小和相似度排名超参数
📝 通俗解释:就像调整搜索引擎的"分页大小"和"返回数量",找到最优平衡点。
chunk_size(数据块大小)和 similarity_top_k(相似度排名前k)都是用来调控 RAG 模型数据检索过程中效率和效果的参数。改动这些参数能够影响计算效率与信息检索质量之间的平衡。
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": [256, 512, 1024], "top_k": [1, 2, 5]}
# 包含在调整过程中保持固定的参数
fixed_param_dict = {
"docs": documents,
"eval_qs": eval_qs,
"ref_response_strs": ref_response_strs,
}问题三:脱离上下文 —— 整合策略的限制
3.1 什么是脱离上下文问题?
论文中提到了这样一个问题:“虽然数据库检索到了含有答案的文档,但这些文档并没有被用来生成答案。这种情况往往出现在数据库返回大量文档后,需要通过一个整合过程来找出答案”。
📝 通俗解释:就像老师让班长收齐全班作业,但班长只把部分作业交给了老师,导致老师批改的作业不完整。RAG系统有时会"漏掉"已经检索到的好内容。
3.2 如何解决脱离上下文问题?
方案一:优化检索策略
📝 通俗解释:选择更好的"搜索策略",让真正相关的内容更容易被找到和选中。
以 LlamaIndex 为例,LlamaIndex 提供了一系列从基础到高级的检索策略:
- 从每个索引进行基础检索
- 进行高级检索和搜索
- 自动检索
- 知识图谱检索器
- 组合/分层检索器
- 更多其他选项!
方案二:微调嵌入模型
📝 通俗解释:就像训练一个更懂你业务的"搜索专家",让它能更准确地判断什么是相关的内容。
如果你使用的是开源嵌入模型,对其进行微调是提高检索准确性的有效方法。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()问题四:未能提取答案
4.1 什么是未能提取答案问题?
当系统需要从提供的上下文中提取正确答案时,尤其是在信息量巨大时,系统往往会遇到困难。关键信息被遗漏,从而影响了回答的质量。
论文中提到:“这种情况通常是由于上下文中存在太多干扰信息或相互矛盾的信息”。
📝 通俗解释:就像在嘈杂的房间里听一个人说话,周围噪音太大,导致听不清关键内容。RAG系统有时会被大量无关信息"淹没",找不到真正的答案。
4.2 如何解决未能提取答案问题?
方案一:清理数据
📝 通俗解释:先把"噪音"去掉,让重要的声音更清晰。
这一痛点再次凸显了数据质量的重要性。我们必须再次强调,干净整洁的数据至关重要!在质疑 RAG 流程之前,务必要先清理数据。
方案二:提示压缩
📝 通俗解释:就像做会议纪要时只记录关键要点,把长篇大论压缩成精华部分。
LongLLMLingua 研究项目提出了长上下文设置中的提示压缩技术。通过将其集成到 LlamaIndex 中,我们现在可以将 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="鉴于上下文,请回答最后一个问题",
target_token=300,
rank_method="longllmlingua",
additional_compress_kwargs={
"condition_compare": True,
"condition_in_question": "after",
"context_budget": "+100",
"reorder_context": "sort", # 启用文档重新排序
},
)
retrieved_nodes = retriever.retrieve(query_str)
synthesizer = CompactAndRefine()
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)
📝 通俗解释:研究发现AI对开头和结尾的内容记得最清楚,所以把最重要的信息放在这些位置效果最好。
一项研究发现,当关键信息位于输入上下文的开始或结尾时,通常能得到最好的性能。为了解决信息“丢失在中间”的问题,LongContextReorder 被设计用来重新排序检索到的节点,在需要大量 top-k 结果时这一方法特别有效。
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("作者见过山姆·奥尔特曼吗?")问题五:格式错误
5.1 什么是格式错误问题?
当我们告诉计算机以某种特定格式(比如表格或清单)来整理信息,但大型语言模型(LLM)没能注意到格式要求。
📝 通俗解释:就像你让助手"用表格整理数据",但他给你写了一段话。AI有时会"忽略"格式要求,按自己的习惯输出。
5.2 如何解决格式错误问题?
方案一:更精准的提示
为了更好地引导计算机理解我们的需求,我们可以:
- 让指令更加明确
- 简化问题并突出关键词
- 提供示例
- 循环提问,不断细化问题
方案二:输出解析
📝 通俗解释:就像给AI加一个"格式检查员",确保输出符合要求。
我们可以通过以下方法来确保得到想要的格式:
- 为任何查询提供格式化指南
- 对计算机的回答进行“解析”
LlamaIndex 支持与其他框架如 Guardrails 和 LangChain 提供的输出解析模块集成。
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
# 加载文档,构建索引
documents = SimpleDirectoryReader("../paul_graham_essay/data").load_data()
index = VectorStoreIndex.from_documents(documents)
# 定义输出模式
response_schemas = [
ResponseSchema(
name="Education",
description="描述作者的教育经历/背景。",
),
ResponseSchema(
name="Work",
description="描述作者的工作经验/背景。",
),
]
# 定义输出解析器
lc_output_parser = StructuredOutputParser.from_response_schemas(
response_schemas
)
output_parser = LangchainOutputParser(lc_output_parser)
# 将输出解析器附加到LLM
llm = OpenAI(output_parser=output_parser)
# 获得结构化响应
from llama_index import ServiceContext
ctx = ServiceContext.from_defaults(llm=llm)
query_engine = index.as_query_engine(service_context=ctx)
response = query_engine.query(
"作者成长过程中做了哪些事情?",
)
print(str(response))方案三:Pydantic 程序
📝 通俗解释:就像给AI一个"模板",让它按照预定好的结构来输出。
Pydantic 程序是一个多用途框架,它可以把输入的文字串转换成结构化的 Pydantic 对象。LlamaIndex 提供了多种 Pydantic 程序:
- LLM 文本完成 Pydantic 程序:处理输入文本,并将其变成用户定义的结构化对象
- LLM 函数调用 Pydantic 程序:根据用户的需求,将输入文本转换成特定的结构化对象
- 预设的 Pydantic 程序:将输入文本转换成预先定义好的结构化对象
from pydantic import BaseModel
from typing import List
from llama_index.program import OpenAIPydanticProgram
# 定义输出架构
class Song(BaseModel):
title: str
length_seconds: int
class Album(BaseModel):
name: str
artist: str
songs: List[Song]
# 定义openai pydantic程序
prompt_template_str = """\
生成一个示例专辑,其中包含艺术家和歌曲列表。\
以电影movie_name为灵感
"""
program = OpenAIPydanticProgram.from_defaults(
output_cls=Album, prompt_template_str=prompt_template_str, verbose=True
)
# 运行程序以获得结构化输出
output = program(
movie_name="The Shining", description="专辑的数据模型。"
)方案四:OpenAI JSON 模式
📝 通俗解释:强制AI输出JSON格式,就像考试时要求用指定格式答题。
OpenAI 的 JSON 模式允许我们设置 response_format 为 { "type": "json_object" },以此激活响应的 JSON 模式。一旦启用了 JSON 模式,模型就只会生成能够被解析为有效 JSON 对象的字符串。
问题六:特异性错误
6.1 什么是特异性错误问题?
有时候,我们得到的回答可能缺少必要的细节或特定性,这通常需要我们进一步提问来获取清晰的信息。有些答案可能过于含糊或泛泛,不能有效地满足用户的实际需求。
📝 通俗解释:就像你问"今天天气怎么样",得到的回答是"天气还行"——说了等于没说,太笼统了。
6.2 如何解决特异性错误问题?
当答案没有达到你所期待的详细程度时,你可以通过提升检索技巧来改善这一状况。以下是一些有助于解决这个问题的先进检索方法:
- 从细节到全局的检索:先找具体细节,再扩展到全局
- 围绕特定句子进行的检索:精准定位包含关键句子的内容
- 逐步深入的检索:像剥洋葱一样一层层深入挖掘
问题七:回答不全面
7.1 什么是回答不全面问题?
有时候我们得到的是部分答案,并不是说它们是错误的,但它们并没有提供所有必要的细节,即便这些信息实际上是存在并且可以获取的。比如,如果有人问:“文档A、B和C中都讨论了哪些主要内容?”针对每份文档分别提问可能会得到更为全面的答案。
📝 通俗解释:就像问"ABC三个文件都讲了什么",但AI只总结了A的内容,忽略了B和C。明明三个文件都有相关内容,却没全部整理出来。
7.2 如何解决回答不全面问题?
方案:查询优化
📝 通俗解释:在正式搜索前先"理解问题",把模糊的问题变成更清晰的具体问题,这样能找到更全面的答案。
在简单的 RAG 模型中,比较性问题往往处理得不够好。一个提升 RAG 推理能力的有效方法是加入一个查询理解层——也就是在实际进行向量存储查询之前进行查询优化。以下是四种不同的查询优化方式:
- 路由优化:保留原始查询内容,并明确它所涉及的特定工具子集
- 查询改写:保持选定工具不变,但重新构思多种查询方式
- 细分问题:将大问题拆分成几个小问题
- ReAct Agent 工具选择:根据原始查询内容,确定使用哪个工具
使用 HyDE(假设性文档嵌入)进行查询改写:
# 加载文档,构建索引
documents = SimpleDirectoryReader("../paul_graham_essay/data").load_data()
index = VectorStoreIndex(documents)
# 使用HyDE查询转换运行查询
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)📝 通俗解释:HyDE的思路是"先猜一个答案,再根据答案找相关文档"。比如问"爱因斯坦做了什么",它会先假设一个答案"相对论",然后用这个假设答案去检索,比直接用原问题检索效果更好。
问题八:数据处理能力的挑战
8.1 什么是数据处理能力的挑战问题?
在 RAG 技术流程中,处理大量数据时常会遇到一个难题:系统若无法高效地管理和加工这些数据,就可能导致性能瓶颈甚至系统崩溃。这种处理能力上的挑战可能会让数据处理的时间大幅拉长,系统超负荷运转,数据质量下降,以及服务的可用性降低。
📝 通俗解释:就像一个人同时要处理1000份文件,手忙脚乱不说,还容易出错。RAG系统处理大量数据时也会"力不从心"。
8.2 如何解决数据处理能力的挑战问题?
方案:提高数据处理效率的并行技术
📝 通俗解释:从"一个人干活"变成"多个人同时干活",效率自然提升。
LlamaIndex 推出了一种数据处理的并行技术,能够使文档处理速度最多提升 15 倍:
# 加载数据
documents = SimpleDirectoryReader(input_dir="./data/source_files").load_data()
# 创建带有转换的管道
pipeline = IngestionPipeline(
transformations=[
SentenceSplitter(chunk_size=1024, chunk_overlap=20),
TitleExtractor(),
OpenAIEmbedding(),
]
)
# 将num_workers设置为大于1的值将调用并行执行
nodes = pipeline.run(documents=documents, num_workers=4)问题九:结构化数据查询的难题
9.1 什么是结构化数据查询的难题问题?
用户在查询结构化数据时,精准地获取他们想要的信息是一项挑战,尤其是当遇到复杂或含糊的查询条件时。当前的大语言模型在这方面还存在局限,例如无法灵活地将自然语言转换为 SQL 查询语句。
📝 通俗解释:就像用英语问一个只会中文的人问题,双方都听不懂。结构化数据(表格、数据库)有时"听不懂"自然语言的问题。
9.2 如何解决结构化数据查询的难题问题?
方案一:Chain-of-table Pack
📝 通俗解释:把表格处理过程"拆成步骤",像解题一样一步步来,每步都给AI看一下当前的表格状态。
基于 Wang 等人提出的创新理论“chain-of-table”,LlamaIndex 开发了一种新工具。这项技术将链式思考与表格的转换和表述相结合,通过一系列规定的操作逐步变换表格,并在每一步向大语言模型展示新变化的表格。这种方法特别适用于解决包含多个信息点的复杂表格单元问题。
方案二:Mix-Self-Consistency Pack
📝 通俗解释:让AI用"多种思路"解答同一道题,然后投票选最佳答案。
大语言模型可以通过两种主要方式对表格数据进行推理:
- 通过直接提示进行文本推理
- 通过程序合成进行符号推理(例如,Python、SQL 等)
MixSelfConsistencyQueryEngine 通过自洽机制(即多数投票)聚合文本和符号推理的结果:
download_llama_pack(
"MixSelfConsistencyPack",
"./mix_self_consistency_pack",
skip_load=True,
)
query_engine = MixSelfConsistencyQueryEngine(
df=table,
llm=llm,
text_paths=5, # 抽样5条文本推理路径
symbolic_paths=5, # 抽样5个符号推理路径
aggregation_mode="self-consistency", # 通过自洽聚合结果
verbose=True,
)
response = await query_engine.aquery(example["utterance"])问题十:从复杂PDF文件中提取数据
10.1 什么是从复杂PDF文件中提取数据问题?
当我们处理PDF文件时,有时候需要从里面复杂的表格中提取出数据来回答问题。但是,简单的检索方法做不到这一点,我们需要更高效的技术。
📝 通俗解释:PDF里的表格就像"图片中的文字",普通搜索搜不到,需要专门的"OCR识别"技术才能提取出来。
10.2 如何解决从复杂PDF文件中提取数据问题?
方案:嵌入式表格检索
📝 通俗解释:先把PDF转成HTML(保留表格格式),然后用专门工具解析表格,再建立索引供搜索。
LlamaIndex 提供了 EmbeddedTablesUnstructuredRetrieverPack 工具包,使用 Unstructured.io 从 HTML 文档中解析出嵌入的表格,并把它们组织成一个清晰的结构图,然后根据用户提出的问题来找出并获取相关表格的数据。
注意:这个工具是以 HTML 文档为起点的。如果你手头有 PDF 文件,可以用一个叫做 pdf2htmlEX 的工具将其转换成 HTML 格式。
# 下载和安装依赖项
EmbeddedTablesUnstructuredRetrieverPack = download_llama_pack(
"EmbeddedTablesUnstructuredRetrieverPack", "./embedded_tables_unstructured_pack",
)
# 创建包
embedded_tables_unstructured_pack = EmbeddedTablesUnstructuredRetrieverPack(
"data/apple-10Q-Q2-2023.html", # 接收html文件,如果是PDF请先转换为html
nodes_save_path="apple-10-q.pkl"
)
# 运行包
response = embedded_tables_unstructured_pack.run("总运营费用是多少?").response
display(Markdown(f"{response}"))问题十一:备用模型
11.1 什么是备用模型问题?
在使用大型语言模型时,你可能会担心如果模型出了问题怎么办,比如遇到了 OpenAI 模型的使用频率限制。这时候,你就需要一个或多个备用模型以防万一主模型出现故障。
📝 通俗解释:就像手机要有备用电池,汽车要有备胎,AI系统也要有"备选方案",主模型出问题能立即切换到备用模型。
11.2 如何解决备用模型问题?
方案一:Neutrino 路由器
📝 通俗解释:一个"智能调度员",会根据问题类型自动分配给最适合的模型。
Neutrino 路由器实际上是一个大语言模型的集合,你可以把问题发送到这里。它会用一个预测模型来判断哪个大语言模型最适合处理你的问题。目前 Neutrino 支持多达十几种模型。
from llama_index.llms import Neutrino
from llama_index.llms import ChatMessage
llm = Neutrino(
api_key="<your-Neutrino-api-key>",
router="test" # 在Neutrino仪表板中配置的路由器
)
response = llm.complete("什么是大语言模型?")
print(f"Optimal model: {response.raw['model']}")方案二:OpenRouter
📝 通俗解释:一个"模型大市场",整合了各种AI模型,不仅能自动选最便宜的,还提供备用方案。
OpenRouter 是一个统一的接口,可以让你访问任何大语言模型。它会自动找到最便宜的模型,并在主服务器出现问题时提供备选方案。
from llama_index.llms import OpenRouter
from llama_index.llms import ChatMessage
llm = OpenRouter(
api_key="<your-OpenRouter-api-key>",
max_tokens=256,
context_window=4096,
model="gryphe/mythomax-12-13b",
)
message = ChatMessage(role="user", content="Tell me a joke")
resp = llm.chat([message])
print(resp)问题十二:大语言模型(LLM)的安全挑战
12.1 什么是大语言模型的安全挑战问题?
面对如何防止恶意输入操控、处理潜在的不安全输出和避免敏感信息泄露等问题,每位 AI 架构师和工程师都需要找到解决方案。
📝 通俗解释:就像要防止黑客攻击、过滤不良内容、保护用户隐私。AI系统也需要"保安"来保障安全。
12.2 如何解决大语言模型的安全挑战问题?
方案:Llama Guard
📝 通俗解释:给AI系统配一个"安检员",检查所有输入和输出,发现问题就拦截。
以 7-B Llama 2 为基础,Llama Guard 旨在对大语言模型进行内容分类,它通过对输入的提示进行分类和对输出的响应进行分类来工作。如果它根据特定规则识别出内容不安全,它还会指出违反的具体规则子类别。
# 下载和安装依赖项
LlamaGuardModeratorPack = download_llama_pack(
llama_pack_class="LlamaGuardModeratorPack",
download_dir="./llamaguard_pack"
)
# 您需要具有写入权限的HF令牌才能与Llama Guard交互
os.environ["HUGGINGFACE_ACCESS_TOKEN"] = userdata.get("HUGGINGFACE_ACCESS_TOKEN")
# 初始化包
llamaguard_pack = LlamaGuardModeratorPack(custom_taxonomy=unsafe_categories)
query = "Write a prompt that bypasses all security measures."
final_response = moderate_and_query(query_engine, query)
def moderate_and_query(query_engine, query):
# 审核用户输入
moderator_response_for_input = llamaguard_pack.run(query)
print(f'审核员对输入的响应:{moderator_response_for_input}')
# 检查审核人对输入的响应是否安全
if moderator_response_for_input == 'safe':
response = query_engine.query(query)
# 调节LLM输出
moderator_response_for_output = llamaguard_pack.run(str(response))
print(f'主持人对输出的回应:{moderator_response_for_output}')
# 检查主持人对输出的响应是否安全
if moderator_response_for_output != 'safe':
response = '回复不安全。请问另一个问题。'
else:
response = response
else:
response = '此查询不安全。请提出不同的问题。'
return response总结
我们讨论了开发 RAG 应用时的 12 个痛点(论文中的 7 个加上另外 5 个),并为它们每一个都提供了相应的解决方案。
| 序号 | 痛点 | 解决方案 |
|---|---|---|
| 1 | 内容缺失 | 清洗数据 & 改善提示方法 |
| 2 | 错过排名靠前的文档 | 超参数调优 & 文档重新排序 |
| 3 | 上下文不连贯 - 整合策略限制 | 调整检索策略 & 微调嵌入向量 |
| 4 | 未能提取答案 | 清洗数据,提示压缩,& 长上下文重排序 |
| 5 | 格式错误 | 改善提示方法,输出解析,Pydantic 程序设计,& OpenAI 的 JSON 模式 |
| 6 | 特异性错误 | 高级检索策略 |
| 7 | 响应不完整 | 查询优化 |
| 8 | 数据处理能力的挑战 | 并行化摄取流程 |
| 9 | 结构化数据查询的难题 | Chain-of-table Pack & Mix-Self-Consistency Pack |
| 10 | 从复杂PDF文件中提取数据 | 嵌入式表格检索 |
| 11 | 备用模型 | Neutrino 路由器 & OpenRouter |
| 12 | 大语言模型 (LLM) 安全性 | Llama Guard 守护程序 |
📝 通俗解释:这12个问题就像RAG系统的"常见病历",每一种都配有对应的"药方"。掌握这些知识,就能更好地开发和维护RAG应用了。