60天企业 AI 实战训练营 · 阶段 2 · 第 2 周

Embedding
与语义检索

让"搜同义句"成为可能 —— 但别让"型号 A123"搜丢

Day 09 讲师 · 包学斌 2026 · 09 含实操 · 请自带电脑
"报销住宿" query text [0.12, -0.87, 0.44, …, 0.31] 1536 维向量 cos(住宿报销, 差旅住宿标准) ≈ 0.92 cos(住宿报销, 会议室预订) ≈ 0.31

按 → 或 空格键 翻页 · F 全屏

今天这堂课解决什么问题

课程目标

01
看懂向量检索
Embedding 把文本变向量,余弦相似度量"语义远近" —— 同义改写也能召回。
02
两路都会用
Dense 管同义表达、Sparse(BM25) 管编号术语 —— 企业场景必须混合。
03
落得了工程
RRF 融合、Top-K 权衡、元数据权限过滤 —— 检索是带边界的安全动作。
它在 60 天课程中的位置
Day 6 · HTTP、REST API 与 FastAPI已完成
Day 7 · 大语言模型工程基础已完成
Day 8 · Prompt Engineering 与 Structured Output已完成
Day 9 · Embedding 与语义检索今天
Day 10 · RAG 完整工程与评测下一课
前天的承诺"RAG 给证据" —— 今天造的就是那台"找证据的机器",明天串成完整闭环
互动 · 举手投票 + 点击选择

员工搜"型号 A123 的保修条款",纯向量检索返回了什么?

先猜一个 —— 这是企业知识库最经典事故

点一个选项,看看它意味着什么 →
01
Part 01

文字进,向量出

语义相近的文本,在空间里住得近

embedding 模型 固定维度 余弦相似度
Part 01 · 文字进,向量出

Embedding:把语义编码成坐标

模型把文本映射到固定维度向量 —— 相似 = 距离近

from openai import OpenAI r = OpenAI().embeddings.create( model="text-embedding-3-small", input="一线城市住宿不超过 650 元") v = r.data[0].embedding # [0.12, -0.87, …] assert len(v) == 1536
主流选择
OpenAI text-embedding-3 系列 · 国产 bge / m3e;维度常见 256–3072。企业优先选中文好、可领域微调的
一个知识库,一个模型
向量空间由模型定义 —— 同一库必须统一 embedding 模型与维度;换模型 = 全量重建索引。
常见坑
混用两个模型的向量算相似度 → 数值全错;文本预处理不统一(大小写、空格)→ 同义文本向量却差得远。
Part 01 · 文字进,向量出

余弦相似度:比方向,不比个头

度量的是夹角
cos ∈ [−1, 1]:方向一致 = 1,正交 = 0,相反 = −1。与向量长度无关 —— "一段话变长"不该改变它的语义方向。
工程加速:归一化后点积
L2 归一化之后,点积 ≡ 余弦且计算更快 —— 生产向量库都这么做。三种常用度量(余弦 / 点积 / 欧氏)别混着用。
阈值是标定出来的
"相似度 > 0.8 才算命中"——换个 embedding 模型 0.8 完全是另一个意思。按业务实测标定,不许拍脑袋。
向量空间的直觉
住宿报销 差旅住宿标准 会议室预订 夹角小 ≈ cos 0.92 夹角大 ≈ 0.31
实操 ① · 8 分钟 · 需要联网

亲手量一次语义距离

1demo/embed_lab.py:对 6 组企业短语求 embedding 并两两算余弦
2找出最相似与最不相似的一对,验证"同义句"确实距离近
3把一句英文"Hotel capex 650 RMB"混进来 —— 跨语言还近吗?
4改脚本第 1 行的模型名(bge 与 3-small 各测一次)—— 观察同一个"0.8"含义完全不同
08:00
验收标准
给出实测最相似的一对短语
说出两个模型下"0.8"分别意味着什么
能解释为何换模型要重建索引

卡住就举手 —— 实操环节助教会巡场

02
Part 02

检索的两条路

Dense 懂同义词,Sparse 认死理

Dense · 向量 Sparse · BM25 各有盲区
Part 02 · 检索的两条路

同一查询,两种引擎的答卷

查询:"住宿报销的额度是多少?" —— 库里三条文档

