Embedding API 不生成文字,它把文本映射成一串数字。语义搜索、RAG、聚类、去重和推荐都靠这串数字工作。接入本身不难,难的是维度、批量、归一化和索引这几处细节,一旦处理错,检索结果会悄悄变差。

本文以 OpenAI 兼容接口为例,讲清请求与响应、Python 与 curl 调用、批量策略、向量存储、RAG 串联、常见报错和迁移检查。

说明:OpenAI、PostgreSQL 及其他文中提及的品牌均为其权利人的商标。本文仅用于技术说明,与相关厂商无隶属关系;接口、模型和价格以官方文档及站内页面为准。

一、接口与鉴权

OpenAI 兼容的 embedding 接口是:

POST https://ai.zhongshangqihang.com/v1/embeddings
Authorization: Bearer sk-你的密钥
Content-Type: application/json

Base URL 只写到 /v1,不要自己拼 /embeddings 后再交给 SDK。SDK 会根据方法自动补路径,重复拼接会得到 404。

请求体最少只需要两个字段:

{
  "model": "text-embedding-3-small",
  "input": ["退货运费谁承担", "退货的物流费由谁支付"]
}

input 可以是单个字符串,也可以是字符串数组。数组会按顺序返回多个向量,适合批量处理。空字符串通常会被拒绝,清洗阶段要过滤掉。

二、请求与响应字段

字段 位置 说明
model 请求 必填,模型名以平台文档和 模型价格 为准
input 请求 字符串或字符串数组;单条不要超过模型上限
dimensions 请求 仅部分模型支持,用于缩短输出维度
encoding_format 请求 通常为 float,也可用 base64 减少传输体积
data[].embedding 响应 向量数组,顺序与输入一致
data[].index 响应 输入在数组中的下标
usage 响应 prompt_tokens 与 total_tokens,用于对账

响应里的 index 很重要。批量请求失败重试或并发合并时,不要假设返回顺序永远和输入一致,按 index 对齐更稳。

三、Python 调用与相似度

安装 SDK 后,最小示例:

import os
from math import sqrt
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["ZSQH_API_KEY"],
    base_url="https://ai.zhongshangqihang.com/v1",
)

resp = client.embeddings.create(
    model="text-embedding-3-small",
    input=["退货运费谁承担", "退货的物流费用由谁支付"],
)
a, b = resp.data[0].embedding, resp.data[1].embedding
cos = sum(x * y for x, y in zip(a, b)) / (
    sqrt(sum(x * x for x in a)) * sqrt(sum(y * y for y in b))
)
print(len(a), round(cos, 4), resp.usage.total_tokens)

这段代码同时验证了三件事:Key 可用、维度符合预期、两条相近文本的相似度合理。正式项目建议用 NumPy 或向量库自带的距离函数,不要在大批量数据上跑纯 Python。

四、维度、归一化与模型一致性

常见 OpenAI embedding 模型的维度:

  • text-embedding-3-small:1536 维。
  • text-embedding-3-large:3072 维。
  • text-embedding-ada-002:1536 维。

dimensions 参数可以把 text-embedding-3 系列的输出缩短,例如从 3072 降到 1024,以节省存储和带宽。缩短会损失一部分信息,是否值得要用自己的评测集验证,不要只看维度数字。

OpenAI 文档称其 embedding 已归一化到长度为 1,因此余弦相似度可以用点积直接算。但如果你用了自建模型、第三方模型或手动截断维度,就要重新做 L2 归一化。最稳妥的做法是:写入和查询使用同一套归一化逻辑,并在代码里写清楚。

还有一个硬约束:向量库里的所有向量必须来自同一个模型、同一维度、同一归一化方式。换模型后旧向量不能继续混用,否则相似度没有可比性。

五、批量请求怎么切

批量能减少 HTTP 开销,但请求不是越大越好。OpenAI 当前文档给出的边界是:单条输入不超过约 8K Token,单次请求所有输入合计不超过 300,000 Token;具体限制以服务商文档为准。

实践上按 Token 预算切批,而不是固定条数:

  1. 先清洗文本,去掉空白、重复和超长段落。
  2. 用分词器估算每条 Token,超长文本先切块。
  3. 按总 Token 预算组批,例如每批控制在几万 Token,留出重试余量。
  4. 对 429 和 5xx 使用指数退避重试,4xx 参数错误不要盲目重试。
  5. 记录每批的模型、维度、Token 和耗时,便于对账和复现。

重试要有幂等思路。可以用“文本哈希 + 模型名 + 维度”做缓存键,避免同一段文本重复计费。批量任务失败后,先确认哪些 index 已经成功,再补缺失部分。

