Mindket

什么是 embeddings(嵌入向量):模型、维度与 pgvector

黑色横幅,标题是“RAG 实战”,下面是“向量、分块、检索与评估”。

嵌入向量是一串浮点数,用来表示一段文本的含义,含义相近的文本,向量也靠得近。这样,搜索就变成了距离计算。归一化的向量让余弦相似度和点积的结果一致,所选的模型和维度决定存储成本,pgvector 则在 Postgres 里存储和查询向量。

本节内容

  • 说清嵌入向量是什么,以及为什么距离能衡量相关程度。
  • 选定模型和输出维度,并且能安全地缩短已存储的向量。
  • 在 Postgres 里用 pgvector 建立、索引和查询向量列。
  • 认出容易踩的限制:索引能接受的维度上限,以及近似索引上的过滤。
全部课程
本页目录
  1. 开始之前
  2. 嵌入向量是什么
  3. 余弦、点积和距离
  4. 选一个模型
  5. 挑选维度,并正确地缩短它
  6. 在 Postgres 里存储和查询向量
  7. HNSW 还是 IVFFlat
  8. 决定你选哪个模型的维度上限
  9. 带过滤条件的查询为什么返回的行更少
  10. Voyage AI 的联合创始人和团队怎样讲同一个嵌入空间
  11. 常见错误
  12. 自己试一试
  13. 下一课
  14. 自测一下
  15. 常见问题
  16. 资料来源

开始之前

你需要 Python 3、和第 1 课一样的 Voyage AI 密钥,以及一个装了 pgvector 的 Postgres 服务器。pgvector 的 README 为每个 Postgres 版本都提供了 Docker 镜像,执行 docker pull pgvector/pgvector:pg18-trixie 就得到装好扩展的 Postgres 18 镜像。它的标签列表里最新的是 0.8.7 版,本课就以这个版本为准。

SQL 可以在 psql 或任何客户端里运行,Python 代码用到 voyageai 和 numpy 两个包。

请用最新的补丁版本,不要用旧镜像。2026 年发布的版本里,有三个修复了建索引时的缓冲区溢出。更新日志里的下一版 0.9.0 仍标着尚未发布,它将不再支持 Postgres 13 和 14。

本课用到的 pgvector 版本
  1. 0.7.0:新类型

    加入 halfvec 和 sparsevec 类型,bit 向量可以建索引,并新增 binary_quantize 函数。

  2. 0.8.0:迭代索引扫描

    带过滤条件的查询可以一直扫描到匹配的行足够多,过滤时选索引也更准。

  3. 0.8.1:Postgres 18

    支持 Postgres 18 rc1,binary_quantize 也更快了。

  4. 0.8.2:并行 HNSW 修复

    修复并行建 HNSW 索引时的缓冲区溢出。

  5. 0.8.7:IVFFlat 修复

    修复建 IVFFlat 索引时的缓冲区溢出,是 2026 年 11 月 5 日能拿到的最新版本。

来源:pgvector CHANGELOG (2026年11月5日 查阅)

嵌入向量是什么

嵌入是一个向量,也就是一串数字。按 OpenAI 指南的定义,两个向量之间的距离衡量它们的相关程度:距离小表示相关度高,距离大表示相关度低。嵌入模型训练的目标,是让含义相近的文本落在相近的位置。

关键词引擎比较的是词,嵌入比较的是含义。检索指南里的查询“When did we go to the moon?”(我们是什么时候登月的)展示了这个差别:最好的答案和查询没有一个词相同,而讲月饼的那句话,共有的词最多。

关键词相似度按交集除以并集计算,语义相似度是用 text-embedding-3-small 得到的余弦相似度。出自 OpenAI 的检索指南。
文本关键词相似度语义相似度
The first lunar landing occurred in July of 1969.0%65%
The first man on the moon was Neil Armstrong.27%43%
When I ate the moon cake, it was delicious.40%28%

余弦、点积和距离

OpenAI 推荐用余弦相似度,并且说距离函数的选择通常影响不大。它的嵌入已经归一化为长度 1,Voyage AI 的也一样。两家的文档都列出了由此带来的两个结果:余弦相似度可以简化为点积,算得稍快一点;余弦相似度和欧氏距离给出的排序完全相同。

pgvector 正是利用了这一点。它的性能说明写道,对于归一化为长度 1 的向量,用内积性能最好。有一个运算符是反着读的:<#> 返回的是负内积,因为 Postgres 的索引扫描只支持升序,所以要乘以 -1,才能当作相似度来读。

