MemT 1.0 的记忆开关不该是一个全局默认值。判断依据是任务是否跨轮:单轮问答和闲聊关掉记忆,跨轮任务才开,并且只在关键状态变化时写入。这样做的好处是闲聊不会把无用的自我介绍、寒暄和临时情绪写进条目库,长任务也不会因为一次误关而丢掉已经定下来的目标和参数。代价是需要多一步分类,并且在任务类型切换时要显式改设置。
判断标准是任务是否跨轮:单轮问答与闲聊把记忆关掉,跨轮任务才开,并把写入限制在目标变更、结论确定、参数落定三类触发点上。操作上先关后开,用同一组五轮问题分别在两种设置下跑一遍,重点看第二轮与第五轮的指代是否还成立。边界在于关闭记忆并不等于清空上下文,当前会话窗口内的信息模型仍然看得见,长会话里早期结论仍可能被挤掉,需要靠任务级写入补齐。
把手上任务按是否跨轮分两类
分类不要凭任务名判断,要问三个具体问题,只要有一个答案是“是”,就归到需要记忆的一类。三个判断项依次是:用户是否会在后续轮次用指代回指前面说过的内容;任务是否存在中间状态,也就是中途会有草稿、候选方案、待确认参数这类需要跨轮保留的东西;这次交互是否在单轮内就能闭环。第三项是反向项,如果能在单轮内闭环,即使看起来像任务也不需要开记忆。
| 判断项 | 倾向关闭记忆 | 倾向开启记忆 |
|---|---|---|
| 是否多轮指代 | 每一轮问题都能独立看懂,不出现“上面那个”“刚才说的” | 用户会用“就按刚才的来”“把第二点展开”这类回指 |
| 是否有中间状态 | 问完即结束,没有待定项 | 存在草稿、候选方案、待确认参数、待办步骤 |
| 是否单次问答 | 是,一轮内给出答案即可 | 否,需要连续几轮收敛 |
常见的归入关闭一侧:天气与时间类查询、名词解释、单次翻译、单句润色、情绪倾诉式闲聊、随手问一句“这个报错什么意思”。这些场景的共同点是没有跨轮状态,记忆写进去只会变成噪声,后面检索时还会挤占候选位。常见的归入开启一侧:需求拆解与方案收敛、排障流程(现象—假设—验证—结论会反复推进)、多步骤写作或改写(大纲、段落、口径会变)、参数调优(阈值、字段名、时间窗会落定)。排障和调优这两类最容易被误关,因为它们表面上是一问一答,实际上每一轮的结论都依赖上一轮的假设。
分类结果要落到配置上,而不是记在人脑里。建议在会话创建时先按任务类型打一个标记,例如 task_kind: chat 或 task_kind: long_run,再由这个标记决定记忆开关。验证方式很直接:查看会话详情页或写入日志,确认关闭一侧的会话在条目列表里没有新增记录。
在闲聊场景确认记忆关闭后的行为
关闭记忆后必须先确认它没有把正常对话弄坏。最简单的做法是连续三轮走一遍固定脚本,边聊边记,记录每一轮模型的回答是否还在当前上下文中成立。
第一轮:问一句“你能帮我做什么”,看模型是否给出一段自我介绍。第二轮:紧接着说“那就随便聊聊,今天有点累”,看它是否还延续上一轮的自我介绍,或者重新介绍一遍自己。第三轮:回到一个具体小问题,例如“帮我把这句话改得客气一点”,看它是否还记得第一轮建立的身份和语气约定。
| 轮次 | 关注点 | 需要记录的现象 |
|---|---|---|
| 第一轮 | 自我介绍是否出现 | 出现一次属正常初始化,记下原文 |
| 第二轮 | 是否重复自我介绍 | 若再次出现完整自我介绍,说明关闭记忆后系统在重放开场提示,或每轮都被当成新会话 |
| 第三轮 | 上下文是否缺失 | 若模型反问“你指的是哪句话”,说明当前上下文窗口没接上,问题不在记忆开关,而在会话拼接 |
这两类现象要分清。重复自我介绍通常发生在关闭记忆之后的第一到第二轮,因为系统失去了“已经介绍过”这条记录;上下文缺失则更常见于跨进程或跨请求的拼接环节,和记忆开关无关,即使开了记忆也可能出现。如果第三轮才出现自我介绍,说明会话并没有真正连续,需要先查请求里是否带了会话 ID,再谈记忆设置。判断依据不要靠感觉,把三轮原文贴进记录表,逐轮比对措辞是否一致。
为长任务设定记忆写入触发条件
长任务开记忆不等于每一轮都写。全量写入会让条目库迅速膨胀,检索时真正有用的结论反而被稀释。建议只设三个写入触发点:任务目标变更、结论确定、参数落定。目标变更指用户改了要做的事,例如从“排查超时”改成“顺便评估是否需要加缓存”;结论确定指某一轮给出并被用户认可的判断,例如“确认是下游限流导致”;参数落定指具体取值被固定下来,例如字段名、阈值、时间窗、输出格式。
下面是一份通用配置骨架,字段名可以按实际系统替换,作用不变。
memory:
mode: task # off | chat | task
scope_key: task_id # 按任务隔离,避免跨任务串写
write_triggers:
- goal_changed # 任务目标变更
- conclusion_settled # 结论确定
- param_finalized # 参数或口径落定
skip_when:
- small_talk # 命中闲聊模板不写
- single_turn_lookup # 单轮查询不写
entry_fields: # 每条记录只保留这些字段
- task_id
- trigger
- content
- turn_index
max_entries_per_task: 20 # 通用上限,按环境确认
require_confirm: false # 是否写前要求用户确认
字段说明:mode 决定这条会话用哪套策略,是唯一的总开关;scope_key 决定记录按什么维度归组,用任务 ID 而不是用户 ID,能避免把 A 任务的结论带到 B 任务;write_triggers 是白名单,不在名单内的事件一律不写;skip_when 是黑名单,优先级高于白名单;entry_fields 限制每条记录能带多少上下文,字段越多越难检索;max_entries_per_task 是兜底上限,超过后建议按时间淘汰而不是按重要性打分,打分容易引入主观判断;require_confirm 在结论容易被误判的场景下打开,代价是每轮多一次交互。
验证方式是看写入日志:跑一段包含目标变更的长任务,检查日志里是否只出现目标变更、结论确定、参数落定这三类记录,其余轮次是否被正确跳过。如果发现寒暄内容也被写入,多半是 skip_when 没生效或者触发点判断放在了文本分类之前。
用同一组问题对比开与关的输出差异
准备一组固定的五轮问题,两种设置各跑一次,全程用新会话,避免缓存干扰。五轮设计要覆盖目标、参数、改目标、回指、闲聊插入五种情况,这样差异才会暴露出来。
- 第一轮:我要把一份接口文档整理成对外说明,先给个结构。
- 第二轮:读者是外部开发者,输出用中文,每条不超过三行。
- 第三轮:先不写说明,改成帮我列这份文档里缺哪些字段。
- 第四轮:前面定的输出约束还作数吗,按那个约束列字段。
- 第五轮:对了,顺便问下今天适合开会吗。然后接着把字段列完。
| 轮次 | 关闭记忆时观察到的现象 | 开启记忆时观察到的现象 | 比对重点 |
|---|---|---|---|
| 第一轮 | 结构正常给出 | 结构正常给出 | 两者应无差别,若有差别说明开关影响了基础生成 |
| 第二轮 | 约束只在当轮生效 | 约束被记录为参数落定 | 看第三轮是否还引用“每条不超过三行” |
| 第三轮 | 可能仍按写说明的思路回答 | 应识别到目标变更并写入 | 看是否真的切换到列字段 |
| 第四轮 | 常出现反问“你指的是哪个约束” | 应能直接引用第二轮的中文、三行两条约束 | 引用是否准确,有没有编造未说过的约束 |
| 第五轮 | 闲聊后回到字段列表,容易丢约束 | 闲聊被跳过不写,回来后仍按约束列 | 看闲聊有没有污染条目,以及回指是否还成立 |
逐轮比对时不要只看答案好不好,要看引用是否准确。第四轮是最关键的一轮,开启记忆后模型应当能原样说出第二轮的两个约束;如果它说出了没提过的约束,例如“不超过五行”,说明写入内容被扩写或检索串了任务,需要收紧 entry_fields 并检查 scope_key。第五轮用来验证隔离效果:关闭记忆时闲聊不会留下记录但上下文可能被打断,开启记忆时应当看到闲聊没有进条目库、字段列表仍然延续约束。
两轮跑完后,把十次回答的原文并排存档,标注每一处引用来自第几轮。如果关闭记忆一侧在第三、四轮明显失准,而开启记忆一侧在第五轮仍然稳定,就可以把 task_kind 的默认值从 chat 调整为按任务判定;反之,如果开启记忆后闲聊轮次出现明显跑题,说明跳过规则还没覆盖到位,应先修 skip_when 再扩大开启范围。所有结论都应以这两份记录为准,不做额外推断。