Skip to content

[Feature] 记忆复用率仪表盘:召回 Top-N / 僵尸记忆率 / 注入命中率可见化 #217

Closed

Description

@modusensus

动机

社区讨论里有一条值得正面回应的批评:记忆插件的存储容易退化成"记事本"——写进去容易,被实际复用很难,且用户看不见复用情况。这条批评在 dsh-mneme 里恰好是可量化的:recall 回执(recordRecall,检索场景已落库)、llm_audit_logs、dream_runs 三处数据都在。缺的不是数据,是把"记忆被用了没有"变成用户与维护者都看得见的信号。

三个用途:

  1. 用户侧:第一次能看见"我的记忆被用上了吗",而不是对着列表猜;
  2. 调参侧:僵尸记忆率是巩固/归档策略的直接依据(哪些该降权、哪些该进巩固候选);
  3. 产品侧:为后续 heat(热度升权)与图谱(结构化关联)提供基线数据——复用率改善与否,要有 before/after 才说得清。

形态建议(v1)

数据面:一个只读聚合路由(如 GET /api/dsh-mneme/recall-stats?window=30d):

  • 近 N 天被召回 / 被注入的 Top-N 记忆(含次数);
  • 僵尸记忆率:活跃记忆中"窗口期内零召回"的占比,按 type / source 拆分;
  • 注入槽位命中率(实际注入条数 / 槽位 × 轮数);
  • 数据源:现有 recall 回执表 + llm_audit_logs,纯读现有表,不新增写入路径(聚合缓存表可选,不阻塞)。

面板:状态页加一张「记忆复用」卡——僵尸率大数字 + Top-N 召回列表 + 按类型拆分条。

可选延伸(不阻塞 v1):autoDream 候选集把"长期零召回"作为排序信号;CLI dsh-mneme stats 输出同口径摘要。

关键口径(实现前先定,防误报)

  1. 僵尸判定要有豁免期:写入后 N 天内(建议 7 天)没机会被召回的记忆不算僵尸,否则新记忆全是僵尸;
  2. 零召回的两种语义要分开:从未进入过检索/注入上下文(没机会)vs 进入了但没命中(不相关)——前者是机会问题,后者是质量信号;
  3. 历史数据可能不全:recallRecordDefault 开启前的窗口算不到,卡片对短窗口数据要标注可信度;
  4. 面板与路由都走只读,失败静默降级(与状态页其他卡同风格)。

验收

改动面:一个聚合查询 + 一个路由 + 一张面板卡 + 测试,适合作为独立 issue 认领;动工前在 issue 里说一声即可。

Activity

  1. heptaspirit commented on Sep 17, 2026

    @heptaspirit
    Collaborator

    认领(heptaspirit)。这个和我们正打算做的一件事正好合流:我们准备给真实库(1800+ 条、recall_runs 全留痕、recall_evals 表已就位)建一套检索回归基线,仪表盘要的三个信号(召回 Top-N / 僵尸记忆率 / 注入命中率)与它是同一套数据面,可以一起铺。它和 #218 heat 也是上下游(访问频率信号源),两个我们都认领了,做的时候会把口径对齐。

  2. heptaspirit commented on Sep 18, 2026

    @heptaspirit
    Collaborator

    口径对齐(认领跟进)。把数据面逐处核实了一遍:三个指标里两个能从现有表直接出,第三个的口径需要拍板。 数据面现状(锚 upstream/main @ 5bd2dab) - recall_runs 是唯一已落库的召回回执:面板搜索、standalone /search、模型工具召回三条路径都走 searchMemories,recallRecordDefault 默认开,滚动清理默认 90 天。注意 candidates 存的是最终返回集(topK 截断后,service.js:862),不是完整候选池。 - 注入无留痕:注入候选只在内存里记跨轮轮换(rotationHistory),不落任何表;llm_audit_logs 只记 LLM 调用。所以「注入槽位命中率」从现有表算不出来。 口径提案 1. 召回 Top-N:窗口内 recall_runs.candidates 按 id 聚合次数,join memories 补 type/source(记忆已删的标 deleted)。纯读。 2. 僵尸记忆率:活跃(未归档未遗忘)且窗口内零候选出现,按 type/source 拆分;豁免期 7 天按当前时刻计(写入不足 7 天不入分母,单独报数);历史数据不全用「窗口内 recall_runs 最早时间戳」标注覆盖率。 3. 两种零召回语义:A「从未进入检索上下文」(零曝光)可直接算;B「进入了但没命中」需要候选池留痕,而现有 candidates 是最终集——v1 先只报 A,卡片口径写明;B 留给后续(要动 recordRecall 的记录形状,不阻塞 v1)。 4. 注入命中率:两个选项。(a) 严格纯读:只能退化成「检索填充率」(topK 配额 vs 实际返回数),不是真注入命中;(b) 注入选中后复用 store.recordRecall 落一行 mode='inject'——现成表加现成滚动清理,一处调用点,跟随 recallRecordDefault 开关。倾向 (b):它同时是 #218 heat 的访问频率信号源(注入触达是最大访问面),两个 issue 的衔接点就在这。是否接受这处写入,请你定。 形态:GET /api/dsh-mneme/recall-stats?window=30(只读聚合,窗口天数 + 扫描行数双钳制,超出标降级口径);面板「记忆复用」卡进状态页网格,自门控风格与热度分布卡同款(接口失败/数据不可信整卡不渲染,脚注标口径)。默认窗口 30 天,不新增配置键出数。测试覆盖豁免期、零召回语义拆分、聚合窗口钳制。

  3. modusensus commented on Sep 18, 2026

    @modusensus
    MemberAuthor

    口径确认:接受方案 (b)——注入命中后复用 store.recordRecall 落一行 mode='inject'。

    接受理由(含文献依据):

    边界确认(对应你方案里的三条):

    1. 跟随 recallRecordDefault 开关,不新增配置键;
    2. 表与滚动清理全部复用现成的(90 天窗口兜住注入路径的行数放大——每轮 N 条 = N 行是这个方案的代价,可接受);
    3. candidates 存最终返回集的现有形状不动,零召回 B 语义留后续,v1 只报 A。

    验收观察点补一条:heatEnabled=false 时写入行为不变(记录照落,只是 heat 不消费)——留痕与消费解耦,两 issue 各自独立验收。三个指标的口径按你的提案执行,免豁免期、窗口钳制、测试覆盖照单全收。

  4. heptaspirit commented on Sep 18, 2026

    @heptaspirit
    Collaborator

    增量已实现:注入终选集落 mode='inject' 审计行(query 存空串——该列 NOT NULL,主动注入没有查询词;跟随 recallRecordDefault 不设新配置键;heatEnabled=false 时照写,留痕与消费解耦,与您补充的验收点一致),recall-stats 新增注入口径(轮数 / 注入条数 / slotFillRate = 注入条数/(轮数×槽位数),注入候选计入 Top-N 与僵尸零曝光判定),面板「记忆复用」卡追加注入段(窗口内无注入行时省略)。分支 heptaspirit:feat/inject-recall-217(基于 #233,避免 stacked diff)——#233 合并后立即 rebase 到 main 开 PR 闭合 #217。全量套件绿(1151 例,含注入落账门控/解耦/聚合口径 6 例新测试)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions