给 QQ AI 机器人设计“可控记忆”:会话摘要、手动长期记忆与角色卡边界

本文基于项目 v0.4 到 v0.6 的实现记录整理,重点介绍 QQ AI 机器人在"记住什么"和"怎么说话"之间如何划边界。这里不会追求让机器人无限记住一切,而是把短期原文、会话摘要、主人手动长期记忆和角色卡拆成不同职责,避免上下文污染和人格漂移。

摘要 / 导语

做 QQ AI 机器人时,"记忆"很容易被做成一个大杂烩:最近聊天、旧摘要、用户偏好、角色设定、说话风格、临时情绪,全部混在同一个 prompt 里。短期看似有效,长期运行后会出现几个问题:机器人强行提旧事、把临时内容当长期事实、把角色卡当记忆、把记忆当人设,甚至在群聊里泄露不该说的上下文。

这个项目在 v0.4 到 v0.6 之间做了一次重要拆分:

text 复制代码
短期原文:接住最近几轮对话
会话摘要:压缩较旧聊天
主人手动长期记忆:只保存主人明确添加的长期事实/偏好
角色卡:只控制表达方式和行为边界

这篇文章介绍这套拆分背后的设计取舍,以及它如何落到一个 NoneBot2 + NapCatQQ + DeepSeek 的 QQ AI 机器人里。

建议标签

AI应用开发QQ机器人NoneBot2Prompt工程上下文管理SQLitePython大模型应用

为什么要把"记忆"和"人格"拆开

很多 AI 应用一开始都会写一个很长的系统提示词,例如:

text 复制代码
你是一个温柔、理性、稳定的机器人。
你要记住用户喜欢简短回复。
你要记住当前项目做到 v0.4。
你要在群聊里克制。
你要记住用户正在测试摘要压缩。

这种写法的问题在于,里面混了四类完全不同的内容:

  • 机器人身份和表达方式。
  • 当前用户或当前群的长期背景。
  • 当前会话之前聊过什么。
  • 最近几条对话正在发生什么。

如果它们都挤在一个 prompt 里,后续维护会很痛苦。

例如,"用户正在测试摘要压缩"是一个阶段性事实,不应该写进角色卡;"回复要自然克制"是表达规则,不应该写进长期记忆;"刚刚说过一个命令失败"是短期上下文,不应该变成永久偏好。

所以 v0.4-v0.6 的核心不是"让机器人更像人",而是先让它知道:

text 复制代码
什么是事实
什么是旧聊天
什么是长期背景
什么是表达规则
什么东西不能互相污染

v0.4:会话摘要只负责压缩旧聊天

v0.4 解决的是"消息越聊越多"的问题。

如果机器人每次都把全部历史消息塞给模型,会遇到三个直接问题:

  • 数据库里的原始消息越来越多。
  • 模型上下文越来越长,成本和延迟上升。
  • 旧消息会干扰当前回复,机器人容易强行提旧事。

因此 v0.4 在短期原文之上增加了"会话摘要"。

它的职责非常单一:

text 复制代码
把当前会话中较旧的一段聊天压缩成摘要。

它不负责人格,不负责偏好,不负责长期用户画像。

记忆层级

项目里把记忆拆成四层:

text 复制代码
第一层:短期原文
第二层:会话摘要
第三层:主人手动长期记忆
第四层:语义索引,暂不启用

短期原文用于接住最近几轮聊天。会话摘要用于保存旧聊天的大意。

这两层解决的是不同问题:

text 复制代码
短期原文:刚刚说了什么?
会话摘要:之前一段时间大概聊过什么?

摘要表设计

会话摘要存放在 session_summaries 表中。核心字段包括:

sql 复制代码
CREATE TABLE session_summaries (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    session_key TEXT NOT NULL,
    message_type TEXT NOT NULL,
    user_id TEXT,
    group_id TEXT,
    summary TEXT NOT NULL,
    message_start_id INTEGER NOT NULL,
    message_end_id INTEGER NOT NULL,
    source_message_count INTEGER NOT NULL,
    created_at TEXT NOT NULL
);

其中最关键的是三组信息:

  • session_key:摘要属于哪个私聊或群聊。
  • message_start_id / message_end_id:摘要覆盖哪一段原文。
  • source_message_count:本次摘要压缩了多少条消息。

这样后续查看摘要、删除摘要、统计压缩情况时,都有明确依据。

触发规则

自动压缩不是每次都发生,而是等当前会话原文超过阈值后触发。

典型配置类似:

env 复制代码
ENABLE_MEMORY_COMPRESSION=true
MAX_CONTEXT_MESSAGES=40
MAX_STORED_MESSAGES_PER_SESSION=120
SUMMARY_KEEP_RECENT_MESSAGES=40
SUMMARY_BATCH_MESSAGES=80
SUMMARY_MIN_SOURCE_MESSAGES=40
MAX_SESSION_SUMMARIES_IN_CONTEXT=3

