File size: 15,091 Bytes
85b19cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# Eval Framework 使用指南

## 1. 整体架构

```
eval_framework/
├── cli.py                    # 入口:CLI 解析 + 三阶段编排 (Pipeline → Eval → Aggregate)
├── config.py                 # EvalConfig 数据类
├── openai_compat.py          # GPT-5 系列 max_tokens→max_completion_tokens 兼容补丁
├── datasets/
│   ├── schemas.py            # 运行时共享数据结构 (NormalizedTurn, MemorySnapshotRecord, RetrievalRecord 等)
│   └── domain_a_v2.py        # domain_a_v2 数据集加载器
├── memory_adapters/
│   ├── base.py               # MemoryAdapter 抽象基类 (7 个接口方法)
│   ├── registry.py           # Baseline 注册表 + Mem-Gallery 默认配置覆盖
│   ├── memgallery_native.py  # Mem-Gallery 11 种内置 baseline 的统一适配器
│   ├── amem.py               # A-Mem 外部 baseline 适配器
│   ├── memoryos.py           # MemoryOS 外部 baseline 适配器
│   └── export_utils.py       # 快照/检索结果归一化工具
├── pipeline/
│   ├── runner.py             # 按 session 顺序喂入对话 → 生成 snapshot/delta → 触发 QA
│   ├── qa_runner.py          # 对每个 checkpoint question 做 retrieve + answer
│   ├── gold_state.py         # Gold memory points 累积构建
│   └── records.py            # PipelineSessionRecord / PipelineCheckpointQARecord
├── evaluators/
│   ├── extraction.py         # Session 级评估:Recall + Correctness + Update + Interference
│   ├── qa.py                 # Checkpoint QA 评估:Answer 正确性 + Evidence 覆盖率
│   └── aggregate.py          # 聚合所有 session/QA 评估到 baseline 级汇总指标
└── judges/
    ├── llm_client.py         # OpenAI 兼容 LLM 调用 + JSON 解析 + 重试 + 并发控制
    └── prompts.py            # 6 套 LLM judge prompt 模板
```

## 2. 运行流程

整个 eval 分三个阶段(`cli.py: run_eval()`):

### Stage 1 — Pipeline(串行,适配器有状态)

```
for each sample:
    adapter = create_adapter(baseline_name)
    adapter.reset()
    for each session in sample.sessions:
        for each turn in session.turns:
            adapter.ingest_turn(turn)          # 喂入一条对话
        adapter.end_session(session_id)        # 触发 session 后处理(如 GA 反思、RF 优化)
        snapshot = adapter.snapshot_memories()  # 拍快照
        delta = adapter.export_memory_delta()  # 导出本 session 增量
        → PipelineSessionRecord

        # 当某个 checkpoint 的 covered_sessions 全部完成时触发 QA
        for each question in checkpoint:
            retrieval = adapter.retrieve(question, top_k=5)
            answer = answer_fn(question, retrieval)   # 可注入外部 LLM 回答
            → PipelineCheckpointQARecord
```

Pipeline 结束后写入 checkpoint 文件 `pipeline_sessions.jsonl` + `pipeline_qa.jsonl`,支持 `--eval-only` 跳过此阶段直接从 checkpoint 恢复。

### Stage 2 — Eval(并行,ThreadPoolExecutor)

- **Session 评估**`evaluators/extraction.py`)— 每个 session 4+ 次 LLM 调用:
  1. **Recall**:本 session 的 gold points 中有多少被 delta 覆盖?
  2. **Correctness**:每条 delta 记忆是 correct / hallucination / irrelevant?
  3. **Update handling**:每个 update gold point → updated / both / outdated
  4. **Interference rejection**:每个 interference gold point → rejected / memorized

- **QA 评估**`evaluators/qa.py`)— 每个 question 2 次 LLM 调用:
  1. **Answer 正确性**:Correct / Hallucination / Omission
  2. **Evidence 覆盖率**:cited memories 覆盖了多少 gold evidence points

### Stage 3 — Aggregate

将所有 session 和 QA 级别的评估结果聚合为 6 个维度的 baseline 级指标:

