Memory系统的学习和计划(临时存放)
记忆系统开发计划书 —— 让 Agent 拥有长期记忆
本文档兼具计划与教学双重目的。每一章节分两部分:
- 「概念讲解」 — 解释为什么这样设计、背后的理论
- 「实施计划」 — 具体的实现步骤和技术细节
如果你对某个概念已经有了解,可以直接跳到实施部分。
目录
- 一、什么是 Agent 记忆系统?
- 二、记忆的三种类型
- 三、架构设计:MemoryRegistry
- 四、Phase 1:记忆核心层
- 五、Phase 2:记忆存储后端
- 六、Phase 3:记忆检索与召回
- 七、Phase 4:Agent 集成
- 八、Phase 5:记忆维护与优化
- 九、项目结构与测试
- 十、学习路径建议
一、什么是 Agent 记忆系统?
概念讲解
当前的 Agent 框架每次运行时,LLM 只能看到当前对话中传入的消息。这意味着:
你:帮我查一下北京的天气
Agent:北京今天25°C
你(5分钟后):我上次查的哪个城市来着?
Agent:……我不知道(因为上次对话已经结束了)
这就是 无状态(stateless) 的问题。每次对话都是"失忆"的。
记忆系统要解决的核心问题就是:
让 Agent 能跨越对话边界,记住、回忆、运用过去的信息。
一个完整的记忆系统会做三件事:
| 能力 | 比喻 | 技术含义 |
|---|---|---|
| 记住 | 像人类记笔记 | 把信息写入某种持久化存储 |
| 回忆 | 像人类翻笔记 | 从存储中检索出相关的信息 |
| 运用 | 像人类结合笔记思考 | 将检索到的信息注入 LLM 上下文中 |
与现有框架的关系
回顾我们框架的 Registry 架构:
Kernel
├── ToolRegistry → Agent 能"做什么"
├── ConfigRegistry → Agent "怎么配置"
├── InfoRegistry → Agent 当前"知道什么"(运行时内存)
├── ProviderRegistry → 谁在"思考"
├── AgentRegistry → 哪些"策略"可用
├── ChainRegistry → 如何"编排"
├── LogRegistry → 发生了什么
└── ❓ MemoryRegistry → Agent "记得什么" ← 我们要实现的
这里要注意区分 InfoRegistry 和 MemoryRegistry:
| 维度 | InfoRegistry | MemoryRegistry(新) |
|---|---|---|
| 用途 | 运行时上下文交换 | 长期知识保留 |
| 生命周期 | 一次会话(纯内存) | 跨会话持久化 |
| 数据结构 | 简单键值对 | 结构化 + 向量索引 |
| 容量 | 小(KB 级) | 大(MB/GB 级) |
| 查询方式 | 精确 key 查找 | 语义搜索 + 按条件过滤 |
| 典型数据 | 当前任务ID、会话状态 | 用户偏好、历史对话摘要、事实知识 |
二、记忆的三种类型
概念讲解
认知科学中,人类的记忆分为三类。Agent 记忆系统也借鉴了这个分类:
记忆
│
┌────┴────┐
│ │
短期记忆 长期记忆
│
┌────┼────┐
│ │ │
情景 语义 程序
1. 情景记忆(Episodic Memory)
"我记得昨天下午3点查过北京的天气。"
- 存什么:过去发生的具体事件、交互记录
- 特点:有时间戳、有上下文、按时间顺序
- Agent 场景:历史对话记录、过去执行过的任务、用户提过的请求
- 实现方式:JSON/JSONL 存储,按时间倒序检索
2. 语义记忆(Semantic Memory)
"我记得用户叫张三,他喜欢简洁的回答。"
- 存什么:从经历中提取的事实性知识、概念、关系
- 特点:去除了时间上下文,只保留事实本身
- Agent 场景:用户的个人信息、偏好设置、领域知识、从对话中提炼的关键信息
- 实现方式:结构化存储 + 向量嵌入,支持语义检索
3. 程序记忆(Procedural Memory)
"我记得上次处理这种问题是用三步流程解决的。"
- 存什么:如何做事的模式、流程、技能
- 特点:隐式知识,通常通过反复执行习得
- Agent 场景:用户偏好的工作流、常用的工具组合、问题解决的策略模式
- 实现方式:存储为模板/流程描述,按场景匹配
在当前框架中的定位
MemoryRegistry
│
├── EpisodicMemory → 情景记忆(对话历史、事件记录)
├── SemanticMemory → 语义记忆(用户事实、知识抽取)
└── ProceduralMemory → 程序记忆(工作流模式、策略模板)
三、架构设计:MemoryRegistry
概念讲解
在设计记忆系统时,我们遵循开闭原则:
对扩展开放,对修改关闭。
具体来说:
- 不修改现有的 Kernel、Agent、Provider 源码(遵循 CLAUDE.md 规则 5)
- 记忆系统作为独立的 Registry 插件接入框架
- 存储后端可插拔:文件系统 / 向量数据库 / 内存,可以灵活切换
架构总览
┌──────────────────────────────────────────────────────┐
│ MemoryRegistry │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Memory Store (接口抽象) │ │
│ │ ┌──────────┐ ┌───────────┐ ┌───────────────┐ │ │
│ │ │ FileStore │ │ VectorStore │ │ InMemoryStore│ │ │
│ │ └──────────┘ └───────────┘ └───────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Episodic │ │ Semantic │ │Procedural│ ← 三种类型 │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Recall 引擎 │ │
│ │ 检索 → 排序 → 裁剪 → 格式化 → 注入上下文 │ │
│ └──────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
MemoryRegistry 接口设计草案
class MemoryRegistry(BaseRegistry):
@property
def name(self) -> str:
return "memory"
# --- 写操作 ---
def remember(self, memory_type: str, content: Any, metadata: dict = None) -> str:
"""存储一条记忆。memory_type: "episodic" | "semantic" | "procedural" """
def forget(self, memory_id: str) -> bool:
"""删除一条记忆。"""
def clear(self, memory_type: str = None) -> None:
"""清空指定类型的记忆。"""
# --- 读操作 ---
def recall(self, query: str, memory_type: str = None, limit: int = 5) -> list:
"""回忆——根据查询检索最相关的记忆。"""
def remember_by_id(self, memory_id: str) -> dict | None:
"""按 ID 精确查找一条记忆。"""
def recent(self, memory_type: str = None, limit: int = 10) -> list:
"""获取最近的记忆(时间降序)。"""
# --- 维护 ---
def consolidate(self, memory_type: str = None) -> int:
"""记忆整合:压缩、去重、摘要化。"""
def stats(self) -> dict:
"""记忆统计:总数、按类型分布、存储占用。"""
数据模型
@dataclass
class MemoryItem:
"""单条记忆的通用数据模型。"""
id: str # 唯一标识(UUID)
type: str # "episodic" | "semantic" | "procedural"
content: str # 记忆内容(文本)
metadata: dict # 元数据(时间戳、来源、重要性等)
embedding: list[float] | None = None # 向量嵌入(用于语义搜索)
created_at: str = "" # 创建时间 ISO 格式
updated_at: str = "" # 最后更新时间
access_count: int = 0 # 访问次数(用于重要性排序)
importance: float = 0.5 # 重要性评分 [0, 1]
四、Phase 1:记忆核心层
实现目标
完成 MemoryRegistry 的基础骨架、数据模型和内存存储后端。
实施步骤
Step 1.1:创建目录和基础文件
myAgent/agent_frame/memory/
├── __init__.py
├── models.py # MemoryItem 等数据模型
├── stores.py # 存储后端抽象和实现
├── registry.py # MemoryRegistry 主类
└── recall.py # 检索与召回引擎
Step 1.2:定义数据模型 memory/models.py
from dataclasses import dataclass, field
from datetime import datetime
import uuid
MemoryType = str # "episodic" | "semantic" | "procedural"
@dataclass
class MemoryItem:
id: str = field(default_factory=lambda: uuid.uuid4().hex)
type: MemoryType = "episodic"
content: str = ""
metadata: dict = field(default_factory=dict)
embedding: list[float] | None = None
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
updated_at: str = field(default_factory=lambda: datetime.now().isoformat())
access_count: int = 0
importance: float = 0.5
def to_dict(self) -> dict:
return {
"id": self.id,
"type": self.type,
"content": self.content,
"metadata": self.metadata,
"created_at": self.created_at,
"updated_at": self.updated_at,
"access_count": self.access_count,
"importance": self.importance,
}
@classmethod
def from_dict(cls, data: dict) -> "MemoryItem":
return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
Step 1.3:定义存储后端接口 memory/stores.py
from abc import ABC, abstractmethod
from .models import MemoryItem
class BaseMemoryStore(ABC):
"""存储后端抽象。所有具体实现必须继承此类。"""
@abstractmethod
def save(self, item: MemoryItem) -> str:
"""保存一条记忆,返回其 ID。"""
...
@abstractmethod
def get(self, item_id: str) -> MemoryItem | None:
"""按 ID 获取记忆。"""
...
@abstractmethod
def delete(self, item_id: str) -> bool:
"""删除一条记忆。"""
...
@abstractmethod
def list(self, memory_type: str = None, limit: int = 100) -> list[MemoryItem]:
"""列出记忆,可选按类型过滤。"""
...
@abstractmethod
def search(self, query: str, memory_type: str = None, limit: int = 5) -> list[MemoryItem]:
"""搜索记忆(至少支持关键词搜索,向量搜索可选)。"""
...
@abstractmethod
def count(self, memory_type: str = None) -> int:
"""统计记忆数量。"""
...
@abstractmethod
def clear(self, memory_type: str = None) -> None:
"""清空记忆。"""
...
Step 1.4:实现内存存储后端
class InMemoryStore(BaseMemoryStore):
"""基于 dict 的内存存储,重启丢失。适合开发和测试。"""
def __init__(self):
self._items: dict[str, MemoryItem] = {}
def save(self, item: MemoryItem) -> str:
item.updated_at = datetime.now().isoformat()
self._items[item.id] = item
return item.id
def get(self, item_id: str) -> MemoryItem | None:
item = self._items.get(item_id)
if item:
item.access_count += 1
return item
def delete(self, item_id: str) -> bool:
return self._items.pop(item_id, None) is not None
def list(self, memory_type: str = None, limit: int = 100) -> list[MemoryItem]:
items = self._items.values()
if memory_type:
items = [i for i in items if i.type == memory_type]
sorted_items = sorted(items, key=lambda x: x.created_at, reverse=True)
return sorted_items[:limit]
def search(self, query: str, memory_type: str = None, limit: int = 5) -> list[MemoryItem]:
"""简单关键词搜索(不分词)。"""
items = self._items.values()
if memory_type:
items = [i for i in items if i.type == memory_type]
query_lower = query.lower()
matched = []
for item in items:
if query_lower in item.content.lower():
matched.append(item)
return sorted(matched, key=lambda x: x.importance, reverse=True)[:limit]
def count(self, memory_type: str = None) -> int:
if memory_type:
return sum(1 for i in self._items.values() if i.type == memory_type)
return len(self._items)
def clear(self, memory_type: str = None) -> None:
if memory_type:
self._items = {k: v for k, v in self._items.items() if v.type != memory_type}
else:
self._items.clear()
Step 1.5:实现 MemoryRegistry 主类
class MemoryRegistry(BaseRegistry):
"""记忆系统主 Registry。"""
@property
def name(self) -> str:
return "memory"
def __init__(self, store: BaseMemoryStore | None = None):
super().__init__()
self._store = store or InMemoryStore()
def remember(self, memory_type: str, content: str, metadata: dict = None, importance: float = 0.5) -> str:
item = MemoryItem(
type=memory_type,
content=content,
metadata=metadata or {},
importance=importance,
)
return self._store.save(item)
def recall(self, query: str, memory_type: str = None, limit: int = 5) -> list[MemoryItem]:
return self._store.search(query, memory_type, limit)
def recent(self, memory_type: str = None, limit: int = 10) -> list[MemoryItem]:
return self._store.list(memory_type, limit)
def forget(self, item_id: str) -> bool:
return self._store.delete(item_id)
def clear(self, memory_type: str = None) -> None:
self._store.clear(memory_type)
def stats(self) -> dict:
return {
"total": self._store.count(),
"episodic": self._store.count("episodic"),
"semantic": self._store.count("semantic"),
"procedural": self._store.count("procedural"),
}
Step 1.6:注册到 agent_frame/__init__.py
在 __init__.py 中导出 MemoryRegistry,使其成为框架的一等公民:
from agent_frame.memory.registry import MemoryRegistry
from agent_frame.memory.models import MemoryItem
验收标准
# 运行以下代码验证 Phase 1
kernel = Kernel()
kernel.install(MemoryRegistry())
mem = kernel.registry("memory")
mem.remember("semantic", "用户喜欢简洁的回答", {"source": "对话"})
mem.remember("episodic", "2024-03-15 用户查询了北京的天气", {"city": "北京"})
results = mem.recall("天气")
assert len(results) > 0
assert results[0].type == "episodic"
print(mem.stats())
# {"total": 2, "episodic": 1, "semantic": 1, "procedural": 0}
五、Phase 2:记忆存储后端
概念讲解
选择存储后端时,需要理解两个概念:
关系型 vs 向量型搜索
传统搜索(关键词) 语义搜索(向量)
│ │
用户搜"天气" 用户搜"今天出门需要带什么"
│ │
匹配含"天气"的记录 匹配含义相似的记录
│ │
"北京天气25°C" ✅ "今天有雨,记得带伞" ✅
"天气预报准确吗" ✅ "北京今天25°C" ❌(数值不匹配但含义相关)
"我今天很开心" ❌ "我今天很开心" ❌(含义不相关)
关键词搜索:靠字面匹配,简单直接,适合精确查找
语义搜索:靠向量距离,理解含义,适合模糊回忆
两者互补,实际系统通常两者都用。
向量嵌入(Embedding)的概念
嵌入是把文字变成一组数字(向量)的技术。含义相近的文字,它们的向量距离也更近。
"我喜欢猫" → [0.12, 0.85, -0.33, 0.67, ...] ← 向量
"我养了一只橘猫" → [0.15, 0.82, -0.30, 0.70, ...] ← 距离近
"今天天气不错" → [-0.45, 0.12, 0.78, -0.23, ...] ← 距离远
生成向量的服务称为 Embedding Model(嵌入模型),常见的有:
- OpenAI
text-embedding-3-small - 本地模型
BGE-M3、text2vec - 框架中可以通过 Provider 扩展支持
实施计划
Step 2.1:文件持久化存储(FileStore)
实现基于 JSON Lines 的持久化存储:
class FileStore(BaseMemoryStore):
"""基于 JSON Lines 文件的持久化存储。"""
def __init__(self, file_path: str = "memory.jsonl"):
self._file_path = file_path
self._items: dict[str, MemoryItem] = {}
self._load_from_disk()
def _load_from_disk(self) -> None:
"""启动时从文件加载已有记忆。"""
path = Path(self._file_path)
if not path.exists():
return
with open(path, "r", encoding="utf-8") as f:
for line in f:
data = json.loads(line)
item = MemoryItem.from_dict(data)
self._items[item.id] = item
def save(self, item: MemoryItem) -> str:
self._items[item.id] = item
self._flush_to_disk()
return item.id
def _flush_to_disk(self) -> None:
"""全部写入文件(适合小规模数据)。"""
path = Path(self._file_path)
path.parent.mkdir(parents=True, exist_ok=True)
with open(path, "w", encoding="utf-8") as f:
for item in self._items.values():
f.write(json.dumps(item.to_dict(), ensure_ascii=False) + "\n")
# ... 其他方法同 InMemoryStore
设计选择说明:
- 使用 JSON Lines(每行一条 JSON)而非 JSON 数组
- 优点:追加写入高效,不需要整体解析,方便手动查看和编辑
- 缺点:删除和修改需要重写整个文件
- 适合记忆量 < 10 万条的场景
Step 2.2:向量存储后端(VectorStore)
基于 ChromaDB(轻量级嵌入式向量数据库):
class ChromaStore(BaseMemoryStore):
"""基于 ChromaDB 的向量存储后端。"""
def __init__(self, collection_name: str = "agent_memory", persist_dir: str = "./chroma_db"):
import chromadb
self._client = chromadb.PersistentClient(path=persist_dir)
self._collection = self._client.get_or_create_collection(
name=collection_name,
metadata={"hnsw:space": "cosine"}, # 使用余弦距离
)
def save(self, item: MemoryItem) -> str:
# ChromaDB 会自动文本转向量(如果配置了 embedding 函数)
self._collection.add(
ids=[item.id],
documents=[item.content],
metadatas=[{
"type": item.type,
"created_at": item.created_at,
"importance": item.importance,
**item.metadata,
}],
)
return item.id
def search(self, query: str, memory_type: str = None, limit: int = 5) -> list[MemoryItem]:
where = {"type": memory_type} if memory_type else None
results = self._collection.query(
query_texts=[query],
n_results=limit,
where=where,
)
# 将 ChromaDB 结果转换为 MemoryItem 列表
...
Step 2.3:接入 Embedding API
向量搜索需要 embedding,有两种方案:
方案 A:LLM Provider 提供 embedding(推荐)
利用现有的 Provider 体系,在 BaseProvider 上添加 embedding 方法:
class BaseProvider(ABC):
@abstractmethod
def chat(self, messages, **kwargs): ...
@abstractmethod
def chat_stream(self, messages, **kwargs): ...
# 新增:
def embed(self, texts: list[str]) -> list[list[float]]:
"""将文本转为向量。默认抛出 NotImplementedError。"""
raise NotImplementedError("This provider does not support embedding")
然后 OpenAI 和 Anthropic Provider 各自实现:
class OpenAIProvider(BaseProvider):
def embed(self, texts: list[str]) -> list[list[float]]:
# 调用 OpenAI Embedding API
url = f"{self.base_url}/embeddings"
resp = self._client.post(url, json={
"model": "text-embedding-3-small",
"input": texts,
})
data = resp.json()
return [d["embedding"] for d in data["data"]]
方案 B:独立 Embedding 服务
创建一个 EmbeddingRegistry 专门管理 embedding 模型:
class EmbeddingRegistry(BaseRegistry):
"""管理 Embedding 模型,独立于 LLM Provider。"""
@property
def name(self) -> str:
return "embedding"
def use(self, name: str, model_class, **config): ...
def embed(self, texts: list[str]) -> list[list[float]]: ...
推荐方案 A——因为用户已经在用 DeepSeek(Anthropic 兼容接口),而 DeepSeek 不提供 embedding API。如果方案 A 走不通,可以回退到:
- 在 VectorStore 内使用一个轻量本地 embedding 模型
- 或用方案 B 独立配置一个 embedding 来源
验收标准
# 文件持久化验证
store = FileStore("test_memory.jsonl")
store.save(MemoryItem(content="测试记忆"))
store2 = FileStore("test_memory.jsonl") # 重新加载
assert store2.count() == 1
# 向量搜索验证(如果用 ChromaDB + embedding)
vstore = ChromaStore()
vstore.save(MemoryItem(id="1", content="用户喜欢喝咖啡"))
vstore.save(MemoryItem(id="2", content="用户喜欢打篮球"))
results = vstore.search("饮料偏好")
assert results[0].id == "1" # "咖啡" 与 "饮料偏好" 语义相关
六、Phase 3:记忆检索与召回
概念讲解
记忆不是存进去就完事了,关键在于如何让 Agent 在正确的时候想起正确的事。
检索策略决定了记忆系统的"智商"。以下是几种常见策略,我们从最简单到最复杂排序:
策略1:最近优先(Recency)
用户最后提到的 5 件事 → 最可能和当前对话相关
策略2:关键词匹配(Keyword)
用户当前提到"天气" → 检索含"天气"的历史记录
策略3:语义相关(Semantic)
用户说"今天出门需要带什么" → 检索含"雨、温度、天气"的记忆
策略4:重要性优先(Importance)
用户反复提到的事 → 比一次性的事更重要
策略5:混合策略(Hybrid)
综合以上策略加权排序 ← 最接近人类回忆的方式
实施计划
Step 3.1:Recall 引擎
# memory/recall.py
@dataclass
class RecallResult:
"""召回结果,带评分。"""
item: MemoryItem
score: float # 相关性评分 [0, 1]
strategy: str # 由哪个策略召回
class RecallEngine:
"""记忆召回引擎——组合多种检索策略。"""
def __init__(self, store: BaseMemoryStore):
self._store = store
self._strategies = [
SemanticStrategy(store, weight=0.5), # 语义相关,权重最高
RecencyStrategy(store, weight=0.3), # 最近优先
ImportanceStrategy(store, weight=0.2), # 重要性优先
]
def recall(
self,
query: str,
memory_type: str = None,
limit: int = 5,
min_score: float = 0.1,
) -> list[RecallResult]:
"""综合召回:混合策略 → 去重 → 加权排序 → 截断。"""
candidates: dict[str, RecallResult] = {}
for strategy in self._strategies:
results = strategy.retrieve(query, memory_type, limit * 2)
for r in results:
if r.item.id in candidates:
# 多个策略都命中的记忆,累加分数
candidates[r.item.id].score += r.score * strategy.weight
else:
candidates[r.item.id] = r
candidates[r.item.id].score *= strategy.weight
# 按分数排序,过滤低分
sorted_results = sorted(
candidates.values(),
key=lambda x: x.score,
reverse=True,
)
return [r for r in sorted_results if r.score >= min_score][:limit]
Step 3.2:实现各检索策略
class BaseRecallStrategy(ABC):
"""检索策略基类。"""
def __init__(self, store: BaseMemoryStore, weight: float = 1.0):
self._store = store
self.weight = weight
@abstractmethod
def retrieve(self, query: str, memory_type: str = None, limit: int = 5) -> list[RecallResult]:
...
class SemanticStrategy(BaseRecallStrategy):
"""语义检索(需要向量存储后端支持)。"""
def retrieve(self, query, memory_type=None, limit=5) -> list[RecallResult]:
items = self._store.search(query, memory_type, limit)
return [
RecallResult(item=item, score=item.importance, strategy="semantic")
for item in items
]
class RecencyStrategy(BaseRecallStrategy):
"""最近优先——按时间倒序。"""
def retrieve(self, query, memory_type=None, limit=5) -> list[RecallResult]:
items = self._store.list(memory_type, limit)
# 分数按排名递减:最新的最高分
return [
RecallResult(item=item, score=max(0, 1 - i / limit), strategy="recency")
for i, item in enumerate(items)
]
class ImportanceStrategy(BaseRecallStrategy):
"""重要性优先——按 access_count 和 importance 的综合评分。"""
def retrieve(self, query, memory_type=None, limit=5) -> list[RecallResult]:
items = self._store.list(memory_type, limit * 3)
scored = sorted(
items,
key=lambda x: x.importance * 0.6 + min(x.access_count / 10, 1) * 0.4,
reverse=True,
)
return [
RecallResult(item=item, score=max(0, 1 - i / limit), strategy="importance")
for i, item in enumerate(scored[:limit])
]
Step 3.3:记忆注入上下文
检索到的记忆需要以某种格式注入到 Agent 的 prompt 中:
class MemoryInjector:
"""将记忆注入到 Agent 的 system prompt 中。"""
def __init__(self, memory_reg: MemoryRegistry, max_tokens: int = 1000):
self._memory = memory_reg
self._max_tokens = max_tokens
def build_context(self, user_message: str) -> str:
"""根据用户消息构建记忆上下文块。"""
results = self._memory.recall(user_message, limit=5)
if not results:
return ""
parts = ["以下是与当前对话相关的历史记忆:"]
for i, r in enumerate(results, 1):
item = r.item
time_str = item.created_at[:10] if item.created_at else "未知时间"
parts.append(f"{i}. [{item.type}] ({time_str}) {item.content}")
return "\n".join(parts)
def inject_to_system_prompt(self, system_prompt: str, user_message: str) -> str:
"""将记忆块注入 system prompt 末尾。"""
context = self.build_context(user_message)
if not context:
return system_prompt
return f"{system_prompt}\n\n---\n{context}"
验收标准
mem = MemoryRegistry(store=FileStore("test_mem.jsonl"))
# 写入一批记忆
mem.remember("episodic", "用户说喜欢用 Python", {"topic": "language"})
mem.remember("semantic", "用户的编程语言偏好: Python", {"fact": True}, importance=0.9)
mem.remember("episodic", "用户问过如何用 Python 做数据分析", {})
# 召回
results = mem.recall("用户用什么语言", limit=3)
assert len(results) >= 1
assert "Python" in results[0].item.content
# 注入
injector = MemoryInjector(mem)
context = injector.build_context("帮我写个脚本")
assert "Python" in context # 相关的记忆被注入
七、Phase 4:Agent 集成
概念讲解
记忆系统只有和 Agent 结合起来才有意义。集成方式决定了记忆在何时被调用。
有三种集成时机:
Agent 执行流程
│
│ ← 步骤A:注入相关记忆到 system prompt
│
用户输入消息
│
│ ← 步骤B:Agent 处理(LLM 调用 + 工具执行)
│
生成回复
│
│ ← 步骤C:将本次交互提炼为记忆并存储
│
▼
| 步骤 | 时机 | 做什么 | 记忆类型 |
|---|---|---|---|
| A | Agent.run() 开头 | 检索相关记忆,注入上下文 | 语义记忆(用户偏好、事实) |
| C | Agent.run() 结尾 | 将本次交互摘要后存储 | 情景记忆(发生了什么) |
实施计划
Step 4.1:创建记忆型 Agent 封装
在 example/ 目录下创建一个使用记忆的 Agent 示例,而非修改 agent_frame/agents/ 中的源码(遵循 CLAUDE.md 规则 5)。
# example/memory_agent.py
"""带记忆的 Agent 示例——基于框架现有 Agent 扩展,不修改框架源码。"""
from agent_frame import Kernel
from agent_frame.providers.anthropic import AnthropicProvider # 使用 DeepSeek
from agent_frame.memory.registry import MemoryRegistry
from agent_frame.memory.stores import FileStore
from agent_frame.memory.recall import MemoryInjector
class MemoryCapableAgent:
"""记忆增强 Agent 包装器。
不继承 BaseAgent,而是组合(Composition)方式:
内部使用 ReActAgent / SimpleAgent 处理核心逻辑,
在调用前后添加记忆检索和存储。
"""
def __init__(self, agent, memory_reg: MemoryRegistry):
self._agent = agent
self._memory = memory_reg
self._injector = MemoryInjector(memory_reg)
self._session_history: list[dict] = []
def run(self, message: str, **kwargs):
# 1. 检索相关记忆并注入
memory_context = self._injector.build_context(message)
system_prompt = kwargs.pop("system_prompt", None) or ""
if memory_context:
system_prompt = self._injector.inject_to_system_prompt(system_prompt, message)
# 2. 执行 Agent
response = self._agent.run(message, system_prompt=system_prompt, **kwargs)
# 3. 将本次交互存入情景记忆
self._memory.remember(
memory_type="episodic",
content=f"用户说: {message[:200]}",
metadata={"response_preview": response.content[:200]},
)
# 4. 尝试提取语义记忆(关键事实)
self._extract_semantic_memory(message, response.content)
return response
def _extract_semantic_memory(self, message: str, response: str) -> None:
"""从交互中提取重要事实,存入语义记忆。
实际实现中可以调用 LLM 做信息提取,这里简化处理。
"""
# 检测是否包含事实性陈述(示例规则)
fact_markers = ["我叫", "我是", "我喜欢", "我要", "我住在"]
for marker in fact_markers:
if marker in message:
self._memory.remember(
memory_type="semantic",
content=f"用户信息: {message.strip()[:200]}",
metadata={"extracted_from": "对话"},
importance=0.8,
)
Step 4.2:实际使用示例
# example/run_memory_agent.py
"""运行带记忆的 Agent。"""
from agent_frame import Kernel
from agent_frame.agents.react import ReActAgent
from agent_frame.providers.openai import OpenAIProvider
from agent_frame.memory.registry import MemoryRegistry
from agent_frame.memory.stores import FileStore
from example.memory_agent import MemoryCapableAgent
# 初始化
kernel = Kernel()
# 核心 Registry
tool_reg = kernel.install(ToolRegistry()).registry("tool")
provider_reg = kernel.install(ProviderRegistry()).registry("provider")
provider_reg.use("deepseek", provider_class=AnthropicProvider,
api_key="...", base_url="https://api.deepseek.com/v1")
# 记忆系统
mem_reg = MemoryRegistry(store=FileStore("agent_memory.jsonl"))
kernel.install(mem_reg)
# 创建 Agent
react_agent = ReActAgent(provider_reg.get(), tool_reg)
memory_agent = MemoryCapableAgent(react_agent, mem_reg)
# 第一轮对话
resp1 = memory_agent.run("我叫张三,帮我查一下Python的创始人")
print(resp1.content)
# 第二轮对话——Agent 已经"记住"了用户名字
resp2 = memory_agent.run("我刚刚说了我叫什么名字?")
# 检索到之前的语义记忆 → 可以正确回答
Step 4.3:记忆系统的错误处理
在 errors.py 中新增记忆相关异常:
class MemoryError(AgentFrameError):
"""记忆系统相关错误的基类。"""
pass
class MemoryStoreError(MemoryError):
"""存储后端错误。"""
pass
class MemoryNotFound(MemoryError):
"""记忆不存在。"""
pass
八、Phase 5:记忆维护与优化
概念讲解
记忆系统运行一段时间后会面临两个问题:
问题1:遗忘曲线
记忆越多 → 检索越慢 → 相关记忆被淹没在不相关记忆中
问题2:存储膨胀
每条对话都存 → 存储无限增长 → 成本逐渐增加
解决方案:
自动遗忘机制:
不重要 + 很久没访问 = 可删除或压缩
记忆整合(Consolidation):
多条相似记忆 → 合并为一条摘要 → 释放空间
重要性评分:
被反复访问的记忆 → 重要性升高 → 更不容易被遗忘
实施计划
Step 5.1:自动遗忘机制
def auto_forget(self, max_items: int = 10000, ttl_days: int = 90) -> int:
"""自动遗忘策略:
1. 超过 max_items 时,删除最旧 + 最不重要的记忆
2. 超过 ttl_days 且重要性 < 阈值的记忆,删除
返回被删除的数量。
"""
deleted = 0
all_items = self._store.list()
# 策略1:超过容量时淘汰
if len(all_items) > max_items:
to_remove = len(all_items) - max_items
# 按 (importance 升序, created_at 升序) 排序
sorted_items = sorted(all_items, key=lambda x: (x.importance, x.created_at))
for item in sorted_items[:to_remove]:
self._store.delete(item.id)
deleted += 1
# 策略2:TTL 过期淘汰
cutoff = (datetime.now() - timedelta(days=ttl_days)).isoformat()
for item in all_items:
if item.created_at < cutoff and item.importance < 0.3:
if self._store.delete(item.id):
deleted += 1
return deleted
Step 5.2:记忆整合
def consolidate(self, memory_type: str = "episodic") -> int:
"""整合同类型记忆。
将内容相似、时间相近的记忆合并为摘要。
需要 LLM 协助生成摘要。
"""
items = self._store.list(memory_type, limit=1000)
# 按时间窗口分组(例如同一天的归为一组)
groups = {}
for item in items:
day = item.created_at[:10] # "2024-03-15"
if day not in groups:
groups[day] = []
groups[day].append(item)
consolidated = 0
for day, day_items in groups.items():
if len(day_items) < 3:
continue # 太少就不整合
contents = [i.content for i in day_items]
# 用 LLM 生成摘要(可选,需 Provider 配合)
# summary = llm.summarize(contents)
# 简化版:删除旧条目,创建一条汇总
summary_content = "; ".join(contents[:5])
if len(contents) > 5:
summary_content += f" ... 等{len(contents)}条记录"
new_item = MemoryItem(
type=memory_type,
content=f"[{day} 摘要] {summary_content}",
metadata={"consolidated": True, "original_count": len(day_items)},
)
# 删除原始条目
for item in day_items:
self._store.delete(item.id)
# 保存摘要
self._store.save(new_item)
consolidated += len(day_items)
return consolidated
Step 5.3:管理 API 总览
# 完整的记忆管理接口
mem = kernel.registry("memory")
# 日常使用
mem.remember("episodic", "...") # 记住
mem.recall("查询", limit=5) # 回忆
mem.forget("some_id") # 遗忘单条
# 维护
mem.consolidate("episodic") # 整合压缩
mem.auto_forget(max_items=10000) # 自动清理
print(mem.stats()) # 查看统计
# 存储后端管理(低层操作)
mem.store.switch("vector") # 切换存储后端
mem.store.export("backup.jsonl") # 导出备份
mem.store.clear() # 清空全部
九、项目结构与测试
最终目录结构
myAgent/agent_frame/
├── __init__.py # 导出 MemoryRegistry 等
├── kernel.py
├── errors.py # 新增 MemoryError 等
├── ...
└── memory/ # 新增:记忆系统模块
├── __init__.py
├── models.py # MemoryItem 数据模型
├── stores.py # BaseMemoryStore + 各实现
├── registry.py # MemoryRegistry 主类
└── recall.py # RecallEngine + 检索策略
myAgent/tests/
├── test_memory_models.py # 测试数据模型
├── test_memory_stores.py # 测试各存储后端
├── test_memory_registry.py # 测试 Registry
└── test_memory_recall.py # 测试召回引擎
example/
├── memory_agent.py # 记忆增强 Agent 封装
└── run_memory_agent.py # 运行示例
测试要点
| 测试 | 覆盖内容 |
|---|---|
test_memory_models.py |
MemoryItem 创建、序列化、反序列化 |
test_memory_stores.py |
增删改查、持久化、重新加载 |
test_memory_registry.py |
接口完整、错误处理、统计 |
test_memory_recall.py |
混合检索、评分排序、去重 |
测试示例:
def test_memory_lifecycle():
mem = MemoryRegistry()
mid = mem.remember("test", "Hello World")
assert mem.recall("Hello")[0].item.id == mid
assert mem.forget(mid) == True
assert len(mem.recall("Hello")) == 0
def test_file_persistence(tmp_path):
path = tmp_path / "test.jsonl"
mem1 = MemoryRegistry(store=FileStore(str(path)))
mem1.remember("test", "Persist me")
mem2 = MemoryRegistry(store=FileStore(str(path)))
assert mem2.stats()["total"] == 1
def test_mixed_recall():
mem = MemoryRegistry()
mem.remember("episodic", "用户查过上海天气", importance=0.3)
mem.remember("semantic", "用户在上海工作", importance=0.9)
results = mem.recall("上海", limit=5)
assert len(results) >= 2
assert results[0].score >= results[-1].score # 按分数降序
十、学习路径建议
如果你对记忆系统的某些概念还不熟悉,以下是一套循序渐进的学习路线:
第一阶段:基础概念(1-2 天)
| 主题 | 建议资源 |
|---|---|
Python dataclass |
Python 官方文档 — 理解 @dataclass、field() |
| 序列化与持久化 | Python json 模块、JSON Lines 格式 |
| 抽象基类 | Python abc 模块、@abstractmethod |
第二阶段:存储技术(2-3 天)
| 主题 | 建议资源 |
|---|---|
| 文件 I/O | Python pathlib、文件读写模式 |
| SQLite(可选) | 轻量关系型数据库,适合结构化记忆 |
| 向量数据库概念 | 搜索"Vector Database 入门" |
第三阶段:语义搜索(3-5 天)
| 主题 | 建议资源 |
|---|---|
| Word Embedding | 搜索"Word2Vec 通俗解释" |
| Cosine Similarity | 余弦相似度的计算公式和直观理解 |
| ChromaDB 入门 | docs.trychroma.com — 5 分钟上手 |
| Embedding 模型 | 了解 BGE-M3、text2vec、OpenAI embedding |
第四阶段:Agent 记忆论文(选读)
如果未来想深入:
| 论文 | 核心思想 |
|---|---|
| MemGPT (2023) | 给 LLM 添加虚拟内存管理,自动在 context 和 storage 之间换入换出 |
| Generative Agents (2023, Stanford) | 25 个 AI 角色在小镇中生活,每个都有完整的记忆流 |
| MemoryBank (2023) | 为对话 Agent 设计的长期记忆系统,包含记忆提取、记忆合并、记忆回想 |
推荐实践路线
Week 1: 实现 Phase 1(核心骨架 + InMemoryStore)
Week 2: 实现 Phase 2 文件存储 + Phase 3 基础召回
Week 3: 实现 Phase 4 Agent 集成
Week 4: 向量搜索 + 自动维护
先跑通最简单的版本,再逐步加高级特性。不要一开始就追求完美。
附录:时序决策记录
| 决策 | 选项 | 选择 | 理由 |
|---|---|---|---|
| 存储后端接口 | 统一接口 vs 各类型独立 | 统一接口 | 简化实现,后期可拆 |
| 向量数据库 | ChromaDB vs Qdrant vs Pinecone | ChromaDB | 嵌入式零运维,适合个人项目 |
| Embedding 来源 | Provider 扩展 vs 独立服务 | 依赖 Provider | 复用现有 API key |
| 记忆注入方式 | System prompt vs 额外消息 | System prompt 注入 | 兼容所有 Agent 类型 |
| 是否修改框架源码 | 改 vs 不改 | 不改 | 遵循 CLAUDE.md 规则 5 |
本文来自博客园,作者:羊扬羊,转载请注明原文链接:https://www.cnblogs.com/sheepcsy/p/20069644

浙公网安备 33010602011771号