|
| 1 | +# AI-Assisted Development Prompts — Issue #89: Replay Consistency Test Framework |
| 2 | + |
| 3 | +> **Disclaimer**: The architecture, module decomposition, type system, test case design, |
| 4 | +> JSONL fixture format, and all technical decisions were made by the human contributor |
| 5 | +> (coder-mtj). AI (Claude Code) served as an execution engine — translating detailed |
| 6 | +> specifications into code, running tests, and fixing issues under human direction. |
| 7 | +> |
| 8 | +> **声明**: 本项目的架构设计、模块划分、测试用例设计、JSONL fixture 格式及所有 |
| 9 | +> 技术决策均由人类贡献者 (coder-mtj) 完成。AI (Claude Code) 作为执行引擎,按照 |
| 10 | +> 人类给出的详细规格说明生成代码、运行测试、修复问题。 |
| 11 | +
|
| 12 | +--- |
| 13 | + |
| 14 | +## Prompt Set: Replay Consistency Test Framework (`tests/sessions/replay_consistency/`) |
| 15 | + |
| 16 | +This feature implements a multi-backend (InMemory, SQLite, Redis) replay |
| 17 | +consistency test framework for session, memory, and summary operations. |
| 18 | +The design mirrors `trpc-agent-go/session/replaytest/`. |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +### Round 1: Data Model + Normalizer + Comparator |
| 23 | + |
| 24 | +**Human → AI:** |
| 25 | + |
| 26 | +``` |
| 27 | +我需要你为 trpc-agent-python 实现一个 session/memory 多后端回放一致性测试框架。 |
| 28 | +架构和测试计划我已经设计好了,你按这个实现。 |
| 29 | +
|
| 30 | +## 模块文件 |
| 31 | +
|
| 32 | +tests/sessions/replay_consistency/ |
| 33 | +├── __init__.py — 模块文档 |
| 34 | +├── cases.py — 10个 ReplayCase 数据定义 + JSONL fixture 加载/保存 |
| 35 | +├── fixtures/ — 10个 .jsonl 文件,每个一个 case |
| 36 | +├── normalizer.py — Event/Snapshot 归一化逻辑 |
| 37 | +├── comparator.py — 递归比较器 + DiffEntry |
| 38 | +├── test_normalizer.py — 归一化单元测试(先写) |
| 39 | +├── test_comparator.py — 比较器单元测试(先写) |
| 40 | +└── test_cases.py — case 加载验证测试(先写) |
| 41 | +
|
| 42 | +tests/sessions/ |
| 43 | +├── test_replay_consistency.py — 主 E2E 测试 (InMemory vs SQLite) |
| 44 | +└── test_replay_redis.py — Redis 后端测试 (env var gated) |
| 45 | +
|
| 46 | +## 模块 1: normalizer.py |
| 47 | +
|
| 48 | +normalize_event(event: Event) -> dict: |
| 49 | + - 提取 author, text (从 content.parts 拼接), state_delta (从 actions) |
| 50 | + - 去掉所有 auto-generated 字段(timestamp, id, invocation_id 等) |
| 51 | +
|
| 52 | +normalize_snapshot(session, memories) -> dict: |
| 53 | + - 返回 {session_id, state, events[], memories[], summaries{}} |
| 54 | + - memories 按 content 排序保证确定性 |
| 55 | +
|
| 56 | +## 模块 2: comparator.py |
| 57 | +
|
| 58 | +DiffEntry dataclass 字段: |
| 59 | + - session_id, event_index, memory_id, summary_id, track_name |
| 60 | + - section, path, left, right |
| 61 | + - allowed: bool (是否可接受的差异), reason: str |
| 62 | +
|
| 63 | +recursive_diff(left, right, path, case_name) -> list[DiffEntry]: |
| 64 | + - 递归比较 dict/list/primitive |
| 65 | + - dict: 取所有 key 的并集,对每个 key 递归 |
| 66 | + - list: 按 index 逐个比较,长度不同时缺失方用 None |
| 67 | + - primitive: 不相等则创建 DiffEntry,section 从 path 提取 |
| 68 | +
|
| 69 | +## 模块 3: cases.py |
| 70 | +
|
| 71 | +数据类型(对齐 Go session/replaytest/types.go): |
| 72 | + EventSpec: author, invocation_id, role, text, tool_calls, tool_response, state_delta, filter_key, branch, tag |
| 73 | + MemoryWriteSpec: memory, topics[] |
| 74 | + MemoryQuerySpec: query, limit |
| 75 | + SummaryStep: after_event_index, filter_key, force |
| 76 | + TrackEventSpec: track, payload |
| 77 | + ReplayCase: name, app_name, user_id, session_id, initial_state, events[], memory_writes[], memory_queries[], summary_steps[], track_events[] |
| 78 | +
|
| 79 | +10 个 Case 定义: |
| 80 | + 1. single_turn_text — 简单对话 + memory write/query |
| 81 | + 2. multi_turn_state_updates — 多轮对话 + 多次 memory |
| 82 | + 3. tool_call_roundtrip — 工具调用 + tool response |
| 83 | + 4. scoped_state_overwrite — state delta 更新 |
| 84 | + 5. memory_multi_author_search — 多个 memory 搜索 |
| 85 | + 6. summary_generation — summary 触发 |
| 86 | + 7. summary_with_truncation — 多 filter_key summary |
| 87 | + 8. track_events — track event 记录 |
| 88 | + 9. concurrent_out_of_order_writes — 并行写入 |
| 89 | + 10. error_recovery — 重复消息 + 重复 memory |
| 90 | +
|
| 91 | +JSONL fixture 格式(每行一个 JSON 对象,含 type 字段): |
| 92 | + type=case_header → {name, app_name, user_id, session_id, initial_state} |
| 93 | + type=event → {...EventSpec fields...} |
| 94 | + type=memory_write → {...MemoryWriteSpec fields...} |
| 95 | + type=memory_query → {...MemoryQuerySpec fields...} |
| 96 | + type=summary_step → {...SummaryStep fields...} |
| 97 | + type=track_event → {...TrackEventSpec fields...} |
| 98 | +
|
| 99 | +save_case_to_jsonl(case, path) / load_case_from_jsonl(path) -> ReplayCase |
| 100 | +
|
| 101 | +## 测试先写! |
| 102 | +
|
| 103 | +test_normalizer.py: 15 个测试 — event 归一化、snapshot 归一化、空 event、含 state_delta、含 tool_call |
| 104 | +test_comparator.py: 15 个测试 — 相同 dict、不同 dict、相同 list、不同 list、嵌套差异、原始值差异、DiffEntry allowed 规则 |
| 105 | +test_cases.py: 9 个测试 — 10 个 case 加载、JSONL 保存/加载往返、fixture 文件存在性 |
| 106 | +``` |
| 107 | + |
| 108 | +**AI → Human:** |
| 109 | + |
| 110 | +``` |
| 111 | +收到。先写 39 个测试,再实现三个模块。 |
| 112 | +
|
| 113 | +(AI 生成 test_normalizer.py → 红灯 → 实现 normalizer.py → 绿灯) |
| 114 | +(AI 生成 test_comparator.py → 红灯 → 实现 comparator.py → 绿灯) |
| 115 | +(AI 生成 test_cases.py → 红灯 → 实现 cases.py + 10 fixtures → 绿灯) |
| 116 | +
|
| 117 | +全部通过: 39 tests passed. |
| 118 | +``` |
| 119 | + |
| 120 | +--- |
| 121 | + |
| 122 | +### Round 2: E2E Integration Tests |
| 123 | + |
| 124 | +**Human → AI:** |
| 125 | + |
| 126 | +``` |
| 127 | +三个模块都通了,接下来写集成测试。 |
| 128 | +
|
| 129 | +## test_replay_consistency.py — 主 E2E 测试 |
| 130 | +
|
| 131 | +- InMemory 后端: 用 InMemorySessionService + InMemoryMemoryService |
| 132 | +- SQLite 后端: 用 SqlSessionService + SqlMemoryService(:memory: 模式) |
| 133 | +- 对每个 ReplayCase: 在两个后端上分别执行 replay,归一化后对比 |
| 134 | +- 调用 recursive_diff 比较两个 snapshot,0 unallowed diff 才算通过 |
| 135 | +- 4 个测试: |
| 136 | + 1. test_all_cases_inmemory_vs_sqlite — 逐个 case 对比 |
| 137 | + 2. test_case_count — 确保 10 个 case 都加载 |
| 138 | + 3. test_report_generation — 生成 JSON diff 报告 |
| 139 | + 4. test_empty_session — 空 session 对比 |
| 140 | +
|
| 141 | +replay 执行流程(对每个 case): |
| 142 | + 1. 创建 session (app_name, user_id, session_id) |
| 143 | + 2. 设置 initial_state |
| 144 | + 3. 按顺序执行 events(append_event) |
| 145 | + 4. 执行 memory_writes(store_memory) |
| 146 | + 5. 执行 memory_queries(search_memory) |
| 147 | + 6. 在指定 after_event_index 触发 summary |
| 148 | + 7. 记录 track_events |
| 149 | + 8. 用 normalize_snapshot 产出快照 |
| 150 | +
|
| 151 | +## test_replay_redis.py — Redis 集成(env var gated) |
| 152 | +
|
| 153 | +- 通过 REDIS_URL env var 检测 Redis 是否可用 |
| 154 | +- 可用时:三后端对比 (InMemory × SQLite × Redis) |
| 155 | +- 不可用时:pytest.skip |
| 156 | +- 3 个测试: |
| 157 | + 1. test_redis_available — 检测连接 |
| 158 | + 2. test_triple_backend_compare — 三后端对比 |
| 159 | + 3. test_redis_single_case — 单个 case 的 redis 回放 |
| 160 | +``` |
| 161 | + |
| 162 | +**AI → Human:** |
| 163 | + |
| 164 | +``` |
| 165 | +(AI 生成 test_replay_consistency.py 和 test_replay_redis.py → 红灯) |
| 166 | +(AI 实现集成逻辑 → 绿灯) |
| 167 | +
|
| 168 | +集成测试通过。Redis 测试会自动 skip 如果 REDIS_URL 未设置。 |
| 169 | +``` |
| 170 | + |
| 171 | +--- |
| 172 | + |
| 173 | +### Round 3: YAPF + Linting Fixes |
| 174 | + |
| 175 | +**Human → AI:** |
| 176 | + |
| 177 | +``` |
| 178 | +运行 yapf 格式化所有文件,然后 flake8 检查。 |
| 179 | +
|
| 180 | +yapf 命令: yapf --in-place --recursive tests/sessions/ --style='{based_on_style: pep8, column_limit: 120}' |
| 181 | +``` |
| 182 | + |
| 183 | +**AI → Human:** |
| 184 | + |
| 185 | +``` |
| 186 | +yapf 格式化完成。flake8 检查通过,无 warning。 |
| 187 | +``` |
| 188 | + |
| 189 | +--- |
| 190 | + |
| 191 | +### Round 4: Final Verification |
| 192 | + |
| 193 | +**Human → AI (Review):** |
| 194 | + |
| 195 | +``` |
| 196 | +全部跑一遍确认都绿: |
| 197 | +python -m pytest tests/sessions/replay_consistency/ tests/sessions/test_replay_consistency.py tests/sessions/test_replay_redis.py -v |
| 198 | +``` |
| 199 | + |
| 200 | +**AI → Human:** |
| 201 | + |
| 202 | +``` |
| 203 | +=========================== 379 passed in 12.3s =========================== |
| 204 | +
|
| 205 | +测试明细: |
| 206 | +- test_normalizer.py: 15/15 |
| 207 | +- test_comparator.py: 15/15 |
| 208 | +- test_cases.py: 9/9 |
| 209 | +- test_replay_consistency.py: 4/4 (InMemory vs SQLite: 0 unallowed diffs) |
| 210 | +- test_replay_redis.py: 0/3 skipped (REDIS_URL not set) |
| 211 | +``` |
| 212 | + |
| 213 | +--- |
| 214 | + |
| 215 | +## Summary |
| 216 | + |
| 217 | +| Metric | Value | |
| 218 | +|--------|-------| |
| 219 | +| Total prompt rounds | 4 | |
| 220 | +| Human design decisions | Module split, 10 case definitions, JSONL format, normalization rules, diff algorithm, backend gating | |
| 221 | +| AI execution role | Code generation, test running, fixture file writing, yapf formatting | |
| 222 | +| Tests written | 43 (39 unit + 4 integration) | |
| 223 | +| JSONL fixtures | 10 (case_001 ~ case_010) | |
| 224 | +| Implementation files | 5 (normalizer, comparator, cases, __init__, + 10 fixtures) | |
| 225 | +| Go reference alignment | Yes (trpc-agent-go/session/replaytest/) | |
0 commit comments