DENSE · 向量最近邻
召回①《差旅制度》"一线城市住宿每晚≤650" ✓
召回②《费用办法》"酒店费用限额说明" ✓(换了词也认得)
机制查询转向量 → ANN 找最近邻
盲区"A123 / PO-8871 / SAP MEINS" 编号型号一搜就糊
SPARSE · BM25 关键词
召回①含"住宿 报销"字样的段落 ✓
召回②"酒店费用限额说明" ✗(没有共同词就无缘)
机制词频 × IDF × 长度归一(经典统计检索)
盲区同义改写、口语化提问命中差;中文还得配好分词
企业语料 = 术语 + 编号 + 口语提问的混合体 —— 单路必有盲区,双路互为兜底
互动 · 快问快答

该走哪条路?

先举手判断,再点击揭晓

1. 搜"PO-2026-8871 这笔单据的状态"?
Sparse/BM25 甚至精确过滤 —— 单号就是字符串,向量的"模糊"在这里是缺点。
BM25
2. 员工问"出差睡哪儿能报多少钱"(没一个词出现在制度里)?
Dense —— "睡"与"住宿"、"报"与"报销"的语义映射是向量擅长的事。
向量
3. "查所有涉及 S/4HANA 迁移的会议纪要"?
混合 —— 术语 "S/4HANA" 交给 BM25 精确锁定,"迁移/上线切换"等表述交给向量泛化。
都要
实操 ② · 10 分钟 · 两人一组

两路引擎各跑一次召回

1demo/search_lab.py:内置 12 条制度片段的小库,纯 Python 实现两路检索
2用脚本给的 4 个查询分别跑 Dense 与 BM25,记录 Top-3
3重点观察"型号 A123"与"睡哪儿"两个查询:各自的失败方在哪
4把两路结果手动求并集 —— 你已经做了一次原始 Hybrid
10:00
验收标准
找出 BM25 独有的命中 ≥1 条
找出 Dense 独有的命中 ≥1 条
能解释"并集"为什么还不够好

离线也能做:Dense 路可用脚本内置的近似向量

03
Part 03

混合检索与 Top-K

融合两路答案,管好召回数量

RRF 融合 Top-K 权衡 留给 Rerank 的空间
Part 03 · 混合检索与 Top-K

Hybrid Search:并行、融合、截断

每点一次,走一站流水线

Dense
向量 Top-N
Sparse
BM25 Top-N
RRF 融合
rank 加权
截取 Top-K
预算内条数
Rerank
明天 Day 10
# RRF:只看名次,不看分值 score(d) = Σ 1 / (k + rank_i(d)) # 两路都排前面的 → 融合分最高 # k 常取 60 · 天然免疫两路分值不可比
为什么对"权重固定不调"说不
加权融合(α·dense + β·bm25)的 α 要按业务实测调;RRF 用名次融合更稳。两路文档 ID 空间必须对齐,否则融不出同一篇。
企业效果
术语 + 口语化查询的混合场景,Hybrid 比任何单路都稳 —— 这是知识库的默认答案。
Part 03 · 混合检索与 Top-K

Top-K:召回数量的两难

K 太小(如 K=2)
正确文档没进候选 → 漏检。模型再强也答不出没给它的证据 —— 后面环节无力回天。
K 太大(如 K=30)
噪声文档涌入 → 干扰模型 + token 费翻倍 + lost in the middle(Day 7 的债)。"让 LLM 自己挑"不划算。
企业问答的起点配方
召回 K = 5~15(先做召回率评测定档,按文档长度动态调整),交给 Rerank 精剪到 3~5 条 再进窗口 —— "先多召回,再精排",别让 LLM 在三十篇里大海捞针。
决策依据只有一个:用 Golden 查询集实测 Recall@K —— 曲线拐点就是你的 K。
互动 · 找茬游戏

这六条检索设计,几条要被打回?

先心里给个判断,再点击揭晓

Dense 与 BM25 并行召回 → RRF 融合 → Top-K → Rerank 标准混合检索流水线
向量检索不做过滤,靠 Prompt 提醒模型"别看别的部门" 权限必须在检索层用元数据过滤 —— 模型可能不遵守(Day 8)
新库沿用旧库的 embedding 维度,随便混存两代向量 不同模型向量不可比 —— 统一模型,换模型重建索引
统一 L2 归一化后用点积代替余弦做相似度 数学等价、速度更快,生产常规
相似度阈值全公司统一 0.8,谁也不许改 阈值含义随 embedding 模型变化 —— 各场景实测标定
用 Recall@K 曲线选 K,而不是"老板觉得 5 就行" 参数跟着评测走 —— 明天把它写进评测闭环
04
Part 04

工程落地:向量库与边界

选型不难,难的是索引、过滤与权限

pgvector HNSW / IVF 元数据权限过滤
Part 04 · 工程落地

向量库:选型与两类索引

