A21第 1 / 1 课时未开始内容版本 1.0.0 · 核验于 2026-10-26

关键词与混合检索

关键词与混合检索 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。

学习目标

读完本课你能读懂本站资料库检索的真实实现——FTS5 全文索引优先、中文退化为 LIKE 子串匹配——并能对两类问题(技术标识符、自然语言)分别分析关键词与向量检索谁更靠谱,最后把两路合起来做混合检索。

问题场景

同一个资料库,两个问题:

  • 问「doc_chunk 表有哪些列」——关键词检索一击命中,因为片段里就写着 doc_chunk;向量检索反而可能把讲「数据表设计」的泛化段落也排进来。
  • 问「怎么让页面不再闪」——关键词检索全军覆没(片段里没有「闪」字);语义上它对应「组件重复渲染排查」,向量检索能捞回来。

没有一种检索对所有问题都强,这就是要对比、要组合的原因。

核心解释

先读本站的真实实现(lib/ai/tools.ts 的 search_documents 分支),它分三层:

// 第一层:FTS5 全文索引(fts_doc 虚表,tokenize='unicode61')
let sql = "SELECT document_id, collection_slug, heading, text FROM fts_doc WHERE fts_doc MATCH ?";
rows = db.prepare(sql).all(query); // 英文按词切分匹配,效率高

// 第二层:中文兜底。FTS5 的 unicode61 分词器不切分中文——
// 「删除接口」会被当成一个整体 token,长中文查询基本匹配不上。
// 于是退化为 LIKE 子串匹配(个人资料量级足够用):
if (rows.length === 0) {
  rows = db.prepare(
    `SELECT document_id, collection_slug, heading, text FROM doc_chunk WHERE text LIKE ? LIMIT 5`
  ).all(`%${query}%`);
}

// 第三层:无命中时返回明确提示,不让模型编造
// { total: 0, message: "资料库没有命中内容;如无必要不要编造资料来源。" }

两个关键认知:

  • FTS5(Full-Text Search,全文检索)是 SQLite 内置的倒排索引。unicode61 分词器按空格和标点切词,对英文友好;中文没有空格,长查询整体当 token,命中率骤降——所以中文场景真正干活的是 LIKE 兜底。这是「个人资料量级」下的务实取舍,不是缺陷。
  • 关键词检索的本质是「字面包含」:精确、零成本、可解释,但对同义改写无能为力。向量检索(A20)本质是「语义距离」:能泛化,但需要嵌入服务,且对精确标识符不可靠。

混合检索就是两路都跑,各取所长再合并。最简单的合并是加权求和:

function hybridSearch(query: string) {
  const kw = keywordSearch(query);            // 关键词路:命中 score=1,未命中 0
  const vec = vectorSearch(query, 5);         // 向量路:余弦分数(接入向量后)
  const scores = new Map();
  for (const h of kw) scores.set(h.id, (scores.get(h.id) ?? 0) + 0.5);
  for (const v of vec) scores.set(v.id, (scores.get(v.id) ?? 0) + 0.5 * v.score);
  return [...scores.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
}

如何验证:拿固定的问题集(5 个技术标识符问题 + 5 个自然语言问题)分别跑三种检索,记录每题 top-1 是否命中期望片段。你会看到清晰分工:标识符题关键词赢,改写题向量赢,混合两头不丢。

示例拆解

本课实验区用预设的语义分数模拟向量检索,对照看两类问题的表现:

问题类型 例子 关键词(LIKE) 向量 结论
技术标识符 doc_chunk 有哪些列 精确命中 可能混入泛化段落 信关键词
自然语言改写 页面不再闪 零命中 命中「重复渲染」 需要向量
编号引用 A15 讲什么 命中 不可靠 信关键词

关键点:

  • 中文查询在本站真正走的是 LIKE 子串匹配,所以查询词要贴近原文用词——「幂等」能中,「防止重复提交生效两次」中不了。
  • 混合合并时两路分数量纲要对齐(都归一化到 0~1),否则一路碾压另一路,合并形同虚设。
  • 混合检索每多一路就多一份成本:向量路每次提问都调嵌入服务,关键词路几乎免费。

动手任务

  1. 跑本课实验区:对「组件重新渲染」和「useState」两个问题,分别记录关键词命中数和向量 top-2,验证「标识符信关键词、改写靠向量」。
  2. 给片段列表加一条「正文含 useState 但讲的是嵌套路由」的干扰片段,观察关键词检索误命中——理解精确匹配的副作用。
  3. 实现实验区任务里的 0.5/0.5 加权合并,观察两个问题的 top-2 变化;回答:哪一路分数权重应该更高,取决于什么?

常见错误

  • 以为 FTS5 对中文友好:unicode61 不切分中文,长中文查询在 MATCH 层基本全空——看到「FTS5 支持全文检索」就以为中文查询没问题,是本站实现里最容易被误读的一处。
  • 用自然语言长句当关键词查询:LIKE 是整串子串匹配,整句进去几乎必然零命中;关键词查询要抽成短词。
  • 合并时不管量纲:余弦分数 0.7 和关键词命中的 1 直接相加,权重失衡,混合结果比单路还差。
  • 两路结果不去重:同一片段两路都命中,合并后重复占位,挤掉别的候选。

检查与复盘

自查清单:

  • 能说出本站检索三层结构(FTS5 MATCH → LIKE 兜底 → 无命中提示)及每层触发条件
  • 能对任意一个新问题预判哪种检索更可能命中,并说理由
  • 能写出加权合并的实现并指出量纲问题

参考资料

动手实验
动手实验
预览
点击「运行」在隔离沙箱中预览
检查题共 2 题,提交后立即显示解析
答案由服务端判定;答错的题会自动进入复习计划。
Q1单选简单

本站检索第一层用 SQLite FTS5(unicode61 分词器),第二层用 LIKE 子串匹配兜底。中文长查询(如「删除接口」)经常在第一层零命中而落到 LIKE,原因是?

Q2多选较难(多选,全部选对才算正确)

关于关键词检索与向量检索的对比及混合合并,下列说法正确的有?(多选)

读完这一课了?

标记完成后,工作台会安排接下来的学习。

Command Palette

Search for a command to run...