聊天接口返回的是文本,而很多 AI 应用首先要解决的是“这两段话是不是在说相近的事情”。例如,用户问“怎么重置密码”,知识库里的“找回账户登录凭证”虽然没有相同的关键词,却应该被检索到。Embedding(嵌入)就是解决这类问题的基础技术:它把文本转换成一串数字,使程序可以用数学方法比较语义接近程度。本文只聚焦这个核心概念,并完成一个可运行的最小实验。

Embedding 到底是什么

Embedding 模型接收一段文本,输出固定长度的浮点数列表,这个列表称为向量。向量中的单个数字通常没有可解释的“词义”,真正有意义的是向量在高维空间中的整体位置。意思相近的文本,经过同一个模型处理后,通常会落在相近的位置;主题不同的文本,距离通常更远。

可以把它想成一张没有坐标轴标签的语义地图。模型在训练时学会了把相关内容放在附近: “退款多久到账”和“退货后什么时候能收到钱”可能相近,而“如何烤面包”则会远一些。Embedding 不是翻译,也不是摘要;它不会直接给你一段人类可读的答案,而是为比较、搜索和聚类提供数值表示。

为什么不用关键词匹配

关键词搜索擅长找字面完全相同的内容,却不擅长处理同义表达、语序变化和概念关联。向量相似度搜索关注的是表示后的距离,因此可以补充关键词搜索的不足。但它也不是万能的:如果文本很短、领域术语很多,或者切分方式不合理,语义表示仍可能不符合业务预期。实际系统经常把关键词检索和向量检索组合起来,而不是把 Embedding 当成唯一答案。

需要注意一个边界:比较必须使用同一个 Embedding 模型。不同模型的向量维度或坐标空间不一定兼容,不能把模型 A 生成的向量直接和模型 B 的向量计算距离。模型升级后,已有向量通常也需要重新生成。

最小可运行示例:生成向量并计算相似度

先在虚拟环境中安装官方 Python SDK 和环境变量加载库:

1
python -m pip install openai python-dotenv

在项目目录的 .env 中配置密钥。这里只读取环境变量,不把真实密钥写入源码:

1
OPENAI_API_KEY=替换为你的真实密钥

下面程序使用 OpenAI Python SDK 当前的 Embeddings 接口。input 可以是一段文本;返回对象的 data[0].embedding 是浮点数向量。示例同时请求两段文本,然后计算余弦相似度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import math
import os

from dotenv import load_dotenv
from openai import OpenAI

load_dotenv()
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise RuntimeError("请先设置 OPENAI_API_KEY")

client = OpenAI(api_key=api_key)
model = "text-embedding-3-small"


def embedding(text: str) -> list[float]:
response = client.embeddings.create(model=model, input=text)
return response.data[0].embedding


def cosine_similarity(a: list[float], b: list[float]) -> float:
dot = sum(x * y for x, y in zip(a, b))
norm_a = math.sqrt(sum(x * x for x in a))
norm_b = math.sqrt(sum(y * y for y in b))
if norm_a == 0 or norm_b == 0:
raise ValueError("不能比较零向量")
return dot / (norm_a * norm_b)


first = embedding("如何重置账户密码?")
second = embedding("忘记登录凭证后怎样找回?")
print("向量维度:", len(first))
print("余弦相似度:", cosine_similarity(first, second))

运行命令是 python embedding_demo.py。程序会实际访问 API,因此需要网络和有效密钥;本文不伪造具体的相似度数值。你应当记录自己运行时的维度和数值,并再把第二句话替换成“今天北京天气如何”做对照。相似度的绝对大小受模型、语言和文本长度影响,不要把某个固定阈值当成通用结论。

余弦相似度如何工作

余弦相似度比较两个向量夹角的余弦值,公式为:

1
similarity(a, b) = (a · b) / (||a|| × ||b||)

分子是对应元素相乘后求和,也就是点积;分母是两个向量长度的乘积。它更关注方向而不是长度,所以适合比较“语义方向”。对常见的 Embedding 向量,结果通常落在接近 0 到 1 的范围,但不要在业务代码中武断假设所有模型都严格满足这个范围。若只比较一批向量,先归一化再用点积也可以得到相同的排序结果。

示例中的 zip 会按位置配对元素,因此两个向量必须长度一致。norm_a == 0 的保护不能省略:零向量没有方向,除法没有意义。生产代码还应确认 API 返回了预期的 data 项,并记录模型名称,便于排查向量来源。

常见问题

向量维度越大,效果一定越好吗? 不一定。维度是模型设计的一部分,不能单独代表质量;更大的向量还会占用更多存储和计算资源。应使用目标数据集做检索效果测试。

相似度高就代表答案正确吗? 不代表。相似度只能说明文本表示接近,不能证明事实正确,也不能代替权限判断、业务规则和答案生成。后续做 RAG 时,还要把召回片段交给模型,并保留来源信息。

为什么两次运行的数值不完全符合直觉? Embedding 模型可能对专有名词、否定词、很短的句子表现不同。先用真实业务句子组成小测试集,再观察排序,而不是只凭两个例子下结论。

能不能把整本书一次生成一个向量? 技术上可以,但一个向量会把大量主题压缩在一起,检索定位会变差。长文应先按语义和长度切分;这会在后续的文档切分主题中展开。

小结

Embedding 把文本转换成可计算的向量,余弦相似度则提供了一个简单的语义接近度指标。本文用 client.embeddings.create() 生成两个向量,并在本地完成相似度计算。掌握这个基础后,下一步就是决定如何切分文档、设计元数据,让向量不仅能比较,还能在真实知识库中被有效检索。