状态与记忆¶
演示 Agent 中常见的记忆与状态管理策略: -
BufferMemory:保留最近 N 轮对话(滑动窗口)。 -SummaryMemory:将历史压缩为摘要 + 最近若干轮。 -StateStore:键值状态存储,支持 TTL 过期。 全部基于内存实现,便于离线测试。 对应教程:第5卷《Agent 开发》第4章 状态与记忆。
文件路径: examples/volume-05-agent\state_memory.py
源代码¶
"""状态与记忆示例。
演示 Agent 中常见的记忆与状态管理策略:
- ``BufferMemory``:保留最近 N 轮对话(滑动窗口)。
- ``SummaryMemory``:将历史压缩为摘要 + 最近若干轮。
- ``StateStore``:键值状态存储,支持 TTL 过期。
全部基于内存实现,便于离线测试。
对应教程:第5卷《Agent 开发》第4章 状态与记忆。
"""
from __future__ import annotations
import time
from dataclasses import dataclass, field
from typing import Any
@dataclass
class BufferMemory:
"""滑动窗口式对话记忆。
Attributes:
window_size: 保留的最近消息条数。
"""
window_size: int = 10
_messages: list[dict[str, str]] = field(default_factory=list)
def add(self, role: str, content: str) -> None:
"""添加一条消息。
Args:
role: 角色。
content: 内容。
"""
self._messages.append({"role": role, "content": content})
if len(self._messages) > self.window_size:
# 保留最后 window_size 条
self._messages = self._messages[-self.window_size :]
def messages(self) -> list[dict[str, str]]:
"""返回当前记忆中的消息列表。"""
return list(self._messages)
def clear(self) -> None:
"""清空记忆。"""
self._messages.clear()
def token_estimate(self, chars_per_token: int = 4) -> int:
"""估算当前记忆占用 token 数。
Args:
chars_per_token: 每 token 字符数。
Returns:
估算 token 数。
"""
total_chars = sum(len(m["content"]) for m in self._messages)
return total_chars // chars_per_token
@dataclass
class SummaryMemory:
"""摘要式记忆:保留一个滚动摘要 + 最近若干条原始消息。
Attributes:
recent_size: 保留的最近原始消息条数。
"""
recent_size: int = 4
_summary: str = ""
_recent: list[dict[str, str]] = field(default_factory=list)
_summarizer_calls: int = 0
def add(self, role: str, content: str, summarizer=None) -> None:
"""添加消息,并在最近消息超量时触发摘要压缩。
Args:
role: 角色。
content: 内容。
summarizer: 可选的摘要函数 ``f(old_summary, dropped_messages) -> new_summary``。
为空时使用简单的拼接。
"""
self._recent.append({"role": role, "content": content})
if len(self._recent) > self.recent_size * 2:
# 把较早的一半折叠进摘要
to_drop = self._recent[: self.recent_size]
self._recent = self._recent[self.recent_size :]
old_summary = self._summary
if summarizer is not None:
self._summary = summarizer(old_summary, to_drop)
self._summarizer_calls += 1
else:
dropped_text = " ".join(m["content"] for m in to_drop)
self._summary = (old_summary + " " + dropped_text).strip()
@property
def summary(self) -> str:
"""当前摘要。"""
return self._summary
@property
def summarizer_calls(self) -> int:
"""摘要函数被调用的次数(便于测试)。"""
return self._summarizer_calls
def messages(self) -> list[dict[str, str]]:
"""返回对外可见的消息列表(摘要 + 最近消息)。"""
result: list[dict[str, str]] = []
if self._summary:
result.append({"role": "system", "content": f"对话摘要:{self._summary}"})
result.extend(self._recent)
return result
@dataclass
class StateStore:
"""带 TTL 的键值状态存储。
Attributes:
default_ttl: 默认存活秒数;``None`` 表示永不过期。
"""
default_ttl: float | None = None
_data: dict[str, tuple[Any, float | None]] = field(default_factory=dict)
def set(self, key: str, value, ttl: float | None = "default") -> None: # type: ignore[assignment]
"""设置键值。
Args:
key: 键。
value: 值。
ttl: 存活秒数;``"default"`` 使用 ``default_ttl``,``None`` 表示永不过期。
"""
if ttl == "default":
ttl = self.default_ttl
expire_at = (time.monotonic() + ttl) if ttl is not None else None
self._data[key] = (value, expire_at)
def get(self, key: str, default=None):
"""获取键值,过期则删除并返回 default。"""
if key not in self._data:
return default
value, expire_at = self._data[key]
if expire_at is not None and time.monotonic() >= expire_at:
del self._data[key]
return default
return value
def delete(self, key: str) -> bool:
"""删除键。
Returns:
是否删除成功(键是否存在)。
"""
return self._data.pop(key, None) is not None
def cleanup_expired(self) -> int:
"""清理所有过期键。
Returns:
清理的键数量。
"""
now = time.monotonic()
expired = [k for k, (_, t) in self._data.items() if t is not None and now >= t]
for k in expired:
del self._data[k]
return len(expired)
def main() -> None:
"""演示三种记忆/状态组件。"""
print("== BufferMemory ==")
buf = BufferMemory(window_size=3)
for i in range(5):
buf.add("user", f"msg{i}")
print(buf.messages())
print("\n== SummaryMemory ==")
sm = SummaryMemory(recent_size=2)
for i in range(6):
sm.add("user", f"片段{i}")
print("摘要:", sm.summary)
print("消息:", sm.messages())
print("\n== StateStore ==")
store = StateStore(default_ttl=100)
store.set("k1", "v1")
store.set("k2", "v2", ttl=0.0) # 立即过期
print("k1:", store.get("k1"))
print("k2:", store.get("k2", "已过期"))
if __name__ == "__main__":
main()