资讯动态

AI对话机器人记忆系统设计:会话摘要、长期记忆与角色边界实践

发布时间:2026/8/8 13:51:00 来源:尧图企业网站定制
1. 项目概述从“健忘”到“可控”AI对话机器人的记忆革命最近在折腾QQ上的AI聊天机器人发现一个挺普遍但又很核心的问题它记性太差了。你跟它聊了十几轮转头问它“我们刚才说到哪了”它很可能给你一个驴唇不对马嘴的回答或者干脆说“我不记得之前的对话了”。这种“金鱼式”的七秒记忆严重破坏了长对话的连贯性和深度也让角色扮演、长期陪伴这类高级玩法成了空中楼阁。市面上很多方案要么是简单粗暴地把所有历史对话都塞给模型导致成本飙升、上下文窗口爆炸要么就是完全无记忆每次对话都像初次见面。所以我给自己定了个目标给我的QQ AI机器人设计一套“可控的记忆系统”。这个“可控”是核心它意味着记忆的存储、读取和遗忘不完全由模型的黑箱决定而是能被我或者说被用户在一定程度上管理和引导。这套系统主要围绕三个核心模块来构建会话摘要、手动长期记忆和角色卡边界。简单来说就是让机器人能自动提炼聊天重点并暂存允许用户手动标记重要信息形成长期记忆同时为它扮演的“角色”设定清晰的行为与记忆边界防止“人设崩塌”或记忆污染。这不仅仅是技术实现更是一种对AI交互体验的设计思考。2. 核心设计思路为什么是“摘要”、“手动”与“边界”在动手敲代码之前我花了大量时间思考架构。为什么是这三个模块它们分别解决了什么问题背后的逻辑是什么2.1 会话摘要对抗“上下文窗口焦虑”的利器最直接的痛点就是上下文长度限制。无论是GPT-3.5的16K还是GPT-4的128K甚至是某些开源模型的200K在理论上无限的对话面前都是杯水车薪。全部历史对话都作为上下文Full Context输入不仅token费用或计算成本高昂更关键的是模型的有效注意力会随着上下文增长而稀释导致它可能抓不住最早但最关键的信息。会话摘要Conversation Summary就是为了解决这个问题。它的核心思想不是记住每一句话而是记住“聊了什么”。在对话进行到一定轮次比如10轮或触发某个节点如话题明显转换时系统会自动调用LLM对刚刚发生的一段对话进行总结生成一段精炼的、包含核心事实、用户意图和机器人回应的文本。这段摘要将作为后续对话的“背景信息”或“前情提要”被送入模型的上下文。设计考量触发机制是定时每N轮还是定性检测到话题切换我选择了混合策略。默认每8-10轮生成一次摘要同时利用一个简单的关键词或语义相似度检测如果当前用户query与上几轮的核心话题差异过大则立即触发摘要固化上一个话题的讨论结果。摘要的粒度摘要不是会议纪要不需要面面俱到。我提示LLM聚焦于用户表达了什么核心诉求或提供了什么关键信息机器人给出了什么关键答复或承诺双方达成了什么共识或遗留了什么待解决的问题避免包含寒暄、语气词等无关细节。摘要的存储与使用生成的摘要会存入一个专门的“短期记忆池”。当新的对话开始时系统不是加载全部原始历史而是加载最近1-2个摘要 最近3-5轮原始对话。这样既保持了话题的连贯性又控制了上下文长度。2.2 手动长期记忆将记忆的“锚点”交给用户自动摘要解决了“记得住”的问题但“记什么”同样重要。AI自动判断什么重要可能和用户的认知有偏差。比如用户随口说“我明天考试”AI可能不会将其标记为高优先级但用户如果说“记住我讨厌吃香菜”这对他未来的互动体验可能至关重要。因此手动长期记忆Manual Long-term Memory引入了用户的直接干预。我设计了简单的命令语法例如用户可以说“记住我住在北京”或“机器人 请记住我的生日是1月1日”。当机器人检测到这类指令时它会提取指令中的事实信息“住在北京”、“生日是1月1日”并将其结构化后存入一个向量数据库如ChromaDB或FAISS中。设计考量指令的自然性不能要求用户学习复杂的命令。通过关键词触发“记住”、“记一下”和意图识别让交互尽可能自然。同时也支持更隐性的方式比如用户多次、强调性地提及某一信息系统可以询问“你似乎多次提到XX需要我帮你记住这个信息吗”记忆的结构化直接存储“用户说我住在北京”这样的原始文本不利于检索。我会用一个小型模型或规则将其转化为类似(实体: 用户, 属性: 居住地, 值: 北京)的三元组或者一个简短的陈述句“用户的居住地是北京”。存储时同时保存文本和其向量嵌入。记忆的检索当新对话发生时系统会将用户当前query向量化并在长期记忆向量库中进行相似度搜索召回最相关的几条记忆例如用户问“北京天气如何”会召回“用户居住在北京”这条记忆然后将这些记忆作为“已知事实”插入到本次对话的上下文提示词中。2.3 角色卡边界为记忆戴上“滤镜”和“枷锁”这是最具挑战性也最有意思的部分。如果机器人扮演一个“傲娇大小姐”角色它不应该“记得”自己昨天还以“贴心管家”的身份和用户聊过天。角色卡边界Character Card Boundary包含两层含义记忆滤镜Memory Filter角色应该只“记得”符合其身份设定的事情。例如一个“中世纪骑士”角色对于用户提到的“电脑”、“手机”等信息其记忆的存储和回忆方式应该被“扭曲”或“过滤”可能变成“发光的魔法石板”、“远程传讯水晶”。这需要在生成摘要或存储长期记忆时用角色的口吻和认知框架重新表述信息。行为与认知枷锁Behavior Cognition Lock防止角色记忆“越界”。必须明确哪些信息是角色“知道”的如角色背景设定哪些是角色“经历”的与用户的对话历史哪些是绝对“不知道”的其他角色的记忆、超出设定的现实知识。这需要通过系统提示词System Prompt进行强约束并在记忆检索后对召回的内容做一次角色符合性校验。设计考量双层提示词架构系统提示词分为两层。一层是固定的、强硬的“元指令”定义记忆隔离原则如“你只能访问和回应当前角色身份下的记忆”。另一层是动态的、包含本次会话摘要和相关长期记忆的“上下文指令”。记忆的“角色化”编码在存储记忆时不仅存储信息内容还打上“角色ID”和“记忆类型”角色设定/对话经历/用户事实的标签。检索时严格按当前角色ID和允许的记忆类型进行过滤。越界处理当query明显需要跨角色记忆或超出边界时如用户问“你上次扮演管家时说了什么”机器人应按照角色设定做出符合逻辑的回应例如表示疑惑、拒绝回答或进行符合角色的创造性解释而不是机械地暴露系统机制。3. 技术实现与核心模块拆解思路清晰后就是具体的工程实现。我的技术栈基于Python使用类似NoneBot2的机器人框架对接QQ大模型接口选用的是国内可稳定访问的API如DeepSeek、GLM等向量数据库选用轻量级的Chroma。3.1 会话摘要模块的实现细节这个模块是一个后台的异步任务它监听对话流并在触发条件满足时工作。import asyncio from typing import List, Dict from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI # 此处以OpenAI格式API为例 class ConversationSummarizer: def __init__(self, llm_client, trigger_rounds10): self.llm llm_client self.trigger_rounds trigger_rounds self.dialogue_buffer: List[Dict] [] # 存储原始对话轮次 self.summary_prompt ChatPromptTemplate.from_messages([ (system, 你是一个高效的对话总结助手。请将以下对话内容浓缩成一个简洁的段落重点提取1) 用户的核心需求或提供的关键信息2) 助手回应的核心观点或解决方案3) 双方已达成的一致或待决定的议题。忽略问候、重复和无关细节。), (human, 对话记录\n{dialogue_text}) ]) async def add_message(self, role: str, content: str): 添加一条对话信息到缓冲区 self.dialogue_buffer.append({role: role, content: content}) if len(self.dialogue_buffer) self.trigger_rounds: await self._try_summarize() async def _try_summarize(self): 尝试触发总结 if len(self.dialogue_buffer) 5: # 至少5轮才总结 return dialogue_text \n.join([f{msg[role]}: {msg[content]} for msg in self.dialogue_buffer]) chain self.summary_prompt | self.llm try: summary_result await chain.ainvoke({dialogue_text: dialogue_text}) new_summary summary_result.content # 存储摘要清空或保留最近2轮原始对话作为衔接 self._save_summary(new_summary) self.dialogue_buffer self.dialogue_buffer[-2:] # 保留最后两轮原始对话 print(f摘要已生成{new_summary[:50]}...) except Exception as e: print(f生成摘要失败{e}) # 失败策略丢弃最老的1/3对话防止缓冲区无限增长 discard_count len(self.dialogue_buffer) // 3 self.dialogue_buffer self.dialogue_buffer[discard_count:] def _save_summary(self, summary: str): 将摘要保存到短期记忆池如Redis或内存列表 # 这里简化为追加到列表 memory_pool.append({type: summary, content: summary, timestamp: time.time()})关键参数与调优trigger_rounds默认10但需要根据模型上下文窗口和对话质量动态调整。在快速问答场景可调大在深度讨论场景可调小。摘要提示词提示词的质量直接决定摘要的效用。必须强调“核心”、“关键”并给出明确的结构指引。我经过多次测试发现让LLM以“此前我们讨论了...”开头能生成更连贯的背景段落。错误处理与降级生成摘要本身依赖LLM可能失败或超时。必须有降级方案比如直接丢弃部分最旧历史或者使用更简单的基于TextRank的关键句提取作为备用。3.2 手动长期记忆模块的实现细节这个模块涉及自然语言理解NLU和向量检索。import re from sentence_transformers import SentenceTransformer import chromadb class LongTermMemoryManager: def __init__(self, embedding_modelparaphrase-multilingual-MiniLM-L12-v2): self.embedder SentenceTransformer(embedding_model) self.client chromadb.PersistentClient(path./memory_db) self.collection self.client.get_or_create_collection(nameuser_memories) # 记忆指令关键词 self.memory_triggers [记住, 记一下, 别忘了, 我的] def parse_memory_command(self, user_input: str) - Dict: 解析用户输入判断是否为记忆指令并提取信息 for trigger in self.memory_triggers: if trigger in user_input: # 简单规则提取去除触发词和标点取后续内容 pattern rf{trigger}[,:\s]*([^。]) match re.search(pattern, user_input) if match: memory_content match.group(1).strip() return {is_memory_cmd: True, content: memory_content, trigger: trigger} return {is_memory_cmd: False} async def store_memory(self, memory_text: str, user_id: str, metadata: Dict None): 存储一条长期记忆到向量数据库 # 生成向量 embedding self.embedder.encode(memory_text).tolist() # 生成唯一ID memory_id f{user_id}_{int(time.time()*1000)} # 准备元数据 base_metadata {user_id: user_id, timestamp: time.time(), type: fact} if metadata: base_metadata.update(metadata) # 存入Chroma self.collection.add( documents[memory_text], embeddings[embedding], metadatas[base_metadata], ids[memory_id] ) print(f记忆已存储{memory_text[:30]}...) async def retrieve_memories(self, query: str, user_id: str, n_results: int 3) - List[str]: 根据查询检索相关长期记忆 query_embedding self.embedder.encode(query).tolist() results self.collection.query( query_embeddings[query_embedding], n_resultsn_results, where{user_id: user_id} # 根据用户ID过滤 ) if results and results[documents]: return results[documents][0] # 返回最相关的几条记忆文本 return []实操要点嵌入模型选择对于中文场景paraphrase-multilingual-MiniLM-L12-v2是一个不错的平衡选择体积小、速度快、支持多语言。如果追求更高精度可以考虑text2vec系列或bge-large-zh但需要更多计算资源。记忆去重在存储前可以计算新记忆与已有记忆的向量相似度如果过高如余弦相似度0.95则视为重复进行更新而非新增。记忆更新与遗忘实现了简单的记忆更新命令“更新一下我现在住在上海了”这需要先检索到原记忆ID然后进行替换。对于“遗忘”命令则直接根据ID或内容相似度删除。3.3 角色卡边界模块的融合设计这个模块不独立存在而是渗透在摘要生成、记忆存储检索和最终提示词组装的各个环节。在摘要生成时系统提示词中需加入角色设定“你现在是[角色名]请以[角色名]的视角和口吻总结以下对话。”这样生成的摘要会自然带有角色滤镜例如一个“猫娘”角色生成的摘要可能会是“主人刚才提到了他今天工作很累咱表示关心并建议他休息主人答应了。”在长期记忆存储时每条记忆的元数据metadata中必须包含character_id字段。在存储由角色对话产生的记忆时调用一个“角色化转述”函数用角色的语言风格重写记忆内容后再存储。在提示词最终组装时 这是最关键的一步决定了机器人本次回复的“认知上下文”。def build_final_prompt(current_query: str, user_id: str, character_config: Dict) - str: 构建最终的对话提示词 character_config: 包含角色ID、系统设定、性格描述等 # 1. 获取短期记忆最近摘要 short_term_memories get_recent_summaries(limit2) # 2. 获取相关长期记忆基于当前query和角色ID过滤 long_term_memories memory_manager.retrieve_memories( querycurrent_query, user_iduser_id, n_results3 ) # 对长期记忆进行角色边界过滤 filtered_long_term_mems filter_memories_by_character(long_term_memories, character_config[id]) # 3. 组装系统提示词 system_prompt f 你正在扮演{character_config[name]}以下是你的角色设定 {character_config[description]} **你必须严格遵守以下规则** - 你的所有知识和记忆都仅限于{character_config[name]}这个身份。 - 你只能回忆起与当前身份相关的经历和信息。 - 如果被问到超出设定或你不知道的事情请以{character_config[name]}的方式回应例如表示好奇、困惑或进行符合世界观的理解。 **已知的背景信息** {chr(10).join(short_term_memories)} **关于用户你长期记得以下事实** {chr(10).join(filtered_long_term_mems)} 现在请开始和用户对话。用户的最新消息是 # 4. 将当前用户query附在最后 final_prompt system_prompt f\n用户{current_query}\n{character_config[name]} return final_promptfilter_memories_by_character函数是边界控制的核心它依据元数据中的character_id和memory_type进行过滤。例如可以设定规则通用用户事实如“讨厌香菜”对所有角色可见而由特定角色对话产生的记忆仅对该角色自身可见。4. 系统整合与工作流将上述模块串联起来就形成了机器人的核心工作流消息接收机器人收到用户消息。意图识别判断是否为记忆管理命令存储、查询、删除记忆。如果是则交由LongTermMemoryManager处理并直接回复。记忆检索如果不是记忆命令则将当前消息作为查询检索相关的长期记忆。上下文构建获取最近的会话摘要结合检索到的长期记忆、当前角色配置调用build_final_prompt函数构建完整的对话上下文。调用LLM生成回复将构建好的提示词发送给大语言模型获得回复。对话记录与摘要触发将本轮对话用户消息AI回复存入ConversationSummarizer的缓冲区。检查是否满足摘要触发条件如果满足则异步执行摘要生成任务更新短期记忆池。回复用户将AI回复发送给用户。这个流程确保了每一次交互机器人都能在一个受控的、丰富的、且符合角色的记忆背景下进行思考与回应。5. 踩坑实录与性能优化心得在实际部署和测试中遇到了不少问题这里分享几个典型的坑和解决方案。坑1摘要质量不稳定有时会“编造”或遗漏关键信息。问题分析这通常是因为摘要提示词不够精确或者对话片段本身歧义较大。解决方案改进提示词在提示词中要求模型以“事实列表”“共识总结”的形式输出。例如“首先列出对话中出现的客观事实点谁做了什么说了什么。然后基于这些事实总结对话的核心进展。”分阶段摘要对于非常长的对话先进行分段摘要再对分段摘要进行总结降低单次摘要的复杂度。加入拒绝摘要机制如果对话轮次太少或内容过于空洞可通过关键词密度判断则跳过本次摘要避免生成无意义的噪音。坑2向量检索召回不相关记忆干扰对话。问题分析相似度搜索基于语义但“苹果”一词可能指水果也可能指公司这会导致当用户讨论“吃苹果”时召回“苹果公司发布新手机”的记忆。解决方案元数据过滤优先在向量搜索前先利用元数据如memory_type: user_preference进行硬过滤缩小搜索范围。混合检索结合关键词BM25和向量检索。先用关键词快速筛选出明显相关的候选集再用向量排序做精排。记忆分库为不同类别的记忆建立不同的集合Collection如“用户个人信息”、“对话历史事件”、“角色专属知识”。根据当前query的意图分类决定查询哪个库。坑3角色边界被突破“穿越”或“OOC”Out Of Character。问题分析尽管在提示词中强调了角色限制但强大的LLM有时会根据其底层知识“推理”出超出设定的内容或者长期记忆中的信息过于直白导致回复口吻不符。解决方案记忆“染色”在存储所有来源于对话的记忆时强制用角色口吻重写一遍。例如用户说“我赢了比赛”在存储为“猫娘”的记忆时就转化为“主人今天在比赛中取得了胜利咱真为他高兴”后处理校验在LLM生成回复后增加一个“角色符合性校验”步骤。用一个轻量级模型或规则判断回复是否严重OOC如果是则要求LLM重写或直接套用一个符合角色的安全回复模板。系统提示词强化在系统提示词中采用更严厉、更具体的措辞例如“你绝对不能以任何形式提及或暗示其他角色身份的存在。所有回忆都必须以‘我记得有一次...’符合当前角色身份的方式开头。”坑4系统延迟明显影响对话流畅度。问题分析向量编码、检索、LLM生成摘要都是耗时操作串行执行会导致用户等待时间过长。解决方案异步化与缓存摘要生成、记忆存储完全异步执行不阻塞主回复流程。对频繁检索的长期记忆如用户昵称进行内存缓存。检索优化使用更快的嵌入模型如all-MiniLM-L6-v2并对向量数据库进行索引优化。对于长期记忆不一定每次对话都检索可以设定一个触发阈值如query长度大于5词或包含特定疑问词。分级上下文不是所有上下文都等量齐观。将上下文分为三级核心上下文最近3轮对话角色设定每次必送、活跃上下文当前话题的摘要高频检索、背景上下文长期记忆低频检索。优先保证核心上下文的低延迟。6. 效果评估与未来展望部署这套系统后最直观的感受就是机器人的对话“智商”和“情商”有了显著提升。它不再频繁地问重复问题能记住用户的偏好并在后续对话中自然引用比如“记得你不吃香菜那我们换个菜”角色扮演也更加沉浸和稳定。我设计了一个简单的评估方法连贯性测试进行一段长达50轮的多话题对话中间穿插对之前话题的提问。对比使用可控记忆系统前后机器人回答的相关性和准确性。角色一致性测试让同一个机器人切换不同角色卡与用户对话测试是否会混淆记忆或出现口吻穿越。用户主观评价让测试用户与机器人自由聊天并填写问卷评价其“记忆力”、“贴心程度”和“角色代入感”。未来的优化方向记忆的主动应用目前记忆主要是被动检索。未来可以让机器人更主动地运用记忆例如在适当时机主动提起“你上次说想学吉他开始行动了吗”记忆的情感权重给记忆打上情感标签积极/消极/重要在检索时加权。用户强调“非常重要”的事情其检索优先级和后续引用频率应更高。记忆的自动整理与泛化定期对长期记忆进行聚类和整理自动生成更抽象的用户画像例如“用户是一个喜欢科幻和编程对美食有要求但讨厌香菜的人”用于更精准的个性化服务。多模态记忆扩展如果机器人支持图像、语音输入记忆系统也需要能存储和检索多模态信息例如“用户上次分享了一张猫的照片他的猫是橘色的”。给AI机器人设计记忆本质上是在设计一种新型的人机关系。它不再是每次重启就清零的玩具而是一个能够积累共同经历、形成独特互动历史的伙伴。“可控”二字至关重要它把记忆的主导权部分交还给了用户让人工智能的“智能”变得更加可预测、可管理也更富有温情。这个过程虽然充满了技术挑战但看到机器人终于能说出一句“我记得你之前说过……”那种成就感绝对是值得的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价