凌晨两点,用户群里发来一张截图:AI 客服回复工单时,把另一个用户的手机号和收货地址带了出来。我第一反应是模型幻觉,结果查 Redis 发现,两个不同 user_id 的会话共用了同一个 session_id 作为 key,上下文直接串号。这不是模型的问题,是记忆层在裸奔。后来我用 pytest + Redis 搭了一套一致性验证,才把这类 bug 摁死。
问题拆解
AI Agent 的记忆存储跟普通缓存不一样:缓存丢了只是慢一点,记忆串了会直接泄露隐私、带偏对话。我们团队用 Redis 存 Agent 的短期记忆,多轮对话靠它保持连贯。上线后偶发出现 A 用户看到 B 的历史消息。根因不是模型,而是记忆层的三类问题:key 设计缺少用户维度、TTL 刷新逻辑混乱、序列化协议不统一。常规方案为什么不行?靠 code review 和手工测试发现不了,因为串号是并发、边界条件叠加后的结果;mock Redis 又验证不了真实 TTL 和原子性。最后我决定把记忆层当基础设施,用 pytest + Redis 写一致性测试。
方案设计
选 pytest + Redis 而不是单测 mock,核心原因是 Redis 的真实行为------TTL 计算、WATCH 乐观锁、序列化后的二进制差异------是 mock 模拟不出来的。pytest 的 fixture 可以给每个测试一个干净的 db,真实 Redis 用 Docker 起一个即可。设计上我们把记忆读写封装成 MemoryStore,然后针对三类不变量写测试:key 隔离性、TTL 与版本兼容、并发追加一致性。为什么不选直接用生产库测?太危险;为什么不选只写单测?因为单测会把 Redis 当普通 dict 用,掩盖 TTL 和并发问题。
核心实现
这段代码解决测试隔离问题:每个测试用例拿到一个独立的 Redis db,避免数据串号影响断言。
python
# conftest.py
import pytest
import redis
@pytest.fixture
def redis_client():
"""连接本地 Redis,测试前清空当前 db,保证隔离"""
client = redis.Redis(host="localhost", port=6379, db=15, decode_responses=True)
client.flushdb()
yield client
client.flushdb()
client.close()
这个类封装了生产级的 Redis 记忆读写,包含 key 命名空间、JSON 版本化、SETEX TTL、以及并发安全的 WATCH 追加。关键行加了注释。
python
# memory_store.py
import json
from typing import Any, Dict, List, Optional
import redis
from redis.exceptions import WatchError
class MemoryStore:
"""AI Agent 短期记忆存储,负责 key 隔离、TTL 和版本化序列化"""
def __init__(self, client: redis.Redis, ttl: int = 3600):
self.client = client
self.ttl = ttl
self.version = 1 # 序列化