先看一个查询:埃及有哪些民族?
一个再普通不过的中文问句。把它原样发给 SQLite FTS5 的中文全文检索,返回结果是空的。不是排得不好,是一条都没有。
我们是在给琅嬛跑检索评测时撞见这件事的:200 条真实查询,FTS 通道全线零召回,一排 0.0000 摆在表格里。第一反应是评测代码写错了,从头查到尾,发现是产品自己的 bug——而且在系统里活得好好的,因为没有任何东西会因为它而死。
病灶:分词把问句拆成了「全 AND」
机理拆开看一点不复杂。
FTS5 拿到查询,先分词。琅嬛 standalone 模式的中文分词用的是 gse,「埃及有哪些民族?」被切成五个 token:埃及、有、哪些、民族、?。
而 FTS5 的查询语义是 AND:一个文档要被命中,必须包含全部 token,一个都不能少。
问题就出在这。正文里当然会有「埃及」和「民族」,但不会有「哪些」,更不会有那个问号。五个 token 缺一个,整条查询就是空集。疑问词和标点只出现在问题里,永远凑不齐于任何一段陈述事实的正文——于是所有带疑问词的查询,全军覆没。
有意思的是,没有一个环节是「错」的。gse 每个词都切得对,FTS5 忠实执行了 AND 语义,两边单独看都无辜,合起来是零。这类 bug 麻烦就麻烦在这:它不藏在某一行的代码里,它藏在两个正确组件的接缝上。
更大的问题:混合检索悄悄变回了单路
如果只是 FTS 挂了,事情还小。真正的麻烦是,它挂在一套混合检索里。
琅嬛的默认检索是向量加全文双路召回、RRF 融合,混合检索那篇里有完整讲法。FTS 这一路全灭之后,融合公式照样运转,返回结果照样有十条——只是这十条全部来自向量那一路。名义上的混合检索,实际只剩单路在干活。
实测数字很能说明这件事有多隐形:修复前,hybrid 的 recall@10 和纯向量完全相同,0.9799,一位不差。用户那边看到的搜索「能用」,日志没有错误,指标没有断崖。一路检索死了,整个系统安静得像什么都没发生过。
这也解释了为什么功能测试一直抓不住它。功能测试问的是「能不能返回结果」,返回的本来就是向量路的结果,答案永远是「能」。要抓住它,得换一个问题:这一路到底贡献了什么。我们是在评测矩阵里把向量通道关掉、只留 FTS 单路跑,才让它现形的。
修复:把问句里的噪声在查询侧滤掉
修法想清楚之后不复杂,第一个要做的决定是修哪一侧。
索引侧不值得动:疑问词和标点的噪声在查询里,文档正文本身没病。该做的是查询进 FTS 之前先过滤——标点直接去掉;「有」「的」「了」这类单字虚词去掉;「哪些」「怎么」「为什么」这类疑问填充词去掉。剩下的实词再进 AND 匹配。
「埃及有哪些民族?」过滤后剩「埃及、民族」,正文里两者齐备,命中。
词表我们刻意做得很保守,宁可少滤几个,也不误杀实词。停用词表扩得越狠,越容易把某个领域的术语误伤成「虚词」,而一次误杀就是一批关键词查询的静默丢失。所以规矩定死:动一次词表,评测重跑一遍新旧对比,数字说话。
修复之后,FTS 从 0 分回到 0.13。看着不起眼,得知道 FTS 的主场在哪才读得懂:向量检索擅长「意思相近」,全文检索擅长「一字不差」——文件名、型号、编号、专有名词。这类关键词型查询正是向量的盲区、FTS 的地盘。问句由向量兜底,关键词靠 FTS 补位,两路各有主场,混合检索才有它存在的理由。修复后 hybrid 也第一次严格高于纯向量,0.9826 对 0.9799。
同一套过滤在 SQLite FTS5 和 PostgreSQL 两个方言下都生效,PG 侧走 zhparser 分词加 plainto_tsquery,整条路在pgvector 中文混合检索那篇里写过。修复随 v1.1.1 发布。
你的 FTS 通道还好吗
如果你的系统也是混合检索,值得花十分钟确认全文检索这一路是不是活的。
最直接的办法是单路探测:想办法把向量路关掉,只留全文检索,拿几条日常问句和几条关键词查询各试一遍。问句查不出来不算异常,FTS 天生不擅长问句;关键词也查不出来,就要警惕了。
再进一步,看两路的贡献分布:融合结果里有多少条来自 FTS 的召回。这个数字长期为零,而你又确信语料里有精确关键词,那大概率也是单路空转。
琅嬛把这做成了正式能力:检索通道的 top_k 参数支持 0 值,vector_top_k=0 就是只跑全文,keyword_top_k=0 就是只跑向量,不改代码就能做通道级诊断。这次复盘的前后所有数字,出自我们刚建好的检索评测体系,这里是完整故事。
想验证自己手里这套,下载页可以取到各平台的二进制。
延伸阅读
- SQLite FTS5 文档:FTS5 的查询语法与匹配语义,AND 语义的官方说明。
- gse 分词器:琅嬛 standalone 模式使用的 Go 中文分词库。
- PostgreSQL 全文检索控件:plainto_tsquery 的词法与匹配规则,PG 侧的对应实现。
- zhparser:基于 SCWS 的 PostgreSQL 中文分词扩展,生产部署侧的搭档。