pgvector 用于 vector 类型的距离运算符,出自它的 README。
运算符返回什么怎样得到相似度
<->L2(欧氏)距离越小越近
<#>负内积乘以 -1
<=>余弦距离用 1 减去它
<+>L1 距离越小越近

选一个模型

Anthropic 不提供嵌入模型。它的指南列出了挑选服务商的三个考虑:训练数据的规模以及和你所在领域的贴合程度,推理速度和延迟,能否用私有数据继续训练。它自己推荐的是 Voyage AI,其中 voyage-4-large 追求质量,voyage-4 兼顾两者,voyage-4-lite 偏重延迟和成本。还有面向代码(voyage-code-4)、金融和法律的专用模型,Voyage 的模型被描述为多语言模型。

OpenAI 现有的两个模型,区别在于尺寸和基准分数。两者每条输入最多接受 8192 个 token,Voyage 4 系列则是 32,000 个。这个系列还有第四个成员 voyage-4-nano,权重公开在 Hugging Face 上。Voyage 的模型页面说,4 系列生成的所有嵌入彼此兼容。

出自 OpenAI 的嵌入指南、Anthropic 的嵌入指南和 Voyage AI 的模型页面。价格会变动,请去各家官网查看。
模型默认维度最长输入说明
text-embedding-3-small15368192 个 tokenMTEB 分数 62.3%
text-embedding-3-large30728192 个 tokenMTEB 分数 64.6%
voyage-4-lite102432,000 个 token针对延迟和成本优化
voyage-4102432,000 个 token质量与效率均衡
voyage-4-large102432,000 个 token通用和多语言检索质量最好
voyage-4-nano51232,000 个 token权重公开在 Hugging Face 上,最短可缩到 128 维

挑选维度,并正确地缩短它

OpenAI 的指南说,更长的向量需要更多的存储、内存和计算。两家都把现有模型训练成了可以只保留向量开头的几个数字,损失却很小。OpenAI 把它做成 dimensions 参数,并且说,缩短到 256 个数字的 text-embedding-3-large 嵌入,在 MTEB 上仍然好于未缩短的 1536 维 text-embedding-ada-002 嵌入。Voyage 把它做成 output_dimension,4 系列可选 2048、1024(默认)、512 和 256。

手动缩短已存储的向量也行,但缩短之后要重新归一化,OpenAI 和 Voyage 都提醒过这一步。Voyage 还支持量化:output_dtype 可以返回 8 位整数,或者按位打包的二进制值,长度是维度的八分之一,它的常见问题解答说,存储空间分别是 32 位浮点数的 4 倍和 32 倍小。

import voyageai
import numpy as np

vo = voyageai.Client()

# 1. Ask the API for a shorter vector
short = vo.embed(
    ["Sample text 1", "Sample text 2"],
    model="voyage-4",
    input_type="document",
    output_dimension=256,
).embeddings
print(len(short[0]))  # 256


# 2. Or shorten a vector you already stored: keep the leading values, then normalize again
def embd_normalize(v: np.ndarray) -> np.ndarray:
    row_norms = np.linalg.norm(v, axis=1, keepdims=True)
    if np.any(row_norms == 0):
        raise ValueError("Cannot normalize rows with a norm of zero.")
    return v / row_norms


full = vo.embed(
    ["Sample text 1", "Sample text 2"], model="voyage-4", input_type="document"
).embeddings  # 1024 numbers each by default
resized = embd_normalize(np.array(full)[:, :256]).tolist()

在 Postgres 里存储和查询向量

pgvector 给 Postgres 加上了向量相似度搜索,所以向量可以和你的其他数据放在一起,享有事务、联表查询和复制。它默认执行精确的最近邻搜索,召回率是完美的。下面的 SQL 照着 README 的入门步骤来写,列的维度按 Voyage 的默认值来定。

-- once per database
CREATE EXTENSION vector;

-- the dimension has to match your embedding model
CREATE TABLE chunks (
    id bigserial PRIMARY KEY,
    source text NOT NULL,
    content text NOT NULL,
    embedding vector(1024)
);

-- vectors go in as text literals
INSERT INTO chunks (source, content, embedding)
VALUES ('kb://refund-policy', 'You may return an item within 30 days.', '[0.01,-0.02, ...]');

-- the four nearest chunks by cosine distance, with the similarity (1 - distance)
SELECT id, source, 1 - (embedding <=> '[0.03,0.01, ...]') AS cosine_similarity
FROM chunks
ORDER BY embedding <=> '[0.03,0.01, ...]'
LIMIT 4;

