AI / Agent

提问找论坛旧帖的向量检索,换模型换文本都没能提高召回

2022 字约 7 分钟

摘要QQ 群里有人提问,机器人用向量检索去论坛里找已有的回答。试了换帖子文本、换查询、换阈值规则和三个新的嵌入模型,召回都没有提高,榜单上更强的模型在又短又口语的提问上反而不如原来的 bge-m3。

我的论坛依托一个 QQ 群,机器人有一个“猜你想问”的功能。群里有人提了个问题,如果论坛里已经有帖子回答过,机器人就引用他那条消息,回一句带帖子链接的话。它分三步,先在本地判断这句话像不像提问,再用向量检索从论坛的帖子里找出几个最相似的候选,最后交给大模型确认候选真的能回答这个问题。

上线之后,我让助手看了看它的触发记录,效果并不好,上线以来只触发了两次。除了其中有一个后台任务的故障,另一个原因是,向量检索这一步漏掉的太多。于是接下来的大半天,我们把这一步反复折腾了一遍。

先把评测搭起来

要改进,先得有一把尺子。之前只有 21 条助手手工标过的提问,样本太小。助手把能找到的数据做成了两份,一份是前几天的群消息,一份是另一段时间的,每条提问都标上了论坛里有没有帖子能回答它。两份合起来,有 41 个“论坛里确实有帖子能回答”的提问。

什么是召回

在这个场景里,召回指的是“论坛里确实有帖子能回答的提问,向量检索把那个帖子找出来了多少”。找不出来,后面的大模型再准也没用。所以召回是整条链路的上限。

当时线上用的相似度门槛是 0.7,这个数是当天下午刚定的。原来的 0.65 是论坛只有 144 个帖子时定的,后来帖子多了,助手用 802 条提问和 962 个有回复的帖子逐条看过,0.65 到 0.7 之间多出来的候选几乎全是闲聊。调到 0.7 之后,一天约 22 条提问会有候选。

当时设计文档里还写着一句,“0.7 时能回答的提问差不多都在里面”。这句话后来被事实推翻了,在 41 条里,0.7 的门槛只找到 19 条,不到一半。

换文本,换查询,都没用

既然漏掉的多,第一反应是向量检索的输入可以改进。助手把能想到的几种改法都在这份评测集上跑了一遍,这些都不需要调用大模型,没有花额度。

试了什么结果
帖子开头取 50、100、200、400 个字结果完全一样,因为帖子开头的长度中位数只有 21 个字
帖子文本再加上第一条回复变差
只用标题,或者标题和开头混合同样的负载下没有提高
查询时带上被引用的消息、提问人前两句话、上一句没有提高,要处理的提问反而变多
改成相对阈值(比如标准分,或者减去前几名的均值)明显变差
第一名比第二名高很多时,放低门槛多找到 1 到 2 个,在误差范围内,不值得加这套规则

漏掉的帖子里有一个很说明问题的现象,大约一半其实已经排在了第一名,只是分数落在 0.63 到 0.69 之间,没有过 0.7 的线。也就是说,排序本身没有错,是分数整体偏低。能动的只有阈值。

阈值召回每天送去大模型确认的提问
0.70(当时)19/4123
0.6823/4132
0.6626/4148
0.6431/4170

把阈值降下来,值不值

降阈值的风险是推错,所以不能只看召回。助手先从 0.66 到 0.70 这一段抽了 15 个提问,交给真实的大模型确认,结果选中 4 个,全部正确,没有推错,也没漏掉。这次只花了不到 2 个积分。

15 个样本太少,定不了阈值。于是把 0.64 到 0.70 整段的 233 个提问全跑了一遍,花了约 30 个积分。

最像的帖子的分数提问数推对推错能答但没推
0.68 到 0.7043211
0.66 到 0.6879160
0.64 到 0.66111243
合计2335114

推错的 11 个,助手逐个看过,有 6 个明显错了,主要是两类,一类是对话里接话、反问,被当成了提问,另一类是帖子话题相近,但问的不是同一件事。其余 5 个说得过去,只是我标注时判得严。

就算把这 5 个都算作推对,降到 0.64,五天里也只是多推对 5 到 10 条,同时多推错约 6 条。降阈值不划算,门槛维持 0.7。

换一个榜单上更强的嵌入模型

向量这一侧还剩一条路,换嵌入模型。我当时用的是 bge-m3,跑在本机的 Ollama 上。助手去查了现在 Ollama 上中文效果最强的几个,选了 qwen3-embedding 的 0.6b 和 4b,以及谷歌新出的 270m 小模型 embeddinggemma。我这台机器的 Ollama 是在 CPU 上跑的,8b 的版本要 4.7GB 内存,在测过 4b 已经不如 bge-m3 之后,就没有再测。

比法是,在送去大模型确认的提问数相同的前提下,各自能找到几个能回答的。下面的数字是五天、1415 条提问,补标注之后能回答的提问共 42 条,所以前面表格里的 41 在这里变成了 42。

模型每天约 24 次约 32 次约 48 次
bge-m3(当时在用)21/4223/4226/42
qwen3 4b 加英文指令182123
qwen3 4b161822
qwen3 0.6b 加英文指令182021
qwen3 0.6b171819
embeddinggemma 270m141622

速度上,qwen3 4b 单条查询约 240 毫秒,给 992 个帖子建索引约 4.5 分钟,0.6b 约 70 毫秒和 1.5 分钟,embeddinggemma 约 33 毫秒和 40 秒,bge-m3 建索引是 58 秒。慢一点本身不是问题,问题是它们找得更少。

新模型找到的、之前没标过的 198 对帖子,助手都补标了,其中只有 7 对真的能回答。所以比较是公平的。结果是,在这份数据上,本机能跑的几个新模型都比 bge-m3 差。

通俗比喻:擅长写论文的人,不一定听得懂菜市场的讨价还价

嵌入模型的榜单大多用长文本检索来打分,qwen3 在这类任务上优势明显。可是我这里要比的是,一句又短又口语的提问,对一个帖子开头二十几个字的标题。这像把两个擅长写长篇论文的人,丢进菜市场听讨价还价,他们的优势并不一定能发挥出来。

群里的提问充满了黑话和缩写,帖子的开头也很短。榜单上的优势主要来自长文本,到了这种短句和短句之间比相似度,就看不出来了。

向量这条路基本走完了

这半天里,换了帖子文本、换了查询、换了阈值规则、换了嵌入模型,没有一个能明显提高召回。所以向量检索这一侧我决定不再折腾,保持 bge-m3 加 0.7 的门槛,在设计文档里记下了试过哪些、为什么没换。

剩下的问题,不是找不出,而是候选里推错的多。这需要在大模型确认的那一步做文章,那是下一件事。

适用边界

这些结论来自我自己的群聊数据,嵌入模型也只在一台用 CPU 跑 Ollama 的机器上测过。标签是助手逐条判断标注的,不是多人盲标。召回的上限也只对这 41 到 42 个样本有意义,样本不大。如果换成更大的语料,或者能运行 8b 的模型,结论不一定一样。

评论