# 什么是 embeddings（嵌入向量）：模型、维度与 pgvector

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

Mindket 编辑部 · 发布于 2026-11-04 · 更新于 2026-11-05 · https://mindket.com/cn/learn/rag/embeddings-explained

## 要点

- 说清嵌入向量是什么，以及为什么距离能衡量相关程度。
- 选定模型和输出维度，并且能安全地缩短已存储的向量。
- 在 Postgres 里用 pgvector 建立、索引和查询向量列。
- 认出容易踩的限制：索引能接受的维度上限，以及近似索引上的过滤。

## 开始之前

你需要 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 年发布的版本里，有三个修复了建索引时的缓冲区溢出。[更新日志](https://github.com/pgvector/pgvector/blob/master/CHANGELOG.md)里的下一版 0.9.0 仍标着尚未发布，它将不再支持 Postgres 13 和 14。

**本课用到的 pgvector 版本**

1. **2024年4月29日** 0.7.0：新类型。加入 halfvec 和 sparsevec 类型，bit 向量可以建索引，并新增 binary_quantize 函数。
2. **2024年10月30日** 0.8.0：迭代索引扫描。带过滤条件的查询可以一直扫描到匹配的行足够多，过滤时选索引也更准。
3. **2025年9月4日** 0.8.1：Postgres 18。支持 Postgres 18 rc1，binary_quantize 也更快了。
4. **2026年2月25日** 0.8.2：并行 HNSW 修复。修复并行建 HNSW 索引时的缓冲区溢出。
5. **2026年10月1日** 0.8.7：IVFFlat 修复。修复建 IVFFlat 索引时的缓冲区溢出，是 2026 年 11 月 5 日能拿到的最新版本。

来源：[pgvector CHANGELOG](https://github.com/pgvector/pgvector/blob/master/CHANGELOG.md) (2026-11-05 查阅)

## 嵌入向量是什么

嵌入是一个向量，也就是一串数字。按 [OpenAI 指南](https://developers.openai.com/api/docs/guides/embeddings)的定义，两个向量之间的距离衡量它们的相关程度：距离小表示相关度高，距离大表示相关度低。嵌入模型训练的目标，是让含义相近的文本落在相近的位置。

关键词引擎比较的是词，嵌入比较的是含义。检索指南里的查询“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 系列生成的所有嵌入彼此兼容。

> **我们对模型排行榜的看法**
>
> 基准分数只能告诉你该试哪些模型，不能告诉你哪个模型在你的文档上最好。我们会挑两个，各自嵌入你自己的语料，再用第 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 倍小。

````python
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 的默认值来定。

````sql
-- 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](https://github.com/pgvector/pgvector) (2026-11-04 查阅)

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

普通 `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 说，条件匹配的行占比很低时，这样能得到快速、精确的搜索。

````sql
-- 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 上](https://x.com/tengyuma/status/2011935594130391344)列出了 Voyage 4 系列的发布内容：`voyage-4`、`voyage-4-lite` 和 `voyage-4-nano` 与 `voyage-4-large` 共用一个嵌入空间，他说这让成本和效果的取舍更灵活。

[Voyage 的发布文章](https://blog.voyageai.com/2026/01/15/voyage-4/)把这笔账讲明白了：文档用 `voyage-4-large` 嵌入一次，查询则用更便宜的 `voyage-4-lite` 来嵌入。

Voyage AI 的官方账号在 2026 年 2 月[在 X 上](https://x.com/VoyageAI/status/2019395836153655581)介绍了 `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 上](https://x.com/tomaarsen/status/2016532114871611776)对 nano 表示欢迎，只提了一点保留：它还没出现在多少基准测试里，没法和 embeddinggemma-300m 这类相近的模型比较。没有公开分数可依，就用“选一个模型”那一节的办法：拿你自己的语料分别嵌入每个候选，再比较检索效果。

> 这意味着，你可以在本地把 voyage-4-nano 和我们的旗舰模型一起使用，一个向量都不用重新建索引。
>
> Voyage AI by MongoDB, Voyage 嵌入模型的开发方，引文为译文, [X](https://x.com/VoyageAI/status/2019395836153655581) (2026-11-05 查阅)

[![Tengyu Ma（@tengyuma）2026 年 1 月 15 日在 X 上列出 Voyage AI 的发布：voyage-4-large 是混合专家架构的嵌入模型，准确率创新高、成本低 40%；voyage-4、-4-lite 和权重公开的 -4-nano 共用一个嵌入空间，让成本和效果的取舍更灵活。下方是它引用的 Voyage AI 帖子](https://mindket.com/blog/shots/x-tengyuma-voyage-4-shared-space.webp?v=634a460c0dd1)](https://x.com/tengyuma/status/2011935594130391344)

Tengyu Ma 在 X 上，2026 年 1 月 15 日

[![Voyage AI by MongoDB（@VoyageAI）2026 年 2 月 5 日在 X 上介绍 voyage-4-nano：它是 Voyage 第一个权重公开的嵌入模型，适合本地开发和做原型，也方便日后上线，并和更大的几个模型共用一个嵌入空间](https://mindket.com/blog/shots/x-voyageai-voyage-4-nano-open-weights.webp?v=fd2988031fd2)](https://x.com/VoyageAI/status/2019395836153655581)

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](https://developers.llamaindex.ai/python/framework/optimizing/basic_strategies/basic_strategies/) 说，文档和查询必须由同一个模型嵌入，换了模型就要重新建索引。Voyage 4 系列是写明的例外，前提是用两个模型都支持的维度。

## 自己试一试

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

## 下一课

[第 3 课](https://mindket.com/cn/learn/rag/rag-chunking)回到嵌入之前的那一步：每个块切多大，怎样按结构切分，以及怎样补上上下文，让一个块更容易被找到。上面提到的 token 上限怎么计算，[AI 里的 token 是什么](https://mindket.com/cn/blog/what-are-tokens-in-ai)一文从头讲起，也说明了按英文估算的字数，为什么不适用于中文。

## 自测一下

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

   A. 什么都不用做，结果已经是归一化的
   B. 重新归一化为长度 1
   C. 重新嵌入原文
   D. 把数值四舍五入成整数

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

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

   A. embedding <-> query
   B. embedding <#> query
   C. embedding <+> query
   D. 1-(embedding <=> query)

   答案：D。 运算符 <=> 返回余弦距离，README 说余弦相似度要用 1 减去这个距离。

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

   A. 会失败，因为 vector 列上的索引最多接受 2,000 维
   B. 能成功，并自动使用半精度
   C. 能成功，维度被截断为 2,000
   D. 能成功，但只能做精确搜索

   答案：A。 vector 索引最多接受 2,000 维。README 给出的出路是 halfvec、二进制量化或更短的向量。

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

   A. 永久删除索引
   B. 把运算符换成 <#>
   C. 启用迭代索引扫描
   D. 调低 hnsw.ef_search

   答案：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) (2026-11-05 查阅)
- [OpenAI Docs: Retrieval](https://developers.openai.com/api/docs/guides/retrieval) (2026-11-04 查阅)
- [Claude Docs: Embeddings](https://platform.claude.com/docs/en/build-with-claude/embeddings) (2026-11-04 查阅)
- [Voyage AI Docs: Text Embeddings](https://www.mongodb.com/docs/voyageai/models/text-embeddings) (2026-11-05 查阅)
- [Voyage AI blog: The Voyage 4 model family (15 January 2026)](https://blog.voyageai.com/2026/01/15/voyage-4/) (2026-11-05 查阅)
- [pgvector README](https://github.com/pgvector/pgvector) (2026-11-05 查阅)
- [pgvector CHANGELOG](https://github.com/pgvector/pgvector/blob/master/CHANGELOG.md) (2026-11-05 查阅)
- [LangChain Docs: Retrieval](https://docs.langchain.com/oss/python/langchain/retrieval) (2026-11-04 查阅)
- [LlamaIndex Docs: Basic strategies](https://developers.llamaindex.ai/python/framework/optimizing/basic_strategies/basic_strategies/) (2026-11-04 查阅)
- [Tengyu Ma on X: the Voyage 4 launches (15 January 2026)](https://x.com/tengyuma/status/2011935594130391344) (2026-11-05 查阅)
- [Voyage AI on X: voyage-4-nano (5 February 2026)](https://x.com/VoyageAI/status/2019395836153655581) (2026-11-05 查阅)
- [Tom Aarsen on X: voyage-4-nano and benchmarks (28 January 2026)](https://x.com/tomaarsen/status/2016532114871611776) (2026-11-05 查阅)

[浏览智能体](https://mindket.com/cn/agents)
