doudou-memory 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,64 @@
1
+ """
2
+ 豆豆记忆系统
3
+ Doudou Memory System
4
+
5
+ 为 AI Agent 打造的科学记忆系统
6
+ """
7
+
8
+ __version__ = "1.0.0"
9
+ __author__ = "Doudou Software Studio"
10
+
11
+ from .core.memory_system import (
12
+ MemorySystem,
13
+ memorize,
14
+ recall,
15
+ get_memory_system
16
+ )
17
+
18
+ from .core.working_memory import (
19
+ WorkingMemory,
20
+ get_working_memory
21
+ )
22
+
23
+ from .core.forgetting_mechanism import (
24
+ DecayManager,
25
+ get_decay_manager,
26
+ EbbinghausForgetting
27
+ )
28
+
29
+ from .core.review_scheduler import (
30
+ ReviewScheduler,
31
+ get_review_scheduler
32
+ )
33
+
34
+ from .core.intelligent_manager import (
35
+ IntelligentMemoryManager,
36
+ smart_memorize
37
+ )
38
+
39
+ __all__ = [
40
+ # 核心系统
41
+ "MemorySystem",
42
+ "get_memory_system",
43
+
44
+ # 工作记忆
45
+ "WorkingMemory",
46
+ "get_working_memory",
47
+
48
+ # 遗忘机制
49
+ "DecayManager",
50
+ "get_decay_manager",
51
+ "EbbinghausForgetting",
52
+
53
+ # 复习调度
54
+ "ReviewScheduler",
55
+ "get_review_scheduler",
56
+
57
+ # 智能管理
58
+ "IntelligentMemoryManager",
59
+ "smart_memorize",
60
+
61
+ # 便捷函数
62
+ "memorize",
63
+ "recall",
64
+ ]
@@ -0,0 +1,312 @@
1
+ """
2
+ L3/L4 遗忘机制模块
3
+ Forgetting Mechanism Module
4
+
5
+ 基于 Ebbinghaus 遗忘曲线
6
+ """
7
+
8
+ import json
9
+ import math
10
+ import logging
11
+ from datetime import datetime, timedelta
12
+ from typing import Dict, List, Optional
13
+ from pathlib import Path
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # 默认存储路径
18
+ DEFAULT_STORAGE_PATH = "D:/openclaw-agents/shared/experience/long_term_memory"
19
+
20
+
21
+ class EbbinghausForgetting:
22
+ """
23
+ Ebbinghaus 遗忘曲线计算器
24
+
25
+ 公式: R = e^(-t/S)
26
+ - R: 保留率
27
+ - t: 时间(小时)
28
+ - S: 记忆强度(半衰期)
29
+ """
30
+
31
+ # 标准遗忘曲线数据点
32
+ FORGETTING_CURVE = {
33
+ 1/3: 58.2, # 20分钟
34
+ 1: 44.2, # 1小时
35
+ 24: 33.7, # 1天
36
+ 24 * 7: 25.4, # 1周
37
+ 24 * 31: 21.1 # 31天
38
+ }
39
+
40
+ def __init__(self, min_retention: float = 0.3):
41
+ self.min_retention = min_retention
42
+
43
+ def calculate_retention(self, age_hours: float,
44
+ strength: float = 24) -> float:
45
+ """
46
+ 计算保留率
47
+
48
+ Args:
49
+ age_hours: 记忆年龄(小时)
50
+ strength: 记忆强度(小时)
51
+
52
+ Returns:
53
+ 保留率 (0-1)
54
+ """
55
+ if age_hours <= 0:
56
+ return 1.0
57
+
58
+ retention = math.exp(-age_hours / strength)
59
+
60
+ # 确保不低于最小保留率
61
+ return max(self.min_retention, min(1.0, retention))
62
+
63
+ def calculate_strength(self, retention: float, age_hours: float) -> float:
64
+ """根据保留率计算记忆强度"""
65
+ if retention <= 0 or retention >= 1:
66
+ return float('inf')
67
+
68
+ return -age_hours / math.log(retention)
69
+
70
+
71
+ class MemoryRecord:
72
+ """记忆记录"""
73
+
74
+ def __init__(self, memory_id: str, content: str,
75
+ importance: float = 0.5, memory_type: str = "episodic"):
76
+ self.id = memory_id
77
+ self.content = content
78
+ self.importance = importance
79
+ self.memory_type = memory_type
80
+ self.created_at = datetime.now()
81
+ self.last_accessed = datetime.now()
82
+ self.access_count = 0
83
+ self.retention_score = 1.0
84
+ self.review_count = 0
85
+ self.next_review: Optional[datetime] = None
86
+ self.archived = False
87
+
88
+ # SM-2 参数
89
+ self.easy_factor = 2.5
90
+ self.interval = 1 # 天数
91
+
92
+ def to_dict(self) -> Dict:
93
+ return {
94
+ "id": self.id,
95
+ "content": self.content,
96
+ "importance": self.importance,
97
+ "memory_type": self.memory_type,
98
+ "created_at": self.created_at.isoformat(),
99
+ "last_accessed": self.last_accessed.isoformat(),
100
+ "access_count": self.access_count,
101
+ "retention_score": self.retention_score,
102
+ "review_count": self.review_count,
103
+ "next_review": self.next_review.isoformat() if self.next_review else None,
104
+ "archived": self.archived,
105
+ "easy_factor": self.easy_factor,
106
+ "interval": self.interval
107
+ }
108
+
109
+ @classmethod
110
+ def from_dict(cls, data: Dict) -> 'MemoryRecord':
111
+ record = cls(
112
+ memory_id=data["id"],
113
+ content=data["content"],
114
+ importance=data.get("importance", 0.5),
115
+ memory_type=data.get("memory_type", "episodic")
116
+ )
117
+ record.created_at = datetime.fromisoformat(data["created_at"])
118
+ record.last_accessed = datetime.fromisoformat(data["last_accessed"])
119
+ record.access_count = data.get("access_count", 0)
120
+ record.retention_score = data.get("retention_score", 1.0)
121
+ record.review_count = data.get("review_count", 0)
122
+ if data.get("next_review"):
123
+ record.next_review = datetime.fromisoformat(data["next_review"])
124
+ record.archived = data.get("archived", False)
125
+ record.easy_factor = data.get("easy_factor", 2.5)
126
+ record.interval = data.get("interval", 1)
127
+ return record
128
+
129
+
130
+ class DecayManager:
131
+ """
132
+ 衰减管理器
133
+
134
+ 管理长期记忆的遗忘衰减和间隔重复
135
+ """
136
+
137
+ def __init__(self, storage_path: str = DEFAULT_STORAGE_PATH):
138
+ self.storage_path = Path(storage_path)
139
+ self.storage_path.mkdir(parents=True, exist_ok=True)
140
+
141
+ self.memories: Dict[str, MemoryRecord] = {}
142
+ self.ebbinghaus = EbbinghausForgetting()
143
+
144
+ self._load_memories()
145
+
146
+ def register_memory(self, memory_id: str, content: str,
147
+ importance: float = 0.5,
148
+ memory_type: str = "episodic") -> str:
149
+ """注册新记忆"""
150
+ record = MemoryRecord(
151
+ memory_id=memory_id,
152
+ content=content,
153
+ importance=importance,
154
+ memory_type=memory_type
155
+ )
156
+
157
+ # 设置首次复习时间
158
+ record.next_review = datetime.now() + timedelta(days=1)
159
+
160
+ self.memories[memory_id] = record
161
+ self._save_memories()
162
+
163
+ logger.info(f"[衰减管理] 注册记忆: {memory_id}")
164
+
165
+ return memory_id
166
+
167
+ def get_memory(self, memory_id: str) -> Optional[Dict]:
168
+ """获取记忆"""
169
+ record = self.memories.get(memory_id)
170
+ if record and not record.archived:
171
+ record.last_accessed = datetime.now()
172
+ record.access_count += 1
173
+ return record.to_dict()
174
+ return None
175
+
176
+ def reinforce_memory(self, memory_id: str, quality: int = 3):
177
+ """
178
+ 强化记忆(SM-2 算法)
179
+
180
+ Args:
181
+ memory_id: 记忆ID
182
+ quality: 回忆质量 (0-5)
183
+ """
184
+ record = self.memories.get(memory_id)
185
+ if not record:
186
+ return
187
+
188
+ # SM-2 算法
189
+ if quality >= 3:
190
+ # 成功回忆
191
+ if record.review_count == 0:
192
+ record.interval = 1
193
+ elif record.review_count == 1:
194
+ record.interval = 3
195
+ else:
196
+ record.interval = int(record.interval * record.easy_factor)
197
+
198
+ # 更新 Easy Factor
199
+ record.easy_factor = max(1.3, record.easy_factor + (0.1 - (5 - quality) * 0.08))
200
+ else:
201
+ # 失败回忆
202
+ record.interval = 1
203
+ record.easy_factor = max(1.3, record.easy_factor - 0.2)
204
+
205
+ record.review_count += 1
206
+ record.next_review = datetime.now() + timedelta(days=record.interval)
207
+ record.retention_score = 1.0
208
+ record.last_accessed = datetime.now()
209
+
210
+ self._save_memories()
211
+
212
+ logger.info(f"[衰减管理] 强化记忆: {memory_id}, 下次复习: {record.next_review.strftime('%Y-%m-%d')}")
213
+
214
+ def apply_decay(self) -> List[str]:
215
+ """
216
+ 应用遗忘衰减
217
+
218
+ Returns:
219
+ 归档的记忆ID列表
220
+ """
221
+ archived_ids = []
222
+ now = datetime.now()
223
+
224
+ for memory_id, record in self.memories.items():
225
+ if record.archived:
226
+ continue
227
+
228
+ # 计算年龄
229
+ age_hours = (now - record.created_at).total_seconds() / 3600
230
+
231
+ # 计算保留率
232
+ record.retention_score = self.ebbinghaus.calculate_retention(age_hours)
233
+
234
+ # 检查是否归档
235
+ if record.retention_score < 0.3 and record.importance < 0.6:
236
+ record.archived = True
237
+ archived_ids.append(memory_id)
238
+ logger.info(f"[衰减管理] 归档: {memory_id}")
239
+
240
+ self._save_memories()
241
+
242
+ return archived_ids
243
+
244
+ def get_memories_for_review(self) -> List[Dict]:
245
+ """获取需要复习的记忆"""
246
+ now = datetime.now()
247
+ reviews = []
248
+
249
+ for memory_id, record in self.memories.items():
250
+ if record.archived:
251
+ continue
252
+
253
+ if record.next_review and record.next_review <= now:
254
+ reviews.append(record.to_dict())
255
+
256
+ return reviews
257
+
258
+ def get_stats(self) -> Dict:
259
+ """获取统计"""
260
+ total = len(self.memories)
261
+ archived = sum(1 for m in self.memories.values() if m.archived)
262
+
263
+ retention_scores = [m.retention_score for m in self.memories.values() if not m.archived]
264
+ avg_retention = sum(retention_scores) / len(retention_scores) if retention_scores else 1.0
265
+
266
+ return {
267
+ "total_memories": total,
268
+ "active_memories": total - archived,
269
+ "archived_count": archived,
270
+ "average_retention": round(avg_retention, 2)
271
+ }
272
+
273
+ def _load_memories(self):
274
+ """加载记忆"""
275
+ memory_file = self.storage_path / "memories.json"
276
+
277
+ if memory_file.exists():
278
+ try:
279
+ with open(memory_file, "r", encoding="utf-8") as f:
280
+ data = json.load(f)
281
+
282
+ for memory_id, record_data in data.items():
283
+ self.memories[memory_id] = MemoryRecord.from_dict(record_data)
284
+
285
+ logger.info(f"[衰减管理] 加载 {len(self.memories)} 条记忆")
286
+ except Exception as e:
287
+ logger.error(f"[衰减管理] 加载失败: {e}")
288
+
289
+ def _save_memories(self):
290
+ """保存记忆"""
291
+ memory_file = self.storage_path / "memories.json"
292
+
293
+ try:
294
+ data = {mid: record.to_dict() for mid, record in self.memories.items()}
295
+
296
+ with open(memory_file, "w", encoding="utf-8") as f:
297
+ json.dump(data, f, ensure_ascii=False, indent=2)
298
+ except Exception as e:
299
+ logger.error(f"[衰减管理] 保存失败: {e}")
300
+
301
+
302
+ # ==================== 全局实例 ====================
303
+
304
+ _decay_manager_instance = None
305
+
306
+
307
+ def get_decay_manager() -> DecayManager:
308
+ """获取衰减管理器实例"""
309
+ global _decay_manager_instance
310
+ if _decay_manager_instance is None:
311
+ _decay_manager_instance = DecayManager()
312
+ return _decay_manager_instance
@@ -0,0 +1,339 @@
1
+ """
2
+ 智能记忆管理器
3
+ Intelligent Memory Manager
4
+
5
+ 功能:
6
+ 1. 自动提取:从对话中自动提取关键记忆
7
+ 2. 操作决策:决定 ADD/UPDATE/DELETE/NOOP
8
+ """
9
+
10
+ import requests
11
+ import json
12
+ import re
13
+ import logging
14
+ from datetime import datetime
15
+ from typing import Dict, List, Optional
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ # 默认 API 配置
20
+ DEFAULT_API_KEY = ""
21
+ DEFAULT_MODEL = "glm-5"
22
+ DEFAULT_API_URL = "https://coding.dashscope.aliyuncs.com/v1/chat/completions"
23
+
24
+
25
+ class IntelligentMemoryManager:
26
+ """
27
+ 智能记忆管理器
28
+
29
+ 实现自动提取和操作决策
30
+ """
31
+
32
+ def __init__(self, api_key: str = None, model: str = DEFAULT_MODEL):
33
+ self.api_key = api_key or DEFAULT_API_KEY
34
+ self.model = model
35
+ self.api_url = DEFAULT_API_URL
36
+
37
+ # 延迟加载
38
+ self._decay_manager = None
39
+ self._working_memory = None
40
+
41
+ def _get_decay_manager(self):
42
+ """延迟加载衰减管理器"""
43
+ if self._decay_manager is None:
44
+ from .forgetting_mechanism import get_decay_manager
45
+ self._decay_manager = get_decay_manager()
46
+ return self._decay_manager
47
+
48
+ def _get_working_memory(self):
49
+ """延迟加载工作记忆"""
50
+ if self._working_memory is None:
51
+ from .working_memory import get_working_memory
52
+ self._working_memory = get_working_memory()
53
+ return self._working_memory
54
+
55
+ def _call_llm(self, prompt: str, max_tokens: int = 500) -> str:
56
+ """调用 LLM"""
57
+ if not self.api_key:
58
+ logger.warning("[智能记忆] API Key 未配置")
59
+ return ""
60
+
61
+ headers = {
62
+ "Authorization": f"Bearer {self.api_key}",
63
+ "Content-Type": "application/json"
64
+ }
65
+
66
+ payload = {
67
+ "model": self.model,
68
+ "messages": [{"role": "user", "content": prompt}],
69
+ "max_tokens": max_tokens
70
+ }
71
+
72
+ try:
73
+ resp = requests.post(self.api_url, headers=headers, json=payload, timeout=60)
74
+
75
+ if resp.status_code == 200:
76
+ result = resp.json()
77
+ return result.get("choices", [{}])[0].get("message", {}).get("content", "")
78
+ else:
79
+ logger.error(f"[智能记忆] API 错误: {resp.status_code}")
80
+ return ""
81
+ except Exception as e:
82
+ logger.error(f"[智能记忆] 调用失败: {e}")
83
+ return ""
84
+
85
+ def _extract_json(self, text: str) -> Optional[Dict]:
86
+ """从文本中提取 JSON"""
87
+ try:
88
+ return json.loads(text)
89
+ except:
90
+ pass
91
+
92
+ # 查找 JSON 块
93
+ json_match = re.search(r'```json\s*([\s\S]*?)\s*```', text)
94
+ if json_match:
95
+ try:
96
+ return json.loads(json_match.group(1))
97
+ except:
98
+ pass
99
+
100
+ # 查找裸 JSON
101
+ json_match = re.search(r'\{[\s\S]*\}', text)
102
+ if json_match:
103
+ try:
104
+ return json.loads(json_match.group())
105
+ except:
106
+ pass
107
+
108
+ return None
109
+
110
+ def extract_memories(self, conversation: str,
111
+ context: str = "") -> List[Dict]:
112
+ """
113
+ 从对话中自动提取记忆
114
+
115
+ Args:
116
+ conversation: 对话内容
117
+ context: 额外上下文
118
+
119
+ Returns:
120
+ 提取的记忆列表
121
+ """
122
+ prompt = f"""从以下对话中提取关键记忆信息,返回 JSON 格式:
123
+
124
+ 对话:
125
+ {conversation}
126
+
127
+ 上下文:{context}
128
+
129
+ 要求:提取重要的事实、偏好、决策,格式如下:
130
+ {{"memories": [{{"content": "...", "type": "fact", "importance": 0.8}}]}}
131
+
132
+ 只返回 JSON,不要其他内容。"""
133
+
134
+ response = self._call_llm(prompt, max_tokens=1000)
135
+
136
+ if not response:
137
+ return []
138
+
139
+ result = self._extract_json(response)
140
+
141
+ if result and "memories" in result:
142
+ memories = result["memories"]
143
+ logger.info(f"[智能记忆] 提取 {len(memories)} 条记忆")
144
+ return memories
145
+
146
+ return []
147
+
148
+ def decide_operations(self, new_memories: List[Dict],
149
+ existing_memories: List[Dict] = None) -> List[Dict]:
150
+ """
151
+ 决定记忆操作
152
+
153
+ Args:
154
+ new_memories: 新提取的记忆
155
+ existing_memories: 现有相关记忆
156
+
157
+ Returns:
158
+ 操作列表
159
+ """
160
+ if not new_memories:
161
+ return []
162
+
163
+ # 如果没有现有记忆,全部 ADD
164
+ if not existing_memories:
165
+ return [{"action": "ADD", "memory": mem} for mem in new_memories]
166
+
167
+ # 构建描述
168
+ existing_desc = "\n".join([
169
+ f"{i+1}. \"{mem.get('content', '')}\" (id: {mem.get('id', 'unknown')})"
170
+ for i, mem in enumerate(existing_memories[:10])
171
+ ])
172
+
173
+ new_desc = "\n".join([f"- {mem.get('content', '')}" for mem in new_memories])
174
+
175
+ prompt = f"""根据新信息和现有记忆,决定要执行的操作:
176
+
177
+ 现有记忆:
178
+ {existing_desc}
179
+
180
+ 新信息:
181
+ {new_desc}
182
+
183
+ 请决定操作(返回 JSON):
184
+ {{"operations": [
185
+ {{"action": "ADD", "memory": {{"content": "...", "type": "fact", "importance": 0.8}}}},
186
+ {{"action": "UPDATE", "memory_id": "...", "new_content": "..."}},
187
+ {{"action": "DELETE", "memory_id": "..."}},
188
+ {{"action": "NOOP"}}
189
+ ]}}
190
+
191
+ 只返回 JSON。"""
192
+
193
+ response = self._call_llm(prompt, max_tokens=1000)
194
+
195
+ if not response:
196
+ # 降级:全部 ADD
197
+ return [{"action": "ADD", "memory": mem} for mem in new_memories]
198
+
199
+ result = self._extract_json(response)
200
+
201
+ if result and "operations" in result:
202
+ logger.info(f"[智能记忆] 决策 {len(result['operations'])} 个操作")
203
+ return result["operations"]
204
+
205
+ # 降级:全部 ADD
206
+ return [{"action": "ADD", "memory": mem} for mem in new_memories]
207
+
208
+ def execute_operations(self, operations: List[Dict]) -> Dict:
209
+ """
210
+ 执行记忆操作
211
+
212
+ Returns:
213
+ 执行结果
214
+ """
215
+ results = {
216
+ "added": 0,
217
+ "updated": 0,
218
+ "deleted": 0,
219
+ "skipped": 0,
220
+ "errors": []
221
+ }
222
+
223
+ decay_manager = self._get_decay_manager()
224
+
225
+ for op in operations:
226
+ action = op.get("action", "NOOP")
227
+
228
+ try:
229
+ if action == "ADD":
230
+ memory = op.get("memory", {})
231
+ content = memory.get("content", "")
232
+ importance = memory.get("importance", 0.5)
233
+ mem_type = memory.get("type", "fact")
234
+
235
+ if content:
236
+ memory_id = f"mem_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
237
+ decay_manager.register_memory(
238
+ memory_id=memory_id,
239
+ content=content,
240
+ importance=importance,
241
+ memory_type=mem_type
242
+ )
243
+ results["added"] += 1
244
+
245
+ elif action == "UPDATE":
246
+ memory_id = op.get("memory_id", "")
247
+ new_content = op.get("new_content", "")
248
+
249
+ if memory_id and new_content:
250
+ if memory_id in decay_manager.memories:
251
+ decay_manager.memories[memory_id].content = new_content
252
+ results["updated"] += 1
253
+
254
+ elif action == "DELETE":
255
+ memory_id = op.get("memory_id", "")
256
+
257
+ if memory_id and memory_id in decay_manager.memories:
258
+ decay_manager.memories[memory_id].archived = True
259
+ results["deleted"] += 1
260
+
261
+ elif action == "NOOP":
262
+ results["skipped"] += 1
263
+
264
+ except Exception as e:
265
+ results["errors"].append(str(e))
266
+
267
+ logger.info(f"[智能记忆] 执行完成: +{results['added']} ~{results['updated']} -{results['deleted']}")
268
+
269
+ return results
270
+
271
+ def process_conversation(self, conversation: str,
272
+ context: str = "") -> Dict:
273
+ """
274
+ 处理对话:提取 + 决策 + 执行
275
+
276
+ Args:
277
+ conversation: 对话内容
278
+ context: 额外上下文
279
+
280
+ Returns:
281
+ 处理结果
282
+ """
283
+ # Phase 1: 提取
284
+ new_memories = self.extract_memories(conversation, context)
285
+
286
+ if not new_memories:
287
+ return {"status": "no_memories", "extracted": 0}
288
+
289
+ # Phase 2: 检索相关记忆
290
+ decay_manager = self._get_decay_manager()
291
+ existing_memories = []
292
+
293
+ for mem in new_memories:
294
+ content = mem.get("content", "")
295
+ for mid, m in decay_manager.memories.items():
296
+ if m.archived:
297
+ continue
298
+ existing_content = m.content
299
+ # 简单关键词匹配
300
+ if any(word in existing_content for word in content.split()[:3] if len(word) > 2):
301
+ existing_memories.append({"id": mid, **m.to_dict()})
302
+
303
+ # Phase 3: 决策
304
+ operations = self.decide_operations(new_memories, existing_memories)
305
+
306
+ # Phase 4: 执行
307
+ results = self.execute_operations(operations)
308
+
309
+ return {
310
+ "status": "ok",
311
+ "extracted": len(new_memories),
312
+ "existing_checked": len(existing_memories),
313
+ "operations": results
314
+ }
315
+
316
+
317
+ # ==================== 全局实例 ====================
318
+
319
+ _manager_instance = None
320
+
321
+
322
+ def get_intelligent_manager() -> IntelligentMemoryManager:
323
+ """获取智能记忆管理器实例"""
324
+ global _manager_instance
325
+ if _manager_instance is None:
326
+ _manager_instance = IntelligentMemoryManager()
327
+ return _manager_instance
328
+
329
+
330
+ # ==================== 便捷函数 ====================
331
+
332
+ def smart_memorize(conversation: str, context: str = "") -> Dict:
333
+ """
334
+ 智能记忆:自动提取 + 决策 + 存储
335
+
336
+ 替代手动 memorize() 调用
337
+ """
338
+ manager = get_intelligent_manager()
339
+ return manager.process_conversation(conversation, context)