教程 / OpenAI 与 API

Embeddings 入门:text-embedding-3 怎么用、维度怎么选

Embeddings 把文本变成一串数字,让“语义相近”变成可以计算的余弦相似度。它是语义搜索、检索增强生成(RAG)、聚类和去重的地基。这篇从选模型讲到上线细节,重点说清楚维度和成本怎么权衡。

更新于 2026-10-24由 GPTHubs 团队 整理约 8 分钟读完OpenAI 与 API

什么时候值得用 Embeddings

Embeddings 适合“找相似的”这类问题:用户搜一句话,你要从几万条文档里找出语义相关的几条;或者你要把一批工单自动归类、把重复问题合并。它不适合需要精确匹配编号、型号、报错码的场景——那些仍然要靠关键词或结构化字段。

  • 语义搜索:查询和文档用词不同但意思相近时仍能命中
  • RAG 检索:先把相关片段捞出来,再交给模型回答,减少幻觉
  • 聚类与分类:把文本向量聚类做主题发现,或接一个简单分类器
  • 去重:相似度超过阈值就认为是同一类问题

两个模型怎么选:small 还是 large

官方提供 text-embedding-3-small 和 text-embedding-3-large 两个尺寸。默认维度分别是 1536 和 3072;large 更贵、更大,通常在检索精度上更好,但差距在中文短文本、领域词汇密集的数据上未必明显。

对比项text-embedding-3-smalltext-embedding-3-large
默认维度15363072
单价低约为 small 的数倍(以官方定价为准)
检索精度一般场景够用更难的数据、更细的语义区分更稳
存储与内存每条向量约 6 KB(float32)每条向量约 12 KB(float32)
建议先用它做基线,便宜且快基线不够用时再换,或只对难样本换

选择顺序建议:先用 small 跑一版检索评测,记录 recall@k;如果错例集中在“语义确实相近但没检索到”,再换 large 对比。直接上 large 往往只是多花钱,因为你没有基线判断它到底提升了多少。

最小可用示例

调用方式很简单:把一批文本传进去,拿回同样数量的向量。注意 input 可以是字符串或字符串数组,一次传多条比循环单条快得多。

把文档切片批量向量化
import OpenAI from "openai";

const client = new OpenAI();

export async function embedDocuments(chunks: string[]) {
  const response = await client.embeddings.create({
    model: "text-embedding-3-small",
    input: chunks,
  });

  return response.data.map((item) => item.embedding);
}

const vectors = await embedDocuments([
  "会员代充多久到账?",
  "支付失败会退款吗?",
]);

查询和文档必须用同一个模型、同一套预处理。换了模型就要整库重算,否则两边的向量不在同一个空间里,相似度没有意义。

维度截断:用更少的维度换成本

text-embedding-3 系列支持用 dimensions 参数把向量截短(large 可以从 3072 降到 1024、512 甚至更低)。截断不是随机丢弃,模型训练时就保证了前面维度的信息密度更高,所以精度下降通常比“按比例丢信息”要温和。

指定输出维度
const response = await client.embeddings.create({
  model: "text-embedding-3-large",
  input: chunks,
  dimensions: 1024,
});
维度存储(每条,float32)适用
3072约 12 KB数据量不大、精度优先
1024约 4 KB大多数检索场景的平衡点
512 及以下约 2 KB百万级以上、或端侧/内存受限

检索质量:分块、归一化与混合检索

实际效果差,多半不是模型问题,而是分块和检索策略问题。文档切得太长,向量会被平均掉;切得太碎,又丢上下文。常见做法是按语义段落切,每块 200–500 字,带一点重叠。

  • 分块:按标题和段落切,保留标题作为块的前缀,能显著提升召回
  • 归一化:把向量做 L2 归一化后,余弦相似度等价于点积,方便用向量库的索引
  • 混合检索:向量检索 + 关键词(BM25)各取前 N 条再融合,专有名词和编号类查询更稳
  • 重排:召回 20 条后用交叉编码器或对话模型重排,取前 3–5 条交给生成模型
余弦相似度(向量已归一化时可直接点积)
export function cosineSimilarity(a: number[], b: number[]) {
  let dot = 0;
  let normA = 0;
  let normB = 0;
  for (let i = 0; i < a.length; i += 1) {
    dot += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }
  return dot / (Math.sqrt(normA) * Math.sqrt(normB));
}

成本与工程细节

环节注意点
首次全量向量化几十万条以上优先走 Batch 接口,成本减半;注意 batch 输出顺序不保证,要用自定义 ID 对齐
增量更新文档改动才重算,用内容哈希做去重,别每次全量重建
缓存相同文本的向量可以直接落库复用,查询侧也可以缓存热门查询的向量
存储pgvector、sqlite-vec、向量数据库都可以;关键是同时存原文、元数据和模型版本
监控记录模型名、维度、分块策略版本,换模型时才能定位是哪一批数据不一致

常见坑

  • 查询和文档用了不同模型或不同 dimensions,相似度完全不可比。
  • 把整篇长文塞进一条向量,关键信息被稀释,检索几乎命中不了。
  • 只做向量检索,用户搜“订单号 12345”这种精确串时召回很差。
  • 没有评测集,改完分块策略不知道是变好还是变坏。
  • 把 embeddings 当成生成模型,指望它直接回答问题和处理多轮对话。

上线前至少准备 30–50 条真实查询作为评测集,记录 recall@5 和首条命中率。每次调整分块、维度或模型都跑一遍,这比任何“最佳实践”都可靠。

常见问题

中文用 Embeddings 效果好吗?

text-embedding-3 系列对中文支持不错,短文本和客服问答类场景通常够用。专有名词、缩写多的领域,建议配合关键词检索做混合召回。

向量必须存数据库吗?

数据量小的时候存 JSON 或 SQLite 也能用,几万条以内暴力计算余弦相似度完全够快。超过十万条再考虑 pgvector 或专门的向量库。

换模型后旧向量还能用吗?

不能混用。不同模型、不同 dimensions 的向量不在同一空间,必须整库重新生成。所以要在数据表里记录模型名和维度,方便迁移。

官方文档与参考

本文依据上述官方文档整理;价格、额度与功能会随官方更新变化,第三方中转站的行为以其自身说明为准。