这是《LLM 应用开发 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Embedding 模型把文本映射为向量,让语义相近的内容在向量空间中接近。它是向量检索的核心,但不是 RAG 的全部。
9.1 基本概念
"如何申请退款" -> [0.12, -0.34, ..., 0.08]
"退货流程是什么" -> [0.10, -0.31, ..., 0.06]
"今天天气很好" -> [0.75, 0.22, ..., -0.11]
常用相似度:
| 方法 | 说明 |
|---|---|
| cosine | 比较方向,常用于文本 |
| dot product | 向量未归一化时受长度影响 |
| euclidean | 距离越小越相似 |
多数文本检索会归一化向量,此时点积与 cosine 排序等价。
9.2 输入设计
不要只向量化正文:
标题路径:员工手册 / 考勤 / 远程办公
正文:远程办公需要提前一天审批...
关键问答:如何申请远程办公?
可检索文本示例:
def build_embedding_text(chunk):
title = " / ".join(chunk.get("title_path", []))
return f"{title}\n{chunk['content']}" if title else chunk["content"]
注意:
- 不要把权限、内部状态等不适合参与语义的内容拼入正文;
- 元数据应放在过滤字段;
- 不同语言选择匹配的模型;
- 任务前缀要遵循模型文档;
- 查询和文档是否需要不同前缀,以官方说明为准。
9.3 调用示例
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
def embed(texts: list[str]) -> list[list[float]]:
response = client.embeddings.create(
model="text-embedding-3-small",
input=texts,
)
return [item.embedding for item in response.data]
批处理建议:
- 控制单批输入条数和 token;
- 处理限流和重试;
- 保存模型名和维度;
- 批量任务支持断点续跑;
- 记录失败文本;
- 输出向量做归一化前先确认模型输出约定。
9.4 维度与存储
向量数量 = chunk 数量
存储量 ~= 向量数量 * 维度 * 4 bytes # float32
示例:
100 万条 * 1536 维 * 4 bytes ~= 6 GB
常见优化:
- 只向量化有价值的文档;
- 去重;
- 降维;
- 量化;
- 冷热分层;
- 按租户或业务分索引;
- 结合关键词索引减少候选集。
9.5 模型切换
向量不可跨模型混用。
embedding-model v1
-> index-v1
embedding-model v2
-> index-v2
切换流程:
- 建立评测集;
- 双索引构建;
- 比较 Recall@K;
- 小流量切换;
- 观察回答质量;
- 保留旧索引一段时间;
- 全量切换;
- 归档旧向量。
9.6 查询改写
用户查询往往短且带上下文依赖。
用户:那这个要多久?
历史:我在问退款。
改写:退款审核需要多久?
常见方法:
| 方法 | 场景 |
|---|---|
| 指代补全 | 多轮对话 |
| 同义改写 | 口语化提问 |
| 拆分子查询 | 复合问题 |
| HyDE | 生成假设答案再检索 |
| 关键词抽取 | 编号、型号、错误码 |
改写要保留原文,避免把用户没说的条件补进去。
9.7 多语言
挑战:
- 中英混合;
- 专业术语翻译不一致;
- 中文分词不等于语义切分;
- 小语种召回差异;
- 文档语言与查询语言不同。
策略:
- 优先选择多语言模型;
- 建立术语表;
- 保留原文关键词;
- 混合检索编号和错误码;
- 按语言评测而非只看总体指标;
- 输出语言与检索语言解耦。
9.8 评估 Embedding
构造标注对:
| query | positive | negative |
|---|---|---|
| 如何申请退款 | 退款流程文档 | 优惠政策 |
| 账号锁定 | 登录失败处理 | 数据库锁定 |
指标:
| 指标 | 说明 |
|---|---|
| Recall@K | 相关文档召回率 |
| MRR | 首个相关的排名倒数 |
| NDCG | 排序质量 |
| Mean Cosine Gap | 正负例间隔 |
| Latency | 向量化延迟 |
| Cost | token 成本 |
9.9 常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| 编号查不到 | 向量对精确词弱 | 混合检索 |
| 长文档召回差 | 语义稀释 | 改切块 |
| 多主题 chunk 不准 | 块太杂 | 拆块 |
| 换模型后效果骤降 | 新旧向量混用 | 重建索引 |
| 相似度分数无法比较 | 模型或空间不同 | 只在同空间比较 |
| 多轮查询指代错 | 未改写 | 查询补全 |
本章小结
Embedding 负责语义召回,效果依赖输入构造、切块质量、模型选择和索引方式。向量空间与模型绑定,切换模型必须重建索引并做对比评测。对编号、术语和精确条件,应结合关键词或数据库过滤。
思考题
- 为什么不同模型的向量不能直接比较?
- 标题路径加入 embedding 文本有什么作用?
- 查询改写如何服务多轮对话?
- 如何评估 Embedding 模型替换收益?
- 什么场景必须使用混合检索?