现在我们已经从概念上理解了RAG流程,让我们通过一个实际示例逐步实现它。我们将完整地走一遍RAG流程的全部五个阶段,从文本分块到为用户查询找到相关文档。
设置向量数据库
在此实现中,我们将使用一个自定义的VectorIndex类,它提供了存储和搜索嵌入所需的基本功能。该类负责处理向量存储、距离计算(使用余弦相似度)以及文档检索。
五步RAG实现
让我们逐步了解RAG流程的每一个步骤:
第1步:按章节对文本进行分块
首先,我们需要将源文档拆分成可管理的块。我们将使用之前介绍过的基于章节的分块方法:
chunks = chunk_by_section(text)这会将我们的report.md文件拆分成可以单独处理的逻辑章节。
第2步:为每个块生成嵌入
接下来,我们将每个文本块转换为能够捕捉其语义含义的数值嵌入:
embeddings = generate_embedding(chunks)这些嵌入使我们能够在不同文本片段之间进行数学比较。
第3步:将嵌入存储到向量数据库中
现在我们创建向量存储,并用我们的嵌入及其关联文本填充它:
store = VectorIndex()
for embedding, chunk in zip(embeddings, chunks):
store.add_vector(embedding, {"content": chunk})请注意,我们同时存储了嵌入和原始文本内容。这一点至关重要,因为当我们稍后检索相似的嵌入时,我们需要访问实际的文本,而不仅仅是数值向量。对于我们开发者来说,单独的嵌入本身并没有用处——我们需要它所代表的人类可读内容。
第4步:为用户查询生成嵌入
当用户提出问题时,我们需要将其查询转换到与我们存储的文档相同的嵌入空间中:
user_embedding = generate_embedding("What did the software engineering dept do last year?")第5步:搜索相关文档
最后,我们搜索向量存储以找到最相关的块:
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工作流程的核心部分,但仍有一些改进可以进行。在实际应用中,您可能会遇到这种基本方法无法按预期工作的情况,我们将在接下来的章节中探讨这些改进方案。