加载中

现在我们已经从概念上理解了RAG流程,让我们通过一个实际示例逐步实现它。我们将完整地走一遍RAG流程的全部五个阶段,从文本分块到为用户查询找到相关文档。

设置向量数据库

在此实现中,我们将使用一个自定义的VectorIndex类,它提供了存储和搜索嵌入所需的基本功能。该类负责处理向量存储、距离计算(使用余弦相似度)以及文档检索。

五步RAG实现

让我们逐步了解RAG流程的每一个步骤:

第1步:按章节对文本进行分块

首先,我们需要将源文档拆分成可管理的块。我们将使用之前介绍过的基于章节的分块方法:

python
chunks = chunk_by_section(text)

这会将我们的report.md文件拆分成可以单独处理的逻辑章节。

第2步:为每个块生成嵌入

接下来,我们将每个文本块转换为能够捕捉其语义含义的数值嵌入:

python
embeddings = generate_embedding(chunks)

这些嵌入使我们能够在不同文本片段之间进行数学比较。

第3步:将嵌入存储到向量数据库中

现在我们创建向量存储,并用我们的嵌入及其关联文本填充它:

python
store = VectorIndex()

for embedding, chunk in zip(embeddings, chunks):
    store.add_vector(embedding, {"content": chunk})

请注意,我们同时存储了嵌入和原始文本内容。这一点至关重要,因为当我们稍后检索相似的嵌入时,我们需要访问实际的文本,而不仅仅是数值向量。对于我们开发者来说,单独的嵌入本身并没有用处——我们需要它所代表的人类可读内容。

第4步:为用户查询生成嵌入

当用户提出问题时,我们需要将其查询转换到与我们存储的文档相同的嵌入空间中:

python
user_embedding = generate_embedding("What did the software engineering dept do last year?")

第5步:搜索相关文档

最后,我们搜索向量存储以找到最相关的块:

python
results = store.search(user_embedding, 2)

for doc, distance in results:
    print(distance, "\n", doc["content"][0:200], "\n")

这会返回两个最相似的块及其余弦距离分数。

上图展示了向量数据库处理用户查询的方式。当我们提出一个问题时,它会被转换为一个嵌入向量,数据库会在高维空间中找到与其"最接近"的已存储向量。

理解结果

当我们使用关于软件工程部门的查询运行此示例时,我们会得到两个相关的章节:

  • 第2节:软件工程 - Project Phoenix稳定性增强(距离:0.71)
  • 方法论章节(距离:0.72)

距离分数越低,内容与我们的查询就越相似。这两个结果都与我们关于软件工程部门完成了哪些工作的问题相关。

为什么要将内容与嵌入一起存储

您可能会想,为什么我们要将原始文本与每个嵌入一起存储。原因很实际:嵌入只是数字数组,对人类没有直接意义。当我们的搜索返回最相似的嵌入时,我们需要实际的文本内容来理解找到了什么,并将其用于生成响应。

一些实现只存储一个指向原始文本的ID,但为了简单起见,我们选择将内容直接与每个向量一起存储。

接下来是什么

此实现涵盖了RAG工作流程的核心部分,但仍有一些改进可以进行。在实际应用中,您可能会遇到这种基本方法无法按预期工作的情况,我们将在接下来的章节中探讨这些改进方案。