主题
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); // 15361
2
3
4
5
6
7
8
2
3
4
5
6
7
8
可以先把它理解成一把哈希:文本进、数字数组出。区别是哈希只保证「相同文本结果相同」,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));
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
入库时若已归一化,这段退化成一次点积,省掉开方,代价是浮点误差,判阈值留点余量。
为什么它比关键词匹配强
- 「怎么让首屏快一点」和「页面加载优化方案」没有一个共同关键词,向量夹角却很小;同义改写、语序变化、错别字、跨语言都能容忍,也不用维护同义词表。
- 关键词是精确的字符串运算,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。
- 搜索:向量召回管「意思对得上」,关键词召回管「字面精确」,两路合并排序就是混合检索。
- 聚类:对一批文本的向量跑聚类,自动发现用户反馈主题,省掉人工打标。
- 去重:两两算余弦相似度,超阈值判重复;量大时先用向量索引做近邻搜索,别全量硬比。