基本 RAG
检索增强生成(RAG)是一种人工智能框架,它结合了大型语言模型(LLM)和信息检索系统的能力。它对于利用外部知识回答问题或生成内容非常有用。RAG 主要有两个步骤:
- 检索:从知识库或外部源检索相关信息,例如使用存储在向量存储中的文本嵌入。
- 生成:将相关信息插入到 LLM 的提示中以生成信息。
在本指南中,我们将介绍一个非常基本的 RAG 示例,您可以在我们的cookbook中找到更深入的指南。
从头开始构建 RAG
本节旨在指导您从头开始构建一个基本的 RAG。我们有两个目标:首先,让用户全面了解 RAG 的内部工作原理,揭开其底层机制的神秘面纱;其次,为您提供构建 RAG 所需的基本基础,仅使用最少的必需依赖项。
导入所需包
第一步是安装 mistralai
和 faiss-cpu
包并导入所需包
from mistralai import Mistral
import requests
import numpy as np
import faiss
import os
from getpass import getpass
api_key= getpass("Type your API Key")
client = Mistral(api_key=api_key)
获取数据
在这个非常简单的示例中,我们从 Paul Graham 写的一篇文章中获取数据
response = requests.get('https://raw.githubusercontent.com/run-llama/llama_index/main/docs/docs/examples/data/paul_graham/paul_graham_essay.txt')
text = response.text
我们还可以将文章保存在本地文件中
f = open('essay.txt', 'w')
f.write(text)
f.close()
将文档分割成块
在 RAG 系统中,将文档分割成更小的块至关重要,以便在后续的检索过程中更有效地识别和检索最相关的信息。在这个示例中,我们简单地按字符分割文本,将 2048 个字符合并成一个块,这样我们得到 37 个块。
chunk_size = 2048
chunks = [text[i:i + chunk_size] for i in range(0, len(text), chunk_size)]
len(chunks)
输出
37
注意事项:
- 块大小:根据您的具体用例,可能需要自定义或尝试不同的块大小和块重叠,以实现 RAG 的最佳性能。例如,较小的块在检索过程中可能更有益,因为较大的文本块通常包含填充文本,这会模糊语义表示。因此,在检索过程中使用较小的文本块可以使 RAG 系统更有效地识别和提取相关信息,且更准确。然而,值得考虑使用较小块所带来的权衡,例如增加处理时间和计算资源。
- 如何分割:虽然最简单的方法是按字符分割文本,但根据用例和文档结构,还有其他选项。例如,为了避免超出 API 调用中的令牌限制,可能需要按令牌分割文本。为了保持块的连贯性,按句子、段落或 HTML 标题分割文本可能很有用。如果处理代码,通常建议按有意义的代码块分割,例如使用抽象语法树 (AST) 解析器。
为每个文本块创建嵌入
对于每个文本块,我们还需要创建文本嵌入,它们是向量空间中文本的数值表示。具有相似含义的词语预计在向量空间中距离更近或距离更短。要创建嵌入,请使用 Mistral AI 的嵌入 API 端点和嵌入模型 mistral-embed
。我们创建了一个 get_text_embedding
函数来从单个文本块获取嵌入,然后我们使用列表推导式来获取所有文本块的文本嵌入。
def get_text_embedding(input):
embeddings_batch_response = client.embeddings.create(
model="mistral-embed",
inputs=input
)
return embeddings_batch_response.data[0].embedding
text_embeddings = np.array([get_text_embedding(chunk) for chunk in chunks])
加载到向量数据库
获取文本嵌入后,常见的做法是将其存储在向量数据库中,以便进行高效处理和检索。有几个向量数据库可供选择。在我们简单的示例中,我们使用开源向量数据库 Faiss,它支持高效的相似性搜索。
使用 Faiss,我们实例化一个 Index 类实例,该类定义了向量数据库的索引结构。然后我们将文本嵌入添加到此索引结构中。
import faiss
d = text_embeddings.shape[1]
index = faiss.IndexFlatL2(d)
index.add(text_embeddings)
注意事项:
- 向量数据库:选择向量数据库时,需要考虑几个因素,包括速度、可扩展性、云管理、高级过滤以及开源与闭源。
为问题创建嵌入
当用户提问时,我们还需要使用与之前相同的嵌入模型为该问题创建嵌入。
question = "What were the two main things the author worked on before college?"
question_embeddings = np.array([get_text_embedding(question)])
注意事项:
- 假设文档嵌入 (HyDE):在某些情况下,用户的提问可能不是用于识别相关上下文的最相关查询。相反,生成基于用户查询的假设答案或假设文档,并使用生成文本的嵌入来检索相似的文本块,这可能更有效。
从向量数据库检索相似的块
我们可以使用 index.search
在向量数据库上执行搜索,它接受两个参数:第一个是问题嵌入的向量,第二个是要检索的相似向量数量。此函数返回向量数据库中最相似于问题向量的向量的距离和索引。然后根据返回的索引,我们可以检索与这些索引相对应的实际相关文本块。
D, I = index.search(question_embeddings, k=2) # distance, index
retrieved_chunk = [chunks[i] for i in I.tolist()[0]]
注意事项:
- 检索方法:有许多不同的检索策略。在我们的示例中,我们展示了一个简单的基于嵌入的相似性搜索。有时当数据存在元数据时,最好先基于元数据过滤数据,然后再执行相似性搜索。还有其他统计检索方法,如 TF-IDF 和 BM25,它们使用文档中术语的频率和分布来识别相关文本块。
- 检索到的文档:我们总是按原样检索单个文本块吗?不总是。
- 有时,我们希望包含实际检索到的文本块周围的更多上下文。我们将实际检索到的文本块称为“子块”,我们的目标是检索“子块”所属的更大的“父块”。
- 有时,我们可能还想为检索到的文档提供权重。例如,基于时间的加权方法将帮助我们检索最新的文档。
- 检索过程中一个常见的问题是“中间丢失”问题,即长上下文中间的信息容易丢失。我们的模型已尝试缓解此问题。例如,在 passkey 任务中,我们的模型展示了通过在长提示中(最长 32k 上下文长度)检索随机插入的 passkey 来实现“大海捞针”的能力。但是,值得考虑尝试重新排序文档,以确定将最相关的块放在开头和结尾是否能带来更好的结果。
在提示中结合上下文和问题并生成响应
最后,我们可以将检索到的文本块作为提示中的上下文信息。这是一个提示模板,我们可以在其中包含检索到的文本和用户问题。
prompt = f"""
Context information is below.
---------------------
{retrieved_chunk}
---------------------
Given the context information and not prior knowledge, answer the query.
Query: {question}
Answer:
"""
然后,我们可以使用 Mistral 聊天完成 API 与 Mistral 模型(例如,mistral-medium-latest)聊天,并根据用户问题和问题的上下文生成答案。
def run_mistral(user_message, model="mistral-large-latest"):
messages = [
{
"role": "user", "content": user_message
}
]
chat_response = client.chat.complete(
model=model,
messages=messages
)
return (chat_response.choices[0].message.content)
run_mistral(prompt)
输出
'The two main things the author worked on before college were writing and programming. They wrote short stories and tried writing programs on an IBM 1401 in 9th grade.'
注意事项:
- 提示技术:大多数提示技术也可以用于开发 RAG 系统。例如,我们可以使用少样本学习通过提供几个示例来指导模型的回答。此外,我们可以明确指示模型以某种特定方式格式化答案。
RAG 示例
在我们的社区驱动的 cookbook 中查找探索各种主题和解决方案的多个 RAG cookbook。
其中包括您可以找到如何执行以下操作:
- 使用 LangChain 进行 RAG:访问我们的社区 cookbook 示例,了解如何使用 LangChain 的 LangGraph 与 Mistral API。这些 cookbook 涵盖了各种实现,包括自适应 RAG、纠错 RAG 和自 RAG,展示了如何集成 LangChain 的功能以增强检索增强生成。
- 使用 LlamaIndex 进行 RAG:访问我们的社区 cookbook 示例,了解如何使用 LlamaIndex 与 Mistral API 通过 ReAct 代理(一个能够使用工具的自主 LLM 驱动代理)对多个文档执行复杂查询。
- 使用 Haystack 进行 RAG:访问我们的社区 cookbook 示例,探索如何使用 Haystack 与 Mistral API 进行文档聊天功能。