六、存进向量库

以 PostgreSQL 的 pgvector 为例,表结构可以简化成:

CREATE TABLE chunks (
  id bigserial PRIMARY KEY,
  content text NOT NULL,
  source text,
  embedding vector(1536)
);
CREATE INDEX ON chunks USING hnsw (embedding vector_cosine_ops);

查询时用余弦距离:

SELECT id, content
FROM chunks
ORDER BY embedding <=> $1
LIMIT 5;

除了向量,至少还要存:原文、来源、所属租户或权限、切块位置、模型名和维度。没有这些元数据,回答无法引用来源,也无法做权限过滤。生产环境还要考虑索引类型、召回参数、备份和增量更新,不要等数据量上来才补。

七、RAG 串联顺序

一个能跑通的 RAG 流程通常是:

  1. 文档解析与清洗,去掉页眉页脚和重复内容。
  2. 按标题或段落切块,块之间保留少量重叠。
  3. 对每个块调用 embedding,批量写入向量库。
  4. 用户提问时,用同一个模型把问题向量化。
  5. 按余弦相似度召回 Top-K,再按权限和来源过滤。
  6. 需要时加关键词检索做混合召回,或加重排模型。
  7. 把检索结果和问题一起交给聊天模型,并要求带引用回答。

切块大小没有统一答案。中文文档常见的起点是 300 到 800 字,再根据问答效果调整。切得太大,检索不精准;切得太小,上下文不完整。与其纠结参数,不如先做一套小评测集,固定问题和标准答案,每次改流程都跑一遍。

八、常见报错与排查

  • 401 Unauthorized:Key 错误、被删除、没带 Bearer,或环境变量为空。
  • 404 Not Found:Base URL 少了 /v1,或路径重复拼接。
  • model not found:模型名不对或账号没有该模型权限。
  • 400 输入过长:单条超过模型上限,先切块再请求。
  • 400 空输入:数组里有空字符串,清洗阶段过滤。
  • 429:频率或并发超限,降低并发并做指数退避。
  • 维度不一致:写入和查询用了不同模型或不同 dimensions。
  • 相似度全是 0 或异常接近:检查是否归一化、是否拿错字段、是否把零向量写进库。

排查时先打印模型名、输入条数、单条长度和返回维度。大多数问题不是“模型效果差”,而是请求和存储的某个环节不一致。

九、迁移与上线检查

从官方 API 或其他网关迁移到 OpenAI 兼容网关,通常只改两处:base_url 和 api_key。但 embedding 有一个额外成本:模型或维度一变,历史向量就要重建。

上线前至少检查:

  • 新平台是否支持你正在用的模型名和维度。
  • 是否需要重新向量化全库,重建窗口和费用能否接受。
  • 查询侧和写入侧是否同时切换,避免新旧向量混用。
  • 是否有灰度方案,可以先切一部分流量对比召回效果。
  • 监控是否覆盖错误率、延迟、Token 消耗和空结果率。
  • 是否有回滚路径,旧 Key 和旧索引在切换后保留一段时间。

如果只是换 Base URL、模型和维度不变,迁移会简单很多;如果连模型一起换,就把它当成一次数据重建项目来排期。具体模型和价格以 模型价格 为准。

常见问题

Embedding 接口和 Chat Completions 有什么区别?

Embedding 返回固定长度的向量,用于检索、聚类和相似度计算;Chat Completions 返回生成的文本。两者鉴权方式通常相同,但请求体和响应结构完全不同。

一次可以传多少条文本?

接口接受字符串数组,但真正的限制是总 Token 和单条长度。OpenAI 当前文档给出的单次请求上限是 300,000 Token,单条约 8K Token。实际批次建议按 Token 预算切,不要卡着上限跑。

向量需要自己归一化吗?

如果平台明确说明向量已归一化,余弦相似度可以用点积计算。自建模型、手动截断维度或不确定来源时,写入和查询都应按同一方式做 L2 归一化。

换了 embedding 模型,旧向量还能用吗?

不能直接混用。不同模型的向量空间不可比,维度相同也不代表语义对齐。换模型后要重建索引,并在元数据里记录模型名和维度。

中文文本需要先分词吗?

通常不需要。现代 embedding 模型直接处理原始文本,分词反而可能丢失上下文。需要处理的是清洗、切块、去重和长度控制。

下一步

想先跑通一条请求,可以从 注册页 创建低额度 Key,把上面的 curl 或 Python 示例改成自己的文本。正式选型时再看 模型价格,并用一小批真实业务文本对比召回效果。价格和模型会调整,以站内当前展示为准。

相关阅读: