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。
|