| 维度 | 聚合方式 | 关键指标 |
|------|---------|---------|
| Memory Recall | 按 session 平均 | `avg_recall`, `avg_update_recall` |
| Memory Correctness | 按 session 平均 | `avg_correctness`, `avg_hallucination` |
| Update Handling | 跨 session 池化 | `score` (updated=1.0, both=0.5, outdated=0.0) |
| Interference Rejection | 跨 session 池化 | `score` (rejected/total) |
| Question Answering | 跨 question 池化 | `correct_ratio`, `hallucination_ratio`, `omission_ratio` |
| Evidence Coverage | 跨 question 池化 | `hit_rate` |

输出文件:
- `session_records.jsonl` — 每条含 pipeline 数据 + eval 结果
- `qa_records.jsonl` — 同上
- `aggregate_metrics.json` — baseline 级汇总

## 3. 支持的 Baselines

### 3.1 Mem-Gallery 内置(11 种)

通过 `MemGalleryNativeAdapter` 统一包装,需要在 `eval_framework/` 同级目录放置 `memengine/``default_config/`(从 Mem-Gallery 的 `benchmark/` 目录复制)。

| Baseline | 类型 | 特性 | 额外依赖 |
|----------|------|------|---------|
| `FUMemory` | text-only | 全量存储(FIFO 截断) | — |
| `STMemory` | text-only | 短期记忆 | — |
| `LTMemory` | text-only | 长期记忆,embedding 检索 | sentence-transformers |
| `GAMemory` | text-only | 带 importance judge + 自反思 | LLM API |
| `MGMemory` | text-only | 多层存储(working/FIFO/recall/archival) | LLM API, sentence-transformers |
| `RFMemory` | text-only | 带 reflection optimizer | LLM API |
| `MMMemory` | multimodal | 多模态记忆 | torch |
| `MMFUMemory` | multimodal | 多模态全量存储 | torch |
| `NGMemory` | multimodal | 知识图谱节点存储 | torch |
| `AUGUSTUSMemory` | multimodal | 概念抽取 + 图谱 | LLM API, torch |
| `UniversalRAGMemory` | multimodal | RAG routing + 存储 | LLM API |

### 3.2 外部适配器

