跳转到主要内容
检索增强生成(RAG)是构建需要从您自己的文档中回答问题的 AI 应用的最有用模式之一。您不再让模型仅依赖记忆,而是先检索相关源材料、将该上下文发送给模型,并要求其附带引用进行回答。 在本教程中,我们将使用 Python、Venice(用于嵌入和聊天补全)、Qdrant(用于向量搜索)和 FastEmbed(用于本地重排序)构建一个私有 RAG 机器人。最后,您将拥有一个本地文档助手的核心部分,它可以摄取您的文件、检索相关 chunk、重新排序它们并附带引用回答。 运行中的 RAG 机器人 在我们继续之前:如果您想运行本文中的代码,您需要一个 Venice API 密钥。将其导出为环境变量:
对完整代码实现感兴趣?请查看 GitHub 仓库

现代 RAG 机器人的工作方式

良好的 RAG 流水线不仅仅是”把文档放进向量数据库”。基本流程如下: 重排序步骤是使这比基本 RAG 演示更有用的升级。向量搜索快速且擅长查找语义相似的 chunk,但它仍可能返回与主题相邻而非直接有用的段落。Cross-encoder 一起读取问题和每个候选 chunk,然后评分该 chunk 实际回答问题的程度。

安装依赖项

我们将使用 OpenAI Python SDK,因为 Venice 暴露了 OpenAI 兼容的 API。我们还将使用支持 FastEmbed 的 Qdrant Python 客户端:
如果您更喜欢将依赖项保存在文件中,使用相同的包创建 requirements.txt

选择模型

创建一个名为 rag_bot.py 的文件,然后从添加导入、数据结构、API URL 和模型名称开始:
嵌入模型名称特意为 OpenAI 兼容。Venice 将兼容的嵌入模型名称映射到 Venice 托管的嵌入模型,因此现有的 OpenAI SDK 代码通常可以通过更改 base_url 和 API 密钥来迁移。 您可以使用以下命令列出可用的 Venice 模型:
对于聊天模型:

创建 Venice 和 Qdrant 客户端

为嵌入和聊天补全创建一个 OpenAI 兼容的 Venice 客户端:
对于 Qdrant,您有三种有用的模式: 对于私有本地机器人,从磁盘上的本地 Qdrant 路径开始:
在生产环境中处理部署有几种不同的方法。但是,如果您使用远程 Qdrant 部署,请记住您的文档 chunk 和元数据将存储在那里。Venice 可以使推理层保持私密,但您仍应为您的数据选择正确的 Qdrant 部署。

加载和分块文档

在本教程中,我们将让机器人摄取本地文件或文件夹。从 .md.rst.txt 文件开始:
文件加载后,我们需要通过”分块”来拆分文本——将其分成数据块。简单的策略可能会均匀地拆分块。但在大多数情况下,这会丢失给定语义边界处的信息,从而降低 RAG 系统的有效性。 我们将使用的分块策略优先选择段落或句子边界,以便模型获得连贯的上下文:
1000 字符的起始 chunk 大小加 150 字符的重叠对于混合 Markdown 和文本文档是一个良好的默认值。较小的 chunk 可以提高精度。较大的 chunk 可以保留更多上下文。正确的设置通常取决于您存储的文档类型。

使用 Venice 嵌入文档

有了 chunk 后,我们批量嵌入它们:
批处理很重要。一次嵌入一个 chunk 很简单,但会增加不必要的延迟。保持批量大小可配置,以便您可以根据工作负载调整吞吐量。

在 Qdrant 中存储向量

在插入点之前,使用正确的向量大小创建 Qdrant 集合。知道向量大小最简单的方法是嵌入第一批,然后使用 len(embeddings[0])
每个点存储向量加上 payload 元数据。Payload 包括原始文本和源路径,以便答案可以引用上下文的来源:
使用从 sourcechunk_index 和内容派生的确定性 UUID。这使得对未更改 chunk 的重复摄取具有幂等性。

检索候选 chunk

在问题时间,机器人嵌入用户的问题并向 Qdrant 请求顶级向量匹配:
这里的 limit 是候选数量。它通常应该高于您计划发送给模型的 chunk 数量,因为下一步将对它们进行重新排序。一个良好的默认值是检索 8 个候选并将最佳 4 个发送到聊天模型。

使用 FastEmbed 重新排序

现在我们添加使检索感觉更智能的部分。
嵌入搜索和 cross-encoder 重排序之间的重要区别是评分的方式。 嵌入搜索将问题的一个向量与每个 chunk 的一个向量进行比较。它快速且可扩展。Cross-encoder 一起评估问题和 chunk。它更慢,但可以更直接地判断相关性。 这就是为什么常见模式是:
  1. 用向量搜索检索更大的候选集。
  2. 在本地仅重新排序这些候选。
  3. 将顶部几个 chunk 发送到语言模型。
一个良好的起点是 candidate_k=8top_k=4。如果正确的来源经常在附近但未进入最终上下文,则增加 candidate_k

使用 Venice 聊天补全回答

选择上下文后,用源编号格式化它:
然后将上下文发送到 Venice 聊天模型:
注意系统 prompt:机器人被告知仅从提供的上下文中回答。这是一个简单但重要的护栏。当检索到的文档不支持答案时,RAG 助手不应自信地从一般模型知识中回答。

运行机器人

将各部分组装成脚本后,将其保存为 rag_bot.py。简单的首次运行可以使用一些内置示例文档,以便您在摄取自己的文件之前验证流水线:
要摄取您自己的文档:
要在磁盘上保留本地 Qdrant 集合并启动交互式聊天:
脚本打印答案,然后打印带有向量和重排序分数的源:
如果您想检查传递给模型的实际文本,添加:

有用的 CLI 选项

将主要的检索旋钮作为 CLI 选项暴露,以便您可以在不编辑代码的情况下调整机器人: 对于重复的本地开发,常见流程是:
然后无需再次摄取即可提出后续问题:

隐私注意事项

对于私有 RAG 设置,请单独考虑每一层: 本教程最私密的默认值是使用 Venice 进行推理、磁盘上的本地 Qdrant 和本地 FastEmbed 重新排序。这为您提供了一个实用的 RAG 机器人,而无需将您的向量数据库 payload 发送到第三方向量存储。

需要预先处理的常见错误

如果您更改嵌入模型,请重建 Qdrant 集合。不同的嵌入模型可能产生不同维度的向量,而 Qdrant 集合期望固定的向量大小。

接下来去哪里

一旦您让基线运行起来,最高影响的改进通常是:
  • 添加针对 PDF、HTML、工单或内部 wiki 页面的文档特定加载器。
  • 存储更丰富的元数据,如标题、标头、日期、所有者和 URL。
  • 在实际问题上调整 candidate_ktop_k、chunk 大小和重叠。
  • 添加评估问题,以便您可以衡量更改前后的检索质量。
  • 流式传输最终的 Venice 聊天补全以获得更好的交互式聊天体验。
RAG 系统易于演示,且出乎意料地容易做得平庸。向量搜索加重新排序的模式是一个强大的基础,因为它在保持检索快速的同时,为机器人向语言模型发送正确上下文提供了更好的机会。