pgvectorPostgres 扩展 —— 起步首选,SQL 与向量一起管轻量
Milvus分布式专业向量库 —— 亿级规模重型
QdrantRust 实现 · 过滤表达力强专业
ES/CKElasticsearch KNN / ClickHouse ANN —— 存量栈顺手延伸复用
ANN 索引:近似换速度
全量比对算不起 —— HNSW(图索引,快而省内存少)与 IVF(聚类分桶,可配 PQ 压缩)二选一起步。
必须支持元数据过滤
tenant / dept / security_level 随向量同库存 —— "按租户查"要在检索时生效,不是查完再筛。
昨天的课再兑现
pgvector 装进 docker compose 十分钟起库 —— 实操③ 就靠它。
Part 04 · 工程落地

最贵的一课:过滤发生在哪一层

✗ 生成后过滤 / 靠 prompt
向量 Top-K 里混进财务部薪酬文档 → 塞进上下文
Prompt:"请勿回答其他部门内容"
模型可能"不遵守";且证据已在窗口里 —— 泄露已经发生(注入课同款教训)。
✓ 检索阶段过滤
WHERE tenant = ? AND dept IN (用户可见范围)
候选集合里根本没有越权文档
越权内容不进模型、不进日志、不进答案 —— 爆炸半径归零
入库时就给每个 chunk 打上 tenant / dept / 密级 标签 —— 权限是元数据设计,不是查询技巧(明天 Day 10 细讲 Metadata)。
实操 ③ · 15 分钟 · 综合演练

起一个带权限过滤的混合检索端点

1docker compose up -d 起 pgvector(demo/docker-compose.yml
2python ingest.py:把 12 条制度片段(含 dept 字段)embedding 入库
3python hybrid_search.py "住宿报销额度":两路召回 + RRF 融合打印 Top-5
4加过滤再跑一次:模拟 dept=HR 的用户,验证 FIN 文档不出现在候选
5Top-K 从 3 调到 10,观察融合结果与 token 估算的变化
15:00
验收清单
pgvector 容器健康运行
"A123 保修"查询在 BM25 路命中
HR 身份查不到 FIN 文档
说出 K 变大时哪个指标先恶化

卡住就举手 —— 实操环节助教会巡场

Part 04 · 复盘 · 互动讨论

三大血泪坑

1
只用向量检索"显得智能"
型号、单号、专有名词统统搜丢 —— 上线首周就被业务退回。稀疏检索必须兜底。
2
库内混用两代 embedding
"升级模型只对新文档生效" → 新旧向量同库互比,相似度全成乱码 —— 换模型就全量重建。
3
权限靠 Prompt 管
越权文档进了上下文就回不去了 —— 过滤必须发生在检索阶段,入库时权限标签就已定。
课堂讨论 · 2 分钟
公司要换更强的 embedding 模型(1536→3072 维),最该担心的不是效果,是什么?
答案:全量重建与一致性窗口
所有历史文档重新向量化(钱 + 时间);灰度期间新旧索引并存不能互查;阈值全部重新标定 —— 选型时就要假设"十年不换"
总结

今天你带走了什么

原理
Embedding:语义相近 → 向量距离近
原理
余弦比方向;归一化后点积更快
原理
Dense 管同义、BM25 管术语编号
原理
RRF 只看名次融合,免疫分值不可比
规范
一库一模型;换模型全量重建索引
规范
阈值按业务实测标定,不抄 0.8
规范
K=5~15 由 Recall@K 曲线定,精排交 Rerank
规范
中文 BM25 必须配分词器
工程
HNSW/IVF 近似检索 + 元数据过滤能力
工程
权限在检索阶段生效,Prompt 不做保安
工程
pgvector + compose 十分钟起步够用
心法
检索参数不靠玄学:一切看评测曲线
"向量负责懂你在说什么,关键词负责记住那个型号。"
课后作业 · 明日预告

今天到此,明天见"全链"

作业 1 · 阈值标定
用 embed_lab.py 为你的部门语料画相似度分布,给出"相关/不相关"的分界建议值与依据。
作业 2 · 元数据设计
给你们的知识库写 chunk 元数据 schema(含 tenant/dept/密级/生效日期),标注明天 RAG 哪些环节会用到。
作业 3 · 阅读
FastGPT 知识库搜索原理(doc.fastgpt.io);pgvector README;复习 BM25 三因子。
明日预告 · Day 10
RAG 完整工程与评测
检索的机器造好了,明天把文档解析、切块、重排、引用与评测串成企业级 RAG 最小闭环 —— 可追溯的回答才算数。

谢谢 · Q&A —— 实操问题随时在群里 @ 包学斌

Day 09 · Embedding 与语义检索 · 包学斌
1 / 22