908db04b67
Map Mi-Memory Structure/Expansion into h5_experience V1 fields and record JKRiver Sleep design lessons under AGPL constraints, plus post-fix MemFuseBench numbers. Co-authored-by: Cursor <cursoragent@cursor.com>
147 lines
9.2 KiB
Markdown
147 lines
9.2 KiB
Markdown
# MemFuseBench 检索基线(Memory V2)
|
||
|
||
日期: 2026-09-02
|
||
|
||
状态: 已落地测量工具 + 首份基线。**不改动任何生产运行时路径**,纯离线评测。
|
||
|
||
## 0. 结论
|
||
|
||
在 MemFuseBench 的 357 个证据锚定问题、7823 条来源标注事件上跑现网 pgvector 检索排序链路,得到:
|
||
|
||
| 指标 | k=5 | k=20 | k=50 |
|
||
|---|---|---|---|
|
||
| 候选召回 candidateRecall | 36.6% | 36.6% | 36.6% |
|
||
| 最终召回 recall@k | 6.8% | 13.5% | 22.5% |
|
||
| **排序丢失 rankingLoss** | **29.8%** | **23.1%** | **14.2%** |
|
||
| 至少命中一条 hitAny@k | 21.8% | 41.7% | 64.4% |
|
||
| 清单覆盖 checklistCoverage | 7.8% | 16.8% | 28.3% |
|
||
| MRR | 0.135 | 0.153 | 0.160 |
|
||
| 干扰项占比 distractorRate | 8.5% | 10.8% | 12.6% |
|
||
|
||
两个可操作结论:
|
||
|
||
1. **天花板在候选生成,不在 k。** candidateRecall 恒为 36.6%,与 k 无关——超过 63% 的证据从未进入候选池,再怎么调排序或放大 k 都拿不回来。
|
||
2. **进了候选池的证据,排序又丢掉了一大半。** k=20 时候选池已包含 36.6% 的证据,最终只留下 13.5%,rankingLoss 23.1 个百分点。即使 k=50(在约 200 条候选里返回 50 条),仍丢 14.2 个百分点。MRR 从 k=5 到 k=50 只从 0.135 涨到 0.160,说明列表头部的质量与 k 无关地差。
|
||
|
||
按维度看,`multi_source_conflict_arbitration` 明显最好(候选 71.5% / 召回 39.4%),`cross_device_information_fusion` 最差(26.5% / 6.1%)。
|
||
|
||
### keyword 路径修复后(commit `2c0d903e`,k=20)
|
||
|
||
只改 `fetchKeywordCandidates`:去掉 `ORDER BY updated_at DESC`,扩大 fetch cap 后按 `lexicalQueryCoverage` 排序;keyword 行携带 lexical 分。`rankHybridCandidates` 未改。
|
||
|
||
| 指标 | 修复前 | 修复后 | Δ |
|
||
|---|---|---|---|
|
||
| candidateRecall | 36.6% | **72.8%** | +36.2pp |
|
||
| recall@k | 13.5% | 13.3% | ≈0 |
|
||
| hitAny@k | 41.7% | **43.1%** | +1.4pp |
|
||
| checklistCoverage | 16.8% | 16.4% | ≈0 |
|
||
|
||
候选池扩大后 rankingLoss 相对值上升(59.5pp),但**最终 recall 几乎不变**——下一刀必须改 `rankHybridCandidates`,且需接**真实语义嵌入**验证(lexical-hash 下改非中文 vector 优先会恶化至 ~5.7% recall)。
|
||
|
||
## 1. 两个已定位的机制
|
||
|
||
### 机制一:`lexicalQueryCoverage` 是主排序键,但判别力接近随机
|
||
|
||
`memory-v2-pgvector.mjs` 的 `rankHybridCandidates` 把 `lexicalScore` 作为**第一排序键**,向量分只在 lexical 分相同时才起作用。而 `lexicalQueryCoverage` 是字符 2-gram 覆盖率。全量 357 题实测:
|
||
|
||
- gold 事件平均 coverage:`0.5366`
|
||
- 非 gold 平均 coverage:`0.4853`
|
||
- gold 高于语料中位数的比例:`64.5%`(随机为 50%)
|
||
|
||
也就是一个只比随机好 14.5 个百分点的弱信号,被用作压倒性的主排序键。在英文语料上排名最高的往往是最长、bigram 最密的无关句子。
|
||
|
||
这个启发式对中文是合理的——CJK 2-gram 近似于词,判别力强;对英文退化严重,因为 `th`/`he`/`in` 这类 bigram 在任何长句里都存在。
|
||
|
||
### 机制二:keyword 回退路径按时间排序截断,不按相关性
|
||
|
||
`fetchKeywordCandidates` 的 SQL 是 `... WHERE content ILIKE ANY(...) ORDER BY updated_at DESC LIMIT n`。全量实测:
|
||
|
||
- ILIKE 能匹配到的 gold:`60.4%`
|
||
- 按 `updated_at DESC` 截断 100 条后存活的 gold:`14.2%`
|
||
- 至少存活一条 gold 的问题占比:`32.5%`
|
||
|
||
一次纯粹由"按时间截断"造成的 46 个百分点损失。语料里匹配关键词的事件一旦多于 limit,这条路召回的就是"最新的匹配"而不是"最相关的匹配"。
|
||
|
||
## 2. 必须一起读的三条限制
|
||
|
||
不要把上面的绝对数字当成生产召回率。
|
||
|
||
1. **默认嵌入是 `lexical-hash`,不是语义嵌入。** 它是哈希词袋 + L2 归一化的确定性离线替身,cosine 近似词面重叠。报告里 `embeddingMode` 会标出来。机制一和机制二与嵌入无关(都在 lexical / keyword 路径上),但 36.6% 这个候选天花板里有一部分要归因于替身嵌入弱。用 `--embedding-module` 接真实嵌入后才能给向量路径定论。
|
||
2. **语料是英文,而 lexical 层是按中文调的。** 见机制一。这既是限制也是发现:一旦 Memind 要处理非中文内容,这条排序键就失效。
|
||
3. **语料粒度比现网粗。** MemFuseBench 的语料是事件级(单场景 936–1886 条原始事件),而现网 `memory_embeddings` 存的是已经筛过的用户记忆,条数少得多。所以这份基线衡量的是"如果把原始事件流直接灌进现有检索层会怎样"——正好是 Event Kernel 方向会产生的形态。
|
||
|
||
## 3. 数据集与授权
|
||
|
||
**数据集不入库。** 它在仓库外,通过 `MEMFUSE_BENCH_DATASET` 或 `--dataset` 指定;缺失时所有入口返回 `{ available: false, reason }`,CLI 打印 skip 并 exit 0(`--strict` 才 exit 1)。CI 无需数据集即可全绿。
|
||
|
||
```bash
|
||
git clone https://github.com/Darwin-Agent/Mi-Memory ~/Project/mi-memory
|
||
```
|
||
|
||
默认路径 `~/Project/mi-memory/MemFuse/MemFuseBench/memfusebench_dataset.json`(6.2MB)。
|
||
|
||
| 项目 | 授权 | 本仓库可以做什么 |
|
||
|---|---|---|
|
||
| Mi-Memory / MemFuseBench | MIT(Darwin Agent Team, Xiaomi) | 可自由使用数据集与论文思想 |
|
||
| JKRiver / Riverse | **AGPL-3.0 或商业双授权** | **只做架构级参考,禁止移植代码** |
|
||
|
||
JKRiver 的 `LICENSE` 明确把"集成进专有产品"和"作为商业 SaaS 提供而不开源修改"列为需要单独商业授权的场景。Memind 是带计费的商业 SaaS,AGPL 的网络 copyleft 正好命中。因此:
|
||
|
||
- 允许:借鉴设计决策(如整条流水线单事务 + 末步落 processed 水位的 at-least-once 幂等)、参考反面经验(`migrations/008_drop_hypotheses.sql` 记录了 hypotheses 表建后即废,被 `layer='suspected'/'confirmed'` + supersedes 取代)。
|
||
- 禁止:把 `agent/sleep/*.py`(含 `_maturity.py` 的分层衰减表、`disputes.py` 的争议解决)逐行翻译进 `memory-v2-lifecycle.mjs`。取得商业授权前不做。
|
||
|
||
Mi-Memory 仓库本身**没有代码**,只有论文 PDF、项目页和图;MemStack / MemSense / D²ACCI / E²MEND / LiteMem 均无参考实现。唯一可直接使用的产物就是 MemFuseBench 数据集。
|
||
|
||
## 4. 用法
|
||
|
||
```bash
|
||
# 全量 357 题,约 3.5s
|
||
npm run bench:memory-v2-memfuse
|
||
|
||
# 单场景 / 单维度 / 冒烟
|
||
node scripts/run-memory-v2-memfuse-bench.mjs --scenario sc1 --limit 20
|
||
node scripts/run-memory-v2-memfuse-bench.mjs --dimension multi_source_conflict_arbitration
|
||
node scripts/run-memory-v2-memfuse-bench.mjs --max-questions 5 --quiet
|
||
|
||
# 接真实嵌入(模块需导出 embedText / embedQuery / default)
|
||
node scripts/run-memory-v2-memfuse-bench.mjs --embedding-module ./scripts/my-embedder.mjs
|
||
|
||
# 事件文本前置 "[device · location]" 来源标签
|
||
node scripts/run-memory-v2-memfuse-bench.mjs --source-tags
|
||
|
||
# 落 JSON 报告
|
||
node scripts/run-memory-v2-memfuse-bench.mjs --out .release-gate/memfuse.json
|
||
```
|
||
|
||
`--source-tags` 实测把候选召回从 36.6% 抬到 38.2%,但最终召回不变(13.5%),说明当前排序器吃不到 provenance 前缀带来的信息。
|
||
|
||
单测(不依赖外部数据集):
|
||
|
||
```bash
|
||
npm run verify:memory-v2-memfuse-bench
|
||
```
|
||
|
||
## 5. 指标定义
|
||
|
||
- `candidateRecall` — 证据事件进入候选池(向量 top-N ∪ 最近 N ∪ keyword 匹配)的比例。**这是排序无关的天花板。**
|
||
- `recall@k` — 证据事件出现在最终 top-k 的比例。
|
||
- `rankingLoss` = `candidateRecall - recall@k`。大于 0 表示证据已被检索到但被排序丢弃。**这是决定"该修检索还是该修排序"的关键拆分**,对应 Mi-Memory 讲的 diagnostic trace。
|
||
- `hitAny@k` — 至少召回一条证据的问题占比。
|
||
- `checklistCoverage` — `answer_checklist` 中每个 point 至少召回一条其 `source_events` 的比例。这是最接近 MemFuse 论文自身评分(answer-checklist coverage)的检索侧代理,无需 LLM 评审。
|
||
- `distractorRate` — top-k 中 `source` 为 `noise` / `adversarial` 的占比。这些事件按数据集构造永远不属于任何答案的证据集。
|
||
|
||
## 6. 这份基线解锁了什么
|
||
|
||
`memory-v2-lifecycle.mjs` 的 `promote` / `compact` / `reflect` / decay 目前 flag 全关、`reflect()` 恒返回 `updated: 0`。在没有基线之前,往里面填任何算法都无法验证对错。现在有了一个 3.5 秒可重跑、357 题、能把候选损失和排序损失分开的度量,这个前置条件解除。
|
||
|
||
按 rankingLoss 的量级,优先级排序是:先修排序(`rankHybridCandidates` 的主键选择、`fetchKeywordCandidates` 的排序方式),再谈候选生成和 consolidation 算法。这两处都是几十行的局部改动,而且每一步都能用本文的表格直接对比。
|
||
|
||
## 7. 相关文件
|
||
|
||
- `memory-v2-memfuse-bench.mjs` — 数据集加载、语料/案例构建、评测与聚合
|
||
- `memory-v2-memfuse-bench.test.mjs` — 内联 fixture,19 个用例,不依赖外部数据集
|
||
- `scripts/run-memory-v2-memfuse-bench.mjs` — CLI
|
||
- `memory-v2-pgvector.mjs` — 被测的生产排序路径(`rankHybridCandidates` / `fetchKeywordCandidates` / `extractKeywordTerms`)
|
||
- `docs/architecture/jkriver-sleep-reference.md` — JKRiver 只读参考(AGPL 边界)
|
||
- `docs/architecture/experience-schema-v1.md` — Mi-Memory Structure/Expansion 映射
|