| Baseline | 来源 | 安装方式 | 需要外部服务 |
|----------|------|---------|-------------|
| `Mem0` | [mem0ai/mem0](https://github.com/mem0ai/mem0) | `pip install mem0ai` | 否(内置 Qdrant + SQLite) |
| `Mem0-Graph` | 同上(graph 模式) | `pip install "mem0ai[graph]"` | 需要 Neo4j |
| `SimpleMem` | [aiming-lab/SimpleMem](https://github.com/aiming-lab/SimpleMem) | clone + requirements | 否 |
| `Omni-SimpleMem` | 同上(omni 模式) | 同上 | 否 |
| `Zep` | [getzep/zep](https://github.com/getzep/zep) | `pip install zep-python` | 需要 Zep server |
| `A-Mem` | [A-Mem](https://arxiv.org/abs/2504.19413) | clone 源码 | 否 |
| `MemoryOS` | [MemoryOS](https://github.com/memodb-io/memobase) | clone 源码 | 否 |

**论文来源:**

| Baseline | 论文 | GitHub |
|----------|------|--------|
| Mem0 / Mem0-Graph | [arXiv:2504.19413](https://arxiv.org/abs/2504.19413) | https://github.com/mem0ai/mem0 |
| SimpleMem | [arXiv:2601.02553](https://arxiv.org/abs/2601.02553) | https://github.com/aiming-lab/SimpleMem |
| Omni-SimpleMem | [arXiv:2604.01007](https://arxiv.org/abs/2604.01007) | https://github.com/aiming-lab/SimpleMem |
| MemVerse | [arXiv:2512.03627](https://arxiv.org/abs/2512.03627) | https://github.com/KnowledgeXLab/MemVerse |
| Memobase | — | https://github.com/memodb-io/memobase |
| Supermemory | — | https://github.com/supermemoryai/supermemory |
| Zep | [arXiv:2501.13956](https://arxiv.org/abs/2501.13956) | https://github.com/getzep/zep |

### 3.3 添加新 Baseline

实现 `MemoryAdapter` 的 7 个抽象方法:

```python
class MyAdapter(MemoryAdapter):
    def reset(self) -> None: ...
    def ingest_turn(self, turn: NormalizedTurn) -> None: ...
    def end_session(self, session_id: str) -> None: ...
    def snapshot_memories(self) -> list[MemorySnapshotRecord]: ...
    def export_memory_delta(self, session_id: str) -> list[MemoryDeltaRecord]: ...
    def retrieve(self, query: str, top_k: int) -> RetrievalRecord: ...
    def get_capabilities(self) -> dict[str, Any]: ...
```

然后在 `registry.py``EXTERNAL_ADAPTER_REGISTRY` 中注册。

## 4. 数据适配

### 4.1 数据集格式(domain_a_v2)

加载器 `load_domain_a_v2_academic(data_dir)` 要求 `data_dir` 下有三个文件:

```
data_dir/
├── domain_a_v2.json                # 主对话数据(JSON array)
├── stage4_memory_points.jsonl      # 每 session 的 gold memory points
└── stage4b_qa_checkpoints.jsonl    # checkpoint QA 题目
```

**`domain_a_v2.json`** 中每个 sample 结构:

```json
{
  "uuid": "unique-id",
  "sample_id": "sample_001",
  "sessions": [
    {
      "_v2_session_id": "S00",
      "dialogue": [
        {
          "role": "user",
          "content": "Hello...",
          "timestamp": "2025-01-01T10:00:00",
          "attachments": [{"caption": "photo of...", "type": "image_caption"}]
        },
        {"role": "assistant", "content": "Hi..."}
      ],
      "memory_points": [...]   // 仅 S00 需要
    },
    {"_v2_session_id": "S01", "dialogue": [...]}
  ]
}
```

**`stage4_memory_points.jsonl`** 每行一个 sample:

```json
{
  "uuid": "...", "sample_id": "sample_001",
  "memory_sessions": [
    {
      "session_id": "S01",
      "memory_points": [
        {
          "memory_id": "m001",
          "memory_content": "User prefers dark mode",
          "memory_type": "preference",
          "memory_source": "normal",
          "is_update": false,
          "original_memories": [],
          "importance": 0.8
        }
      ]
    }
  ]
}
```

**`stage4b_qa_checkpoints.jsonl`** 每行一个 sample:

```json
{
  "uuid": "...", "sample_id": "sample_001",
  "checkpoints": [
    {
      "checkpoint_id": "cp01",
      "covered_sessions": ["S00", "S01"],
      "questions": [
        {
          "question": "What theme does the user prefer?",
          "answer": "Dark mode",
          "question_type": "preference_recall",
          "question_type_abbrev": "pref",
          "difficulty": "easy",
          "evidence": [{"memory_id": "m001"}]
        }
      ]
    }
  ]
}
```

### 4.2 适配自有数据

若要接入新数据源,有两条路径:

**路径 A:转换为 domain_a_v2 格式**(推荐)
- 将原始对话整理为上述三文件格式
- 直接使用现有 CLI 运行

**路径 B:编写新的 dataset loader**
-`datasets/` 下新建加载器,返回 `DomainAV2AcademicBundle`(或等价结构)
-`cli.py``run_eval()` 中通过 `load_domain_bundle` 参数注入

### 4.3 关键数据结构

每条对话 turn 会被归一化为 `NormalizedTurn````python
NormalizedTurn(
    sample_id="sample_001",
    session_id="S01",
    turn_index=0,
    role="user",           # "user" | "assistant"
    text="Hello...",
    attachments=(Attachment(caption="...", type="image_caption"),),
    timestamp="2025-01-01T10:00:00",
)
```

Memory 的 gold 标注支持三种来源标记:
- `normal` — 正常记忆点
- `interference` — 干扰信息(不应被记忆)
- `is_update=True` — 更新型记忆(应替换旧记忆)

## 5. 环境配置(uv)

### 5.1 安装 uv

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

### 5.2 初始化项目环境

```bash
cd /data1/toby/nips26

# 创建虚拟环境
uv venv .venv --python 3.11
source .venv/bin/activate
```

### 5.3 安装核心依赖

```bash
# 最小依赖(可跑 FUMemory/STMemory 等纯文本 baseline)
uv pip install openai tenacity

# embedding 检索类 baseline(LTMemory, GAMemory, MGMemory 等)
uv pip install sentence-transformers

# 多模态 baseline(MMMemory, NGMemory, AUGUSTUSMemory 等)
uv pip install torch torchvision transformers

# 外部 baseline(A-Mem, MemoryOS)— 按各自文档安装额外依赖
# A-Mem 需要其源码目录下的 requirements
# MemoryOS 需要 memoryos 包
```

### 5.4 环境变量(.env 文件)

在项目根目录 (`nips26/`) 创建 `.env` 文件,框架会自动加载:

```bash
# .env
# 必需 — LLM API(pipeline 答题 + judge 评估统一使用)
OPENAI_API_KEY=sk-...
OPENAI_BASE_URL=https://api.openai.com/v1   # 或兼容端点
OPENAI_MODEL=gpt-4o

# 可选
OPENAI_TEMPERATURE=0.0
OPENAI_MAX_TOKENS=1024
OPENAI_TIMEOUT=120
JUDGE_TEMPERATURE=0.0         # judge 专用温度
LLM_MAX_CONCURRENT=5          # LLM 并发上限
```

### 5.5 Mem-Gallery 本地依赖

Mem-Gallery 内置 baseline 需要将其源码放到 `eval_framework/` 的同级目录:

```bash
# 假设 Mem-Gallery repo 在 /path/to/Mem-Gallery
cp -r /path/to/Mem-Gallery/benchmark/memengine  /data1/toby/nips26/
cp -r /path/to/Mem-Gallery/benchmark/default_config  /data1/toby/nips26/
```

最终目录结构应为:

```
nips26/
├── eval_framework/
├── memengine/          # Mem-Gallery 记忆引擎
└── default_config/     # Mem-Gallery 默认配置
```

## 6. 运行示例

### 基本运行

```bash
# 运行单个 baseline
python -m eval_framework.cli \
    --dataset /path/to/domain_a_v2_data/ \
    --baseline FUMemory \
    --output-dir eval_framework/results/FUMemory

# smoke 模式(只跑第 1 个 sample,快速验证)
python -m eval_framework.cli \
    --dataset /path/to/domain_a_v2_data/ \
    --baseline FUMemory \
    --output-dir eval_framework/results/FUMemory_smoke \
    --smoke

# dry-run(不实际运行,打印配置)
python -m eval_framework.cli \
    --dataset /path/to/domain_a_v2_data/ \
    --baseline FUMemory \
    --dry-run

# 仅重跑 eval 阶段(从 checkpoint 恢复,pipeline 不重跑)
python -m eval_framework.cli \
    --dataset /path/to/domain_a_v2_data/ \
    --baseline FUMemory \
    --output-dir eval_framework/results/FUMemory \
    --eval-only

# 调整 eval 并发数
python -m eval_framework.cli \
    --dataset /path/to/domain_a_v2_data/ \
    --baseline MGMemory \
    --output-dir eval_framework/results/MGMemory \
    --max-eval-workers 10
```

### 批量跑所有 baseline

```bash
DATASET="/path/to/domain_a_v2_data"
for baseline in FUMemory STMemory LTMemory GAMemory MGMemory RFMemory A-Mem MemoryOS; do
    echo "=== Running $baseline ==="
    python -m eval_framework.cli \
        --dataset "$DATASET" \
        --baseline "$baseline" \
        --output-dir "eval_framework/results/$baseline"
done
```

### 输出文件说明

运行完成后 `output-dir` 下包含:

```
results/FUMemory/
├── pipeline_sessions.jsonl     # Stage 1 checkpoint — session 级 pipeline 结果
├── pipeline_qa.jsonl           # Stage 1 checkpoint — QA 级 pipeline 结果
├── session_records.jsonl       # 最终 session 结果(含 eval)
├── qa_records.jsonl            # 最终 QA 结果(含 eval)
└── aggregate_metrics.json      # baseline 级汇总指标
```

## 7. LLM API 开销估算

每个 sample 的 LLM 调用量:

| 来源 | 调用次数 |
|------|---------|
| Pipeline answer(每个 QA question) | N_questions |
| Session Recall judge | N_sessions |
| Session Correctness judge | N_sessions |
| Update judge | N_update_points(逐条) |
| Interference judge | N_interference_points(逐条) |
| QA Answer judge | N_questions |
| QA Evidence judge | N_questions |

典型场景下一个 sample 约 20-50 次 LLM 调用。通过 `LLM_MAX_CONCURRENT` 控制并发避免 rate limit。