postgresml-django:在 Django ORM 中自动生成向量嵌入,用 PostgresML 实现数据库内语义搜索
postgresml-django:在 Django ORM 中自动生成向量嵌入,用 PostgresML 实现数据库内语义搜索
postgresml-django 是 PostgresML 官方发布的一个 Python 模块,用于弥合 PostgresML 机器学习能力与 Django ORM 之间的鸿沟,让 Django 开发者可以用几行代码为模型字段自动生成向量嵌入(embedding),并直接在数据库中完成向量相似度搜索。本文基于 postgresml-django 发布公告展开,并结合仓库中的 pgml.embed() API 文档、数据库内嵌入生成指南以及 Django 嵌入搜索示例博文,讲清模块的用法、底层原理与实战路径。读完你将掌握:如何用 VectorField 自动生成嵌入、如何执行向量相似度搜索、如何为 Django 项目挑选嵌入模型,以及两条接入 PostgresML 的技术路线各自适合什么场景。
一、什么是 postgresml-django
postgresml-django 是连接 PostgresML 与 Django ORM 的桥梁。它发布于 2024 年 9 月,核心价值是让 Django 应用无需理解底层机器学习细节,就能把高级 AI 能力集成进项目。官方公告将其能力概括为三点:
- 自动生成数据库内嵌入:为 Django 模型中指定的字段自动生成向量嵌入,整个过程在 PostgreSQL 数据库内部完成;
- 直接在数据库中进行向量相似度搜索:不需要额外的向量数据库或外部推理服务;
- 无缝集成高级机器学习能力:嵌入模型运行在数据库内,Django 应用无需本地安装、加载或推理模型。
典型应用场景包括推荐系统、语义搜索引擎,以及任何需要文本相似度比较的应用(例如在 pgml-sdks/pgml/python/README.md 中可以看到 PostgresML Python SDK 同样围绕 embedding、collection 与 vector search 组织能力)。
二、快速开始:定义一个自动嵌入的 Django 模型
官方公告给出了最简用法:只需让模型继承 Embed,并为需要生成向量的字段声明一个 VectorField:
from django.db import models
from postgresml_django import VectorField, Embed
class Document(Embed):
text = models.TextField()
text_embedding = VectorField(
field_to_embed="text",
dimensions=384,
transformer="intfloat/e5-small-v2"
)
# Searching
results = Document.vector_search("text_embedding", "query to search against")
上面这段代码(见 公告原文)包含几个关键参数:
| 参数 | 作用 | 示例值说明 |
|---|---|---|
field_to_embed |
指定从模型中的哪个字段生成嵌入 | 上例指向 text 字段 |
dimensions |
输出向量的维度,必须与所选 transformer 模型的输出维度一致 | 384 对应 intfloat/e5-small-v2 的输出维度 |
transformer |
Hugging Face 上的嵌入模型名称,PostgresML 会在数据库内加载并运行它 | intfloat/e5-small-v2 |
Embed 基类负责拦截模型的保存流程,把 field_to_embed 指向的文本送入 transformer 指定的模型,计算出的向量自动写入 text_embedding 列。vector_search(field, query) 则是类方法:传入要检索的向量字段名与查询文本,返回按语义相似度排序的结果。整个流程中,Django 开发者不需要接触任何 SQL、不需要安装模型权重、也不需要调用外部 API。
三、底层原理:VectorField 背后的 pgml.embed()
postgresml-django 的“数据库内嵌入”能力,底层依赖 PostgresML 的原生 SQL 函数 pgml.embed()。其完整签名见 API 文档:
pgml.embed(
transformer TEXT,
"text" TEXT,
kwargs JSONB
)
| 参数 | 说明 | 示例 |
|---|---|---|
transformer |
Hugging Face 嵌入模型名称 | intfloat/e5-small-v2 |
text |
要嵌入的文本,可以是字符串,也可以是 PostgreSQL 表中的一个列名 | 'I am your father, Luke' |
kwargs |
推理时传递给模型的附加参数 | '{"prompt": "query: "}'::JSONB |
该函数从 Hugging Face 下载模型并在 PostgreSQL 进程内完成推理——这正是官方博文强调“模型运行在数据库内部、Django 应用无需在本地安装模型”的技术依据。
在纯 SQL 场景下,pgml.embed() 可以直接作为**生成列(Generated Column)**的表达式,实现与 VectorField 等价的“写入时自动嵌入”效果。API 文档中的完整示例是:
CREATE TABLE star_wars_quotes (
quote TEXT NOT NULL,
embedding vector(384) GENERATED ALWAYS AS (
pgml.embed('intfloat/e5-small-v2', quote, '{"prompt": "passage: "}')
) STORED
);
INSERT INTO star_wars_quotes (quote)
VALUES
('I find your lack of faith disturbing'),
('I''ve got a bad feeling about this.'),
('Do or do not, there is no try.');
可以看到:pgml.embed() 每次向 quote 列写入或更新文本时都会自动生成新的嵌入向量,向量以 vector(384) 类型存储。这与 postgresml-django 中 VectorField(field_to_embed="text", dimensions=384, transformer="intfloat/e5-small-v2") 的效果一一对应,只是前者由模块替你生成了这部分 SQL 与模型编排逻辑。
嵌入完成后,相似度检索在 SQL 层也只需一行:用查询文本生成嵌入向量,再通过 <=>(pgvector 的余弦距离运算符)与表中所有向量比较:
SELECT quote
FROM star_wars_quotes
ORDER BY pgml.embed(
'intfloat/e5-small-v2',
'Feel the force!',
'{"prompt": "query: "}'::JSONB
)::vector <=> embedding DESC
LIMIT 1;
关于距离度量,仓库的 Vector Similarity 指南 介绍了 L1(曼哈顿距离,pgvector 运算符 <+>)、L2(欧氏距离,<->)、内积(<#>)与余弦距离(<=>)四种方案;示例应用选用的 <=> 即余弦距离,它只比较向量夹角、不受向量模长影响,是文本语义检索的常见选择。
四、模型选择:dimensions 与 transformer 如何搭配
VectorField 中的 dimensions 必须严格等于所选模型输出的向量维度,否则向量列与模型输出不匹配。仓库的 In-database Embedding Generation 指南 列出了 PostgresML serverless 实例即时可用的三个模型:
| 模型 | 参数量(M) | 特点 |
|---|---|---|
intfloat/e5-small-v2 |
33.4 | 高质量、最低延迟(公告快速开始示例即选用它,dimensions=384) |
mixedbread-ai/mxbai-embed-large-v1 |
335 | 质量更高、延迟更高 |
Alibaba-NLP/gte-large-en-v1.5 |
434 | 支持最长 8k token 输入 |
选型时注意两点:第一,不同模型在“延迟 vs 质量”之间存在权衡,规模越大质量通常越高、延迟也越高;第二,如果在自己硬件上运行 PostgresML,需要正确配置硬件,或选择能在 CPU 上高效运行的嵌入模型——官方文档明确指出 GPU 加速模型在批量场景下可在亚毫秒级完成嵌入计算。
五、两条接入路径对比:模块封装 vs 手写 Expression
仓库中另一篇配套博文 Using PostgresML with Django and embedding search 展示了 postgresml-django 模块出现之前的原生接入方式,两者形成鲜明对照,也解释了为什么该模块值得兴奋。
路径 A:postgresml-django 模块(公告主题)
如前文所示,Embed + VectorField 把“生成嵌入”“存储向量”“相似度搜索”全部封装好,开发成本集中在声明式模型定义上。
路径 B:手写 Django Expression + RawSQL
该示例应用只有一个 TodoItem 模型,嵌入列基于 GeneratedField + pgvector.django 的 VectorField 定义:
embedding = models.GeneratedField(
expression=EmbedSmallExpression("description"),
output_field=VectorField(dimensions=768),
db_persist=True,
)
GeneratedField 是数据库自动填充的列,应用创建实例时无需手动写入任何值,从根源上保证了数据的一致性与准确性。向量列 VectorField(dimensions=768) 对应 vector(768) 列。由于当时 PostgresML 还没有 Django 插件,作者需要自己实现表达式类,把嵌入生成翻译成 SQL:
class EmbedSmallExpression(models.Expression):
output_field = VectorField(null=False, blank=False, dimensions=768)
def __init__(self, field):
self.embedding_field = field
def as_sql(self, compiler, connection, template=None):
return f"pgml.embed('Alibaba-NLP/gte-base-en-v1.5', {self.embedding_field})", None
搜索端点在 QuerySet 上添加一个 RawSQL 注解即可完成“嵌入查询文本 → 余弦相似度检索 → 按相关性排序”三步(见 博文原文):
results = TodoItem.objects.annotate(
similarity=RawSQL(
"pgml.embed('Alibaba-NLP/gte-base-en-v1.5', %s)::vector(768) <=> embedding",
[query],
)
).order_by("similarity")
对比可见:路径 B 需要理解 Expression.as_sql、pgvector 类型与距离运算符;而路径 A 的 vector_search 正是把这些重复劳动抽象成一行调用。这也是公告把“简洁性(Simplicity)”列为首要兴奋点的直接证据。
六、性能优势:为什么“数据库内”更快
公告总结的第二大兴奋点是性能:借助 PostgresML 在数据库内部完成向量运算,尤其在大数据集上能显著提升速度与效率。仓库指南给出了三点具体支撑:
- 降低延迟:本地计算消除了外部服务调用的网络开销;
- 增强安全:数据始终留在数据库内,减少数据暴露面;
- 成本更优:自建硬件比外部托管服务在大规模场景下更具成本效益。
此外,PostgresML 支持批量嵌入(batching):嵌入生成的大部分成本在于把各层模型权重从内存流式传输到处理器,而不是实际计算本身。通过 array_agg 一次处理多条文本,权重可以跨多条输入复用,从而摊薄加载开销——官方示例用 SELECT pgml.embed('intfloat/e5-small-v2', array_agg(body)) FROM documents; 演示了这一点,并建议批量规模取 10–100 条 以提升吞吐、降低成本(见 In-database Embedding Generation)。
七、实战验证:一个可运行的 Django 语义搜索数据流
把上述能力拼装起来,一个完整的 Django 语义搜索应用是这样运作的(以配套博文的 to-do 应用为例):
1. 创建数据。 应用仅含 TodoItem 一个模型(description、due_date、completed、embedding 四列)。通过 cURL 创建一条待办事项:
curl \
--silent \
-X POST \
-d '{"description": "Make a New Year resolution list", "due_date": "2025-01-01"}' \
-H 'Content-Type: application/json' \
http://localhost:8000/api/todo/
返回体中除了业务字段,还包含 PostgresML 为 description 自动生成的 768 维嵌入向量(示例返回中为节省篇幅被截断):
{
"id": 5,
"description": "Make a New Year resolution",
"due_date": "2025-01-01",
"completed": false,
"embedding": "[-2.60886201e-03 -6.66755587e-02 -9.28235054e-02 [...]]"
}
如果修改描述文本,嵌入向量也会随之改变,直观体现模型对文本语义的理解。
2. 语义搜索。 搜索端点接受查询词、completed 过滤条件与 limit 参数:
curl \
--silent \
-H "Content-Type: application/json" \
'http://localhost:8000/api/todo/search/?q=resolution&limit=1' | \
jq ".[0].description"
即便库中存在大量待办事项,语义搜索也能按相关性把最匹配的那条排在最前:
"Make a New Year resolution"
增大 limit 会返回更多文档,且严格按相关度递减排序。这正是在上一节 RawSQL 注解背后的完整执行链路:pgml.embed() 在数据库内生成查询向量 → <=> 计算余弦距离 → ORDER BY similarity 排序。对于传统 PostgreSQL 的 tsvector 关键词匹配而言,语义搜索不再依赖字面词频,而是理解“用户到底想要什么”。
八、总结
postgresml-django 的价值可以浓缩为两句话:简洁性——用声明式字段定义替代手写 SQL 表达式与模型编排,让不同水平的 Django 开发者都能使用机器学习能力;性能——把嵌入生成与向量检索全部下沉到 PostgresML 数据库内部,配合 GPU 加速与批处理,避免了外部 API 的网络开销。它与仓库中另一条“手写 Expression”路径互为印证,前者是后者的抽象升级,后者则清晰展示了底层的 pgml.embed() SQL 机制。
如果你希望进一步探索:模块底层的函数契约可查阅 pgml.embed() API 文档,数据库内嵌入与批处理的完整 SQL 示例在 In-database Embedding Generation 指南,向量距离度量选型参考 Vector Similarity 指南,而 Python 侧 SDK 的整体能力组织可见 pgml-sdks/pgml/python/README.md。对于 Django 之外的调用方,JavaScript 语义搜索示例 也展示了同一套 vector_search 抽象在其它语言 SDK 中的形态,可以互相印证。