-- an approximate index for cosine distance, built once the table has data
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops);
  1. 启动装有 pgvector 的 Postgres,然后在每个要用它的数据库里执行一次 CREATE EXTENSION vector;。
  2. 建表。vector(1024) 固定了维度,所以必须和你的模型一致。把文本和来源放在向量旁边,一次查询就能拿到提示词需要的全部内容。
  3. 以文本字面量的形式插入向量,比如 [0.1,0.2,0.3]。批量导入时 README 推荐用 COPY,并且在首批数据导入之后再建索引。
  4. 用 ORDER BY embedding <=> query LIMIT k 查询。没有索引时,pgvector 会扫描每一行。
  5. 表里有数据之后再建索引。近似索引用一部分召回率换速度,所以它的结果可能和精确搜索不同。
  6. 让索引和运算符对应。vector_cosine_ops 服务于 <=>,README 说,每一种你要用来查询的距离函数,都要各建一个索引。

HNSW 还是 IVFFlat

pgvector 提供两种近似索引。HNSW 建立一个多层图。IVFFlat 把向量分到若干列表里,搜索时只看离查询最近的那几个列表。影响召回率和速度的设置,以及 README 给出的默认值如下:

  • HNSW 建索引:m 是 16,ef_construction 是 64。ef_construction 越高,召回率越好,代价是建索引时间和插入速度。
  • HNSW 查询:hnsw.ef_search 是 40。值越高召回率越好,代价是速度。
  • IVFFlat 建索引:要选 lists,一百万行以内从行数除以 1000 开始,超过一百万行就从行数的平方根开始。
  • IVFFlat 查询:ivfflat.probes 是 1,README 建议从 lists 的平方根开始试。
pgvector 里的 HNSW 与 IVFFlat
HNSW 我们的选择IVFFlat
速度与召回率的权衡更好是否
建索引更快否是
占用内存更少否是
建索引前表里必须已有数据否是

我们的选择:HNSW。 它的速度与召回率权衡更好,也没有训练步骤,代价是建索引时间和内存。

来源:pgvector README (2026年11月4日 查阅)

决定你选哪个模型的维度上限

普通 vector 列上的索引最多接受 2,000 维。这就把 text-embedding-3-large 默认返回的 3072 个数字排除在外了。README 给了三条出路:把列存成或索引成 halfvec,上限提高到 4,000 维;量化成位,上限提高到 64,000 维;或者让模型直接返回更短的向量。

半精度是在索引里做一次类型转换:CREATE INDEX ON items USING hnsw ((embedding::halfvec(3)) halfvec_l2_ops);,查询时在 ORDER BY 里用同样的转换。如果非要在同一列里放不同维度的向量,README 的办法是加一个 model_id 列,再给每个模型建一个部分索引。

HNSW 的限制,出自 pgvector README。IVFFlat 支持前三种。
类型索引最多接受的维度
vector2,000
halfvec4,000
bit64,000
sparsevec1,000 个非零元素

带过滤条件的查询为什么返回的行更少

用了近似索引时,pgvector 是在扫描完索引之后才应用 WHERE 条件的。README 举了个例子:如果某个条件匹配 10% 的行,而 hnsw.ef_search 是默认的 40,平均只有 4 行符合。按租户或文档类型过滤的 RAG 查询会直接碰上这个问题。

从 0.8.0 开始有了迭代索引扫描,它会一直扫描,直到匹配的行足够多,或者触到扫描上限。另一条路是在过滤列上建 B-tree 索引,README 说,条件匹配的行占比很低时,这样能得到快速、精确的搜索。

-- with an approximate index, the WHERE runs after the index scan, so this can return fewer than 5 rows
SELECT * FROM items WHERE category_id = 123 ORDER BY embedding <-> '[3,1,2]' LIMIT 5;

-- pgvector 0.8.0 and later: keep scanning the index until enough rows match
SET hnsw.iterative_scan = strict_order;

-- a higher ef_search gives better recall at the cost of speed (the default is 40)
SET hnsw.ef_search = 100;

Voyage AI 的联合创始人和团队怎样讲同一个嵌入空间

按 Tengyu Ma 的 X 简介,他是 Voyage AI 的联合创始人,当过 CEO,现在是 MongoDB 的首席 AI 科学家。2026 年 1 月,他在 X 上列出了 Voyage 4 系列的发布内容:voyage-4、voyage-4-lite 和 voyage-4-nano 与 voyage-4-large 共用一个嵌入空间,他说这让成本和效果的取舍更灵活。

