mindforge-memory 6.4.1__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.
Files changed (64) hide show
  1. MindForge.py +20 -0
  2. mindforge/__init__.py +111 -0
  3. mindforge/adapters/__init__.py +18 -0
  4. mindforge/adapters/claude_adapter.py +84 -0
  5. mindforge/adapters/crewai_memory.py +76 -0
  6. mindforge/adapters/generic_api.py +183 -0
  7. mindforge/adapters/langgraph_store.py +463 -0
  8. mindforge/adapters/openclaw_adapter.py +91 -0
  9. mindforge/api/__init__.py +1 -0
  10. mindforge/api/server.py +1567 -0
  11. mindforge/cli/__init__.py +1 -0
  12. mindforge/cli/main.py +13607 -0
  13. mindforge/core/__init__.py +37 -0
  14. mindforge/core/embedding.py +847 -0
  15. mindforge/core/encryption.py +597 -0
  16. mindforge/core/indexer.py +363 -0
  17. mindforge/core/mindforge.py +6298 -0
  18. mindforge/core/paths.py +43 -0
  19. mindforge/core/query.py +501 -0
  20. mindforge/core/storage.py +18656 -0
  21. mindforge/core/types.py +314 -0
  22. mindforge/core/version.py +56 -0
  23. mindforge/daemon/__init__.py +0 -0
  24. mindforge/daemon/server.py +277 -0
  25. mindforge/mcp/__init__.py +4 -0
  26. mindforge/mcp/server.py +2559 -0
  27. mindforge/modules/__init__.py +138 -0
  28. mindforge/modules/categorizer.py +151 -0
  29. mindforge/modules/conflict_detector.py +356 -0
  30. mindforge/modules/connectors.py +331 -0
  31. mindforge/modules/context_window.py +337 -0
  32. mindforge/modules/event_bus.py +572 -0
  33. mindforge/modules/evolution.py +210 -0
  34. mindforge/modules/experience.py +402 -0
  35. mindforge/modules/federated.py +722 -0
  36. mindforge/modules/federated_acl.py +393 -0
  37. mindforge/modules/follow_up.py +315 -0
  38. mindforge/modules/hybrid_search.py +565 -0
  39. mindforge/modules/importer.py +374 -0
  40. mindforge/modules/integrator.py +196 -0
  41. mindforge/modules/intent_router.py +359 -0
  42. mindforge/modules/knowledge_graph.py +395 -0
  43. mindforge/modules/memory_compressor.py +592 -0
  44. mindforge/modules/memory_decay.py +474 -0
  45. mindforge/modules/memory_passport.py +250 -0
  46. mindforge/modules/multi_agent.py +534 -0
  47. mindforge/modules/multimodal.py +304 -0
  48. mindforge/modules/outcome_learning.py +186 -0
  49. mindforge/modules/personality.py +275 -0
  50. mindforge/modules/principled_forgetting.py +258 -0
  51. mindforge/modules/privacy.py +736 -0
  52. mindforge/modules/recall.py +263 -0
  53. mindforge/modules/session_focus.py +305 -0
  54. mindforge/modules/share_conflict.py +521 -0
  55. mindforge/modules/skill_extractor.py +340 -0
  56. mindforge/modules/skill_pack.py +205 -0
  57. mindforge/modules/timeline_viz.py +206 -0
  58. mindforge/modules/v6.py +931 -0
  59. mindforge_memory-6.4.1.dist-info/METADATA +281 -0
  60. mindforge_memory-6.4.1.dist-info/RECORD +64 -0
  61. mindforge_memory-6.4.1.dist-info/WHEEL +5 -0
  62. mindforge_memory-6.4.1.dist-info/entry_points.txt +3 -0
  63. mindforge_memory-6.4.1.dist-info/licenses/LICENSE +21 -0
  64. mindforge_memory-6.4.1.dist-info/top_level.txt +2 -0
