引言:为什么要重新造轮子?¶
核心痛点:为什么现在的机器人像个“复读机”?
最近我想搭建一个QQ机器人来活跃个人群的气氛。试过许多市面上开源的框架,效果大多差强人意。它们普遍存在以下问题:
- 说话不自然:机械感重,缺乏“人味儿”。
- 句式匮乏:回复总是采用相似的模版。
- 过度拟合:过分依赖聊天记录中已有的表达,容易被群友带偏,变成无意义的“捧哏”。
- 缺乏上下文:无法理解话题的来龙去脉,只能对最新的一句话做反应。
我认为,产生这种状况的根本原因在于缺乏多轮对话的引导机制。
大模型接收的文本往往局限于截取的一小段聊天记录,而真实群聊中,一个话题的跨度可能远超截取范围。AI 既不知道话题从何而起,也容易受到冗长、没头没尾且充满噪声的聊天记录影响。在这种环境下,AI 很容易“学坏”,模仿群友的破碎语言。即使你写了 Prompt 要求“不要复制已有的发言”,在庞大的上下文噪声面前,这条规则也很容易失效。
架构思路:怎么做?¶
为了解决上述问题,我设想了一个分层架构,将机器人的思维过程解耦。机器人的大脑被分为三层:上下文处理层、兴趣度分析层、回答层。
注:本文仅讨论机器人程序的内部逻辑架构,不涉及协议端(如如何 hook QQ 消息)的讨论。
1. 上下文处理层(The Context Layer)¶
职责:信息的清洗与归档。
群聊是多线程并发的,一个群内可能同时穿插着多个话题。
- 话题分类与队列化:该层负责将杂乱的消息流按“话题”进行分类,将属于同一话题的消息塞入对应的话题队列。
- 记忆压缩(Summary):为了控制 Token 消耗和队列长度,当某话题队列达到阈值时,调用 LLM 对旧消息进行总结(Summarize)。清空原队列,仅保留“总结”作为新的队首,后续消息继续入队。这相当于让机器人拥有了“长期记忆”。
2. 兴趣度分析层(The Interest Layer)¶
职责:注意力的分配。
- 计算兴趣度(KoI):在回答层工作结束后,该层对当前所有活跃的话题队列进行分析,计算出一个 0 到 1 的兴趣度(Interest Score)。这个分数决定了机器人“想不想插嘴”。
- 动态反馈机制:
- 热度反馈:话题更新越频繁,兴趣度越高(避免挖坟)。
- 正向/负向激励:接收来自第三层的反馈。如果机器人上次参与了话题且反响不错,下次对该类话题的兴趣度会乘以一个 >1 的系数;反之则乘以 <1 的系数。
3. 回答层(The Response Layer)¶
职责:决策与输出。
- 行动判断:根据第二层提供的兴趣度分布,提取兴趣度最高的话题。让 LLM 基于当前群内氛围(Atmosphere Analysis)做最终判断:是抖个机灵,还是保持沉默?
- 氛围感知:具体的兴趣度数值只是参考,LLM 需要结合上下文判断是否“适宜”。例如大家在争论严肃问题时,即使触发关键词,机器人也应选择闭嘴。
- 输出与闭环:生成的回复不仅发给用户,也要回传给第一层,作为上下文的一部分,防止机器人“忘记”自己说过的话。
项目具体实现细节¶
一、 上下文处理层:基于 MoE 的话题路由¶
1. 存储设计:二级记忆架构¶
为了兼顾性能与持久化,我们采用类似 Redis + SQL 的二级存储策略:
- 热数据(内存):最新的消息以字典形式存放,通过
Message_ID快速索引。 - 冷数据(数据库):旧消息和历史记录存入数据库,作为持久化备份。
- 数据结构:在话题队列中,仅存储
Message_ID的引用,实际内容通过 ID 查询,减少内存开销。
2. 多轮对话与话题路由¶
引入 LangChain 来管理多轮对话逻辑,并采用 MoE (Mixture of Experts, 混合专家) 思想处理消息分类:
- 专家链条:为每个活跃的话题队列建立一个独立的分析链(Agent)。
- 置信度评估:每当新消息到来,所有 Agent 同时分析该消息属于自己话题的置信度。
- 路由冲突解决:
- 若两个队列都认为消息属于自己,选择置信度高的。
- 若置信度相同,优先归入序号靠前(创建较早或活跃度高)的队列。
3. 多群管理与进程隔离¶
- 进程模型:每个群聊分配独立的监管进程,建立
Message_Queue_Set <-> Group_ID的映射。 - 资源限制:为了防止算力发散,单群限制同时存在最多 3个 活跃话题队列。
- 替换策略:若已满3个,新话题触发时,强制替换掉当前更新频率最低(最冷门)的旧队列。
4. 队列生命周期管理(Lifecycle)¶
自动销毁(TTL):若一个队列经过 15~20 条其他消息的更新周期后,仍无新消息入队,视为话题终结,销毁队列。
滚动总结:当队列长度超过 10~12 条时,触发 LLM 总结任务。
* Input:当前队列所有消息。
* Output:一段精简的剧情摘要。
* Action:[摘要] + [新消息] 组成新队列。
有效性标记:将队列分为“有总结”和“无总结”两类。后续层级仅处理“有总结”的队列,确保机器人发言时掌握了足够的前置信息,避免“半瓶子醋”。
二、 兴趣度分析层:基于关键词池的动态评分¶
1. 文本级分析¶
为了降低 API 调用成本,本层主要依赖关键词匹配进行初步筛选,而非每次都调用大模型。
2. 动态兴趣池 (KoI_Pool) 算法¶
我设计了一套动态演化的兴趣算法,让机器人不仅有“出厂设置”,还能“紧跟时事”:
- 初始化:读取
config,将预设关键词(如“游戏”、“编程”、“猫娘”)加入KoI_Pool,基础得分设为 1。 - 趋势感知:启动一个后台监管进程,每隔 10~20 分钟采样数据库中的近期群消息。调用 LLM 提取高频新词(如最近流行的“幻兽帕鲁”),将其加入 Pool,并赋予更高的得分 3。
-
目的:这让机器人更倾向于参与当前群内最火爆的讨论。
-
得分计算:
Raw_Interest = ∑(匹配到的关键词得分)。
3. 概率归一化¶
在所有队列计算完原始得分后,使用 Softmax 函数 将分数转化为概率分布。这能拉大“热门话题”与“冷门话题”的差距,作为最终参数传递给第三层。
三、 回答层:拟人化的决策机制¶
1. 沉默是金¶
当所有话题的计算兴趣度均为 0(即全是无关水贴),该层直接休眠,回到第一层监听循环。这避免了机器人无意义的刷屏。
2. 决策逻辑 (The Decision)¶
LLM 接收兴趣度最高的话题队列作为 Context,进行最后一步判断。这里 Prompt 的设计至关重要:
- 引导原则:Prompt 中必须包含“优先选择不参与”的指令,强迫 LLM 寻找“必须发言”的理由(如被 @,或者是它极度感兴趣的领域)。
- 氛围检测:LLM 需分析话题的情感色彩。如果是争吵、悲伤的话题,即使兴趣度高,也强制
return False。
3. 闭环反馈 (Feedback Loop)¶
拒绝参与:若 LLM 决定沉默,该话题在下一轮的兴趣度计算中,会乘以一个降权系数(如 0.8),使其快速冷却。
主动参与:
* 正向反馈:若 LLM 决定发言,反馈给第一层,提高该话题队列 Agent 的置信度阈值(更容易捕获后续消息)。
* 兴趣粘性:该话题在第二层的兴趣度乘以增权系数(如 1.5),模拟人类“聊得正嗨”的状态,提高延续对话的可能性。