15.1 先定义检索任务与评估集
如果没有“哪些结果算相关”的外部判断,搜索系统只能证明自己能够执行。 返回三行、命中一个熟悉商品、甚至计划使用了索引,都不等于检索质量好。
本节先冻结问题。第 15.2–15.5 节才允许比较实现。
15.1.1 精确筛选、词法相关与语义相似
先问是在判断资格,还是比较相关
下面两个 SQL 看起来都在“找商品”,合同完全不同:
-- 布尔资格:结果要么符合,要么不符合
SELECT product_id, title
FROM product
WHERE active
AND category = 'outdoor'
AND price < 500;
-- 排名:每个候选都有先后
SELECT product_id, title
FROM product
WHERE active
AND category = 'outdoor'
ORDER BY relevance_score DESC, product_id
LIMIT 10;前者是 filtering。正确性来自布尔谓词、约束、权限和事务快照;B-tree、 hash、BRIN 或分区裁剪可能让它更快,但不会改变逻辑答案。
后者是 retrieval/ranking。正确性至少包含:
- 相关对象是否进入候选集;
- 不相关对象是否被抑制;
- 最相关对象是否排得足够靠前;
- 同分时结果是否稳定;
- 过滤是否在正确阶段生效;
- 结果是否符合当前用户的权限与业务状态。
不要把两个问题压成一个“不透明相关性 SQL”。先用可验证谓词确定资格,再在 合格集合中比较相关性,最容易推理。
四种相似不是一回事
以商品检索为例:
| 输入与目标 | 更接近的能力 | 例子 |
|---|---|---|
| SKU、状态、类别完全相等 | 精确过滤 | sku = 'AUD-001' |
| 词形、布尔词、短语与字段权重 | 全文词法检索 | headphones 匹配词位 |
| 字符局部重合与错拼 | trigram 模糊匹配 | wireles hedphones |
| 模型编码的意图相近 | 向量相似 | music on the go |
词法相关不等于字符串包含。PostgreSQL FTS 会把文本解析为 token,再经
词典归一为 lexeme;英语配置能把 rats 归一成 rat,也可能去掉 stop
word。它支持 AND、OR、NOT、短语和位置,适合“文档含有哪些有意义的词”。
字符串相似不等于语言含义。pg_trgm 按三个连续字符的集合衡量重合,
所以对局部拼写错误很有效;database 与 databse 很近,但字符很近并不
保证商品意图相同。
向量相似也不是数据库自动理解语义。数据库只看一串数和一个距离函数; “这串数表达什么”来自模型、输入模板、截断、版本和归一化。手工坐标也能 做向量近邻,但不能称为训练模型的语义质量证据。
因此“语义检索”至少应展开为:
model identity
+ input construction
+ preprocessing/tokenization
+ vector dimension
+ normalization
+ distance/operator
+ relevance evaluation缺任意一项,未来都很难解释同一文本为何得到不同结果。
把任务写成合同
一份可执行任务定义至少回答:
document_unit: one product row
searchable_fields: [title, description]
eligible_if:
active: true
category: query.category_filter
query_language: English
top_k: 3
tie_breaker: product_id ascending
relevance_scale: 0..3
latency_scope: not established by this fixture本章没有把价格、库存、租户或行级权限塞进夹具,但生产合同不能省略。尤其是 租户与 ACL:一个相关性极高却无权读取的对象不是“排名较低”,而是根本不能 进入候选集合。
先写反例
本章八个查询不是八种同义表达,而是有意覆盖失败模式:
| 查询 | 想观察什么 |
|---|---|
wireless headphones | 精确商品词 |
wireles hedphones | 两处错拼 |
music on the go | 描述性意图 |
coffee bean grinder | 标题与描述共同提供词 |
make espresso at home | 任务表达 |
trail hydration | 类别过滤与多候选 |
postgre databse tuning | 技术词错拼 |
semantic nearest neighbor | 长尾精确术语 |
如果评估集只有实现者已经看过的成功查询,它只会确认实现者的直觉。先把必然 失败的查询写进去,后续比较才有信息量。
15.1.2 查询、候选、排序和过滤的分层
一条检索链有六个阶段
raw request
-> query parsing / normalization
-> hard filters and authorization
-> candidate generation
-> per-source ranking
-> fusion / re-ranking
-> top-K response and evidence每一层有不同的失败:
| 阶段 | 典型失败 |
|---|---|
| 查询解析 | 非法语法、空 query、极长输入、语言选错 |
| 硬过滤 | 跨租户泄漏、停用对象复活、过滤放到 ANN 之后导致不足 K |
| 候选生成 | FTS 零召回、模糊门槛太低、ANN 漏召回 |
| 单路排序 | 字段权重错、距离函数错、同分漂移 |
| 融合/重排 | 分数尺度乱加、弱源挤掉强源、重排器超时 |
| 响应 | 无稳定游标、证据不可解释、缓存越权 |
分层不是为了制造更多组件,而是为了让每个结论可以单独测。
查询解析必须显式
生产接口通常接收 raw text。不要让客户端偷偷决定它是:
- 所有词必须出现;
- 任一词出现;
- 完整短语;
- 支持引号与排除词;
- 前缀查询;
- 还是严格
tsquery表达式。
本章显式使用:
websearch_to_tsquery(
'pg_catalog.english'::regconfig,
raw_query
)它接受普通 web 风格文本、引号、OR 和减号排除,并且官方文档说明不会因
输入语法抛错。这降低了语法层事故,但不代表可以无限接受输入:长度、token
数、超时、并发和滥用仍要在接口层设限。
先过滤还是先 ANN,要写出物理含义
逻辑目标是:
WHERE active
AND category = 'outdoor'
ORDER BY embedding <-> :query_vector
LIMIT 3;精确执行可以枚举所有合格行再排序。近似索引通常先沿图或倒排结构取候选, 然后应用 PostgreSQL 过滤;当过滤选择性高时,扫描出的近邻大多被过滤掉, 最终可能不足三行。
所以“SQL 把 WHERE 写在 ORDER BY 前面”不证明物理上先过滤。要看:
- 执行计划;
- 过滤选择性;
- 返回行数;
- ANN 与 exact 的集合差异;
ef_search、iterative scan、partial index 或 partition 策略。
本章的 HNSW filtered plan 明确显示:
Index Scan using product_search_embedding_hnsw_idx
Filter: (active AND category = 'outdoor')它证明过滤发生在 HNSW 扫描结果上。hnsw.iterative_scan = 'strict_order' 能在过滤后不足时继续扫描,仍受最大扫描限制和数据分布影响,
不是“保证召回”的开关。
候选深度与返回深度不是一个 K
若最终返回 3 个结果,每路只取 3 个候选,融合器没有纠错空间。常见设计是:
lexical top N_l
fuzzy top N_f
vector top N_v
-> union/deduplicate
-> fusion or re-rank
-> final top K本章每路取前 4,最终取 3,只为保持 SQL 可读。生产候选深度应通过质量/延迟
曲线决定,不能照抄 4。
候选源还必须带上:
query_id
document_id
source
source_rank
source_score_or_distance
filter/version/model identity否则融合以后只剩一个总分,无法追问“它为何出现”。
排名必须确定
浮点分数可以相同,近似索引也可能在近似等距对象间选择不同结果。所有实验 查询都追加稳定 tie-breaker:
ORDER BY score DESC, product_id;这不会让近似算法变成精确算法,却能消除同分的随机输出,让回归测试和分页 至少有明确顺序。线上游标还要把排序键全部编码进去,不能只按一个不唯一分数 翻页。
过滤正确性优先于相关性
本章商品 17:
Legacy Wireless Earbuds
active = false
embedding = [0.95,0,0,0]它对两个 audio 查询非常接近,是专门布置的 canary。verify.sql 断言它
不能出现在全文、模糊、精确向量或混合的任何排名中。若出现,实验立即失败,
即使平均 NDCG 更高也不能发布。
这条原则可推广为:
authorization / tenant / lifecycle correctness
beats relevance score15.1.3 建立带人工相关性标签的查询集合
三份输入,三个不同身份
本章把输入拆成:
frozen-corpus.csv:可被检索的对象;frozen-queries.csv:查询、过滤与查询 向量;frozen-judgments.csv:查询—商品的 分级相关性。
数据库中对应:
product_search(product_id, ..., embedding, search_document)
eval_query(query_id, raw_query, category_filter, embedding, intent)
relevance_judgment(query_id, product_id, grade, rationale)把查询与标注分开很重要:查询是实际输入分布,标注是人对业务意图的判断。 模型和检索器都不能改写标注来让自己得分更高。
先定义标注等级
本章使用 1–3 级正相关:
| grade | 含义 |
|---|---|
| 3 | 直接满足意图,是理想结果 |
| 2 | 明确相关,但不是最佳 |
| 1 | 可接受的邻近结果 |
| 0/无行 | 未标注为相关 |
每条标注都有 rationale,例如:
q06,7,3,water bottle exactly serves trail hydration
q06,8,2,backpack includes a hydration sleeve
q06,9,1,water filter is related to outdoor water needs理由不是装饰。两位标注者发生分歧时,没有理由就无法判断是规范模糊、文档 信息不足,还是标注错误。
真实项目应进一步规定:
- 标注者是否知道检索器输出;
- 一个查询由几人独立标注;
- 分歧怎样仲裁;
- 未展示对象是“不相关”还是“未判断”;
- 如何抽样候选池,避免只标注现有系统召回到的对象;
- 是否按用户群、语言、地区、设备与时间分层;
- PII、敏感类别与数据保留规则。
如果只标注旧检索器的候选,新方法召回的新对象会被误当成零相关,这叫 pooling bias。
四个指标回答四个问题
令前 (K) 个结果为 (R_K),相关集合为 (G)。
Precision@K:
[ P@K = \frac{|R_K \cap G|}{K} ]
它问“展示位有多少是相关的”。本章即使某策略只返回一行,也仍除以 3; 空位会损失质量,避免系统靠少返回来虚增 precision。
Recall@K:
[ R@K = \frac{|R_K \cap G|}{|G|} ]
它问“已知相关对象覆盖了多少”。本章每个查询恰好有 3 个相关对象,因此 Precision@3 与 Recall@3 数值相同;这是夹具结构的巧合,不是两个指标等价。
MRR@K:
[ MRR@K = \frac{1}{|Q|} \sum_{q \in Q} \begin{cases} 1 / rank_q, & rank_q \le K \ 0, & \text{otherwise} \end{cases} ]
它只关心第一个相关结果多靠前,适合“用户通常点第一个可用答案”的任务。 多个高质量结果的次序差异不会充分反映在 MRR 中。
NDCG@K 使用分级相关性:
[ DCG@K = \sum_{i=1}^{K} \frac{2^{grade_i}-1}{\log_2(i+1)} ]
[ NDCG@K = \frac{DCG@K}{IDCG@K} ]
它奖励高等级对象靠前,并以理想次序归一。本章用 NDCG 暴露了“向量找全, 次序却不理想”以及“混合在某个错拼查询上变差”。
指标不是越多越科学。先从用户行为选择指标:
| 任务 | 更重要的信号 |
|---|---|
| 唯一答案/导航 | MRR、Success@K |
| 商品列表前三屏 | NDCG、Precision@K、业务转化 |
| 法务/安全材料发现 | Recall@K、漏召回审计 |
| ANN 服务路径 | exact-relative recall + latency |
冻结不等于永久
corpus SHA-256
query SHA-256
judgment SHA-256
loader SHA-256
row counts
model id + dimension + method
text/vector license boundary自动化执行:
cmp frozen-corpus.csv evidence/corpus.csv
cmp frozen-queries.csv evidence/queries.csv
cmp frozen-judgments.csv evidence/judgments.csv数据库多一个空格、少一条标注、换一个向量都会被发现。
但冻结集只是一个版本。遇到以下变化应生成新版本,而不是覆盖旧 golden:
- 真实查询分布改变;
- 商品/文档类型扩展;
- 模型、模板或预处理改变;
- 语言配置与词典改变;
- 人工标注规则改变;
- 融合目标或业务约束改变。
发布报告要同时写:
quality delta on frozen regression set
+ quality on fresh holdout set
+ online behavior under guarded experiment只在一个反复调参的集合上提高,会逐渐把参数拟合到测试集。
本章验收
运行:
PG36_EVIDENCE_DIR="$PWD/evidence/ch15" \
./static/labs/ch15/task.sh evaluate审校器要求:
17 corpus rows
8 query rows
24 judgment rows
all three exports byte-identical
every query has exactly three positive judgments
product 17 inactive and absent from every ranking到这里还没有决定用哪种检索器。我们只是让后续每个决定都有同一把尺。