MemT 1.0 记忆写入与检索是两套动作——别当成一个开关来调

文章导读
MemT 1.0 里,写入和检索是两个独立环节:写入决定“记了什么、以什么粒度记”,检索决定“这次问法能不能把它捞出来”。记忆效果不理想时只把总开关打开或关掉,等于同时动了两个变量,改完既不知道是存错还是取错,也没法判断下次该动哪一侧。比较稳妥的做法是先让两侧都有可观察的输入输出,再用控制变量法一次只动一侧。
📋 目录
  1. 一 分别记录写入与检索的输入输出
  2. 二 固定检索侧只改写入内容看变化
  3. 三 固定写入侧只改检索条件看变化
  4. 四 把两侧观察结果整理成对照清单
A A

MemT 1.0 里,写入和检索是两个独立环节:写入决定“记了什么、以什么粒度记”,检索决定“这次问法能不能把它捞出来”。记忆效果不理想时只把总开关打开或关掉,等于同时动了两个变量,改完既不知道是存错还是取错,也没法判断下次该动哪一侧。比较稳妥的做法是先让两侧都有可观察的输入输出,再用控制变量法一次只动一侧。

处理 MemT 1.0 记忆不准的问题,先把写入侧(写入原文、粒度、来源轮次、记忆 id)和检索侧(查询原文、top_k、过滤条件、命中条目与得分)分别落日志,再做两次单变量对比:固定检索条件只换写入粒度,固定写入内容只换查询表述。只有两侧都留痕,才能判断是“存得不对”还是“取不出来”。如果当前日志里没有命中条目和得分,建议先补日志再调参,否则任何改动都无法归因;线上调整返回条数前,需要结合其他调用方的行为一起确认影响面。

分别记录写入与检索的输入输出

这一步的目标不是调效果,而是让两侧行为都能被看见。建议在调用 MemT 的封装层(业务代码里统一包一层写入函数和检索函数)打结构化日志,而不是依赖页面上的最终回答。下面的骨架是通用形态,字段名可按你自己的 SDK 替换,关键是四类信息不能缺:写入侧要有原文、粒度、来源轮次;检索侧要有查询原文、返回条数、命中条目和得分。

// 写入侧:每次写入调用返回后落一条
{
  "side": "write",
  "ts": "<ISO 时间>",
  "turn": "<会话 id + 轮次>",
  "fact": "<写入原文>",
  "granularity": "sentence | paragraph | structured",
  "source": "<来源渠道>",
  "mem_id": "<系统返回的记忆 id>"
}

// 检索侧:每次检索调用返回后落一条
{
  "side": "retrieve",
  "ts": "<ISO 时间>",
  "query": "<查询原文>",
  "top_k": 5,
  "filters": "<时间范围 / 标签等>",
  "hits": [
    {"mem_id": "<id>", "score": "<系统返回分值>", "text": "<命中原文片段>"}
  ]
}

字段较多时可先按下表核对一遍,缺哪列补哪列:

侧字段记录什么填写样例(占位)
写入fact实际写入的文本原文用户希望周报在周五下午提交
写入granularity单句 / 段落 / 结构化sentence
写入turn + ts来源会话与轮次、时间session-A / 第 12 轮
写入mem_id系统返回的记忆标识mem_xxx
检索query查询原文,不要只记改写后的周报什么时候交
检索top_k / filters返回条数与过滤条件5 / 不限时间
检索hits[].score命中条目及其分值mem_xxx / 见系统返回

落完日志后先做一次自查:拿一条刚写入的 mem_id,用它的原文当查询去检索,看能否命中。命中不了,说明问题在写入或索引侧,后面的对比才有意义。

MemT 1.0 记忆写入与检索是两套动作——别当成一个开关来调

固定检索侧只改写入内容看变化

这一组只回答一个问题:写进去的东西本身,是否适合被检索到。做法是把检索条件完全锁死(同一条 query、同一个 top_k、同一组 filters),只改变同一条事实的写入粒度,分三次写入,并记录命中情况。注意三次写入要用不同的会话或标记隔开,避免混在一起归因不了。

同一条事实的三种粒度可以是:

MemT 1.0 记忆写入与检索是两套动作——别当成一个开关来调
  • 细粒度单句:用户希望周报在周五下午提交
  • 中粒度段落:把上下文一起写进去,例如交代谁在什么场景下提的、是否变更过
  • 结构化字段:{"对象":"周报","字段":"提交时间","值":"周五下午","来源轮次":12}
写入粒度命中条数首条得分首条是否直接可用备注
单句待填待填待填待填
段落待填待填待填待填
结构化待填待填待填待填

判断口径建议这样定:如果某一粒度的首条直接可用、而另外两种命不中,说明写入粒度对这条事实影响明显,先把这类事实的写入方式统一;如果三种粒度都命不中,问题更可能在检索侧或索引未生效,转去做第二组对比。段落粒度容易把关键信息稀释,结构化粒度依赖写入方的字段约定,各有适用场景,不宜一概而论。

固定写入侧只改检索条件看变化

这一组只回答另一个问题:写进去的内容没问题,是不是查询表述没匹配上。做法是保持写入内容不变(就用上一组里表现最稳的那条记录),只改检索侧的输入,逐条记录命中差异。一次只改一个维度,别同时改表述又改条数。

  • 原句查询:直接用用户当时的问法
  • 同义改写:换一种说法,不加新信息
  • 加限定词:补上主体、时间范围等限定
  • 去口语词:去掉“那个、帮我看看”之类的填充词
  • 只改 top_k:从 1 调到 3、5、10,观察目标条目是否出现在靠后位置
检索变体querytop_k目标条目是否命中首条得分备注
原句待填待填待填待填待填
同义改写待填待填待填待填待填
加限定词待填待填待填待填待填
调大 top_k待填待填待填待填待填

如果调大 top_k 后目标条目出现但排名靠后,这属于排序或匹配质量问题,不是记忆没写进去;如果换几种表述都命不中,而用原文查询能命中,说明查询侧的改写策略需要单独看。线上直接放大 top_k 会把更多无关条目带进上下文,需要结合下游对上下文体量的要求一起确认。

MemT 1.0 记忆写入与检索是两套动作——别当成一个开关来调

把两侧观察结果整理成对照清单

两组对比做完,就可以把现象和排查顺序固化下来,下次遇到类似问题不用重新推演。下表是通用顺序,建议按行从上往下走,确认一行再走下一行:

现象先查哪侧验证动作
检索返回空,或全是无关条目先查写入用该事实的原文当 query、适当放大 top_k 检索一次,看记录是否存在
写入日志里查不到这条事实先查写入检查调用是否真的触发写入、是否被长度或格式规则截断
能命中但内容答非所问先查写入换段落或结构化粒度重写同一条事实,固定检索条件再测一次
同一问题换个问法结果差异很大先查检索固定写入,横向比较原句、同义改写、加限定词的命中结果
目标条目总是排在后面先查检索只调 top_k 看排名位置,确认是排序问题而非缺失
两条记忆互相矛盾先查写入按来源轮次和时间排序,确认是否缺少新旧覆盖规则

执行上的两条边界:一是同一轮排查只动一侧,动完再测,不然结果无法归因;二是把“开/关”只当成最后的手段,先确认写入覆盖、粒度选择和查询改写这三处具体配置,再决定是否需要整体关闭。日志字段和对比表格可以按团队习惯裁剪,但写入原文、查询原文、命中条目与得分这四项建议一直留着,它们是判断存错还是取错的唯一依据。