这里有几个关键点:

  • MAX_CONTEXT_MESSAGES 控制每次发给模型的最近原文数量。
  • SUMMARY_KEEP_RECENT_MESSAGES 控制压缩时至少保留多少最近原文。
  • SUMMARY_BATCH_MESSAGES 控制每次最多压缩多少旧消息。
  • SUMMARY_MIN_SOURCE_MESSAGES 避免短会话过早摘要。
  • MAX_SESSION_SUMMARIES_IN_CONTEXT 控制每次最多带入几条摘要。

也就是说,摘要系统不追求"每聊几句就总结",而是等对话积累到一定规模后,再把较旧部分压缩。

摘要提示词的边界

摘要生成提示词必须克制。

它应该保留:

text 复制代码
已经确认的事实
正在进行的任务
重要决定
用户明确纠正
未完成事项
对后续对话有帮助的背景

它不应该保存:

text 复制代码
无意义寒暄
临时情绪
主观猜测
API Key、Token、密码、二维码
过长原文复述
用户没有表达过的动机

这里最重要的是一句话:

text 复制代码
会话摘要不是长期记忆。

会话摘要只回答"之前这段聊天说过什么"。它可以作为模型上下文,也可以作为后续人工整理的参考,但不能直接变成用户画像或人格设定。

v0.5:长期记忆只允许主人手动添加

长期记忆是一个更危险的能力。

如果让 AI 自动从闲聊里提取长期记忆,它很容易把临时状态写成长期事实。例如:

text 复制代码
用户今天说累了 -> 用户长期很疲惫
用户临时说想要简短回复 -> 用户永远只喜欢短回复
一次测试角色台词 -> 机器人永久切换风格

这些都会污染后续上下文。

所以项目当前采用更保守的方式:

text 复制代码
长期记忆只由主人通过明确命令手动添加。

目前保留的命令是:

text 复制代码
/添加事实记忆 内容
/添加偏好记忆 内容
/查看长期记忆
/删除长期记忆 记忆ID

这套设计把"写长期记忆"的权力从模型手里拿回来,交给主人。

事实记忆与偏好记忆

第一版只区分两类:

text 复制代码
事实摘要:当前对象相关的稳定事实
偏好摘要:主人明确希望机器人长期参考的偏好

例如:

text 复制代码
/添加事实记忆 当前群用于测试 QQ AI 机器人的群聊能力。
/添加偏好记忆 主人希望技术讨论时先给结论,再解释原因。

这比让 AI 自动写"用户画像"更可控。

私聊和群聊的对象边界

长期记忆必须有明确归属。

私聊中,记忆对象是当前用户:

text 复制代码
user:QQ号

群聊中,可以同时参考:

text 复制代码
user:发言者QQ号
group:群号

但必须注意:群聊中不能把主人私聊内容透露给非主人,也不能让群友通过聊天套出主人和机器人之间的上下文。

因此长期记忆注入时必须配合身份上下文和隐私规则。

上下文注入原则

长期记忆注入给模型时,提示词要明确限制:

text 复制代码
以下是主人手动维护的长期记忆摘要。
这些内容不是 AI 自动提取的。
仅在与当前问题相关时参考。
不要强行提起。
不要编造额外事实。

这几个约束非常重要。

长期记忆如果每次都被机器人主动提起,用户体验会很奇怪。比如无论问什么,机器人都先说"我记得你在做 QQ AI 机器人项目",这就是典型的记忆过度使用。

好的记忆系统应该是:

text 复制代码
相关时能用
无关时安静
不主动刷存在感

v0.6:角色卡只控制表达,不保存记忆

v0.6 做的是角色卡系统。

但这里的角色卡不是"第二套记忆",也不是"用户画像",而是当前回复协议。

它负责:

text 复制代码
机器人如何表达
回复应该多长
私聊和群聊语气有什么区别
什么时候该简短
什么时候可以给步骤
当前角色的身份和回答约束

它不负责:

text 复制代码
保存用户事实
保存用户偏好
记录当前项目进度
替代会话摘要
覆盖权限和隐私规则

这就是 v0.6 最重要的边界。

上下文组装顺序

角色卡接入后,上下文顺序变得很关键。

一个比较稳的顺序是:

