在实施 RAG 驱动的生成式 AI 时,项目执行过程中会不可避免地遇到复杂性。嵌入将庞大的结构化或非结构化文本转化为紧凑的高维向量,以捕捉其语义精髓,从而实现更快速、更高效的信息检索。然而,随着处理越来越大的数据集,文档嵌入的创建和存储成为必要,我们将不可避免地面临存储问题。此时你可能会问,为什么不使用关键词而使用嵌入呢?答案很简单:虽然嵌入需要更多存储空间,但它们能够捕捉文本的更深层语义含义,相比僵化的、经常匹配的关键词,嵌入在检索时具有更细腻的上下文感知能力。这带来了更好、更相关的检索结果。因此,我们的选择是转向向量存储,在其中嵌入得以组织并能快速访问。
本章将从探讨如何从原始数据转换到 Activeloop Deep Lake 向量存储开始,通过加载 OpenAI 嵌入模型来实现。这需要安装和实施多个跨平台包,从而引出此类系统的架构。我们将把 RAG 流水线组织成独立的组件,因为将 RAG 流水线分解为独立部分将使多个团队能够同时开展项目。接着,我们将为 RAG 驱动的生成式 AI 流水线制定蓝图。最后,我们将使用 Python 从头构建一个由 Activeloop Deep Lake、OpenAI 和自定义函数组成的三组件 RAG 流水线。
此编程过程将深入到跨平台环境中与包和依赖项相关的问题。我们还将面临数据分块、嵌入向量并将其加载到向量存储中的挑战。我们将通过检索查询增强 GPT-4o 模型的输入并生成可靠的输出。在本章结束时,您将全面了解如何利用向量存储中嵌入的文档来增强生成式 AI 的能力。
总结来说,本章涵盖以下主题:
- 介绍文档嵌入和向量存储
- 如何将 RAG 流水线分解为独立组件
- 构建从原始数据到 Activeloop Deep Lake 的 RAG 流水线
- 应对跨平台包和库的环境挑战
- 利用大型语言模型 (LLM) 通过 OpenAI 嵌入模型对数据进行嵌入
- 查询 Activeloop Deep Lake 向量存储以增强用户输入
- 使用 OpenAI GPT-4o 生成增强后的可靠输出
让我们从学习如何将原始数据转换为向量存储开始。
从原始数据到向量存储中的嵌入
嵌入将任何形式的数据(文本、图像或音频)转换为实数,从而将一个文档转换为一个向量。这些文档的数学表示使我们能够计算文档之间的距离并检索相似数据。
原始数据(书籍、文章、博客、图片或歌曲)首先被收集并清理以去除噪声。准备好的数据接着被输入到一个模型中,例如 OpenAI 的 text-embedding-3-small
,以对数据进行嵌入。例如,本章中我们将实现的 Activeloop Deep Lake 会将文本分解成预定义的块,每个块由一定数量的字符组成。例如,块的大小可以设置为 1,000 个字符。我们可以让系统优化这些块的大小,正如我们将在下一章的“优化分块”部分中实现的那样。这些文本块使处理大量数据更为便捷,并为文档提供更详细的嵌入,如下所示:
透明性自参数化模型诞生以来一直是 AI 领域的圣杯,在这些模型中,信息被埋藏在学习到的参数中,形成了“黑箱”系统。RAG 是一个游戏规则改变者,如图 2.1 所示,因为其内容完全可追溯:
- 左侧(文本) :在 RAG 框架中,每一条生成的内容都可以追溯到其源数据,确保输出的透明性。OpenAI 生成模型在生成响应时会考虑增强后的输入。
- 右侧(嵌入) :数据嵌入直接可见,并与文本关联,这与将数据来源编码在模型参数中的参数化模型形成了对比。
一旦我们获得文本和嵌入,下一步就是高效存储它们以便快速检索。这正是向量存储的用武之地。向量存储是一种专门设计的数据库,能够处理嵌入等高维数据。我们可以在无服务器平台(如 Activeloop)上创建数据集,如图 2.2 所示。我们可以通过 API 在代码中创建和访问这些数据集,正如我们将在本章的“构建 RAG 流水线”部分中所做的那样。
向量存储的另一个特点是它能够使用优化方法进行数据检索。向量存储内置了强大的索引方法,我们将在下一章讨论。这种检索能力使得 RAG 模型能够在生成阶段快速找到并检索最相关的嵌入,增强用户输入,并提高模型生成高质量输出的能力。
现在我们将探讨如何组织一个 RAG 流水线,从数据收集、处理和检索到增强输入的生成。
在流水线中组织 RAG
一个 RAG 流水线通常会收集数据并对其进行清理、分块,将文档嵌入并存储在向量存储数据集中。然后查询向量数据集,以增强生成式 AI 模型的用户输入,从而生成输出。然而,当涉及使用向量存储时,强烈建议不要将整个 RAG 序列运行在一个单一程序中。我们应至少将过程分为三个组件:
- 数据收集和准备
- 数据嵌入并加载到向量存储的数据集中
- 查询向量化数据集,以增强生成式 AI 模型的输入并生成响应
让我们来看这种组件化方法的主要原因:
- 专门化:使团队中的每个成员可以专注于他们最擅长的部分,例如数据收集和清理、运行嵌入模型、管理向量存储或调整生成式 AI 模型。
- 可扩展性:更容易在技术演进时对独立组件进行升级,并使用专门的方法扩展不同组件。例如,原始数据的存储可以扩展在不同的服务器上,而嵌入向量存储在云平台的向量化数据集中。
- 并行开发:允许各团队按各自进度推进,无需等待其他团队。可以在一个组件上不断进行改进,而不会干扰其他组件的流程。
- 维护与组件无关:一个团队可以在一个组件上工作,而不会影响系统的其他部分。例如,如果 RAG 流水线已投入生产,用户可以继续通过向量存储查询和运行生成式 AI,而团队修复数据收集组件。
- 安全和隐私:每个团队可以在独立的权限、访问和角色下工作,以减少安全和隐私问题。
可以看到,在实际生产环境或大型项目中,单个程序或团队管理端到端流程的情况是很少见的。现在我们准备绘制 RAG 流水线的蓝图,并在本章中使用 Python 构建它。
一个由 RAG 驱动的生成式 AI 流水线
让我们深入了解一个现实中的 RAG 流水线是什么样的。想象一下,我们是一个需要在几周内交付完整系统的团队。从一开始,我们就被各种问题包围:
- 谁来收集和清理所有数据?
- 谁来负责设置 OpenAI 的嵌入模型?
- 谁来编写代码以启用这些嵌入并管理向量存储?
- 谁来负责实现 GPT-4 并管理其输出内容?
几分钟内,每个人看起来都很担忧。这一切让人感觉不堪重负——老实说,谁会想到要独自解决所有这些问题?
所以我们这样做:我们分成三组,每组负责流水线的不同部分,如图 2.3 所示。
每个小组负责一个组件的实现:
- 数据收集和准备(D1 和 D2) :一个团队负责数据的收集和清理。
- 数据嵌入和存储(D2 和 D3) :另一个团队负责通过 OpenAI 的嵌入模型处理数据,并将这些向量存储在 Activeloop Deep Lake 数据集中。
- 增强生成(D4、G1-G4 和 E1) :最后一个团队负责基于用户输入和检索查询生成内容的大任务。他们使用 GPT-4,尽管听起来任务量很大,但实际上更轻松些,因为他们无需等待其他人,只需让计算机进行计算并评估输出。
突然之间,项目看起来就没那么可怕了。每个人都专注于自己的部分,大家可以不被其他团队干扰地工作。这样,我们可以更快地推进工作,并在没有常见延误的情况下完成任务。
该项目的组织方式(如图 2.3 所示)是第 1 章《为什么选择检索增强生成?》中图 1.3 所展示的 RAG 生态系统框架的一个变体。
现在我们可以开始构建 RAG 流水线了。
构建 RAG 流水线
现在我们将构建一个 RAG 流水线,实现上一节描述并在图 2.3 中展示的流水线。我们将假设有三个团队(团队 #1、团队 #2 和团队 #3)并行工作来实现流水线的三个组件:
- 团队 #1:负责数据收集和准备
- 团队 #2:负责数据嵌入和存储
- 团队 #3:负责增强生成
第一步是为这些组件设置环境。
设置环境
让我们直接面对这个问题。安装跨平台、跨库的包及其依赖项可能相当具有挑战性!重要的是要考虑这种复杂性,并准备好让环境正确运行。每个包都有依赖项,可能会有版本冲突。即使我们调整版本,应用程序也可能无法正常运行。因此,请花时间安装正确版本的包和依赖项。
在本节中,我们将仅描述所有三个组件的环境设置,并在需要时引用此部分。
安装包和库
要在本节中构建 RAG 流水线,我们需要安装包并冻结包版本,以防止依赖冲突和库功能问题,例如:
- 依赖版本之间可能存在冲突。
- 某些库需要更新以使应用程序运行。例如,2024 年 8 月安装 Deep Lake 需要 Pillow 版本 10.x.x,而 Google Colab 的版本是 9.x.x。因此,必须先卸载 Pillow 并重新安装最新版本,然后再安装 Deep Lake。Google Colab 很可能会更新 Pillow。这类情况在快速发展的市场中时有发生。
- 如果版本冻结过久,可能会导致功能弃用。
- 如果版本冻结过久,可能会因缺少升级而无法修正错误。
因此,如果冻结版本,应用程序可能在一段时间内保持稳定,但也可能遇到问题。而如果版本升级过快,其他库可能会不再正常工作。这没有万能的解决方案!这是一个持续的质量控制过程。
在本节中,我们将冻结版本。接下来,让我们逐步完成安装步骤,以为流水线创建环境。
安装过程涉及的组件
让我们首先描述在每个笔记本的“安装环境”部分中安装的组件。并非所有笔记本都需要安装这些组件;此部分仅作为包的清单。
在流水线的第一个部分 1. 数据收集和准备 中,我们只需要安装 Beautiful Soup 和 Requests:
!pip install beautifulsoup4==4.12.3
!pip install requests==2.31.0
这解释了为什么流水线的这一组件应保持独立。这对于喜欢创建与网络交互接口的开发人员来说是一个简单的任务,也非常适合希望参与数据收集和分析的初级开发人员。
在本节中我们将构建的其他两个流水线组件 2. 数据嵌入和存储 以及 3. 增强生成,需要更多关注和安装 requirements01.txt 中的依赖项,如上一节所述。现在,让我们继续逐步安装。
挂载驱动器
在本示例中,程序在 Google Colab 中挂载 Google Drive,以安全地读取 OpenAI API 密钥以访问 OpenAI 模型,并使用 Activeloop API 令牌进行身份验证以访问 Activeloop Deep Lake 数据集:
# Google Drive 选项来存储 API 密钥
# 将您的密钥存储在文件中并读取(可以直接在笔记本中输入,但可能会被旁边的人看到)
from google.colab import drive
drive.mount('/content/drive')
您可以选择将密钥和令牌存储在其他地方。只需确保它们在安全的位置即可。
创建子进程从 GitHub 下载文件
这里的目的是编写一个函数,用于从 GitHub 下载 grequests.py
文件。该程序包含一个使用 curl 下载文件的功能,并在必要时添加私有令牌:
import subprocess
url = "https://raw.githubusercontent.com/Denis2054/RAG-Driven-Generative-AI/main/commons/grequests.py"
output_file = "grequests.py"
# 使用私有令牌准备 curl 命令
curl_command = [
"curl",
"-o", output_file,
url
]
# 执行 curl 命令
try:
subprocess.run(curl_command, check=True)
print("下载成功。")
except subprocess.CalledProcessError:
print("文件下载失败。")
grequests.py
文件包含一个可以在需要时接受私有令牌或其他凭据安全系统的函数,用于使用 curl 命令检索数据:
import subprocess
import os
# 如有必要,在文件名后添加私有令牌
def download(directory, filename):
# GitHub 仓库中图像文件的基本 URL
base_url = 'https://raw.githubusercontent.com/Denis2054/RAG-Driven-Generative-AI/main/'
# 文件的完整 URL
file_url = f"{base_url}{directory}/{filename}"
# 使用 curl 下载文件,包括 Authorization 头部用于私有令牌
try:
# 准备 curl 命令,带有 Authorization 头
# curl_command = f'curl -H "Authorization: token {private_token}" -o {filename} {file_url}'
curl_command = f'curl -H -o {filename} {file_url}'
# 执行 curl 命令
subprocess.run(curl_command, check=True, shell=True)
print(f"成功下载 '{filename}'。")
except subprocess.CalledProcessError:
print(f"下载 '{filename}' 失败。请检查 URL、网络连接,以及令牌是否正确并具备适当权限。")
安装依赖项
现在,在使用 Activeloop Deep Lake 和 OpenAI 时,我们将安装本节所需的依赖项。我们只需要:
!pip install deeplake==3.9.18
!pip install openai==1.40.3
截至 2024 年 8 月,Google Colab 的 Pillow 版本与 deeplake 包存在冲突。不过,deeplake 的安装包会自动处理这个问题。您只需重新启动会话并再次运行,因此在每个安装 deeplake 的笔记本中,第一个安装命令行是 pip install deeplake==3.9.18
。
安装依赖项后,我们必须运行一行代码来为 Activeloop 激活公共 DNS 服务器:
# 用于 Google Colab 和 Activeloop(Deeplake 库)
# 此行将字符串 "nameserver 8.8.8.8" 写入文件,指定 DNS 服务器地址为 8.8.8.8,这是 Google 的公共 DNS 服务器之一。
with open('/etc/resolv.conf', 'w') as file:
file.write("nameserver 8.8.8.8")
认证过程
您需要在 OpenAI 注册以获取 API 密钥:openai.com/。使用密钥前请务必查看定价政策。首先,激活OpenAI 的 API 密钥:
# 检索并设置 OpenAI API 密钥
f = open("drive/MyDrive/files/api_key.txt", "r")
API_KEY=f.readline().strip()
f.close()
# 设置 OpenAI API 密钥
import os
import openai
os.environ['OPENAI_API_KEY'] =API_KEY
openai.api_key = os.getenv("OPENAI_API_KEY")
接下来,激活 Activeloop 的 Deep Lake API 令牌:
# 检索并设置 Activeloop API 令牌
f = open("drive/MyDrive/files/activeloop.txt", "r")
API_token=f.readline().strip()
f.close()
ACTIVELOOP_TOKEN=API_token
os.environ['ACTIVELOOP_TOKEN'] =ACTIVELOOP_TOKEN
您需要在 Activeloop 注册以获取 API 令牌:www.activeloop.ai/。使用令牌前请务必查看定价政策。
环境安装完成后,您可以隐藏我们刚运行的“安装环境”单元,以便专注于流水线组件的内容,如图 2.4 所示。
安装单元格将被隐藏,但仍然可以运行,如图 2.5 所示。
现在我们可以专注于每个流水线组件的具体内容。让我们从数据收集和准备开始。
1. 数据收集和准备
数据收集和准备是流水线的第一个组件,正如本章前面所述。团队 #1 将专注于他们的组件,如图 2.6 所示。
让我们参与并帮助团队 #1。我们的工作明确,可以享受实现组件的过程。我们将检索并处理 10 篇 Wikipedia 文章,提供有关太空探索各方面的全面视角:
- 太空探索:关于太空探索的历史、技术、任务和计划的概述(en.wikipedia.org/wiki/Space_…
- 阿波罗计划:关于将首批人类送上月球的 NASA 计划及其重要任务的详细信息(en.wikipedia.org/wiki/Apollo…
- 哈勃太空望远镜:关于迄今为止建造的最重要的望远镜之一的信息,它在许多天文发现中发挥了关键作用(en.wikipedia.org/wiki/Hubble…
- 火星车:关于被送往火星以研究其表面和环境的火星车的深入信息(en.wikipedia.org/wiki/Mars_r…
- 国际空间站(ISS) :关于 ISS 的细节、建造、国际合作及其在空间研究中的角色(en.wikipedia.org/wiki/Intern…
- SpaceX:涵盖 SpaceX 的历史、成就和目标,它是最具影响力的私人航天公司之一(en.wikipedia.org/wiki/SpaceX…
- 朱诺号(航天器) :关于绕木星轨道运行并研究木星、其结构和卫星的 NASA 太空探测器的信息(en.wikipedia.org/wiki/Juno_(…
- 旅行者计划:关于旅行者任务的细节,包括它们对我们理解外太阳系和星际空间的贡献(en.wikipedia.org/wiki/Voyage…
- 伽利略号(航天器) :关于研究木星及其卫星的任务的概述,为我们提供了关于这颗气态巨行星及其系统的宝贵数据(en.wikipedia.org/wiki/Galile…
- 开普勒太空望远镜:关于专为发现绕其他恒星运行的地球大小行星而设计的空间望远镜的信息(en.wikipedia.org/wiki/Kepler…
这些文章涵盖了太空探索的广泛主题,从历史计划到现代技术进步和任务。
现在,打开 GitHub 仓库中的 1-Data_collection_preparation.ipynb
。我们将首先收集数据。
收集数据
我们只需导入 requests
用于 HTTP 请求,from bs4 import BeautifulSoup
用于 HTML 解析,以及正则表达式模块 import re
:
import requests
from bs4 import BeautifulSoup
import re
然后选择我们需要的 URL:
# Wikipedia 文章的 URL
urls = [
"https://en.wikipedia.org/wiki/Space_exploration",
"https://en.wikipedia.org/wiki/Apollo_program",
"https://en.wikipedia.org/wiki/Hubble_Space_Telescope",
"https://en.wikipedia.org/wiki/Mars_rover",
"https://en.wikipedia.org/wiki/International_Space_Station",
"https://en.wikipedia.org/wiki/SpaceX",
"https://en.wikipedia.org/wiki/Juno_(spacecraft)",
"https://en.wikipedia.org/wiki/Voyager_program",
"https://en.wikipedia.org/wiki/Galileo_(spacecraft)",
"https://en.wikipedia.org/wiki/Kepler_Space_Telescope"
]
此列表在代码中定义,但它也可以存储在数据库、文件或其他格式(如 JSON)中。现在我们可以准备数据了。
准备数据
首先,我们编写一个清理函数。此函数使用正则表达式删除给定文本字符串中的数字引用(如 [1]
、[2]
等),并返回清理后的文本:
def clean_text(content):
# 移除通常显示为 [1]、[2] 等的引用
content = re.sub(r'[\d+]', '', content)
return content
然后,我们编写一个经典的获取和清理函数,该函数通过从文档中提取所需内容来返回整洁的文本:
def fetch_and_clean(url):
# 获取 URL 的内容
response = requests.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
# 查找文章的主要内容,忽略侧边栏和标题
content = soup.find('div', {'class': 'mw-parser-output'})
# 移除通常包含“References”、“Bibliography”等标题的参考文献部分
for section_title in ['References', 'Bibliography', 'External links', 'See also']:
section = content.find('span', id=section_title)
if section:
# 从该部分到文档末尾的所有内容移除
for sib in section.parent.find_next_siblings():
sib.decompose()
section.parent.decompose()
# 提取并清理文本
text = content.get_text(separator=' ', strip=True)
text = clean_text(text)
return text
最后,将内容写入 llm.txt
文件,以供负责数据嵌入和存储功能的团队使用:
# 写入清理后的文本的文件
with open('llm.txt', 'w', encoding='utf-8') as file:
for url in urls:
clean_article_text = fetch_and_clean(url)
file.write(clean_article_text + '\n')
print("内容已写入 llm.txt")
输出确认文本已写入:
内容已写入 llm.txt
程序可以根据项目的具体需求进行修改,以将数据保存到其他格式和位置。然后可以在我们继续处理下一批数据之前,验证文件内容:
# 打开文件并读取前 20 行
with open('llm.txt', 'r', encoding='utf-8') as file:
lines = file.readlines()
# 打印前 20 行
for line in lines[:20]:
print(line.strip())
输出显示了将要处理的文档的前几行:
探索太空、行星和卫星的活动 “太空探索”在此指代此处的活动。关于公司,请参见 SpaceX。欲了解更广泛的主题覆盖范围,请参见 Exploration。 阿波罗 11 号任务期间,巴兹·奥尔德林在月球上取样...
该组件可以由喜欢在网络或公司数据环境中搜索文档的团队管理。团队将获得识别项目最佳文档的经验,这是任何 RAG 框架的基础。
现在,团队 #2 可以对数据进行嵌入处理并存储文档。
2. 数据嵌入和存储
团队 #2 的任务是专注于流水线的第二个组件。他们将接收批量的已准备数据进行处理,无需担心数据的获取问题,因为团队 #1 已经通过其数据收集和准备组件提供了支持。
让我们现在来帮助团队 #2 完成任务。打开 GitHub 仓库中的 2-Embeddings_vector_store.ipynb
文件。我们将对团队 #1 提供的数据进行嵌入和存储,并检索一批文档以进行处理。
检索已准备的文档批次
首先,我们从服务器上下载一批由团队 #1 提供的文档,这是即将持续传入的文档流中的第一批。在本例中,我们假设这是太空探索文件:
from grequests import download
source_text = "llm.txt"
directory = "Chapter02"
filename = "llm.txt"
download(directory, filename)
请注意,source_text = "llm.txt"
将被添加数据到我们的向量存储的函数使用。接着,我们简单检查一下文档,以确保团队 #1 已经验证过信息的准确性:
# 打开文件并读取前 20 行
with open('llm.txt', 'r', encoding='utf-8') as file:
lines = file.readlines()
# 打印前 20 行
for line in lines[:20]:
print(line.strip())
输出结果是满意的,如以下摘录所示:
探索太空、行星和卫星的活动 “太空探索”在此指代此处的活动。
现在我们将对数据进行分块。我们将确定一个块大小,以字符数为单位。在本例中,CHUNK_SIZE = 1000
,但我们可以使用不同策略选择块大小。在第 7 章《使用 Wikipedia API 和 LlamaIndex 构建可扩展的知识图谱 RAG》中,我们将通过自动无缝分块进一步优化块大小。
分块对于优化数据处理是必要的:选择文本部分、嵌入和加载数据。它还使嵌入的数据集更易于查询。以下代码对文档进行分块以完成准备过程:
with open(source_text, 'r') as f:
text = f.read()
CHUNK_SIZE = 1000
chunked_text = [text[i:i+CHUNK_SIZE] for i in range(0, len(text), CHUNK_SIZE)]
现在我们准备创建一个向量存储,以对数据进行向量化或将数据添加到现有向量存储中。
检查向量存储是否存在并在不存在时创建
首先,我们需要定义 Activeloop 向量存储路径,无论数据集是否存在:
vector_store_path = "hub://denis76/space_exploration_v1"
请务必将 hub://denis76/space_exploration_v1
替换为您的组织和数据集名称。
然后,我们编写一个函数来尝试加载向量存储,若不存在则自动创建:
from deeplake.core.vectorstore.deeplake_vectorstore import VectorStore
import deeplake.util
try:
# 尝试加载向量存储
vector_store = VectorStore(path=vector_store_path)
print("向量存储已存在")
except FileNotFoundError:
print("向量存储不存在。您可以创建一个新的。")
# 创建向量存储的代码在此处
create_vector_store = True
输出确认向量存储已创建:
您的 Deep Lake 数据集已成功创建!
向量存储已存在
接下来,我们需要创建一个嵌入函数。
嵌入函数
嵌入函数会将我们创建的数据块转换为向量,以支持基于向量的搜索。在此程序中,我们将使用 "text-embedding-3-small"
来对文档进行嵌入。
OpenAI 提供其他可用的嵌入模型:platform.openai.com/docs/models…。在第6章《使用 Pinecone 扩展 RAG 银行客户数据》中,嵌入部分提供了嵌入模型的替代代码。无论如何,建议在生产环境中选择嵌入模型之前进行评估。检查 OpenAI 提供的每个嵌入模型的特性,关注其长度和能力。在本例中,选择 text-embedding-3-small
是因为它在效率和速度方面表现出色:
def embedding_function(texts, model="text-embedding-3-small"):
if isinstance(texts, str):
texts = [texts]
texts = [t.replace("\n", " ") for t in texts]
return [data.embedding for data in openai.embeddings.create(input=texts, model=model).data]
OpenAI 的 text-embedding-3-small
嵌入模型通常使用受限维数的嵌入,以在获取足够细节的嵌入、计算工作负载和存储空间之间找到平衡。请确保在运行代码前检查模型页面和价格信息:platform.openai.com/docs/guides…
现在我们准备开始填充向量存储。
将数据添加到向量存储
我们将添加数据的标志设为 True
:
add_to_vector_store = True
if add_to_vector_store:
with open(source_text, 'r') as f:
text = f.read()
CHUNK_SIZE = 1000
chunked_text = [text[i:i+1000] for i in range(0, len(text), CHUNK_SIZE)]
vector_store.add(text=chunked_text,
embedding_function=embedding_function,
embedding_data=chunked_text,
metadata=[{"source": source_text}]*len(chunked_text))
源文本 source_text = "llm.txt"
已经被嵌入并存储。数据集结构的摘要显示数据集已成功加载:
Creating 839 embeddings in 2 batches of size 500:: 100%|██████████| 2/2 [01:44<00:00, 52.04s/it]
Dataset(path='hub://denis76/space_exploration_v1', tensors=['text', 'metadata', 'embedding', 'id'])
tensor htype shape dtype compression
------- ------- ------- ------- -------
text text (839, 1) str None
metadata json (839, 1) str None
embedding embedding (839, 1536) float32 None
id text (839, 1) str None
请注意,数据集包含四个张量:
- embedding:每个数据块被嵌入为一个向量
- id:ID 是一个字符串,且唯一
- metadata:元数据包含数据的来源——在本例中是
llm.txt
文件 - text:数据集中文本块的内容
数据集结构可能因项目而异,正如我们将在第 4 章《面向无人机技术的多模态模块化 RAG》中看到的那样。我们还可以随时可视化数据集的组织情况以验证结构。以下代码将显示刚刚展示的摘要:
# 打印向量存储的摘要
print(vector_store.summary())
如果需要,我们还可以可视化向量存储的信息。
向量存储信息
Activeloop 的 API 参考文档提供了管理数据集所需的全部信息:docs.deeplake.ai/en/latest/。一旦登录app.activeloop.ai/datasets/my…,我们可以可视化数据集。
我们还可以通过一行代码加载数据集:
ds = deeplake.load(vector_store_path)
输出提供了一个路径,用于在线可视化、查询和探索数据集:
此数据集可以通过 ds.visualize() 在 Jupyter Notebook 中可视化,或在 https://app.activeloop.ai/denis76/space_exploration_v1 上查看。
hub://denis76/space_exploration_v1 加载成功。
您还可以通过登录 Activeloop 并访问您的数据集直接查看数据集。您将找到在线数据集探索工具,用于查询数据集等,如下所示。
在众多可用功能中,我们可以显示数据集的估算大小:
# 估算数据集的大小(以字节为单位)
ds_size = ds.size_approx()
获取大小后,我们可以将其转换为兆字节和吉字节:
# 将字节转换为兆字节,并限制为小数点后 5 位
ds_size_mb = ds_size / 1048576
print(f"数据集大小(兆字节):{ds_size_mb:.5f} MB")
# 将字节转换为吉字节,并限制为小数点后 5 位
ds_size_gb = ds_size / 1073741824
print(f"数据集大小(吉字节):{ds_size_gb:.5f} GB")
输出显示数据集的大小,以兆字节和吉字节为单位:
数据集大小(兆字节):55.31311 MB
数据集大小(吉字节):0.05402 GB
团队 #2 的数据嵌入和存储流水线组件似乎运行正常。现在让我们探索增强生成部分。
3. 增强输入生成
增强生成是流水线的第三个组件。我们将使用检索到的数据来增强用户输入。该组件处理用户输入、查询向量存储、增强输入并调用 gpt-4-turbo
,如图 2.9 所示。
图 2.9 显示,流水线组件 #3 完全称得上“检索增强生成(RAG)”之名。然而,如果没有团队 #1 和团队 #2 提供必要的信息来生成增强输入内容,这个组件将无法运行。
让我们来看看团队 #3 是如何完成任务的。打开 GitHub 仓库中的 3-Augmented_Generation.ipynb
文件。笔记本中的“安装环境”部分在本章的“设置环境”部分中进行了描述。我们选择向量存储(将向量存储路径替换为您的向量存储路径):
vector_store_path = "hub://denis76/space_exploration_v1"
然后加载数据集:
from deeplake.core.vectorstore.deeplake_vectorstore import VectorStore
import deeplake.util
ds = deeplake.load(vector_store_path)
我们打印一条确认向量存储存在的消息。在此阶段,团队 #2 之前已确保一切正常,因此我们可以快速继续前进:
vector_store = VectorStore(path=vector_store_path)
输出确认数据集存在并已加载:
Deep Lake Dataset in hub://denis76/space_exploration_v1 already exists, loading from the storage
我们假设流水线组件 #2 已按数据嵌入和存储部分中的构建完成并填充了 vector_store
,并已验证其可查询。现在,让我们处理用户输入。
输入和查询检索
我们需要嵌入函数来对用户输入进行嵌入:
def embedding_function(texts, model="text-embedding-3-small"):
if isinstance(texts, str):
texts = [texts]
texts = [t.replace("\n", " ") for t in texts]
return [data.embedding for data in openai.embeddings.create(input=texts, model=model).data]
请注意,我们使用与数据嵌入和存储组件相同的嵌入模型,以确保输入和向量数据集之间的完全兼容性:text-embedding-ada-002
。
现在我们可以使用交互式提示输入,或批量处理用户输入。在本例中,我们处理一个已输入的用户输入,例如可从用户界面获取。
首先,我们请求用户输入或定义一个输入:
def get_user_prompt():
# 请求用户输入搜索提示
return input("请输入您的搜索查询:")
# 获取用户的搜索查询
#user_prompt = get_user_prompt()
user_prompt = "Tell me about space exploration on the Moon and Mars."
然后将提示插入搜索查询并将输出存储在 search_results
中:
search_results = vector_store.search(embedding_data=user_prompt, embedding_function=embedding_function)
用户提示和存储在 search_results
中的搜索结果格式化后可以显示。首先,打印用户提示:
print(user_prompt)
我们还可以对检索到的文本进行换行,以获得格式化的输出:
# 定义一个将文本换行至指定宽度的函数
def wrap_text(text, width=80):
lines = []
while len(text) > width:
split_index = text.rfind(' ', 0, width)
if split_index == -1:
split_index = width
lines.append(text[:split_index])
text = text[split_index:].strip()
lines.append(text)
return '\n'.join(lines)
不过,这里我们只选择顶部的一个结果并打印出来:
import textwrap
# 假设搜索结果按最佳结果排序
top_score = search_results['score'][0]
top_text = search_results['text'][0].strip()
top_metadata = search_results['metadata'][0]['source']
# 打印顶部搜索结果
print("Top Search Result:")
print(f"Score: {top_score}")
print(f"Source: {top_metadata}")
print("Text:")
print(wrap_text(top_text))
以下输出显示我们获得了一个相当好的匹配:
Top Search Result:
Score: 0.6016581654548645
Source: llm.txt
Text:
探索太空、行星和卫星的活动 “太空探索”在此指代此处的活动。
如需了解公司信息,请参见 SpaceX。欲了解更广泛的主题覆盖范围,请参见
探索。阿波罗 11 号任务期间巴兹·奥尔德林在月球上取样,...
我们已经准备好使用检索到的附加信息来增强输入。
增强输入
程序将检索到的顶部文本添加到用户输入中:
augmented_input = user_prompt + " " + top_text
print(augmented_input)
输出显示了增强后的输入:
Tell me about space exploration on the Moon and Mars. Exploration of space, planets …
现在 gpt-4o
可以处理增强后的输入并生成内容:
from openai import OpenAI
client = OpenAI()
import time
gpt_model = "gpt-4o"
start_time = time.time() # 在请求之前开始计时
请注意,我们在计时过程中。接下来我们编写生成 AI 调用,向创建的消息添加角色:
def call_gpt4_with_full_text(itext):
# 将所有行连接成一个字符串
text_input = '\n'.join(itext)
prompt = f"Please summarize or elaborate on the following content:\n{text_input}"
try:
response = client.chat.completions.create(
model=gpt_model,
messages=[
{"role": "system", "content": "You are a space exploration expert."},
{"role": "assistant", "content": "You can read the input and answer in detail."},
{"role": "user", "content": prompt}
],
temperature=0.1 # 根据需要微调参数
)
return response.choices[0].message.content.strip()
except Exception as e:
return str(e)
使用增强后的输入调用生成模型;计算并显示响应时间和输出:
gpt4_response = call_gpt4_with_full_text(augmented_input)
response_time = time.time() - start_time # 计算响应时间
print(f"Response Time: {response_time:.2f} seconds") # 打印响应时间
print(gpt_model, "Response:", gpt4_response)
输出显示原始响应和响应时间:
Response Time: 8.44 seconds
gpt-4o Response: Space exploration on the Moon and Mars has been a significant focus of human spaceflight and robotic missions. Here's a detailed summary…
让我们使用 textwrap
格式化输出并打印结果。print_formatted_response(response)
首先检查响应中是否包含 Markdown 特性。如果是,则格式化响应;如果不是,则执行标准文本换行输出:
import textwrap
import re
from IPython.display import display, Markdown, HTML
import markdown
def print_formatted_response(response):
# 通过检测标题、粗体、列表等模式来检查是否包含 markdown
markdown_patterns = [
r"^#+\s", # 标题
r"^*+", # 项目符号
r"**", # 粗体
r"_", # 斜体
r"[.+](.+)", # 链接
r"-\s", # 列表中的破折号
r"```" # 代码块
]
# 如果匹配任一模式,则假设响应为 markdown 格式
if any(re.search(pattern, response, re.MULTILINE) for pattern in markdown_patterns):
# 检测到 markdown,将其转换为 HTML 以获得更好的显示效果
html_output = markdown.markdown(response)
display(HTML(html_output)) # 使用 display(HTML()) 在 Colab 中渲染 HTML
else:
# 未检测到 markdown,按纯文本格式换行打印
wrapper = textwrap.TextWrapper(width=80)
wrapped_text = wrapper.fill(text=response)
print("Text Response:")
print("--------------------")
print(wrapped_text)
print("--------------------\n")
print_formatted_response(gpt4_response)
输出效果令人满意:
月球探索
历史任务:
1. 阿波罗任务:NASA 的阿波罗计划,尤其是阿波罗 11 号,于 1969 年实现了人类首次登月。巴兹·奥尔德林等宇航员收集了核心样本并进行了实验。
2. 月球任务:各国进行了多项任务来探索月球,包括机器人着陆器和轨道器。
科学目标:
3. 地质研究:了解月球的组成、结构和历史。
4. 资源利用:研究氦-3 和水冰等资源的开采潜力。
未来计划:
1. 阿耳特弥斯计划:NASA 计划在 2020 年代末让人类重返月球并建立可持续存在。
2. 国际合作:与其他航天机构和私人公司合作建立月球基地并进行科学研究。
火星探索
机器人任务:
1. 火星车:NASA 的火星车,如好奇号和毅力号,一直在火星表面探索,分析土壤和岩石样本,并寻找过去生命的迹象。
2. 轨道器:各种轨道器一直在绘制火星表面地图并研究其大气层…
让我们引入一个评估指标来衡量输出的质量。
使用余弦相似度评估输出
在本节中,我们将实现余弦相似度来衡量用户输入与生成式 AI 模型输出之间的相似性。我们还将测量增强的用户输入与生成式 AI 模型输出的相似性。首先定义一个余弦相似度函数:
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
def calculate_cosine_similarity(text1, text2):
vectorizer = TfidfVectorizer()
tfidf = vectorizer.fit_transform([text1, text2])
similarity = cosine_similarity(tfidf[0:1], tfidf[1:2])
return similarity[0][0]
然后,让我们计算用户提示与 GPT-4 的响应之间的相似度得分:
similarity_score = calculate_cosine_similarity(user_prompt, gpt4_response)
print(f"Cosine Similarity Score: {similarity_score:.3f}")
尽管输出对人类来说似乎可以接受,但得分较低:
Cosine Similarity Score: 0.396
这似乎表明我们可能遗漏了一些内容或需要使用其他度量方法。
让我们尝试计算增强输入与 GPT-4 的响应之间的相似度:
# 使用现有函数的示例
similarity_score = calculate_cosine_similarity(augmented_input, gpt4_response)
print(f"Cosine Similarity Score: {similarity_score:.3f}")
得分更高:
Cosine Similarity Score: 0.857
我们可以使用其他方法吗?使用词频-逆文档频率(TF-IDF)的余弦相似度依赖于精确的词汇重叠,并考虑了语言的重要特征,如语义含义、同义词或上下文用法。因此,对于概念上相似但用词不同的文本,此方法可能会产生较低的相似度分数。
相比之下,使用句子转换器计算相似度的嵌入能够捕捉单词和短语之间更深层次的语义关系。这种方法更能识别文本之间的上下文和概念相似性。让我们试试这种方法。
首先,安装 sentence-transformers
:
!pip install sentence-transformers
在会话结束时安装此库需小心,因为它可能会与 RAG 流水线的需求产生潜在冲突。根据项目需求,此代码可以成为另一个独立的流水线组件。
截至 2024 年 8 月,使用 Hugging Face 令牌是可选的。如果 Hugging Face 需要令牌,请在 Hugging Face 注册以获取 API 令牌,检查条件,并按说明设置密钥。
我们现在将使用 MiniLM 架构来完成任务,选择 all-MiniLM-L6-v2
。此模型可通过我们正在使用的 Hugging Face Model Hub 获取。它是 sentence-transformers
库的一部分,sentence-transformers
是 Hugging Face Transformers 库的扩展。我们使用此架构是因为它提供了一个紧凑高效的模型,能够快速生成有意义的句子嵌入。现在让我们通过以下函数实现它:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
def calculate_cosine_similarity_with_embeddings(text1, text2):
embeddings1 = model.encode(text1)
embeddings2 = model.encode(text2)
similarity = cosine_similarity([embeddings1], [embeddings2])
return similarity[0][0]
现在我们可以调用该函数,计算增强用户输入与 GPT-4 的响应之间的相似度:
similarity_score = calculate_cosine_similarity_with_embeddings(augmented_input, gpt4_response)
print(f"Cosine Similarity Score: {similarity_score:.3f}")
输出显示句子转换器更有效地捕捉了文本之间的语义相似性,得到了较高的余弦相似度分数:
Cosine Similarity Score: 0.739
指标的选择取决于项目各阶段的具体要求。在第 3 章《使用 LlamaIndex、Deep Lake 和 OpenAI 构建基于索引的 RAG》中,我们将在实现基于索引的 RAG 时提供高级指标。然而,当前阶段 RAG 流水线的三个组件已成功构建。让我们总结一下本次的探索并进入下一阶段!
总结
在本章中,我们深入探讨了使用 RAG 驱动的生成式 AI 的复杂性,重点关注在处理大数据集时文档嵌入的重要作用。我们从原始文本到嵌入的转换过程,并将它们存储在向量存储中。与参数生成式 AI 模型不同,像 Activeloop 这样的向量存储提供 API 工具和可视化界面,使我们可以随时查看嵌入的文本。
RAG 流水线详细介绍了将 OpenAI 嵌入集成到 Activeloop Deep Lake 向量存储中的组织过程。RAG 流水线被分解为不同的组件,这些组件可以根据项目的不同而有所变化。这种分离使多个团队能够同时工作而不依赖其他团队,从而加速开发并专注于个别方面,如数据收集、嵌入处理和生成增强式 AI 过程的查询生成。
随后我们构建了一个三组件 RAG 流水线,首先强调了特定跨平台包和仔细系统架构规划的必要性。所用资源包括从头构建的 Python 函数、用于在向量存储中组织和存储嵌入的 Activeloop Deep Lake 数据集、OpenAI 嵌入模型和 OpenAI 的 GPT-4o 生成式 AI 模型。程序引导我们通过 Python 构建一个三部分的 RAG 流水线,涉及环境设置、依赖管理及处理实现挑战,例如数据分块和向量存储集成。
此过程提供了嵌入文档到向量存储中并利用它们增强生成式 AI 输出的深入理解,使我们能够将这些见解应用于组织内有条理的流程和团队中的实际 AI 应用。向量存储增强了对信息检索精度有较高要求的文档的检索。索引进一步提升了 RAG 的检索速度和相关性。下一章将进一步介绍高级索引方法,以便更好地检索和增强输入。
问题
请用“是”或“否”回答以下问题:
- 嵌入是否将文本转换为高维向量以加速 RAG 中的检索?
- 在检索详细语义内容时,关键词搜索是否比嵌入更有效?
- 是否推荐将 RAG 流水线分为独立的组件?
- RAG 流水线是否仅由两个主要组件组成?
- Activeloop Deep Lake 是否可以同时处理嵌入和向量存储?
- 本章是否使用了 OpenAI 的 text-embedding-3-small 模型生成嵌入?
- 在 RAG 驱动的系统中,数据嵌入是否可见并直接可追溯?
- 一个 RAG 流水线是否可以在不分解为独立组件的情况下顺畅运行?
- 将大文本分块为较小部分是否必要以进行嵌入和存储?
- 是否使用余弦相似度指标评估检索信息的相关性?
参考文献
- OpenAI Ada 嵌入文档:platform.openai.com/docs/guides…
- OpenAI GPT 生成内容文档:platform.openai.com/docs/models…
- Activeloop API 文档:docs.deeplake.ai/en/latest/
- MiniLM 模型参考:huggingface.co/sentence-tr…
延伸阅读
- OpenAI 关于嵌入的文档:platform.openai.com/docs/guides…
- Activeloop 文档:docs.activeloop.ai/