# 嵌入与检索

嵌入把一张图片或一段文本转换为经 L2 归一化的向量。方向相近的向量描述相近的内容，因此可用于图像相似度、近重复检测、以图搜图、以文搜图，以及对照一组标签的零样本分类。该接口只返回向量：保存向量并按相似度排序由调用方自行处理。不同模型的向量不可比较，用某个模型建立索引的图片库，须用同一模型查询。

## 向量与比较

响应包含 `dimensions`（每个向量的长度）和 `embeddings`（每个输入对应一项）。向量是普通的 JSON 数字数组（float32 数值，不是 base64）。向量已做 L2 归一化，无需再次归一化：两个输入的余弦相似度等于其向量的点积，数值越高表示越相似。录制的响应中 `dimensions` 为 768；代码应读取该字段，而不是写死长度。

`siglip2`：图像与文本之间的得分数值很小。下方录制的示例中，最佳匹配得分为 0.133，无关组合约为 0。有意义的只是排名和得分之间的差距，因此应在同一个查询内部比较各得分，不假设固定的截断值。

> **Note**
>
> `dinov3` 的输出受 DINOv3 License 约束。基于它构建的产品应显示标识 "Built with DINOv3"。详见[署名](https://stack.foldbun.com/zh/docs/models/dinov3/#署名)。

## 图像相似度

先对查询图片和各候选图片分别生成嵌入，再按与查询向量的点积对候选图片排序。图片库较大时，应在客户端保存向量并使用向量索引；该接口只返回嵌入。

![以一只红色帆布鞋为查询图（image 0），与三张图片比较：红色帆布鞋特写（0.714）、带黑色条纹的白色运动鞋（0.495）、灰色背包（0.091），每张图下方有对应分数的进度条。](../../../../../assets/docs/dinov3-similarity.webp)

*`dinov3`：Playground 的录制结果：查询图（image 0，蓝色边框）与另外三张图片的余弦相似度。外观最接近的图片得分最高，另一款运动鞋居中，无关物体接近零。*

## 去重与检索

同一张图片的副本（重新编码、缩放或轻度编辑）与原图的得分，预期远高于无关图片，因此设定相似度阈值即可把同一个调用变成去重过滤器。API 不提供推荐阈值，应在图片库的带标注样本上确定。以图搜图时，先对查询图生成一次嵌入，与已保存的向量比较，返回得分最高的结果。

## 以文搜图

每张图片只需嵌入一次（每张图片一次调用）并保存向量。搜索时，用一次 `texts` 调用嵌入查询文本，再按点积对图片排序。仅含文本的调用，其响应中没有 `image`。

![查询文本 "a bowl of ramen" 位于三张照片上方：一碗拉面（0.133，高亮）、夜间雨后的街道（0.000）、趴在沙地上的狗（-0.002），每张照片带有得分条。](../../../../../assets/docs/siglip2-text-search.webp)

*`siglip2`：Playground 的录制结果：一条文本查询对三张图片打分。拉面照片排名第一，另外两张得分约为 0。*

## 零样本分类

嵌入一张图片和一组候选标签（每次调用最多 64 条），再按标签与图片的点积对标签排序，得分最高的标签即预测结果。标签就是普通文本，可以在每次请求中更换，无需重新训练。

![一张带配菜的拉面照片，旁边是五个按得分排序的标签："a bowl of ramen" 0.133（高亮）、"一碗拉面" 0.108、"a cup of coffee" 0.055、"a salad" 0.035、"a pizza" 0.017。](../../../../../assets/docs/siglip2-zero-shot.webp)

*`siglip2`：Playground 的录制结果：一张图片对五个标签打分。匹配的标签排名第一，用中文描述同一道菜的标签排名第二。*

## 支持的模型

| | [`dinov3`](https://stack.foldbun.com/zh/docs/models/dinov3/#嵌入与检索) | [`siglip2`](https://stack.foldbun.com/zh/docs/models/siglip2/#嵌入与检索) |
|---|---|---|
| 模型 | DINOv3 ViT-B/16 | SigLIP 2 base，patch 16，224 px |
| 输入 | 一张图片 | 一张图片，或 1 到 64 条文本（每条 1 到 1000 个字符），二者不可同时给出 |
| 输出 | 每张图片一个纯视觉向量 | 每张图片或每条文本一个向量，位于同一个空间 |
| 标价（额度） | 每张图片 0.05 | 每张图片 0.05，每条文本 0.01 |
| 模型池 | 公共池：所有已注册账户可用，每个 UTC 日包含 100 次调用 | 公共池：所有已注册账户可用，每个 UTC 日包含 100 次调用 |
| 向量长度 | 录制的响应中为 768；请读取 `dimensions` | 录制的响应中为 768；请读取 `dimensions` |
| 录制的处理时间 | 216 ms（一次录制的调用） | 约 200 ms |
| 署名 | "Built with DINOv3" | 无 |
| 适用场景 | 查询内容是图片 | 查询内容是句子或一组标签，或需要将文本与图片互相比较 |

- 图像与图像之间的相似度、去重和以图搜图（"查找与这张相似的图片"），选用 `dinov3`。
- 以文搜图、零样本分类，以及需要将文本与图片互相比较的场景，选用 `siglip2`。

## 调用示例

将第一张图中的图片用 `dinov3` 做嵌入：

```bash
curl https://api.stack.foldbun.com/v1/embeddings \
  -H "Authorization: Bearer $FOLDBUN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "dinov3",
    "image": { "id": "img_36aec8cbc5b68661a926869a8bc8aa79de584edbb2aec2454bd1f1c169af09d7" }
  }'
```

以下响应有删减：向量已缩短。

```json
{
  "id": "call_01a1913e-868a-7d5a-841c-5913396a9ea1",
  "model": "dinov3",
  "image": {
    "id": "img_36aec8cbc5b68661a926869a8bc8aa79de584edbb2aec2454bd1f1c169af09d7",
    "width": 1512,
    "height": 1008,
    "bytes": 342140,
    "contentType": "image/jpeg"
  },
  "dimensions": 768,
  "embeddings": [
    { "index": 0, "vector": [-0.007488, -0.036278, -0.000432] }
  ],
  "cost": 0.05,
  "balance": 12.45,
  "timing": { "queueMs": 0, "processingMs": 216 },
  "billing": {
    "lines": [
      { "unit": "image", "quantity": 1, "unitPrice": 0.05, "amount": 0.05, "waived": false }
    ],
    "total": 0.05,
    "charged": true,
    "pool": null
  }
}
```

### 响应解读

- `dimensions` 为 768：每个向量都是这个长度。代码应读取该字段，而不是写死长度。
- `embeddings[0]` 包含 `index`（0）和 `vector`，即 L2 归一化的 float32 数值（768 个中显示 3 个）。`index` 是在 `texts` 中的位置；图片为 0。
- 仅含文本的 `siglip2` 调用按输入顺序，为每条文本返回一项，且没有 `image`。

同一模型的两个向量的余弦相似度等于它们的点积：

```python
def cosine(a: list[float], b: list[float]) -> float:
    return sum(x * y for x, y in zip(a, b))
```

两个向量来自使用同一模型的两次调用，每张图片一次。

全部字段：[API 参考](https://stack.foldbun.com/zh/docs/api/operations/embed/)。

**本次调用的费用。** 0.05（`image`）额度，与本响应的实际扣费一致。使用 `siglip2` 时，图片同样为 0.05，每条文本为 0.01，因此下面流程中的三个标签共 0.03。单价见[计费](https://stack.foldbun.com/zh/docs/billing/#价目表)。

### Python 完整流程

完整的 Python 流程，图片和标签分两次调用：

```python
import os

import requests

API = "https://api.stack.foldbun.com/v1/embeddings"
HEADERS = {"Authorization": f"Bearer {os.environ['FOLDBUN_API_KEY']}"}


def embed(**body):
    r = requests.post(API, headers=HEADERS, json={"model": "siglip2", **body}, timeout=60)
    r.raise_for_status()
    return [e["vector"] for e in r.json()["embeddings"]]


texts = ["a bowl of ramen", "a pizza", "a salad"]
image_vec = embed(image={"id": "img_f4e4f5a8a14f25689a345acf67ea1a83c5760e219c0dcf5255717cdb9bd5e225"})[0]
scores = [sum(a * b for a, b in zip(image_vec, v)) for v in embed(texts=texts)]
for text, score in sorted(zip(texts, scores), key=lambda p: -p[1]):
    print(f"{score:.3f}  {text}")
```

## 接口与参考

| 接口 | 在本用例中 | 参考 |
|---|---|---|
| `POST /v1/embeddings` | 每张图片一个向量；使用 `siglip2` 时每条文本也一个向量，按输入顺序返回 | [API 参考](https://stack.foldbun.com/zh/docs/api/operations/embed/) |

- [`dinov3`](https://stack.foldbun.com/zh/docs/models/dinov3/) 和 [`siglip2`](https://stack.foldbun.com/zh/docs/models/siglip2/)：各模型的工作原理、限制和实测延迟。
- [计费](https://stack.foldbun.com/zh/docs/billing/#价目表)：两个模型均计 `image`，仅 `siglip2` 计 `text`。
- [署名](https://stack.foldbun.com/zh/docs/models/dinov3/#署名)：基于 `dinov3` 构建的产品须标注 “Built with DINOv3”。
