feat(api): Memory compaction 管线与调度修复,同步环境变量示例

Memory compaction(近重复 chunk 软排除)
- 新增 compaction 调度:Redis debounce、scheduler gate、增量游标;任务结束时 finalize,避免 gate 长期占用并处理运行期新 trigger。
- Celery memory_compaction_run:debounce 未到点则 retry;用户级 Redis 锁;成功路径更新游标并 finalize;异常时释放 scheduler gate 并 self.retry,避免静默卡死调度与瞬时失败不重试。
- compaction_service:多层判定 + canonical 打分;无 embedding 时停止前移游标(awaiting_embeddings);curation details 补全 trigger 等上下文。
- ingest_transcript_sync:同步路径尽力写入 embedding,与异步 ingest 行为对齐,避免 compaction 永远扫不到无向量 chunk。
- repo:新增 update_chunk_embedding_sync。
测试
- 扩展 test_memory_compaction:调度合并、finalize、ingest embedding、无向量游标、异常路径 gate+retry 等回归用
This commit is contained in:
Kevin
2026-03-30 10:46:35 +08:00
parent 0d999cb769
commit e884409410
15 changed files with 1699 additions and 7 deletions

View File

@@ -0,0 +1,208 @@
---
name: ""
overview: ""
todos: []
isProject: false
---
# Memory 整理Compaction管线计划修订版
## 目标与约束
- 事件触发 + **防抖**、**增量范围**(非每次全库)、**软操作优先**(合并/标记 exclude硬删极少且高置信、全量大扫除低频。
- **不修改** `User` 档案字段(出生年、职业等);仅操作 memory 相关表。
---
## 必须满足MVP 硬约束)
| 约束 | 说明 |
| --------- | ----------------------------------------------------------------------------------------- |
| **幂等** | 同一批次重复执行,结果不应放大(重复 exclude、重复 curation_action 应被跳过或 no-op。 |
| **用户级互斥** | 同一用户同一时刻只允许一个 compaction 在跑(见下「用户级短锁」)。 |
| **单次上限** | 扫描 chunk 数、写入 exclude 数、候选对数均有限流与上限。 |
| **只做软操作** | `set_chunk_excluded` + `MemoryCurationAction`;不物理删 `memory_sources`/`memory_chunks`MVP。 |
| **可追踪** | 每次 exclude 记录原因、对比对象 chunk id、触发上下文见 context。 |
---
## 1) 调度层:防抖主路径(收紧)
**不再把「revoke 旧 Celery task + 重新 `apply_async`」作为主路径**原因Celery revoke 对 **已 prefetch / 已执行** 的任务 **强保证不足**;多 worker、网络抖动下 task_id 链路易碎。
**主路径(推荐)**
- Redis 键例如:`debounce_until:{user_id}``pending:{user_id}`(值可为 Unix 秒或 ISO 时间),表示「最早允许执行 compaction 的时间」。
- 每次触发memoir / recompose / manual / beat只做**把 debounce 窗口往后推**(或 `SET` + TTL**尽量少发** Celery 任务。
- 任务 `memory_compaction_run` 设计为 **幂等**worker 执行开头校验:
- 当前时间是否 **仍落在 debounce 窗口内**(若设计为「到点再跑」,则相反:未到点则短 `retry``return`
- **是否已取得用户级锁**(见 §2
- 可选:是否有 **更新的 trigger** 覆盖了本次 run例如比较 `pending_version` / `last_trigger_seq`)。
**增强项(可选)**
- revoke + task_id 可作为 **补充**,不作为依赖正确性的主路径。
**分层原则**
- **调度层**:尽量少发任务。
- **执行层**即使多发、retry、重叠调度**安全**(幂等 + 锁 + 上限)。
---
## 2) 用户级短锁MVP 标配,非可选)
`memory_compaction_run(user_id)` **开头**
- 键:`lock:memory_compaction:{user_id}`
- **TTL**515 分钟(可配置,略大于单次任务 P99
- **获取失败**:直接 **skip**,结构化日志 `lock_contention` / `skipped_reason=lock_not_acquired`
**覆盖场景**`process_memoir_segments``recompose_chapters_for_story` 双触发、worker 重试、beat/手动全量与增量重叠。
---
## 3) 增量范围(明确规则:以 chunk 为索引)
**MVP 定义:增量对象 = 新进入对比池的 chunks**,不以 `memory_sources.created_at` 单独作为主粒度。
**原因**
- 一个 source 下多 chunksource 时间粒度太粗;
- source 更新不一定等于 chunk 语义变化。
**推荐规则**
- 维护 **per-user cursor**`last_compaction_cursor`Redis 或 DBMVP 可用 Redis `compaction:chunk_cursor:{user_id}``max(processed_chunk_updated_at)` 或单调序号)。
-`**memory_chunks.updated_at`(或 `created_at`> cursor** 的 chunk 作为 **增量 chunk 集合**(分页 + 上限)。
- 对比范围:增量 chunk 仅与 **同 user、未 excluded****历史 chunk** 做近重复检测(可再限:同 source 优先、或时间窗内)。
**可选收窄**:若 context 带 `candidate_chunk_ids` / `candidate_source_ids`,与上述集合 **求交****并集取优**(实现时二选一并写死文档)。
---
## 4) 近重复判定:多层保护(不只全局 embedding 阈值)
配置项 `memory_compaction_chunk_similarity_threshold` **保留**,但 **自动 exclude 条件** 至少需 **多层组合**(建议 **三项中满足 ≥2 项** 才自动 exclude可配置
| 层 | 说明 |
| ------------- | ---------------------------------------- |
| **Embedding** | 同 `user_id`、余弦相似度 ≥ 阈值 |
| **文本规则** | 归一化(空白/标点)后高 overlap、containment、或长度比例接近 |
| **元数据邻近** | 时间相近;或 source / story / chapter 关联接近(若有) |
**必须**embedding 高相似 **不等于** 重复(主题/情绪相近的不同场景)。
---
## 5) 保留哪条 chunkcanonical 选择策略(非「一律排除较晚」)
**不采用**简单「排除较晚一条」。
**MVP 打分函数(示例因子,可加权)**
- `referenced_count` / 被引用次数(若有)
- `metadata` 完整度
- **文本长度**(信息更全)
- **canonical 来源加成**:来自 story/chapter 流水线、或 `source_type` / 标记为 canonical 的来源
**结果**:保留 **分高者**exclude **分低者**;同分再 tie-break如更早的 ingest、或更长的文本
---
## 6) Context触发批次信息收紧
`story_ids` / `chapter_ids` / `story_dispatch_ids` / `chapters_to_enqueue` 外,**显式传入**
| 字段 | 说明 |
| ---------------------------------------------- | ----------------------------------------------------------- |
| `trigger_source` | `memoir_segments` / `chapter_recompose` / `manual` / `beat` |
| `trigger_time` | ISO8601 或 monotonic |
| `candidate_chunk_ids` / `candidate_source_ids` | 直接候选(可选) |
| `pipeline_run_id` / `request_id` | 与 memoir task / HTTP 对齐,便于追溯 |
写入日志与(可选)`MemoryCurationAction.details`
---
## 7) 可观测性:指标 + 日志
**日志**(保留):结构化,`user_id`、耗时、跳过原因、exclude 对。
**指标(建议 Prometheus 或现有 metrics 体系)**
- `memory_compaction_runs_total`
- `memory_compaction_skipped_total`(含 lock、debounce、空增量
- `memory_compaction_chunks_scanned_total`
- `memory_compaction_chunks_excluded_total`
- `memory_compaction_candidates_total`
- `memory_compaction_duration_ms`
- `memory_compaction_lock_contention_total`
**派生关注**
- exclude rate
- duplicate detection **人工抽检** precision流程外记录非自动指标
**原因**compaction 最常见问题是 **悄悄做错**,而非 crash。
---
## 架构示意(修订)
```mermaid
flowchart LR
subgraph triggers [Triggers]
MS[memoir_segments]
RC[chapter_recompose]
M[manual]
B[beat]
end
subgraph redis [Redis]
DEB[debounce_until_or_pending]
LOCK[lock memory_compaction user]
CUR[last_compaction_cursor]
end
subgraph worker [Worker]
T[memory_compaction_run idempotent]
end
triggers --> DEB
DEB --> T
T --> LOCK
T --> CUR
```
---
## 代码锚点(不变)
- 调度挂钩:`[process_memoir_segments](api/app/tasks/memoir_tasks.py)` 成功路径;可选 `[recompose_chapters_for_story](api/app/tasks/chapter_compose_tasks.py)`(双触发由锁 + 幂等吸收)。
- 业务逻辑:新 `features/memory/compaction_service.py`;任务:`tasks/memory_compaction_tasks.py`;配置:`core/config.py`
---
## 阶段切分(不变,与修订对齐)
- **MVP**Redis 防抖主路径 + 用户锁 + chunk 增量 + 多层近重复 + 打分保留 + context + 日志 + 指标。
- **阶段二**pending 建议表 / LLM 仲裁。
- **阶段三**:低频全量 + 分片。
---
## Implementation todos修订
1. Config`memory_compaction_`*(含 debounce 秒、锁 TTL、chunk 上限、相似度与「至少 K 层满足」)。
2. Redis`debounce_until` / `pending` 主路径;`lock:memory_compaction:{user_id}``last_compaction_cursor`(或等价)。
3. Service幂等 compactionchunk 索引增量;多层判定 + 打分 excludecuration_action details 含对比 id 与 context。
4. Celery`memory_compaction_run` 入口锁与 debounce 校验;注册 task。
5. Hooks`memoir_tasks` / 可选 `chapter_compose_tasks` 传完整 context。
6. Metrics + logs + `.env.example` 文档。