在 RAG(Retrieval-Augmented Generation)知识库系统中,很多人首先关注的是 Embedding、向量数据库和大模型,却容易忽略一个非常重要的模块:
关键词检索。
向量检索擅长解决"语义相似"的问题,但对于错误码、函数名、技术名词、产品型号以及业务关键词等内容,传统词法检索往往更加准确。
因此,一个完整的知识库检索系统通常不会只使用向量检索,而是采用:
Keyword / Sparse Retrieval
+
Vector Retrieval
+
Fusion
+
Reranker
本文结合一个实际知识库项目中的检索优化过程,介绍关键词检索如何从最初简单的 中文 N-gram + OR 查询,逐步演进到:
Query Analyzer
+
ParadeDB BM25
+
pgvector
+
RRF
+
Reranker
整个过程最有价值的地方,并不是最终选择了什么技术,而是:
如何通过真实检索结果发现问题,并一步一步演进检索架构。
一、为什么知识库仍然需要关键词检索?
考虑这样一个 Query:
订单超时
假设知识库中存在一份故障排查文档:
订单超时问题排查
当订单服务调用库存服务超过 3 秒时,
系统会触发超时重试机制......
对于这种查询,用户非常明确地希望找到:
包含"订单超时"这一业务关键词的内容
而不仅仅是寻找:
与"订单超时"语义相似的内容
类似的场景还有:
ERR_CONNECTION_RESET
runtime.Gosched
CVE-2026-XXXX
OrderService
Redis
HTTP 504
这些 Query 都具有非常明显的字面匹配特征。
因此在 Hybrid Retrieval 中:
Keyword Search
→ 找字面相关内容
Vector Search
→ 找语义相关内容
两种方式通常是互补关系。
二、第一版方案:中文 N-gram
项目早期为了降低系统复杂度,并没有直接引入 Elasticsearch 等搜索引擎。
系统本身已经使用:
PostgreSQL
+
pgvector
因此第一版关键词检索选择直接基于 PostgreSQL 实现。
核心方案是:
中文 N-gram。
例如:
订单超时
经过 1-gram:
订
单
超
时
经过 2-gram:
订单
单超
超时
最终得到:
订
单
超
时
订单
单超
超时
文档入库时,同样对正文生成 N-gram Token,并建立全文索引。
整体流程类似:
Document
↓
N-gram Tokenizer
↓
Keyword Index
查询阶段:
Query
↓
N-gram Tokenizer
↓
Keyword Search
第一版方案具有明显优势:
-
实现简单;
-
不需要额外部署搜索服务;
-
不依赖复杂中文分词器;
-
中文召回率较高;
-
可以直接集成 PostgreSQL。
对于项目 MVP 来说,这是一个比较合理的实现。
三、第一个问题:OR 查询导致严重误召回
问题出现在一次关键词搜索测试中。
用户搜索:
订单超时
理论上真正目标文档应该是:
订单超时问题排查
但系统却返回了很多其他内容。
进一步排查发现,查询实际上被转换成:
订 | 单 | 超 | 时 | 订单 | 单超 | 超时
也就是:
订
OR 单
OR 超
OR 时
OR 订单
OR 单超
OR 超时
于是只要 Chunk 中出现其中任意一个 Token,都可能进入候选集。
例如某份文档包含:
订单创建流程
因为命中:
订单
会被召回。
另一份文档包含:
HTTP 接口超时处理
因为命中:
超时
也会被召回。
甚至某些包含:
定时任务
的内容,也可能因为低区分度字符进入宽松候选。
因此问题的本质并不是:
搜索系统召回了完全不包含查询内容的文档。
而是:
1-gram 信息量过低,同时 OR 查询又过于宽松。
最终表现为:
High Recall
Low Precision
召回率很高,但精确率明显不足。
四、第二个问题:TopK 不是"必须返回 K 条"
当时系统配置:
TopK = 8
假设第一条真正相关结果的分数明显较高,而后面的候选只存在弱匹配。
如果直接执行:
ORDER BY score DESC
LIMIT 8;
很容易形成一种错误行为:
既然 TopK 等于 8,就尽量返回 8 条。
实际上:
TopK 表示最多返回 K 条,而不是必须填满 K 条。
例如:
TopK = 8
真正有效结果 = 2
那么:
最终返回 2 条
完全合理。
强行补齐剩余 6 条低质量候选,反而会降低后续 RAG 的上下文质量。
五、第三个问题:"知识充分"不能只看候选数量
原来的 Retrieval Pipeline 中,还存在一个更隐蔽的问题:
结果数量 >= MinCount
↓
知识充分
假设:
真正相关 Chunk:2
低质量 Chunk:6
总候选:
8
如果只按照数量判断,系统可能认为:
知识充分
但真正能够回答问题的证据并不一定足够。
因此:
召回数量不能直接代表知识质量。
更合理的流程应该是:
Keyword ─┐
│
Vector ──┼→ Fusion → Reranker → Sufficiency
│
Other ───┘
也就是说:
Knowledge Sufficiency 应该尽量根据精排后的有效证据判断,而不是根据最初召回多少候选判断。
六、第一次优化:分层召回
发现 N-gram 的问题后,并没有立即替换整个关键词检索架构。
第一阶段先采取低成本优化:
Exact Phrase
↓
2-gram Strong Recall
↓
1-gram Weak Fallback
例如:
订单超时
可以分析成:
Exact:
订单超时
Strong:
订单
超时
Weak:
订
单
超
时
原来的:
所有 Token 一次 OR
被改造成逐渐放宽检索条件。
七、Query Relaxation:逐步放宽,而不是一次放宽
新的检索流程变成:
Query
↓
Exact Phrase
↓
是否得到高质量结果?
├─ Yes → Stop
└─ No
↓
Strong Terms
↓
是否得到有效候选?
├─ Yes → Stop
└─ No
↓
Weak 1-gram
这种方式可以理解为:
Query Relaxation(查询放宽)。
核心思想非常简单:
查询一开始尽量严格,只有结果不足时才逐渐放宽条件。
因此 1-gram 的定位从:
默认召回方式
变成:
最后兜底方式
这能够明显减少低区分度汉字带来的误召回。
八、为什么又引入 Query Coverage?
即使减少了 1-gram,普通关键词 OR 查询仍然可能产生噪声。
例如用户搜索:
Redis 分布式锁实现
经过分析得到:
Redis
分布式锁
实现
如果使用:
Redis OR 分布式锁 OR 实现
那么:
Redis 缓存设计
只因为包含:
Redis
也可能进入候选。
因此可以引入:
Query Coverage。
假设 Query 有 3 个主要关键词。
Chunk A:
使用 Redis 实现分布式锁
命中:
Redis
分布式锁
实现
Coverage:
3 / 3 = 100%
而 Chunk B:
Redis 缓存常见问题
只命中:
Redis
Coverage:
1 / 3 ≈ 33%
因此可以设置:
Coverage >= 60%
→ 保留
Coverage < 60%
→ 过滤或降权
Coverage 本质上是在解决:
OR 太宽
和:
AND 太严格
之间的平衡问题。
九、新的问题:长自然语言 Query 怎么处理?
用户实际使用知识库时,并不会总是输入几个关键词。
例如:
请帮我查询订单服务调用库存服务发生超时时,
系统是如何进行重试和补偿的
如果继续直接做字符级 N-gram:
请
帮
我
查
询
订
单
服
务
......
Token 数会快速膨胀。
如果这些 Token 再全部 OR:
Token 越多
↓
查询条件越宽
↓
候选越来越多
↓
Precision 越差
因此需要认识到:
自然语言问题不能直接等价于关键词检索表达式。
十、引入 Query Analyzer
于是 Retriever 前增加:
Query Analyzer
它主要负责:
Normalize
↓
Query 类型判断
↓
实体提取
↓
关键词提取
↓
停用词处理
例如:
请帮我查询订单服务调用库存服务发生超时时,
系统是如何重试和补偿的
经过分析后可能得到:
Entities:
订单服务
库存服务
Keywords:
调用超时
重试
补偿
而:
请
帮我
查询
如何
等低价值表达不再直接参与关键词检索。
这时架构逐渐变成:
Query
↓
Query Analyzer
↓
┌───────────┴───────────┐
↓ ↓
Short Query Long Query
↓ ↓
Exact Phrase Entity Extract
+ Keyword Extract
Strong Token
↓ ↓
└───────────┬───────────┘
↓
Keyword Retrieval
十一、开始出现新的问题:是不是正在自己实现搜索引擎?
到这个阶段,关键词检索模块已经逐渐包含:
Tokenizer
Phrase
Strong Token
Weak Token
Coverage
Query Relaxation
Keyword Score
Ranking
Boost
Fallback
如果继续完善,还可能需要:
中文分词
专业词典
短语搜索
字段权重
模糊匹配
高亮
相关性评分
这时就必须思考一个工程问题:
我们是不是正在自己重新实现一个简化版全文搜索引擎?
继续不断为 N-gram 增加规则当然也能工作,但系统复杂度和维护成本会越来越高。
因此开始重新调研成熟知识库项目的关键词检索实现。
十二、成熟 RAG 系统的共同趋势
调研成熟知识库项目后,可以发现一个非常明显的趋势:
Query
↓
┌──────────┴──────────┐
↓ ↓
Sparse / Keyword Dense Vector
↓ ↓
└──────────┬──────────┘
↓
Fusion
↓
Reranker
关键词检索通常使用:
BM25
全文检索
Elasticsearch
PostgreSQL FTS
Sparse Retrieval
而不是长期依赖:
1-gram
+
2-gram
+
大量 OR
于是项目思路发生了变化。
从:
如何继续把自己的 N-gram 检索修好?
变成:
是否应该把底层词法相关性问题交给专业全文搜索组件?
十三、为什么选择 ParadeDB?
项目原本已经使用:
PostgreSQL
+
pgvector
如果再引入 Elasticsearch,架构会变成:
PostgreSQL
↓
数据同步
↓
Elasticsearch
还需要额外处理:
双写
数据同步
一致性
ES 部署
ES 索引生命周期
对于中小型个人知识库项目而言,这套架构可能偏重。
因此最终考虑:
ParadeDB。
整体仍然围绕 PostgreSQL:
PostgreSQL
│
┌────────┴────────┐
↓ ↓
ParadeDB / BM25 pgvector
Keyword Vector
│ │
└────────┬────────┘
↓
RRF
↓
Reranker
这样能够在不过度增加基础设施复杂度的情况下,引入成熟的 BM25 全文检索能力。
十四、为什么 BM25 比简单 N-gram OR 更适合作为正式关键词检索?
简单 OR 检索更关注:
是否命中某个 Token
而 BM25 会综合考虑:
Term Frequency
Document Frequency
Document Length
可以简单理解为:
一个词在当前文档中越重要,同时在整个知识库中越少见,它的区分能力通常越强。
例如查询:
订单超时
如果整个知识库中大量文档都包含:
订单
那么仅仅命中"订单"的区分度并不高。
而同时包含:
订单
+
超时
并且主要内容都围绕"订单超时排查"的 Chunk,应该拥有明显更高的相关性。
这比:
只要命中任意字符就进入候选
更加适合作为正式搜索系统的相关性排序机制。
十五、Exact Phrase 仍然值得保留
即使引入 BM25,也不意味着所有检索逻辑都应该交给 BM25。
例如:
ERR_CONNECTION_RESET
OrderService
Redis Cluster
订单超时
这些 Query 都具有明显的完整短语特征。
因此比较合理的关键词检索策略仍然是:
Exact Phrase Boost
+
BM25 Retrieval
完整短语匹配作为高权重信号,BM25 负责更加通用的词法相关性排序。
十六、Query Analyzer 也不能因为用了 ParadeDB 就删除
ParadeDB 解决的是:
怎么搜索。
Query Analyzer 解决的是:
应该搜索什么。
例如:
订单服务请求库存服务发生超时时,
为什么会重复扣减库存?
可以经过 Query Analyzer 转换成:
订单服务
库存服务
调用超时
重复扣减
库存
再交给搜索系统。
因此:
Raw Query
↓
Query Rewrite
↓
Query Analyzer
↓
Retrieval Query
这一层仍然非常重要。
十七、最终 Hybrid Retrieval 架构
最终完整检索链路可以设计为:
User Query
↓
Query Rewrite
↓
Query Analyzer
↓
┌───────────┴───────────┐
↓ ↓
Keyword Retrieval Vector Retrieval
↓ ↓
ParadeDB BM25 pgvector
↓ ↓
Keyword TopN Vector TopN
└───────────┬───────────┘
↓
RRF
↓
Reranker
↓
Quality Filter
↓
Final TopK
↓
Knowledge Sufficiency
↓
RAG Answer
十八、为什么需要 RRF?
BM25 和向量搜索采用的是两套完全不同的评分体系。
例如:
BM25 Score = 10.5
向量检索:
Cosine Similarity = 0.84
这两个数字不能简单:
10.5 + 0.84
因为它们没有统一量纲。
因此可以使用:
RRF(Reciprocal Rank Fusion)
RRF 更关注:
结果分别在各个检索通道中的排名
而不是直接比较原始 Score。
例如某个 Chunk:
BM25:Rank 1
Vector:Rank 2
另一个:
BM25:Rank 18
Vector:Rank 15
显然第一条更值得进入后续精排。
十九、为什么 RRF 后还需要 Reranker?
关键词检索和向量检索的主要目标仍然偏向:
Recall。
也就是尽量先找到可能相关的候选。
例如:
BM25 TopN:30
Vector TopN:30
经过 RRF 合并后,仍然可能保留几十条 Candidate。
这些内容不能全部送给 LLM。
因此需要:
Reranker
根据:
Query + Chunk
进行更加精细的相关性判断。
可以简单理解为:
Retriever
→ 找得全
Reranker
→ 排得准
二十、Knowledge Sufficiency 应该放在什么位置?
最开始:
召回数量
↓
知识是否充分
这种判断方式是不合理的。
最终更合理的是:
BM25
\
RRF → Reranker → Knowledge Sufficiency
/
Vector
Knowledge Sufficiency 可以综合:
是否存在有效结果
Rerank Score
有效证据数量
证据是否真正覆盖问题
而不能简单:
候选数量 >= 5
就认定知识充分。
二十一、哪些旧逻辑可以逐渐删除?
引入 BM25 后,以前为了修补 N-gram 而设计的很多复杂逻辑都可以逐渐退出主流程:
1-gram
2-gram
全部 OR
Strong Token
Weak Token
复杂 Coverage 排序
自定义 Keyword Score
Coverage 可以继续作为:
调试指标
但不再承担核心相关性排序职责。
最终代码职责可以更加明确:
QueryAnalyzer
↓
KeywordSearchRepository
↓
ParadeDB BM25
VectorSearchRepository
↓
pgvector
RetrievalService
↓
Parallel Retrieval
FusionService
↓
RRF
Reranker
↓
Precision Ranking
二十二、整个关键词检索架构的版本演进
V0:字符级 N-gram
1-gram
+
2-gram
+
OR
优点:
实现简单
Recall 高
问题:
Precision 较差
V1:分层召回
Exact Phrase
+
2-gram
+
1-gram Fallback
+
Coverage
+
Query Relaxation
能够明显缓解单字误召回问题。
但搜索规则逐渐复杂。
V1.1:Query Analyzer
增加:
Normalize
关键词提取
实体提取
停用词
长短 Query 分流
解决自然语言 Query 直接进行字符级 N-gram 的问题。
V2:BM25 + Vector Hybrid Retrieval
最终:
Query Analyzer
↓
┌───────────────┐
↓ ↓
BM25 Vector
↓ ↓
└───────┬───────┘
↓
RRF
↓
Reranker
↓
Final TopK
底层词法相关性计算交给成熟全文检索能力。
项目自身则更加专注于:
Query Understanding
Hybrid Retrieval
Fusion
Reranking
Knowledge Sufficiency
二十三、这次架构演进真正值得总结的是什么?
最开始并没有为了技术复杂度直接引入搜索引擎,而是采用了简单方案:
N-gram
通过真实检索测试发现:
Precision 问题
于是先进行低成本优化:
Query Relaxation
+
Coverage
继续开发后发现:
长 Query
Phrase
Boost
Ranking
中文分词
让自研检索逻辑越来越复杂。
最终才决定:
引入专业搜索组件
这是一个非常典型的软件架构演进过程:
Simple Solution
↓
Real Problem
↓
Local Optimization
↓
Complexity Growth
↓
Architecture Refactoring
技术选型真正需要考虑的,并不是:
哪种技术看起来更加高级?
而是:
当前问题是否已经复杂到值得交给一个专业组件解决。
结语
知识库检索质量,很大程度上决定了最终 RAG 的回答质量。
如果 Retriever 一开始就召回大量错误内容:
Bad Retrieval
↓
Bad Context
↓
LLM
↓
Bad Answer
模型能力再强,也很难完全弥补错误上下文带来的影响。
因此,一个更加完整的 RAG 系统,需要逐渐从:
能够搜索
发展到:
能够稳定召回真正相关的知识
最终形成:
Query Understanding
↓
Keyword / Sparse Retrieval
+
Vector Retrieval
↓
Fusion
↓
Reranker
↓
Knowledge Sufficiency
↓
Generation
从 N-gram 到 BM25 的这次演进,本质上也是从:
自己实现一个能够工作的关键词查询
逐步走向:
构建一套真正面向 RAG 场景的 Hybrid Retrieval 系统。
而这也是整个关键词检索优化过程中最重要的收获。