Skip to content

Embedding 到底在做什么:从文本到向量 ​

做搜索、做 RAG、做推荐,都绕不开 embedding(嵌入)。对前端来说最容易糊的是:几行接口调用而已,为什么它比关键词匹配强,又为什么有时候烂得莫名其妙。这篇只讲三件事:它把文本变成什么、相似度怎么比、什么时候不管用。

Embedding 本质:把文本压成一串数字 ​

模型不认识字符串,只认数字。embedding 就是把一段文本送进模型,拿回一个定长数字数组:

ts
// 示意代码:一次典型的 embedding 调用
const res = await fetch("/api/embeddings", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ model: "text-embedding-3-small", input: "首屏加载优化" }),
});
const json = await res.json();
console.log(json.data[0].embedding.length); // 1536

可以先把它理解成一把哈希:文本进、数字数组出。区别是哈希只保证「相同文本结果相同」,embedding 还保证「意思相近的文本,数组也相近」。这就是它能干活的全部原因。

维度是什么:向量的长度 ​

维度(dimension)就是数组里数字的个数,决定一条向量占多少内存、算一次相似度花多少算力。

  • 1536 维用 float32 存,一条约 6KB,十万条就是几百 MB。
  • 维度越高表达力越强,存储和检索成本也跟着涨。
  • 有些模型支持用 dimensions 参数把向量截短(如 text-embedding-3 系列),拿精度换成本。

语义相近,就是方向相近 ​

把 1536 维想成 1536 个坐标轴,每个向量是空间里的一个箭头。

  • 箭头长短受文本长度影响,没有语义意义。
  • 方向才承载语义,比较前一般先归一化再看夹角。
  • 夹角越小越像:接近 0 度几乎同义,接近 90 度基本无关。

余弦相似度:一段能用的实现 ​

余弦相似度(cosine similarity)就是夹角余弦值,落在 -1 到 1 之间,越接近 1 越像:

ts
// 示意代码:余弦相似度
function cosine(a: number[], b: number[]): number {
  let dot = 0, na = 0, nb = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    na += a[i] * a[i];
    nb += b[i] * b[i];
  }
  return dot / (Math.sqrt(na) * Math.sqrt(nb));
}

入库时若已归一化,这段退化成一次点积,省掉开方,代价是浮点误差,判阈值留点余量。

为什么它比关键词匹配强 ​

  • 「怎么让首屏快一点」和「页面加载优化方案」没有一个共同关键词,向量夹角却很小;同义改写、语序变化、错别字、跨语言都能容忍,也不用维护同义词表。
  • 关键词是精确的字符串运算,embedding 是模糊的语义近似,两者互补而非替代。

为什么它又不完全可靠 ​

  • 同义词被压成一个点:「便宜」和「廉价」几乎分不开。
  • 否定词常被吃掉:「支持退款」和「不支持退款」的向量可能非常接近。
  • 长文本只能压成一个点,整篇文档挤进一个向量,细节会被抹平。
  • 它不做精确判断:订单号、价格区间这类条件,关键词或结构化过滤更靠谱。

警告

别把它当成万能相似度:它是语义近似,不是事实正确,否定词、数字、专有名词常被抹平,精确条件该交给关键词或数据库。同一批向量必须出自同一个模型,换模型或换版本,向量空间就对不上,检索结果会莫名其妙地烂。模型名、维度、价格都会变,别把抄来的数字硬编码进业务代码。

常见模型与维度(以官方文档为准) ​

  • text-embedding-3-small:默认 1536 维,官方标价 0.02 美元 / 百万 token,单条输入有 token 上限,量级约 8k。
  • text-embedding-3-large:默认 3072 维,官方标价 0.13 美元 / 百万 token。
  • BGE-M3:1024 维,覆盖上百种语言,最长 8192 token。
  • bge-large-zh-v1.5:1024 维,中文场景常见的开源选择。

一次能拿多少条,怎么批量 ​

  • 接口的 input 能直接传字符串数组,一次带一批文本,返回顺序与输入一一对应。
  • 单条长度、批次条数、整批总 token 都有上限,超了会报错或被截断。
  • 常规做法:长文档先切块(chunk),几百条一批发出,并发控速,失败重试。
  • 落库时把原文、切块位置、模型名、维度一起存,将来换模型才算有据可依。

四种场景里它分别怎么用 ​

  • RAG 问答:文档切块入向量库;提问时把问题也转向量,取最近几个块拼进 prompt。
  • 搜索:向量召回管「意思对得上」,关键词召回管「字面精确」,两路合并排序就是混合检索。
  • 聚类:对一批文本的向量跑聚类,自动发现用户反馈主题,省掉人工打标。
  • 去重:两两算余弦相似度,超阈值判重复;量大时先用向量索引做近邻搜索,别全量硬比。
最近更新