agentscope-ai / agentscope-ai/agentscope
[Bug]:RedisMemory的_compressed_summary仅存在于内存,对于多节点服务或进程重启后会出现数据不一致
- 主要语言
- Python
- 星标
- 31.5k
- 派生
- 3.5k
- 平均合并
- 1 天 23 小时
- 30 天内合并 PR
- 95
描述
## `_compressed_summary` 在 RedisMemory 中的存储现状
### 结论:**仅存在于 Python 内存,完全不在 Redis 中**
查遍 [`RedisMemory`] 整个类,它定义了 4 个 Redis 键模式常量,没有任何一个与摘要相关:
```python
SESSION_KEY = "user_id:{uid}:session:{sid}:messages" # 消息ID有序列表
MESSAGE_KEY = "user_id:{uid}:session:{sid}:msg:{msg_id}" # 消息JSON内容
MARK_KEY = "user_id:{uid}:session:{sid}:mark:{mark}" # 标记下的消息ID
MARKS_INDEX_KEY = "user_id:{uid}:session:{sid}:marks_index" # 标记名称集合
# ❌ 没有 SUMMARY_KEY
```
`_compressed_summary` 仅继承自 [`MemoryBase`]的内存初始化:
```python
# MemoryBase.__init__()
self._compressed_summary: str = ""
self.register_state("_compressed_summary") # 只注册到 StateModule,不写 Redis
```
`RedisMemory` **没有覆盖** `update_compressed_summary()`、`state_dict()` 或 `load_state_dict()`,因此摘要永远不会进入 Redis。
---
## 与普通消息数据的存储对比
| 特性 | 普通消息 | `_compressed_summary` |
|------|---------|----------------------|
| 存储位置 | Redis(`msg:{id}` 键) | Python 对象内存 |
| 进程重启后 | ✅ 持久保留 | ❌ 丢失 |
| 多实例共享 | ✅ 支持 | ❌ 不支持 |
| 跨 session 恢复 | ✅ 有机制 | ❌ 没有机制 |
| 序列化方式 | `json.dumps(msg.to_dict())` | 无(只在内存) |
---
## 由此导致的潜在问题
当 `RedisMemory` + 压缩机制组合使用,**进程重启后会出现数据不一致**:
```
重启前(内存中):
Redis: [Msg_A(compressed), Msg_B(compressed), Msg_C, Msg_D]
内存: _compressed_summary = "摘要..."
重启后(从 Redis 恢复):
Redis: [Msg_A(compressed), Msg_B(compressed), Msg_C, Msg_D] ← 仍在
内存: _compressed_summary = "" ← ❌ 丢失!
结果:
get_memory(exclude_mark="compressed") 返回 [Msg_C, Msg_D]
但没有摘要前置 → 大量上下文永久丢失
```
---
## 对比 InMemoryMemory 的正确做法
[`InMemoryMemory`] 通过 `JSONSession` / `RedisSession` 持久化时,覆盖了 `state_dict()` 确保摘要被序列化:
```python
def state_dict(self) -> dict:
return {
**super().state_dict(), # 包含 _compressed_summary
"content": [...],
}
def load_state_dict(self, state_dict):
self._compressed_summary = state_dict.get("_compressed_summary", "") # 恢复摘要
...
```
---
## 如果需要在 RedisMemory 中持久化摘要
需要给 `RedisMemory` 补充 `SUMMARY_KEY` 和覆盖 `update_compressed_summary` 方法:
```python
SUMMARY_KEY = "user_id:{user_id}:session:{session_id}:summary"
async def update_compressed_summary(self, summary: str) -> None:
self._compressed_summary = summary # 同步内存
# 同时写入 Redis
key = self.key_prefix + self.SUMMARY_KEY.format(
user_id=self.user_id,
session_id=self.session_id,
)
await self._client.set(key, summary)
await self._refresh_session_ttl()
```
并在 `__init__` 中加载已有摘要:
```python
# 从 Redis 恢复摘要(如果存在)
summary = await self._client.get(summary_key)
if summary:
self._compressed_summary = summary
```
这本质上是 `RedisMemory` 当前实现中的一个设计缺口。
贡献指南
评估
这个 Issue 还没有评估数据。