AI / Agent

机器人推错了一条帖子之后,我们做了评测集并加上 rerank

2752 字约 10 分钟

摘要群里的“猜你想问”把一句接话当成了提问,还借了候选帖子的标题来补主语。为此先看了真实推过的十条,再搭起标注评测集,最后改成向量加 rerank 筛候选、大模型分两步判断,推对从 20 个增加到 37 个,推错从 19 个减到 9 个。

在另一篇文章里,我记录了对“猜你想问”向量检索的一轮实验,结论是这一步已经榨干了,我决定不再动它。剩下的问题,在于候选里推错的偏多。我先在提示词上下了功夫,让大模型回答之前,先把提问人想知道什么用一句话写出来,在五天、349 条有候选的提问上,推错从 29 个降到了 15 个,推对没有减少。我以为这件事就算告一段落了。

直到有一天,群里有人看到机器人回了一条莫名其妙的话,大家都很疑惑。

一句接话,被当成了提问

事情是这样的。大家在聊一个会议,有人回了一句“不是已经完了吗”,指的是那个会议。机器人却在这句话后面,推荐了论坛里一个叫“现在保研都结束了吗”的帖子。

助手把这一轮重新查了一遍,发现三层都有责任。

  1. 本地挑提问这一步没拦住。这句话有 7 个字,带“吗”,就被当成了提问。
  2. 向量相似度刚好过线。这句话几乎没有实际内容,和那个帖子的相似度是 0.709,刚好比 0.7 的门槛高一点点,整个论坛里只有这一个帖子过线。
  3. 大模型确认那一步是主要问题。助手用当时的前文和这个帖子,把这一步重放了 5 次,有 2 次推荐了。推荐的那两次,它写的“提问人想知道什么”都是“保研是不是已经结束了”。前文聊的明明是会议,它却从候选帖子的标题里借了“保研”当主语,把一句接话补成了帖子正好能回答的问题。

我之前加的那一步,让它先写出提问人想知道什么,恰好把这个毛病埋进去了。写这句话的时候,它已经看到了候选帖子,所以会被帖子带偏。

助手先在提示词里加了两条,主语只能从提问和前面的消息里找,不能从帖子里借,并补了一个反问附和的例子。同一条输入重放 8 次,7 次判成“不是在问”,1 次照样借了主语。有用,但治不了根。

先从理论上比较,再决定要不要实验

这时有两条路。一条是继续改提示词,另一条是把大模型的判断拆成两步,第一步只给提问和前文,不给帖子,让它写出提问人想知道什么;第二步再拿这句话去对候选帖子。我没有直接让助手动手,而是让它先从理论上比较一遍,再决定要不要做实验。

只改提示词分两步问
效果这一条从 5 次推错 2 次降到 8 次 1 次,但机制没变,内容越空的句子越容易被帖子带偏写“想知道什么”时根本看不到帖子,借主语从机制上就不会发生
副作用新加的规则可能把真在问的反问也判成“不是在问”有些要看了帖子才懂的提问会被漏掉,但这类现在推出去本来也是在猜
成本多几十个 token有候选时多一次调用,每轮平均和现在差不多
延迟不变有提问时回复晚 40 秒左右

助手倾向分两步。推错的话,全群都看得见,漏推却没人注意到,所以这个功能应该宁可少推。

先看真实推过的十条

在花钱做实验之前,助手建议先做一件很便宜的事,把上线以来实际推出去的 10 条逐条看一遍,不调用大模型,看推错了几条,各是什么原因。

结果是 5 条推得对,2 条说得过去,3 条推错。而推错的 3 条里,只有今天这一条是借主语。

另外两条的毛病完全不同。一条问的是提前进组能不能报销租房,帖子讲的是补贴和劳务费,没有提到租房。另一条问的是下一届现在的套磁进度,帖子是别人在说自己的情况。这两条都是话题相近,具体问的不是同一件事。提示词里其实写了“只是话题相近的不算”,可模型没有守住。

这个结果推翻了助手原来的预期。它之前说“如果推错的大多是借主语,就直接分两步”,现在借主语只占三分之一,光分两步解决不了另外那两类。

于是它补了一条要求。每推荐一个帖子,大模型都要说出是哪一层楼回答了那个问题,插件去核对这一层确实存在,说不出来就不推。把那两条回头看,前一条的帖子里没有任何一层提到租房,后一条没有任何一层在说下一届,按这个要求都推不出去。这比“只是话题相近的不算”这样的抽象规矩好执行得多。

搭一份评测集