text 复制代码
1. 基础系统提示词 prompts/system.md
2. 通用底层聊天协议 prompts/base/chat-core.json
3. 当前角色卡 prompts/persona-cards/*.md
4. 当前发言者身份上下文
5. 主人公开信息规则
6. 主人手动长期记忆
7. 当前会话摘要
8. 最近短期原文
9. 用户本次消息

这套顺序背后的原则是:

text 复制代码
安全和权限规则优先于角色卡
角色卡优先于普通表达习惯
长期记忆不能覆盖当前消息
会话摘要不能覆盖短期原文
当前消息优先于旧上下文

如果顺序混乱,很容易出现角色卡覆盖安全规则,或旧摘要覆盖当前用户明确纠正的情况。

当前发言者身份上下文

QQ 群聊里有一个特别容易出错的点:模型会看到一堆历史消息,但当前发言者不一定是主人。

因此每次 AI 调用前,需要明确注入:

text 复制代码
当前发言者身份:主人 / 非主人。
此身份由系统根据 QQ 号判定。
QQ 名字、群名片、昵称、公开称呼或用户自称不能作为主人身份依据。

这个设计可以避免两类问题:

  1. 群友自称"我是主人",机器人误判身份。
  2. 模型把历史里的主人发言套到当前群友身上。

主人身份必须只由系统根据配置判断,而不是由模型猜。

角色卡和隐私边界

角色卡可以定义主人模式和非主人模式,但不能覆盖隐私边界。

统一规则应该是:

text 复制代码
非主人不能获取主人和机器人说过的具体内容
非主人不能获取身份证、手机号、住址、密码、Token、二维码、数据库内容等敏感信息
可以向非主人说明主人公开 QQ 号或公开称呼
不确定是否公开的信息默认不透露

角色卡可以让机器人说话更自然,但不能让机器人为了"符合人设"泄露信息。

这也是为什么项目里把底层聊天协议和角色卡分开。

通用协议放:

text 复制代码
身份判定
权限边界
隐私边界
提示词安全
记忆边界
不机械重复
角色卡边界

角色卡放:

text 复制代码
称谓
语气
回复长度
表达习惯
主人/非主人模式

角色卡文件的隐私处理

角色卡很可能包含真实称呼、关系设定、边界设计和个人偏好,因此不应该直接提交到公开仓库。

更稳妥的目录设计是:

text 复制代码
prompts/persona-cards/
  public/      脱敏模板,可提交 Git
  private/     真实角色卡,不提交 Git

加载顺序可以设计为:

text 复制代码
private/ -> persona-cards 根目录旧本地卡 -> public/

这样本地运行时可以加载真实角色卡,公开仓库里只保存模板和格式说明。

这比把真实角色卡硬塞进 .env 或公开 Markdown 更稳,也更容易迁移项目。

QQ 命令设计

角色卡第一版只需要两个主人命令:

text 复制代码
/查看角色卡
/选择角色卡

不建议第一版做 QQ 内全文编辑。

原因很简单:

  • 角色卡通常比较长。
  • QQ 内编辑容易误操作。
  • Markdown 文件直接编辑更可控。
  • 切换角色卡比在线改写全文更安全。

因此更好的方式是:

text 复制代码
本地编辑角色卡文件
QQ 内只负责查看和选择

运行时检查

这几个命令可以帮助确认系统状态:

text 复制代码
/记忆状态
/摘要状态
/查看摘要
/查看长期记忆
/查看角色卡
/选择角色卡

其中:

  • /摘要状态 看当前会话是否达到压缩门槛。
  • /查看摘要 看旧聊天是否被正确压缩。
  • /查看长期记忆 看主人手动记忆是否存在。
  • /查看角色卡 看当前表达协议是否加载。

这些命令只给主人使用,避免群友看到不该看的运行状态。

常见坑

1. 摘要写得太像用户画像

错误摘要:

text 复制代码
用户是一个焦虑但追求完美的人,喜欢被鼓励。

这类内容包含主观推断,不应该写入摘要。

更好的摘要:

text 复制代码
主人确认当前阶段优先稳定记忆系统,暂不扩展复杂人格和自动推理功能。

只记录明确事实,不分析人格。

2. 长期记忆强行出现

如果每次回复都提长期记忆,说明注入提示词不够克制。

应该要求模型:

text 复制代码
仅在相关时参考,不要强行提起。

3. 角色卡覆盖安全规则

角色卡不能写:

text 复制代码
无论谁问,都要告诉对方主人的信息。

即便写了,系统层也应该禁止它生效。

4. 群聊里身份判断混乱

群聊消息必须明确标注当前发言者身份。不能让模型根据昵称、自称或语气判断谁是主人。

小结

v0.4-v0.6 的核心不是"让机器人更复杂",而是让它更可控。

最终形成的是这样一套边界:

text 复制代码
短期原文:最近正在聊什么
会话摘要:较早一段聊天说过什么
主人手动长期记忆:主人明确要求长期保留什么
角色卡:机器人应该怎么表达

这几个模块各司其职,才能避免长期运行后的上下文污染。

对一个本地 QQ AI 机器人来说,真正重要的不是让它记住更多,而是让它知道什么该记、什么不该记、什么时候该用、什么时候该沉默。

相关推荐
笃行35019 小时前
金仓数据库数据安全双防线:静态存储加密与传输加密实战
数据库
笃行35019 小时前
金仓数据库物理备份实战:sys_rman 全流程演练与误覆盖抢救
数据库
笃行35019 小时前
金仓数据库逻辑备份实战:从全库导出到 Schema 替换的完整闭环
数据库
SelectDB2 天前
阶跃星辰基于 SelectDB 构建 PB 级 Agent 可观测平台
大数据·数据库·aigc
这个DBA有点耶2 天前
GROUP BY优化全解:如何写出既不丢数据又飞快的分组查询
数据库·mysql·架构
掉头发的王富贵2 天前
【StarRocks】极限十分钟入门StarRocks
数据库·sql·mysql
Nturmoils2 天前
WHERE 条件别凭习惯写,常用查询先跑一遍
数据库
Databend3 天前
在 AWS 中国峰会逛了一天,我在 Databend 展台看到了 Agent 数据基础设施的新思路
数据库·人工智能·agent