Voyage 的发布文章把这笔账讲明白了:文档用 voyage-4-large 嵌入一次,查询则用更便宜的 voyage-4-lite 来嵌入。

Voyage AI 的官方账号在 2026 年 2 月在 X 上介绍了 voyage-4-nano。它的权重以 Apache 2.0 许可证公开,和更大的几个模型共用同一个嵌入空间。它还支持套娃表示学习(Matryoshka Representation Learning),也就是让缩短后的向量损失很小的那种训练方式,并支持 int8 和二进制量化。

兼容的向量,长度也得一样才能比较,而 vector(1024) 列只接受这一个长度。nano 的几种尺寸,只和其他型号最小的两种重合。混用之前,在两个模型上都把 output_dimension 设成其中一种,再按它设好列的维度。

Tom Aarsen 的 X 简介写着他负责 Hugging Face 的 Sentence Transformers。他在 2026 年 1 月底在 X 上对 nano 表示欢迎,只提了一点保留:它还没出现在多少基准测试里,没法和 embeddinggemma-300m 这类相近的模型比较。没有公开分数可依,就用“选一个模型”那一节的办法:拿你自己的语料分别嵌入每个候选,再比较检索效果。

这意味着,你可以在本地把 voyage-4-nano 和我们的旗舰模型一起使用,一个向量都不用重新建索引。

Voyage AI by MongoDB, Voyage 嵌入模型的开发方,引文为译文 · X (2026年11月5日 查阅)
Tengyu Ma(@tengyuma)2026 年 1 月 15 日在 X 上列出 Voyage AI 的发布:voyage-4-large 是混合专家架构的嵌入模型,准确率创新高、成本低 40%;voyage-4、-4-lite 和权重公开的 -4-nano 共用一个嵌入空间,让成本和效果的取舍更灵活。下方是它引用的 Voyage AI 帖子
Tengyu Ma 在 X 上,2026 年 1 月 15 日
Voyage AI by MongoDB(@VoyageAI)2026 年 2 月 5 日在 X 上介绍 voyage-4-nano:它是 Voyage 第一个权重公开的嵌入模型,适合本地开发和做原型,也方便日后上线,并和更大的几个模型共用一个嵌入空间
Voyage AI 在 X 上,2026 年 2 月 5 日

X 官方嵌入卡片的截图,拍摄于 2026-11-05,时间为协调世界时(UTC)。两条帖子都被 X 用“Show more”截断了。点击截图可到 X 上打开原帖。

常见错误

  • 截断向量之后原样使用。保留开头的数字之后要重新归一化,否则向量的长度不再是 1,点积也就不再等于余弦相似度。
  • 把 <#> 的输出当成相似度来读。它是负内积。
  • 在空表上建 IVFFlat 索引。README 把这列为结果太少的常见原因,并建议先删掉索引,等表里数据多了再建。
  • 按字符数估算 token。OpenAI 的常见问题解答推荐用 tiktoken 的 cl100k_base 编码来计算第三代模型的 token 数,这样才不会超出模型的输入上限。
  • 以为查不到的行就是不匹配。NULL 向量不会被索引,余弦距离下的零向量也不会。
  • 换了模型却不重新嵌入。LlamaIndex 说,文档和查询必须由同一个模型嵌入,换了模型就要重新建索引。Voyage 4 系列是写明的例外,前提是用两个模型都支持的维度。

自己试一试

  • 用你的模型嵌入那三句登月的句子,把你得到的分数和 OpenAI 的表格对比,看排序在哪里不同。
  • 把一个向量缩短到原来长度的四分之一,分别做归一化和不做,再打印两者的 np.linalg.norm。
  • 导入几百个块,对查询执行 EXPLAIN (ANALYZE, BUFFERS),再加上 HNSW 索引重新执行一遍。README 把它列为调试性能的办法。
  • 分别把 hnsw.ef_search 设为默认值和 100,各跑一次带过滤条件的查询,数一数返回的行数。

下一课

第 3 课回到嵌入之前的那一步:每个块切多大,怎样按结构切分,以及怎样补上上下文,让一个块更容易被找到。上面提到的 token 上限怎么计算,AI 里的 token 是什么一文从头讲起,也说明了按英文估算的字数,为什么不适用于中文。