10 条样本不够用来比较方案,我让助手去做一份真正的评测集,而且要留下来,以后调整时直接用。

数据是 1528 条提问,来自两段时间。它们都是本地筛出来的、论坛里有相似度 0.6 以上候选的提问,每条都标了是不是真的在提问,以及它和每个候选之间的关系。

标注含义
Y帖子问的就是这件事,提问时已经有楼层回答了
P说得过去,能帮上一些
N不能,或者这条根本不是在问

一段时间用来调参数,另一段用来验证,不能照着验证集去改。

标注时要小心一个陷阱,帖子后来的楼层不能被提问时的判断看到。助手后来发现,评测时给模型看的帖子,混进了提问之后才有的楼层,这等于偷看了答案,于是把评测改成只给提问发生那一刻已经存在的楼层,并因此重新标注了 9 对。

这套评测还有几个现实限制。大模型开着思考时,同一个提问两次的回答会不一样,差两三个算不上好坏。评测里不模拟“每个群 10 分钟最多回 2 条、同一个帖子 6 小时内不重复推”这些限流规则。所有标签都是助手逐条判断标注的,不是多人盲标。

向量分得出话题,分不出是不是同一件事

有了评测集,助手开始尝试各种变体,实验只允许一个并发。一个重要的发现来自候选筛选这一步。

向量检索擅长判断“话题相近”,却分不清“同一个问题”和“只是话题相近”,而大模型推错的主要原因正是后者。助手引入了 rerank 模型,它把提问和每个帖子放在一起细读,再给出一个相关度分数。

通俗比喻:海选和面试

向量检索像看简历筛人,快,但只能看出“都是做算法的”。rerank 像面试官把应聘者和岗位放在一起细聊,慢一点,但能看出这个人到底合不合适。

所以先用简历筛出十来个,再请面试官看一遍,最后才轮到最贵的大模型来拍板。

在评测集的调参部分,用“这个提问和这个帖子是不是能回答”这个标准,对所有候选对打分,向量相似度的区分度(AUC)约为 0.72,rerank 约为 0.87。这是当时还没标完时的中间结果,但差距够大。

rerank 的门槛也扫了一遍。

rerank 门槛推的次数推对
0.52815
0.33317
0.23817

门槛再低,推对的数量不再增加,推错的却多了,所以选 0.3。

开思考、换更强的模型,都没推得更好

另外两个尝试是开思考,以及把第二步换成更强的模型,价格是三倍。它们都没有推得更好,却慢得多、贵得多。开思考时,一次要 20 秒以上,不开思考只要 1 到 3 秒。

我还问过,rerank 模型能不能放在本机跑。助手查了资料,Ollama 当时还没有 rerank 的接口,要另外装 llama.cpp 的服务,还要自己做分数换算,每周能省的积分不到 50。我看完觉得太麻烦,决定直接用接口提供的,一天的消耗本来就很少。

最后的结果

最终的流程是,向量相似度门槛从 0.7 降到 0.6,最多取 10 个候选,再用 rerank 打分,保留至少 0.3 分的前 3 个,然后分两步问大模型,两步都不开思考,第二步还要说出回答问题的楼层,楼层必须确实存在。

用同一份评测集,把两段时间加起来,对比原来的做法。

推的次数能回答说得过去推错
原来(开思考,一轮一次判断)55201619
现在(rerank 加分两步)7437289

在有帖子能回答的 56 个提问里,原来推对了 20 个,现在 37 个。推错的主要来源,原来是把接话、玩笑当成提问,或者把话题相近的帖子当成答案;第二步一起判断时,又常把好的帖子也否掉,所以现在改成一个一个帖子判断。

成本也很低。一天约有 60 条提问有候选,大模型每天五六万 token,约 6 个积分。rerank 每次 0.05 个积分,一天约 140 次,约 7 个积分。回复在提问后一分钟那一轮里,几秒内发出。

还没有验证的部分

这套改动部署在当天夜里,之后群里没有新的提问,所以新流程还没有在线上真正跑过。评测集的结论来自历史数据,真实效果还要看接下来几天的日志和群里的反应。

排查时还顺带发现一个小问题,同一个帖子很可能在不到 6 小时里被推了两次。“6 小时内不重复推”的记录只存在内存里,中间插件重新加载过一次,记录就清空了。这个我暂时没改。

适用边界

评测集的标签是人工判断的,数据来自同一个群的两段时间。上面的数字说明新做法在这份数据上明显更好,不能说明它在别的群或者别的话题下也一样。

评论