@@ -0,0 +1,395 @@
1
+ """
2
+ MindForge v5.0 知识图谱引擎
3
+ 自动提取实体与关系,构建动态知识网络
4
+ """
5
+
6
+ import re
7
+ import json
8
+ import uuid
9
+ import time
10
+ import sqlite3 # v5.4.7 修复 C-3:异常处理中引用 sqlite3.OperationalError 需要此导入
11
+ from dataclasses import dataclass, field
12
+ from typing import List, Dict, Optional, Tuple
13
+ from collections import defaultdict, deque # v5.4.7 修复 L-7:BFS 使用 deque
14
+
15
+
16
+ @dataclass
17
+ class KnowledgeEntity:
18
+ """知识实体"""
19
+ id: str
20
+ name: str
21
+ entity_type: str
22
+ description: str = ""
23
+ metadata: Dict = field(default_factory=dict)
24
+ created_at: float = 0.0
25
+
26
+
27
+ @dataclass
28
+ class KnowledgeRelation:
29
+ """知识关系"""
30
+ id: str
31
+ from_entity: str
32
+ to_entity: str
33
+ relation_type: str
34
+ weight: float = 1.0
35
+ memory_ids: List[str] = field(default_factory=list)
36
+ metadata: Dict = field(default_factory=dict)
37
+ created_at: float = 0.0
38
+
39
+
40
+ @dataclass
41
+ class GraphPath:
42
+ """图路径"""
43
+ entities: List[str]
44
+ relations: List[str]
45
+ total_weight: float
46
+
47
+
48
+ ENTITY_PATTERNS = {
49
+ "technology": [
50
+ r'\b(python|java|javascript|typescript|go|rust|c\+\+|ruby|php)\b',
51
+ r'\b(react|vue|angular|django|flask|spring|fastapi)\b',
52
+ r'\b(mysql|postgresql|mongodb|redis|elasticsearch|sqlite)\b',
53
+ r'\b(docker|kubernetes|k8s|aws|gcp|azure)\b',
54
+ # v5.7.9:中文技术栈词
55
+ r'(python|java|mysql|redis|sqlite|docker|kubernetes|微信小程序|浏览器插件)',
56
+ ],
57
+ "person": [
58
+ r'(?:Mr\.|Mrs\.|Ms\.|Dr\.|Prof\.)\s+[A-Z][a-z]+',
59
+ ],
60
+ "organization": [
61
+ r'(?:Inc|Ltd|LLC|Corp|Corporation|Company|University|Institute)\b',
62
+ # v5.7.9:中文组织后缀(公司/集团/大学/研究院等)
63
+ r'[\u4e00-\u9fff]{2,12}(?:公司|集团|大学|研究院|实验室|工作室|团队|中心)',
64
+ ],
65
+ "product": [
66
+ # v5.7.9:常见产品/平台名
67
+ r'\b(wechat|alipay|feishu|lark|dingtalk|douyin|chrome|vscode|github|gitlab|slack|notion)\b',
68
+ r'(微信|支付宝|飞书|钉钉|抖音|浏览器|编辑器)',
69
+ ],
70
+ }
71
+
72
+ RELATION_PATTERNS = [
73
+ (r'(.+?)\s+(?:使用|利用|采用|运用)\s+(.+)', "uses"),
74
+ (r'(.+?)\s+(?:是|属于|归类为)\s+(.+)', "is_a"),
75
+ (r'(.+?)\s+(?:包含|包括|有)\s+(.+)', "contains"),
76
+ (r'(.+?)\s+(?:相关于|关联|有关)\s+(.+)', "related_to"),
77
+ (r'(.+?)\s+(?:优化|改进|提升)\s+(.+)', "improves"),
78
+ (r'(.+?)\s+(?:基于|依赖|需要)\s+(.+)', "depends_on"),
79
+ ]
80
+
81
+
82
+ class KnowledgeGraph:
83
+ """知识图谱引擎"""
84
+
85
+ def __init__(self, storage=None, auto_load=True):
86
+ self.storage = storage
87
+ self.entities: Dict[str, KnowledgeEntity] = {}
88
+ self.relations: Dict[str, KnowledgeRelation] = {}
89
+ self._adjacency: Dict[str, List[Tuple[str, str, float]]] = defaultdict(list)
90
+ # v5.7.9:同进程内已抽取记忆 ID(防抖,避免全库增量重复建关系)
91
+ self._processed_memory_ids: set = set()
92
+ if self.storage is not None and auto_load:
93
+ self._ensure_tables()
94
+ self._load_from_db()
95
+
96
+ def extract_entities(self, text: str) -> List[Tuple[str, str]]:
97
+ """从文本中提取实体(简易版)"""
98
+ entities = []
99
+
100
+ for entity_type, patterns in ENTITY_PATTERNS.items():
101
+ for pattern in patterns:
102
+ matches = re.findall(pattern, text, re.IGNORECASE)
103
+ for match in matches:
104
+ if isinstance(match, tuple):
105
+ match = match[0]
106
+ entities.append((match.strip().lower(), entity_type))
107
+
108
+ seen = set()
109
+ unique = []
110
+ for name, etype in entities:
111
+ key = (name, etype)
112
+ if key not in seen:
113
+ seen.add(key)
114
+ unique.append((name, etype))
115
+
116
+ return unique
117
+
118
+ def extract_relations(self, text: str, entities: List[str]) -> List[Tuple[str, str, str]]:
119
+ """提取实体间关系(简易版)"""
120
+ relations = []
121
+ text_lower = text.lower()
122
+
123
+ for pattern, rel_type in RELATION_PATTERNS:
124
+ matches = re.findall(pattern, text_lower)
125
+ for match in matches:
126
+ if len(match) >= 2:
127
+ subj = match[0].strip()
128
+ obj = match[1].strip()
129
+
130
+ subj_entity = self._find_matching_entity(subj, entities)
131
+ obj_entity = self._find_matching_entity(obj, entities)
132
+
133
+ if subj_entity and obj_entity and subj_entity != obj_entity:
134
+ relations.append((subj_entity, rel_type, obj_entity))
135
+
136
+ return relations
137
+
138
+ def _find_matching_entity(self, text: str, entities: List[str]) -> Optional[str]:
139
+ text_lower = text.lower()
140
+ for entity in entities:
141
+ if entity.lower() in text_lower:
142
+ return entity
143
+ return None
144
+
145
+ def _ensure_tables(self) -> None:
146
+ """v5.7.9:确保图谱持久化表存在(旧库升级容错)"""
147
+ try:
148
+ conn = self.storage._get_conn()
149
+ conn.executescript("""
150
+ CREATE TABLE IF NOT EXISTS knowledge_graph (
151
+ id TEXT PRIMARY KEY,
152
+ entity TEXT,
153
+ entity_type TEXT,
154
+ description TEXT,
155
+ metadata TEXT DEFAULT '{}',
156
+ created_at REAL
157
+ );
158
+ CREATE TABLE IF NOT EXISTS graph_relations (
159
+ id TEXT PRIMARY KEY,
160
+ from_entity TEXT,
161
+ to_entity TEXT,
162
+ relation_type TEXT,
163
+ weight REAL DEFAULT 1.0,
164
+ memory_ids TEXT DEFAULT '[]',
165
+ metadata TEXT DEFAULT '{}',
166
+ created_at REAL
167
+ );
168
+ """)
169
+ conn.commit()
170
+ except sqlite3.OperationalError as _kg_err:
171
+ logger.warning("创建知识图谱表失败: %s", _kg_err)
172
+
173
+ def _load_from_db(self) -> None:
174
+ """v5.7.9:从持久化表重建内存态(重启后实体/关系/邻接可用)"""
175
+ try:
176
+ conn = self.storage._get_conn()
177
+ for row in conn.execute(
178
+ "SELECT id, entity, entity_type, description, metadata, created_at "
179
+ "FROM knowledge_graph"
180
+ ):
181
+ eid, name, etype, desc_text, meta, created = row
182
+ self.entities[eid] = KnowledgeEntity(
183
+ id=eid, name=name, entity_type=etype or "general",
184
+ description=desc_text or "",
185
+ metadata=json.loads(meta) if meta else {},
186
+ created_at=created or 0.0,
187
+ )
188
+ for row in conn.execute(
189
+ "SELECT id, from_entity, to_entity, relation_type, weight, "
190
+ "memory_ids, metadata, created_at FROM graph_relations"
191
+ ):
192
+ rid, fe, te, rtype, weight, mids, meta, created = row
193
+ rel = KnowledgeRelation(
194
+ id=rid, from_entity=fe, to_entity=te, relation_type=rtype,
195
+ weight=weight or 1.0,
196
+ memory_ids=json.loads(mids) if mids else [],
197
+ metadata=json.loads(meta) if meta else {},
198
+ created_at=created or 0.0,
199
+ )
200
+ self.relations[rid] = rel
201
+ self._adjacency[fe].append((te, rtype, rel.weight))
202
+ self._adjacency[te].append((fe, rtype, rel.weight))
203
+ for mid in rel.memory_ids:
204
+ self._processed_memory_ids.add(mid)
205
+ except sqlite3.OperationalError as _kg_err:
206
+ logger.warning("加载知识图谱失败: %s", _kg_err)
207
+
208
+ def add_entity(self, name: str, entity_type: str = "general",
209
+ description: str = "", metadata: Optional[Dict] = None) -> KnowledgeEntity:
210
+ """添加实体"""
211
+ entity_id = str(uuid.uuid4())
212
+ now = time.time()
213
+
214
+ entity = KnowledgeEntity(
215
+ id=entity_id,
216
+ name=name,
217
+ entity_type=entity_type,
218
+ description=description,
219
+ metadata=metadata or {},
220
+ created_at=now,
221
+ )
222
+
223
+ self.entities[entity_id] = entity
224
+
225
+ if self.storage:
226
+ try:
227
+ conn = self.storage._get_conn()
228
+ conn.execute("""
229
+ INSERT OR IGNORE INTO knowledge_graph (id, entity, entity_type, description, metadata, created_at)
230
+ VALUES (?, ?, ?, ?, ?, ?)
231
+ """, (entity_id, name, entity_type, description,
232
+ json.dumps(metadata or {}, ensure_ascii=False), now))
233
+ conn.commit()
234
+ except sqlite3.OperationalError as _kg_err:
235
+ logger.warning("写入知识图谱实体失败: %s", _kg_err)
236
+
237
+ return entity
238
+
239
+ def add_relation(self, from_name: str, to_name: str, relation_type: str,
240
+ weight: float = 1.0, memory_id: str = "") -> KnowledgeRelation:
241
+ """添加关系"""
242
+ from_entity = self._get_or_create_entity(from_name)
243
+ to_entity = self._get_or_create_entity(to_name)
244
+
245
+ rel_id = str(uuid.uuid4())
246
+ now = time.time()
247
+
248
+ relation = KnowledgeRelation(
249
+ id=rel_id,
250
+ from_entity=from_entity.id,
251
+ to_entity=to_entity.id,
252
+ relation_type=relation_type,
253
+ weight=weight,
254
+ memory_ids=[memory_id] if memory_id else [],
255
+ created_at=now,
256
+ )
257
+
258
+ self.relations[rel_id] = relation
259
+ self._adjacency[from_entity.id].append((to_entity.id, relation_type, weight))
260
+ self._adjacency[to_entity.id].append((from_entity.id, relation_type, weight))
261
+
262
+ if self.storage:
263
+ try:
264
+ conn = self.storage._get_conn()
265
+ conn.execute("""
266
+ INSERT INTO graph_relations (id, from_entity, to_entity, relation_type, weight, memory_ids, metadata, created_at)
267
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
268
+ """, (rel_id, from_entity.id, to_entity.id, relation_type,
269
+ weight, json.dumps([memory_id] if memory_id else []),
270
+ json.dumps({}), now))
271
+ conn.commit()
272
+ except sqlite3.OperationalError as _kg_err:
273
+ logger.warning("写入知识图谱关系失败: %s", _kg_err)
274
+
275
+ return relation
276
+
277
+ def _get_or_create_entity(self, name: str) -> KnowledgeEntity:
278
+ for entity in self.entities.values():
279
+ if entity.name.lower() == name.lower():
280
+ return entity
281
+ return self.add_entity(name)
282
+
283
+ def process_memory(self, memory_id: str, content: str) -> Tuple[List[KnowledgeEntity], List[KnowledgeRelation]]:
284
+ """处理一条记忆,提取实体和关系"""
285
+ # v5.7.9:同进程内已处理记忆直接跳过(防抖)
286
+ if memory_id and memory_id in self._processed_memory_ids:
287
+ return [], []
288
+ entities_data = self.extract_entities(content)
289
+ entity_names = [name for name, _ in entities_data]
290
+
291
+ added_entities = []
292
+ for name, etype in entities_data:
293
+ entity = self._get_or_create_entity(name)
294
+ if entity.entity_type == "general" and etype != "general":
295
+ entity.entity_type = etype
296
+ added_entities.append(entity)
297
+
298
+ relations = self.extract_relations(content, entity_names)
299
+ added_relations = []
300
+ for subj, rel_type, obj in relations:
301
+ relation = self.add_relation(subj, obj, rel_type, memory_id=memory_id)
302
+ added_relations.append(relation)
303
+
304
+ if memory_id:
305
+ self._processed_memory_ids.add(memory_id)
306
+
307
+ return added_entities, added_relations
308
+
309
+ def get_related_entities(self, entity_name: str, depth: int = 2,
310
+ max_results: int = 20) -> List[Tuple[str, str, float]]:
311
+ """获取相关实体(广度优先)"""
312
+ entity = self._find_entity_by_name(entity_name)
313
+ if not entity:
314
+ return []
315
+
316
+ visited = {entity.id}
317
+ queue = deque([(entity.id, 0, 1.0)]) # v5.4.7 修复 L-7:使用 deque 替代 list.pop(0)
318
+ results = []
319
+
320
+ while queue:
321
+ current_id, current_depth, current_weight = queue.popleft()
322
+ if current_depth > depth:
323
+ continue
324
+
325
+ for neighbor_id, rel_type, weight in self._adjacency.get(current_id, []):
326
+ if neighbor_id in visited:
327
+ continue
328
+ visited.add(neighbor_id)
329
+
330
+ total_weight = current_weight * weight
331
+ neighbor = self.entities.get(neighbor_id)
332
+ if neighbor:
333
+ results.append((neighbor.name, rel_type, total_weight))
334
+
335
+ if current_depth < depth:
336
+ queue.append((neighbor_id, current_depth + 1, total_weight))
337
+
338
+ results.sort(key=lambda x: x[2], reverse=True)
339
+ return results[:max_results]
340
+
341
+ def find_path(self, from_name: str, to_name: str, max_depth: int = 3) -> Optional[GraphPath]:
342
+ """查找两个实体间的路径"""
343
+ from_entity = self._find_entity_by_name(from_name)
344
+ to_entity = self._find_entity_by_name(to_name)
345
+ if not from_entity or not to_entity:
346
+ return None
347
+
348
+ from queue import deque
349
+ queue = deque([(from_entity.id, [from_entity.id], [])])
350
+ visited = {from_entity.id}
351
+
352
+ while queue:
353
+ current, path, rels = queue.popleft()
354
+ if len(path) > max_depth + 1:
355
+ continue
356
+
357
+ if current == to_entity.id:
358
+ entity_names = []
359
+ for eid in path:
360
+ e = self.entities.get(eid)
361
+ entity_names.append(e.name if e else eid)
362
+ total_w = 1.0
363
+ return GraphPath(entities=entity_names, relations=rels, total_weight=total_w)
364
+
365
+ for neighbor_id, rel_type, weight in self._adjacency.get(current, []):
366
+ if neighbor_id in visited:
367
+ continue
368
+ visited.add(neighbor_id)
369
+ queue.append((neighbor_id, path + [neighbor_id], rels + [rel_type]))
370
+
371
+ return None
372
+
373
+ def _find_entity_by_name(self, name: str) -> Optional[KnowledgeEntity]:
374
+ name_lower = name.lower()
375
+ for entity in self.entities.values():
376
+ if entity.name.lower() == name_lower:
377
+ return entity
378
+ return None
379
+
380
+ def get_entity_stats(self) -> Dict:
381
+ """获取图谱统计"""
382
+ type_counts = defaultdict(int)
383
+ for entity in self.entities.values():
384
+ type_counts[entity.entity_type] += 1
385
+
386
+ rel_counts = defaultdict(int)
387
+ for relation in self.relations.values():
388
+ rel_counts[relation.relation_type] += 1
389
+
390
+ return {
391
+ "total_entities": len(self.entities),
392
+ "total_relations": len(self.relations),
393
+ "entity_types": dict(type_counts),
394
+ "relation_types": dict(rel_counts),
395
+ }