fastgpt 社区版探究:mongo db 全文检索算法探秘
近几日在研究fast gpt 的源码,对于它的混合检索比较好奇:全文检索是怎么实现的?向量检索呢?
看了源码才了解到,实际上fastgpt 支持三种向量库,一是minlvs , 二是 pgsql vector, 三是oceanbase(阿里的)。
向量检索,以pgvector为例简单说一下,就是在pg库里加载了pgvector插件,在postgresql 库中创建了 modeldata 的表,其中有字段名为 vector,类型也是vector,就存储切块后的内容的向量值。是1536维的,据说在精准度和计算量之间达到了一个比较好的平衡。
建立向量索引,以及搜索算法,都是pgvector所支持的,不再细说。
令我好奇的是,其全文检索是基于mongo db内置的来实现的。从源码来看,用到了jieba 分词,将句子分此后,用空格连接,以便兼容mongo db的算法(按照英文习惯,单词之间用空格分隔)。这说明实际上原始内容存储到mongo db的数据库字段里,也应该是经过分词后,将每个词之间采用空格分开的。
那么,接下来我的疑问便是,其内部究竟是怎么实现的。为此我求助了腾讯元宝的DeepSeek深度搜索,提示词如下:
假如在 mongo db 中存储了文章的内容,并对内容字段建立了全文检索的索引,那么加入搜索一个经过中文分词后的句子“中国 汽车 品牌 世界 最强”,在mongo db 内部是怎么进行检索并对检索结果进行打分的?假如有个记录的内容是 “中国的汽车品牌 是世界上最强的没有之一” ,另一个是“日本汽车比不上中国汽车”, 它们各自的得分会是多少?这个得分是按照内容包含了相应的关键字就得分呢?还是关键字在其中出现次数多也有助于高的得分呢?还是怎么样的?
以下便是值得记录的它的回答:
MongoDB 的全文检索基于 倒排索引 和 TF-IDF 算法 进行匹配和打分。以下是针对你问题的详细分析:
一、检索过程解析
1. 索引构建
-
对文章内容字段建立全文索引后,MongoDB 会:
-
分词:将文本拆分为词元(如
["中国", "汽车", "品牌", "世界", "最强"]
)。 -
构建倒排索引:记录每个词元出现的文档位置及频率。
"中国" → [文档1_ID, 文档2_ID] "汽车" → [文档1_ID, 文档2_ID] "品牌" → [文档1_ID] "世界" → [文档1_ID] "最强" → [文档1_ID]
-
2. 查询处理
-
查询
中国 汽车 品牌 世界 最强
被拆分为独立词元。 -
MongoDB 执行 OR 逻辑:匹配包含任意查询词元的文档(除非使用
""
指定短语)。
二、相关性打分机制
得分由 TF-IDF 算法 计算,公式简化如下:
Score = TF(词频) × IDF(逆文档频率)
1. TF (词频)
-
词在文档中出现的次数越高,得分越高。
-
示例:
-
文档1:
"中国的汽车品牌是世界上最强的没有之一"
-
中国:1
,汽车:1
,品牌:1
,世界:1
,最强:1
-
-
文档2:
"日本汽车比不上中国汽车"
-
中国:1
,汽车:2
(其他词未出现)
-
-
2. IDF (逆文档频率)
-
词在整个集合中的稀有程度。词越常见,IDF 越低。
-
公式:
IDF = log(总文档数 / 包含该词的文档数)
-
假设集合中有 1000 篇文档:
-
汽车
出现在 800 篇 → IDF = log(1000/800) ≈ 0.1 -
最强
出现在 10 篇 → IDF = log(1000/10) ≈ 2.0
-
3. 最终得分计算
-
文档1:
Score = (TF_中国 × IDF_中国) + (TF_汽车 × IDF_汽车) + ... ≈ (1×0.2) + (1×0.1) + (1×1.5) + (1×0.3) + (1×2.0) = 4.1
-
文档2:
Score = (TF_中国 × IDF_中国) + (TF_汽车 × IDF_汽车) ≈ (1×0.2) + (2×0.1) = 0.4
✅ 结论:文档1 得分远高于文档2,因为它包含更多查询词元且包含高 IDF 词(如“最强”)。
三、其他影响因素
-
词元位置与权重:
-
标题中的词可能比正文权重更高(取决于索引配置)。
-
-
短语匹配:
-
若查询用引号包裹
"中国汽车"
,连续出现的短语会获得额外加分。
-
-
停用词过滤:
-
“的”、“是”等停用词会被忽略,不计入打分。
-
-
词干提取:
-
英文中“running”会被归并为“run”,中文需依赖分词工具(如你的代码中的
jiebaSplit
)。
-
四、你的示例结果对比
文档内容 | 匹配词元 | TF-IDF 得分 | 原因分析 |
---|---|---|---|
中国的汽车品牌是世界上最强的没有之一 | 中国、汽车、品牌、世界、最强 | 较高 (如4.1) | 命中全部查询词元,且包含高 IDF 词(如“最强”) |
日本汽车比不上中国汽车 | 中国、汽车 | 较低 (如0.4) | 仅命中2个词元,且“汽车”因常见导致 IDF 低 |
关键结论
-
命中词元数量和词元的稀有度(IDF)是得分的核心因素。
-
代码中的
score: { $meta: 'textScore' }
正是获取这个 TF-IDF 分数。 -
中文场景需依赖分词工具(如你的
jiebaSplit
)预处理查询词,否则 MongoDB 默认按空格分词(对中文不友好)。
ok,本篇结束。感谢AI 的回复。