
掌握检索增强生成:简化 Sqlite 初学者的 Rag 实现
- Rifx.Online
- Machine Learning , Data Science , Best Practices
- 26 Feb, 2025
我们真的可以用单文件架构实现 RAG 吗?
这是关于使用 SQLite 进行机器学习的两部分系列中的第二部分。在我上一篇文章中,我深入探讨了 SQLite 如何迅速成为 Web 应用程序的生产就绪数据库。在本文中,我将讨论如何使用 SQLite 执行检索增强生成(RAG)。
本文中提到的代码可以在这里找到。
当我第一次学习如何作为一个新兴的数据科学家执行检索增强生成(RAG)时,我遵循了_传统路径_。这通常看起来像:
- 谷歌检索增强生成并寻找教程
- 找到最流行的框架,通常是 LangChain 或 LlamaIndex
- 找到最流行的云向量数据库,通常是 Pinecone 或 Weaviate
- 阅读大量文档,将所有部分组合在一起,然后成功!
事实上,我确实写了一篇关于我在 LangChain 中使用 Pinecone 构建 RAG 系统的经验的文章(链接)。
使用带有云向量数据库的 RAG 框架并没有什么特别错误。然而,我认为对于初学者来说,这使情况变得过于复杂。我们真的需要一个完整的框架来学习如何做 RAG 吗?进行 API 调用到云向量数据库是必要的吗?这些数据库充当黑箱,这对学习者(或者说对任何人)来说都不是好事。
在本文中,我将向您展示如何在最简单的栈上执行 RAG。实际上,这个“栈”只是带有 sqlite-vec 扩展和 OpenAI API 的 SQLite,以使用它们的 embedding 和聊天模型。我建议您阅读第一部分以深入了解 SQLite 以及它如何迅速为 Web 应用程序做好生产准备。就我们在这里的目的而言,了解 SQLite 是最简单的数据库类型就足够了:您仓库中的一个单文件。
所以放弃您的云向量数据库和臃肿的框架,让我们来做一些 RAG。
SQLite-Vec
SQLite数据库的一个强大功能是使用扩展。对于熟悉Python的人来说,扩展就像库一样。它们是用C语言编写的模块化代码,旨在扩展SQLite的功能,使曾经不可能的事情变为可能。一个流行的SQLite扩展示例是Full-Text Search (FTS)扩展。这个扩展允许SQLite在大量文本数据中执行高效搜索。由于该扩展纯粹用C编写,我们可以在任何可以运行SQLite数据库的地方运行它,包括树莓派和浏览器。
在本文中,我将介绍被称为sqlite-vec的扩展。它赋予SQLite执行vector search的能力。vector search类似于full-text search,因为它允许在文本数据中进行高效搜索。然而,vector search并不是搜索文本中的确切单词或短语,而是具有语义理解。换句话说,搜索“horses”将找到“equestrian”、“pony”、“Clydesdale”等的匹配。full-text search无法做到这一点。
sqlite-vec利用virtual tables,就像大多数SQLite扩展一样。虚拟表类似于常规表,但具有额外的功能:
- 自定义数据源: SQLite中标准表的数据存储在一个单一的db文件中。对于虚拟表,数据可以存储在外部源中,例如CSV文件或API调用。
- 灵活的功能: 虚拟表可以添加专门的索引或查询能力,并支持复杂的数据类型,如JSON或XML。
- 与SQLite Query Engine的集成: 虚拟表与SQLite的标准查询语法无缝集成,例如
SELECT
、INSERT
、UPDATE
和DELETE
选项。最终,由扩展的编写者来支持这些操作。 - 模块的使用: 虚拟表的后端逻辑由一个module(用C或其他语言编写)实现。
创建虚拟表的典型语法如下所示:
CREATE VIRTUAL TABLE my_table USING my_extension_module();
该语句的重要部分是my_extension_module()
。这指定了将为my_table
虚拟表提供后端支持的模块。在sqlite-vec中,我们将使用vec0
模块。
代码讲解
本文的代码可以在 这里 找到。它是一个简单的目录,绝大多数文件为 .txt 文件,我们将使用这些文件作为我们的虚拟数据。由于我是一个物理爱好者,绝大多数文件与物理相关,只有少数文件与其他随机领域相关。在本次讲解中,我不会展示完整的代码,而是会突出重要的部分。下面是该仓库的树状视图。请注意,my_docs.db
是 SQLite 用于管理我们所有数据的单文件数据库。
.
├── data
│ ├── cooking.txt
│ ├── gardening.txt
│ ├── general_relativity.txt
│ ├── newton.txt
│ ├── personal_finance.txt
│ ├── quantum.txt
│ ├── thermodynamics.txt
│ └── travel.txt
├── my_docs.db
├── requirements.txt
└── sqlite_rag_tutorial.py
第一步是安装必要的库。下面是我们的 requirements.txt
文件。如你所见,它只有三个库。我建议创建一个最新版本的 Python 虚拟环境(本文使用的是 3.13.1),然后运行 pip install -r requirements.txt
来安装这些库。
sqlite-vec==0.1.6
openai==1.63.0
python-dotenv==1.0.1
第二步是创建一个 OpenAI API 密钥,如果你还没有的话。我们将使用 OpenAI 为文本文件生成 embeddings,以便进行 vector search。
import sqlite3
from sqlite_vec import serialize_float32
import sqlite_vec
import os
from openai import OpenAI
from dotenv import load_dotenv
client = OpenAI(api_key=os.getenv('OPENAI_API_KEY'))
第三步是将 sqlite-vec 扩展加载到 SQLite 中。我们将在本文的示例中使用 Python 和 SQL。加载扩展后立即禁用加载扩展的能力是一种良好的安全实践。
db_path = 'my_docs.db'
db = sqlite3.connect(db_path)
db.enable_load_extension(True)
sqlite_vec.load(db)
db.enable_load_extension(False)
接下来,我们将创建我们的虚拟表:
db.execute('''
CREATE VIRTUAL TABLE documents USING vec0(
embedding float[1536],
+file_name TEXT,
+content TEXT
)
''')
documents
是一个包含三列的虚拟表:
sample_embedding
: 1536维的 float,用于存储我们样本文档的 embeddings。file_name
: 存储我们在数据库中存储的每个文件名称的文本。请注意,这一列和下一列前面都有一个+
符号。这表明它们是 辅助字段。 之前在 sqlite-vec 中只能在虚拟表中存储 embedding 数据。然而,最近 推出了一项更新,允许我们在表中添加我们不想嵌入的字段。在这种情况下,我们在与我们的 embeddings 相同的表中添加了内容和文件名称。这将使我们能够轻松查看哪些 embeddings 对应于哪些内容,同时避免了额外的表和 JOIN 语句的需要。content
: 存储每个文件内容的文本。
现在我们在 SQLite 数据库中设置了虚拟表,我们可以开始将文本文件转换为 embeddings 并将其存储在我们的表中:
def get_openai_embedding(text):
response = client.embeddings.create(
model="text-embedding-3-small",
input=text
)
return response.data[0].embedding
for file_name in os.listdir("data"):
file_path = os.path.join("data", file_name)
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
embedding = get_openai_embedding(content)
if embedding:
db.execute(
'INSERT INTO documents (embedding, file_name, content) VALUES (?, ?, ?)',
(serialize_float32(embedding), file_name, content)
)
db.commit()
我们基本上遍历每个 .txt 文件,嵌入每个文件的内容,然后使用 INSERT INTO
语句将 embedding
、file_name
和 content
插入到 documents
虚拟表中。最后的提交语句确保更改被持久化。请注意,我们在这里使用的是来自 sqlite-vec 库的 serialize_float32
。SQLite 本身没有内置的向量类型,因此它将向量存储为二进制大对象 (BLOBs),以节省空间并允许快速操作。在内部,它使用 Python 的 struct.pack()
函数,该函数将 Python 数据转换为 C 风格的二进制表示。
最后,要执行 RAG,你可以使用以下代码进行 K-Nearest-Neighbors (KNN 风格) 操作。这是 vector search 的核心。
query_text = "What is general relativity?"
query_embedding = get_openai_embedding(query_text)
if query_embedding:
rows = db.execute(
"""
SELECT
file_name,
content,
distance
FROM documents
WHERE embedding MATCH ?
ORDER BY distance
LIMIT 3
""",
[serialize_float32(query_embedding)]
).fetchall()
print("Top 3 most similar documents:")
top_contexts = []
for row in rows:
print(row)
top_contexts.append(row[1])
我们首先从用户那里获取查询,在这种情况下是 “What is general relativity?”,并使用与之前相同的 embedding 模型对该查询进行嵌入。然后我们执行 SQL 操作。让我们分解一下:
SELECT
语句意味着检索到的数据将有三列:file_name
、content
和distance
。前两列我们已经提到过。distance
将在 SQL 操作期间计算,稍后会详细介绍。FROM
语句确保你从documents
表中提取数据。WHERE embedding MATCH ?
语句在数据库中所有向量与查询向量之间执行相似性搜索。返回的数据将包括一个distance
列。这个距离只是一个浮点数,测量查询与数据库向量之间的相似性。数字越高,向量越接近。sqlite-vec 提供了几种计算这种相似性的方法。ORDER BY distance
确保以相似性递减的顺序(高 -> 低)对检索到的向量进行排序。LIMIT 3
确保我们只获取与查询嵌入向量最近的前三个文档。你可以调整这个数字,看看获取更多或更少向量如何影响结果。
给定我们的查询“What is general relativity?”,以下文档被提取。效果相当不错!
Top 3 most similar documents:
('general_relativity.txt', 'Einstein’s theory of general relativity redefined our understanding of gravity. Instead of viewing gravity as a force acting at a distance, it interprets it as the curvature of spacetime around massive objects. Light passing near a massive star bends slightly, galaxies deflect beams traveling millions of light-years, and clocks tick at different rates depending on their gravitational potential. This groundbreaking theory led to predictions like gravitational lensing and black holes, phenomena later confirmed by observational evidence, and it continues to guide our understanding of the cosmos.', 0.8316285610198975)
('newton.txt', 'In classical mechanics, Newton’s laws of motion form the foundation of how we understand the movement of objects. Newton’s first law, often called the law of inertia, states that an object at rest remains at rest and an object in motion continues in motion unless acted upon by an external force. This concept extends into more complex physics problems, where analyzing net forces on objects allows us to predict their future trajectories and behaviors. Over time, applying Newton’s laws has enabled engineers and scientists to design safer vehicles, more efficient machines, and even guide spacecraft through intricate gravitational fields.', 1.2036118507385254)
('quantum.txt', 'Quantum mechanics revolutionized our understanding of the microscopic world. Unlike classical particles, quantum entities such as electrons can exhibit both wave-like and particle-like behaviors. Phenomena like quantum superposition suggest that particles can exist in multiple states at once, and the act of measurement often “collapses” these states into one observed outcome. This strange and counterintuitive theory underpins modern technologies like semiconductors and lasers, and it provides a conceptual framework for emerging fields like quantum computing and cryptography.', 1.251380205154419)
然后,我们可以将模型的上下文填充这三份文档,并让它尝试回答我们的提
这忠实于我们提供给模型的文档。干得好 4o-mini
!
结论
sqlite-vec
是一个由 Mozilla Builders Accelerator 项目赞助的项目,因此它背后有一些重要的支持。非常感谢 Alex Garcia,sqlite-vec
的创作者,帮助推动 SQLite 生态系统,并使得使用这个简单的数据库实现机器学习成为可能。这是一个维护良好的库,定期有更新。截至 11 月 20 日,他们甚至 添加了元数据过滤!也许我应该用 SQLite 重新做我之前提到的 RAG 文章 🤔。
该扩展还为几种流行的编程语言提供了绑定,包括 Ruby、Go、Rust 等等。
能够将我们的 RAG 流程简化到基本要素是非常了不起的。回顾一下,不需要像 Postgres、MySQL 等数据库服务的启动和关闭。也不需要对云供应商进行 API 调用。如果您通过 Digital Ocean 或 Hetzner 直接部署到服务器,您甚至可以避免与 AWS、Azure 或 Vercel 等托管云服务相关的 昂贵且不必要的复杂性。
我相信这种简单的架构可以用于各种应用。它更便宜,更易于维护,并且迭代速度更快。一旦您达到一定规模,迁移到更强大的数据库,例如具有 RAG 功能的 pgvector 扩展的 Postgres,可能会更有意义。对于更高级的功能,例如分块和文档清理,框架可能是正确的选择。但对于初创公司和小型企业来说,SQLite 是通往成功的道路。
祝您玩得开心,亲自尝试 sqlite-vec
!
简单的 RAG 架构。作者创作。