注:POCs具体含义请参见后续文本或脚注。
大多数快速的概念验证(POC)震撼你,这些概念验证允许用户借助对话式人工智能来探索数据。当你突然能够和你的文档、数据或代码库进行对话时,感觉就像突然拥有了魔法。
这些POC在文档数量有限的小数据集上效果显著。然而,就像大多数东西在实际应用中一样,当你尝试在大规模数据上运行时,问题很快就会出现。当你仔细检查并深入研究AI给出的答案时,你会发现:
- 你的代理没有回复完整的信息,有些重要的数据被遗漏了。
- 你的代理给出的答案并不一致。
- 你的代理无法告诉你它从哪里获取了哪些信息,这使得答案的实用性大大降低。
原来RAG里的真正魔法不在生成模型那一步,而是在检索与重组的过程中。一深入了解,原因就很明显了……
* RAG = 检索增强生成 — RAG 的维基百科定义如下
RAG过程——举例
那么,一个配备了RAG功能的AI代理是如何回答问题的?快速回顾一下,一个简单的RAG过程是如何运作的:
- 这一切始于发起一个查询。用户提出了一个问题,或者某个系统正在尝试回答一个问题。
- 使用查询进行搜索。通常你会嵌入查询并进行相似性搜索,但你也可以进行传统的全文搜索,或者两者结合,或者直接查找信息。
- 搜索结果是一组文档,我们暂时简称为文档。
- 将这些文档与查询的核心结合,形成易于理解的上下文,以便AI能够处理。
- AI根据问题和文档,生成答案。
- 理想状态下,这个答案会经过事实验证,以确认AI的答案是否基于文档,并且/或者是否适合受众。
其实最不光彩的秘密是,RAG过程的本质是检索和生成(Retrieval and Generation),你必须在AI采取任何行动之前给出答案,这样它才能给出你想要的答案。
换句话说:
- AI所做的工作(步骤5)是做出判断,并清楚地表达答案
- 工程师要做的工作(步骤3以及4)是找到答案,并以一种AI可以理解的方式整理答案
当然,这要看情况,因为如果判断是关键因素,那么AI模型就能搞定一切。但在很多商业场景中,找出并正确组合这些答案的各个部分,才是更重要的部分。
如果你想要实现有效的RAG过程,通常需要解决哪些工程问题?首先需要解决的是如何处理数据摄入、拆分和分块,以及文档解释等问题。之前我在文章中提到过一些相关问题,这里就不赘述了。假设你已经解决了数据摄入的问题,并且已经有了一个良好的向量存储或搜索索引,比如一个良好的向量存储或搜索索引。
常见挑战。
- 重复 — 即使是简单的生产系统,通常也会有重复的文档。当系统规模较大、用户或租户众多、连接到多个数据源或处理版本问题时,情况更是如此。
- 近似重复 — 含有几乎相同数据但有细微差异的文档。近似重复分为两类:
— 有意义的 — 例如进行的小修改或添加,比如日期字段的更新
— 无意义的 — 例如:标点、语法或空格的细微差异,或仅仅是由于时间或处理流程导致的差异 - 数据量 — 有些查询会产生非常大的相关响应数据集
- 数据新鲜度与质量 — 哪些片段对于AI来说质量最高,哪些片段从时间(新鲜度)角度来看最为相关?
- 数据多样性 — 如何确保搜索结果的多样性以确保AI获得充足的信息?
- 查询措辞和歧义 — 触发RAG流程的提示可能措辞不当,无法获得最佳结果,甚至具有歧义
- 响应个性化 — 查询可能需要根据提问者不同给出不同的响应
这个列表还可以继续下去,但你应该明白了,对吧?
基于无限大小的上下文窗口能解决这个问题吗?简短回答:不行。
使用非常大的上下文窗口的成本和性能影响不应被低估,因为每次查询的成本可能会增加到原来的10倍或100倍,不包括任何后续的用户或系统交互。
然而,撇開這不談,想像一下這種情況。
我们把安放在一个房间里,房间里有一张写着内容的纸。纸上写着:患者乔:复杂的脚部骨折。现在我们问安,患者是否有脚骨折?她的回答是“是的,他有”。
现在我们给安一百页乔的病历。她的回答变成了“嗯,这要看你指的是哪个时间段,他当时……”
现在我们给安成千上万页有关所有病人的资料……
你很快就会发现,我们如何定义问题(或者说,我们的提示)变得非常重要。上下文范围越大,就需要考虑更多的细节。
此外,上下文窗口越大,可能的答案也就越多。这听起来是个好主意,但实际上这种方法容易让人偷懒,如果不小心处理,可能会削弱应用的功能。
这里有几个建议的方法在扩展RAG系统从原型验证(PoC)到生产环境的过程中,这里是一些具体的方法来应对常见的数据挑战。
复制在多源系统中,重复不可避免。通过使用指纹(内容的哈希值)、文档ID或语义哈希,可以在内容输入时识别完全重复并防止冗余。不过,合并重复内容的元数据也很有价值,这可以让用户知道某些内容出现在多个来源中,从而增加内容的可信度或突出数据集中的重复现象。
# 生成指纹以去重
def fingerprint(doc_content):
return hashlib.md5(doc_content.encode()).hexdigest()
# 存储指纹并过滤重复项,同时整合元数据
fingerprints = {}
unique_docs = []
for doc in docs:
fp = fingerprint(doc['content'])
if fp not in fingerprints:
fingerprints[fp] = [doc]
unique_docs.append(doc)
else:
fingerprints[fp].append(doc) # 合并来源信息
接近重复
几乎相同的文档(相似但不完全相同)常常包含重要的更新或细微添加。因为即使是小小的改动,比如状态更新,也可能包含重要信息,所以在处理几乎相同的文档时,保持最新版本变得非常重要。一种实用的方法是先用余弦相似度来检测,然后在每组文档中保留最新版本,并标出任何有意义的更新。
从sklearn.metrics.pairwise导入cosine_similarity函数
从sklearn.cluster导入DBSCAN聚类算法
导入numpy作为np(或np as np)
# 使用DBSCAN对嵌入进行聚类,以便查找近重复项
聚类 = DBSCAN(eps=0.1, min_samples=2, metric='余弦相似度').fit(doc_embeddings)
# 按聚类标签整理文档
聚类文档集 = {}
for 索引, 标签 in enumerate(clustering.labels_):
if 标签 == -1:
continue
if 标签 not in 聚类文档集:
聚类文档集[标签] = []
聚类文档集[标签].append(docs[索引])
# 过滤聚类以保留每个聚类中最新的文档
过滤文档 = []
for 聚类文档列表 in 聚类文档集.values():
# 选择具有最新时间戳或最高相关性的文档
最新文档 = max(聚类文档列表, key=lambda d: d['timestamp'])
过滤文档.append(最新文档)
卷
当查询返回大量相关的文档时,有效处理这些文档至关重要。一种方法是采用分层的方法:
- 主题提取:对文档进行预处理以提取特定主题或摘要。
- Top-k 过滤:合成之后,根据相关性评分过滤摘要内容。
- 相关性评分:采用相似性度量(如BM25或余弦相似度),在检索之前优先考虑最相关的文档。
这种方法通过检索更易于AI处理的合成信息来减轻工作负担。其他策略可能包括按主题批量处理文档或预先整理摘要,以进一步简化检索过程。
数据新鲜度 vs. 数据质量保持质量和新鲜度的平衡非常重要,尤其是在变化迅速的数据集中更是如此。有很多种评分方法,这里有一个常用的策略:
- 复合评分:使用来源可靠性、内容深度和用户参与度等因素计算质量评分。
- 时效性加权:用时间戳权重调整评分,强调时效性。
- 筛选阈值:只有符合综合质量和时效性阈值的文档才能被检索。
其他策略可能有只给高质量来源打分,或对旧文档采用衰减因子。
数据类型多样确保多样化的数据源有助于创建平衡的响应。通过将文档按来源分组(例如,不同的数据库、作者或内容类型),并从每个来源中选择最佳片段是一种有效的方法。其他方法还包括根据独特视角评分或应用多样性的限制,以避免对任何单一文档或视角过度依赖。
# 通过分组并从每个来源选择顶级片段来确保多样性
from itertools import groupby
k = 3 # 每个来源的顶级片段数
docs = sorted(docs, key=lambda d: d['source'])
grouped_docs = {key: list(group)[:k] for key, group in groupby(docs, key=lambda d: d['source'])}
# 从分组后的文档中提取所有顶级片段
diverse_docs = [doc for docs in grouped_docs.values() for doc in docs]
查询措辞与歧义
模糊的查询可能会导致不太理想的结果。直接使用用户的原始提示不是获取他们所需结果的最佳方式。比如,之前聊天里可能已经有过相关的信息交换。或者用户粘贴了一大段文本并问了相关的问题。
为了确保你使用精简的查询,一种方法是让提供给模型的RAG工具要求模型将问题改写为更详细的搜索查询,类似于精心制作用于谷歌的搜索查询。这种方法使用户意图与RAG检索过程更加一致。虽然下面的表述还有待改进,但大致意思已经表达出来了。
tools = [{
"name": "搜索我们内部公司数据库",
"description": "搜索我们内部公司数据库中的相关文档",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "以句子的形式输入搜索词,就像在谷歌搜索一样。请确保包含问题中的所有重要细节。"
}
},
"required": ["query"]
}
}]
个性化回复
为了给用户提供个性化的回复,可以直接将用户的特定信息整合到RAG的上下文中。通过在最终上下文中增加用户特定的信息层,可以让AI考虑到个人的偏好、权限或历史记录,而不改变核心的检索流程。
通过解决这些数据挑战,你的RAG系统可以由一个有说服力的概念验证(POC)演进为一个稳定且高效的解决方案。最终,RAG的有效性更多地依赖于精心的工程设计,而非AI模型本身。虽然AI可以生成流利的答案,但真正的窍门在于我们如何检索和组织信息。所以,下次当你对一个AI系统的对话能力感到惊讶时,记得这很可能是幕后一个设计精巧的检索过程的结果。
我希望这篇文章能让你对RAG过程有些了解,并且让你明白为什么你体验到的那种神奇的感觉并不一定来源于AI模型,而是很大程度上取决于你的检索过程的设计思路。当你跟你的数据对话时,那种神奇的感觉很大程度上取决于你的检索过程的设计。
请分享你的想法。
共同學習,寫下你的評論
評論加載中...
作者其他優質文章