91 lines
3.7 KiB
Markdown
91 lines
3.7 KiB
Markdown
# 启动增量恢复设计
|
||
|
||
## 问题
|
||
|
||
PicoBot 重启后,`Session::from_storage()` 全量加载 `messages` 表,恢复的 history 可能直接超出上下文窗口,首次 LLM 调用即触发压缩,浪费 token。
|
||
|
||
## 设计
|
||
|
||
### 核心思路
|
||
|
||
用 `last_compressed_message_at` 标记最后压缩时刻。恢复时:
|
||
- 加载该标记之后的原始消息
|
||
- 用该 session 的 Timeline 条目替代已压缩部分
|
||
- `seq_counter` 统一从 SQLite 查 `MAX(seq) + 1`
|
||
|
||
```
|
||
messages 表 memories(timeline)
|
||
┌──────────────────────────┐ ┌───────────────────────────┐
|
||
│ created_at = T1..T5 │ ← 跳过 │ session = feishu:oc:dialog │
|
||
│ (压缩已覆盖,用Timeline替代)│ │ created_at 降序 │
|
||
├──────────────────────────┤ ├───────────────────────────┤
|
||
│ created_at > T6 │ ← 加载 │ 只取最近 3 条 │
|
||
└──────────────────────────┘ └───────────────────────────┘
|
||
```
|
||
|
||
### 数据变更
|
||
|
||
**`sessions` 表加列:**
|
||
```sql
|
||
last_compressed_message_at INTEGER
|
||
```
|
||
|
||
**`SessionMeta` / `Session` 加字段:** `last_compressed_message_at: Option<i64>`
|
||
|
||
### Storage 层新增方法
|
||
|
||
| 方法 | SQL |
|
||
|------|-----|
|
||
| `get_max_message_seq(session_id)` | `SELECT MAX(seq) FROM messages WHERE session_id = ?` |
|
||
| `load_messages_after_timestamp(session_id, after_ts)` | `WHERE created_at > ?` |
|
||
| `load_session_timelines(session_id, limit)` | `WHERE session_id = ? AND category = 'timeline' ORDER BY created_at DESC LIMIT ?` |
|
||
|
||
### 压缩跟踪
|
||
|
||
`compress_if_needed()` 返回值改为 `CompressionResult { history, created_timelines: bool }`。
|
||
`compress_once()` 中 LLM 摘要路径才置 `true`(Tier 2),Tier 1/3 不产生 Timeline。
|
||
|
||
**记录时机**(`handle_message` 正常流、溢出重试流、`/compact` 统一):
|
||
```rust
|
||
if result.created_timelines {
|
||
session.last_compressed_message_at = Some(now());
|
||
session.persist_session_meta().await;
|
||
}
|
||
```
|
||
|
||
### Session::from_storage() 恢复逻辑
|
||
|
||
有压缩标记时:
|
||
1. `load_session_timelines(limit=4)` → 取 3 条给 LLM,第 4 条判"有更多"
|
||
2. 有更多 → 插入提示 user 消息
|
||
3. 逐条插入 Timeline 为 `[Previous Context]` user 消息
|
||
4. `load_messages_after_timestamp(after_ts)` → 原始尾消息
|
||
5. `repair_tool_call_chains`
|
||
|
||
无压缩标记 → 全量加载(现有行为)。
|
||
|
||
统一:`seq_counter = MAX(seq) + 1`
|
||
|
||
### 系统提示词
|
||
|
||
`Session.last_compressed_message_at` 非空时追加:
|
||
```
|
||
## 历史会话
|
||
之前的对话摘要已归档。如需回顾历史上下文,使用 `timeline_recall` 工具搜索。
|
||
```
|
||
|
||
## 改动清单
|
||
|
||
| # | 文件 | 改动 |
|
||
|---|------|------|
|
||
| 1 | `storage/session.rs` | `SessionMeta` 加 `last_compressed_message_at` |
|
||
| 2 | `storage/mod.rs` | DDL migration + upsert/get_session 加列 |
|
||
| 3 | `storage/mod.rs` | 新增 `get_max_message_seq`, `load_messages_after_timestamp` |
|
||
| 4 | `storage/memory.rs` | 新增 `load_session_timelines` |
|
||
| 5 | `agent/context_compressor.rs` | 返回值改为 `CompressionResult` 含 `created_timelines` |
|
||
| 6 | `session/session.rs` | `Session` 加字段,`persist_session_meta` 加字段 |
|
||
| 7 | `session/session.rs` | `from_storage()` 重写恢复逻辑 |
|
||
| 8 | `session/session.rs` | `handle_message()` 压缩后记录标记 |
|
||
| 9 | `session/session.rs` | `/compact` 命令压缩后记录标记 |
|
||
| 10 | `session/session.rs` | `build_system_prompt()` 注入 `last_compressed_message_at` |
|