自测一下

  1. 你把存好的 1024 个数字的向量缩短为前 256 个数字,比较之前必须做什么?

    查看答案

    B. 重新归一化为长度 1 OpenAI 和 Voyage AI 都说,手动缩短的向量必须重新归一化,否则点积就不再等于余弦相似度。

  2. 在 pgvector 里,哪个表达式能得到已存向量和查询向量的余弦相似度?

    查看答案

    D. 1-(embedding <=> query) 运算符 <=> 返回余弦距离,README 说余弦相似度要用 1 减去这个距离。

  3. 你把 3072 个数字的向量存进 vector 列,并建立 HNSW 索引,会发生什么?

    查看答案

    A. 会失败,因为 vector 列上的索引最多接受 2,000 维 vector 索引最多接受 2,000 维。README 给出的出路是 halfvec、二进制量化或更短的向量。

  4. 带过滤条件的 HNSW 查询返回的行数少于 LIMIT,README 建议怎么办?

    查看答案

    C. 启用迭代索引扫描 过滤发生在索引扫描之后,所以会丢行。从 0.8.0 开始可用的迭代扫描,会一直扫描到匹配的行足够多。

常见问题

嵌入(embedding)和向量(vector)有什么区别?

嵌入是嵌入模型根据一段内容生成的向量。任何一串数字都是向量,但只有距离能代表相关程度的向量才是嵌入。

做 RAG 该用哪个嵌入模型?

从各家的指南里挑两个,在你自己的问题上测试。Anthropic 推荐 Voyage AI,其中 voyage-4 兼顾质量和成本。OpenAI 提供 text-embedding-3-small 和 text-embedding-3-large。

该选多少维?

先用模型的默认值,只有存储或延迟逼着你时再缩短。两家都把现有模型训练成缩短后的向量损失很小,也都列出了各自支持的尺寸。自己截断向量之后要重新归一化。

需要自己给嵌入向量做归一化吗?

OpenAI 和 Voyage AI 不需要,因为它们返回的向量长度都是 1。只有在你自己缩短向量之后才要重新归一化,因为截掉数字会改变长度。选与你的索引相匹配的运算符即可。

需要单独的向量数据库吗?

起步阶段不需要。pgvector 把向量放在你其他数据的旁边,一次查询就能联表,它的 README 列出了精确和近似搜索、复制和时间点恢复。要更大的空间,它建议用副本,或者用 Citus、PgDog 分片。

嵌入对中文有用吗?

有用,需要用多语言模型。Voyage AI 说它的文本嵌入模型天生支持多语言,所以跨语言的相似度同样成立。

一次请求能嵌入多少条文本?

Voyage AI 每次嵌入调用最多接收 1,000 条文本,这些文本的总 token 数还按模型设有上限。把你的块分批发送,才能同时满足这两个限制。

资料来源

  1. OpenAI Docs: Vector embeddings (2026年11月5日 查阅)https://developers.openai.com/api/docs/guides/embeddings
  2. OpenAI Docs: Retrieval (2026年11月4日 查阅)https://developers.openai.com/api/docs/guides/retrieval
  3. Claude Docs: Embeddings (2026年11月4日 查阅)https://platform.claude.com/docs/en/build-with-claude/embeddings
  4. Voyage AI Docs: Text Embeddings (2026年11月5日 查阅)https://www.mongodb.com/docs/voyageai/models/text-embeddings
  5. Voyage AI blog: The Voyage 4 model family (15 January 2026) (2026年11月5日 查阅)https://blog.voyageai.com/2026/01/15/voyage-4/
  6. pgvector README (2026年11月5日 查阅)https://github.com/pgvector/pgvector
  7. pgvector CHANGELOG (2026年11月5日 查阅)https://github.com/pgvector/pgvector/blob/master/CHANGELOG.md
  8. LangChain Docs: Retrieval (2026年11月4日 查阅)https://docs.langchain.com/oss/python/langchain/retrieval
  9. LlamaIndex Docs: Basic strategies (2026年11月4日 查阅)https://developers.llamaindex.ai/python/framework/optimizing/basic_strategies/basic_strategies/
  10. Tengyu Ma on X: the Voyage 4 launches (15 January 2026) (2026年11月5日 查阅)https://x.com/tengyuma/status/2011935594130391344
  11. Voyage AI on X: voyage-4-nano (5 February 2026) (2026年11月5日 查阅)https://x.com/VoyageAI/status/2019395836153655581
  12. Tom Aarsen on X: voyage-4-nano and benchmarks (28 January 2026) (2026年11月5日 查阅)https://x.com/tomaarsen/status/2016532114871611776

分享本节课

看看建立在检索之上的智能体

浏览 Mindket 上列出的智能体,每个都有预览,看看带着你自己数据的智能体能做什么。

浏览智能体

本节课的 Markdown 版本 · RAG 实战课:检索增强生成 · 全部课程