开始之前
你需要 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。
0.7.0:新类型
加入 halfvec 和 sparsevec 类型,bit 向量可以建索引,并新增 binary_quantize 函数。
0.8.0:迭代索引扫描
带过滤条件的查询可以一直扫描到匹配的行足够多,过滤时选索引也更准。
0.8.1:Postgres 18
支持 Postgres 18 rc1,binary_quantize 也更快了。
0.8.2:并行 HNSW 修复
修复并行建 HNSW 索引时的缓冲区溢出。
0.8.7:IVFFlat 修复
修复建 IVFFlat 索引时的缓冲区溢出,是 2026 年 11 月 5 日能拿到的最新版本。
来源:pgvector CHANGELOG (2026年11月5日 查阅)
嵌入向量是什么
嵌入是一个向量,也就是一串数字。按 OpenAI 指南的定义,两个向量之间的距离衡量它们的相关程度:距离小表示相关度高,距离大表示相关度低。嵌入模型训练的目标,是让含义相近的文本落在相近的位置。
关键词引擎比较的是词,嵌入比较的是含义。检索指南里的查询“When did we go to the moon?”(我们是什么时候登月的)展示了这个差别:最好的答案和查询没有一个词相同,而讲月饼的那句话,共有的词最多。
| 文本 | 关键词相似度 | 语义相似度 |
|---|---|---|
| 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,才能当作相似度来读。
| 运算符 | 返回什么 | 怎样得到相似度 |
|---|---|---|
| <-> | 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 系列生成的所有嵌入彼此兼容。
| 模型 | 默认维度 | 最长输入 | 说明 |
|---|---|---|---|
| text-embedding-3-small | 1536 | 8192 个 token | MTEB 分数 62.3% |
| text-embedding-3-large | 3072 | 8192 个 token | MTEB 分数 64.6% |
| voyage-4-lite | 1024 | 32,000 个 token | 针对延迟和成本优化 |
| voyage-4 | 1024 | 32,000 个 token | 质量与效率均衡 |
| voyage-4-large | 1024 | 32,000 个 token | 通用和多语言检索质量最好 |
| voyage-4-nano | 512 | 32,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);- 启动装有 pgvector 的 Postgres,然后在每个要用它的数据库里执行一次
CREATE EXTENSION vector;。 - 建表。
vector(1024)固定了维度,所以必须和你的模型一致。把文本和来源放在向量旁边,一次查询就能拿到提示词需要的全部内容。 - 以文本字面量的形式插入向量,比如
[0.1,0.2,0.3]。批量导入时 README 推荐用COPY,并且在首批数据导入之后再建索引。 - 用
ORDER BY embedding <=> query LIMIT k查询。没有索引时,pgvector 会扫描每一行。 - 表里有数据之后再建索引。近似索引用一部分召回率换速度,所以它的结果可能和精确搜索不同。
- 让索引和运算符对应。
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的平方根开始试。
| 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 列,再给每个模型建一个部分索引。
| 类型 | 索引最多接受的维度 |
|---|---|
| vector | 2,000 |
| halfvec | 4,000 |
| bit | 64,000 |
| sparsevec | 1,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 和我们的旗舰模型一起使用,一个向量都不用重新建索引。
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 是什么一文从头讲起,也说明了按英文估算的字数,为什么不适用于中文。
自测一下
你把存好的 1024 个数字的向量缩短为前 256 个数字,比较之前必须做什么?
查看答案
B. 重新归一化为长度 1 OpenAI 和 Voyage AI 都说,手动缩短的向量必须重新归一化,否则点积就不再等于余弦相似度。
在 pgvector 里,哪个表达式能得到已存向量和查询向量的余弦相似度?
查看答案
D. 1-(embedding <=> query) 运算符 <=> 返回余弦距离,README 说余弦相似度要用 1 减去这个距离。
你把 3072 个数字的向量存进 vector 列,并建立 HNSW 索引,会发生什么?
查看答案
A. 会失败,因为 vector 列上的索引最多接受 2,000 维 vector 索引最多接受 2,000 维。README 给出的出路是 halfvec、二进制量化或更短的向量。
带过滤条件的 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 数还按模型设有上限。把你的块分批发送,才能同时满足这两个限制。
资料来源
- OpenAI Docs: Vector embeddings https://developers.openai.com/api/docs/guides/embeddings
- OpenAI Docs: Retrieval https://developers.openai.com/api/docs/guides/retrieval
- Claude Docs: Embeddings https://platform.claude.com/docs/en/build-with-claude/embeddings
- Voyage AI Docs: Text Embeddings https://www.mongodb.com/docs/voyageai/models/text-embeddings
- Voyage AI blog: The Voyage 4 model family (15 January 2026) https://blog.voyageai.com/2026/01/15/voyage-4/
- pgvector README https://github.com/pgvector/pgvector
- pgvector CHANGELOG https://github.com/pgvector/pgvector/blob/master/CHANGELOG.md
- LangChain Docs: Retrieval https://docs.langchain.com/oss/python/langchain/retrieval
- LlamaIndex Docs: Basic strategies https://developers.llamaindex.ai/python/framework/optimizing/basic_strategies/basic_strategies/
- Tengyu Ma on X: the Voyage 4 launches (15 January 2026) https://x.com/tengyuma/status/2011935594130391344
- Voyage AI on X: voyage-4-nano (5 February 2026) https://x.com/VoyageAI/status/2019395836153655581
- Tom Aarsen on X: voyage-4-nano and benchmarks (28 January 2026) https://x.com/tomaarsen/status/2016532114871611776


