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.
- MindForge.py +20 -0
- mindforge/__init__.py +111 -0
- mindforge/adapters/__init__.py +18 -0
- mindforge/adapters/claude_adapter.py +84 -0
- mindforge/adapters/crewai_memory.py +76 -0
- mindforge/adapters/generic_api.py +183 -0
- mindforge/adapters/langgraph_store.py +463 -0
- mindforge/adapters/openclaw_adapter.py +91 -0
- mindforge/api/__init__.py +1 -0
- mindforge/api/server.py +1567 -0
- mindforge/cli/__init__.py +1 -0
- mindforge/cli/main.py +13607 -0
- mindforge/core/__init__.py +37 -0
- mindforge/core/embedding.py +847 -0
- mindforge/core/encryption.py +597 -0
- mindforge/core/indexer.py +363 -0
- mindforge/core/mindforge.py +6298 -0
- mindforge/core/paths.py +43 -0
- mindforge/core/query.py +501 -0
- mindforge/core/storage.py +18656 -0
- mindforge/core/types.py +314 -0
- mindforge/core/version.py +56 -0
- mindforge/daemon/__init__.py +0 -0
- mindforge/daemon/server.py +277 -0
- mindforge/mcp/__init__.py +4 -0
- mindforge/mcp/server.py +2559 -0
- mindforge/modules/__init__.py +138 -0
- mindforge/modules/categorizer.py +151 -0
- mindforge/modules/conflict_detector.py +356 -0
- mindforge/modules/connectors.py +331 -0
- mindforge/modules/context_window.py +337 -0
- mindforge/modules/event_bus.py +572 -0
- mindforge/modules/evolution.py +210 -0
- mindforge/modules/experience.py +402 -0
- mindforge/modules/federated.py +722 -0
- mindforge/modules/federated_acl.py +393 -0
- mindforge/modules/follow_up.py +315 -0
- mindforge/modules/hybrid_search.py +565 -0
- mindforge/modules/importer.py +374 -0
- mindforge/modules/integrator.py +196 -0
- mindforge/modules/intent_router.py +359 -0
- mindforge/modules/knowledge_graph.py +395 -0
- mindforge/modules/memory_compressor.py +592 -0
- mindforge/modules/memory_decay.py +474 -0
- mindforge/modules/memory_passport.py +250 -0
- mindforge/modules/multi_agent.py +534 -0
- mindforge/modules/multimodal.py +304 -0
- mindforge/modules/outcome_learning.py +186 -0
- mindforge/modules/personality.py +275 -0
- mindforge/modules/principled_forgetting.py +258 -0
- mindforge/modules/privacy.py +736 -0
- mindforge/modules/recall.py +263 -0
- mindforge/modules/session_focus.py +305 -0
- mindforge/modules/share_conflict.py +521 -0
- mindforge/modules/skill_extractor.py +340 -0
- mindforge/modules/skill_pack.py +205 -0
- mindforge/modules/timeline_viz.py +206 -0
- mindforge/modules/v6.py +931 -0
- mindforge_memory-6.4.1.dist-info/METADATA +281 -0
- mindforge_memory-6.4.1.dist-info/RECORD +64 -0
- mindforge_memory-6.4.1.dist-info/WHEEL +5 -0
- mindforge_memory-6.4.1.dist-info/entry_points.txt +3 -0
- mindforge_memory-6.4.1.dist-info/licenses/LICENSE +21 -0
- mindforge_memory-6.4.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MindForge v6.0.0 功能 2:记忆时间线可视化
|
|
3
|
+
|
|
4
|
+
把记忆按时间组织成可截图分享的 Web 可视化:记忆时间线、四层分布、
|
|
5
|
+
偏好主题、偏好趋势。**只输出聚合统计,不含明文内容**(隐私安全)。
|
|
6
|
+
|
|
7
|
+
- build_timeline_visual(): 按日序列连续桶统计
|
|
8
|
+
- layers_distribution / categories / preferences 聚合
|
|
9
|
+
- preference_trend: 偏好标签随时间变化
|
|
10
|
+
- graph 规模:知识图谱节点/边(供可视化页展示)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
import time
|
|
17
|
+
from collections import OrderedDict
|
|
18
|
+
from datetime import datetime, timedelta
|
|
19
|
+
from typing import Any, Dict, List, Optional
|
|
20
|
+
|
|
21
|
+
# 偏好标签前缀(从记忆 tags 提取主题)
|
|
22
|
+
_PREFERENCE_PREFIXES = ("user_preference", "preference", "profile")
|
|
23
|
+
# 需要排除的通用标签(不算偏好主题)
|
|
24
|
+
_GENERIC_TAGS = {"imported", "general", "favorite", "important"}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _day_key(ts: float) -> str:
|
|
28
|
+
"""时间戳 -> YYYY-MM-DD"""
|
|
29
|
+
try:
|
|
30
|
+
return datetime.fromtimestamp(float(ts)).strftime("%Y-%m-%d")
|
|
31
|
+
except (TypeError, ValueError, OSError):
|
|
32
|
+
return datetime.fromtimestamp(0).strftime("%Y-%m-%d")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _tag_theme(tags: List[str]) -> Optional[str]:
|
|
36
|
+
"""从标签中提取偏好主题(去重、过滤通用标签)。"""
|
|
37
|
+
for t in tags or []:
|
|
38
|
+
t = (t or "").strip()
|
|
39
|
+
if not t or t in _GENERIC_TAGS:
|
|
40
|
+
continue
|
|
41
|
+
for p in _PREFERENCE_PREFIXES:
|
|
42
|
+
if t.startswith(p):
|
|
43
|
+
name = t[len(p):].strip(":_- ")
|
|
44
|
+
if name:
|
|
45
|
+
return name
|
|
46
|
+
return t
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _build_daily_buckets(start_ts: float, end_ts: float,
|
|
51
|
+
days: int) -> "OrderedDict[str, Dict[str, Any]]":
|
|
52
|
+
"""构建从 start 到 end 的 days 个连续日历日桶(含边界修正)。"""
|
|
53
|
+
if days <= 0:
|
|
54
|
+
days = 1
|
|
55
|
+
# 边界修正:保证 days 个日历日(start_dt = end_dt - (days-1))
|
|
56
|
+
end_dt = datetime.fromtimestamp(end_ts).replace(
|
|
57
|
+
hour=0, minute=0, second=0, microsecond=0)
|
|
58
|
+
start_dt = end_dt - timedelta(days=days - 1)
|
|
59
|
+
buckets: "OrderedDict[str, Dict[str, Any]]" = OrderedDict()
|
|
60
|
+
for i in range(days):
|
|
61
|
+
d = start_dt + timedelta(days=i)
|
|
62
|
+
key = d.strftime("%Y-%m-%d")
|
|
63
|
+
buckets[key] = {
|
|
64
|
+
"date": key,
|
|
65
|
+
"count": 0,
|
|
66
|
+
"layers": {},
|
|
67
|
+
"categories": {},
|
|
68
|
+
}
|
|
69
|
+
return buckets
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def build_timeline_visual(storage, category: Optional[str] = None,
|
|
73
|
+
layer: Optional[str] = None,
|
|
74
|
+
days: int = 365,
|
|
75
|
+
limit: int = 20000) -> Dict[str, Any]:
|
|
76
|
+
"""构建时间线可视化聚合数据(不含明文)。
|
|
77
|
+
|
|
78
|
+
返回 dict:
|
|
79
|
+
{
|
|
80
|
+
"generated_at", "days", "start_date", "end_date",
|
|
81
|
+
"total_memories", "active_memories",
|
|
82
|
+
"timeline": [{"date","count","layers","categories"}...],
|
|
83
|
+
"layers_distribution": {layer: count},
|
|
84
|
+
"categories": {category: count},
|
|
85
|
+
"preferences": {"themes": [{name,count}...], "trend": [{date, themes:[{name,count}]}...]},
|
|
86
|
+
"graph": {"entities": n, "relations": n},
|
|
87
|
+
"four_tier_summary": {"sensory":n,"short_term":n,"long_term":n,"permanent":n},
|
|
88
|
+
"privacy": "aggregate-only"
|
|
89
|
+
}
|
|
90
|
+
"""
|
|
91
|
+
from mindforge.core.types import MemoryLayer
|
|
92
|
+
|
|
93
|
+
now = time.time()
|
|
94
|
+
layer_names = [e.value for e in MemoryLayer]
|
|
95
|
+
buckets = _build_daily_buckets(now - 86400 * max(1, days), now, days)
|
|
96
|
+
|
|
97
|
+
layers_dist: Dict[str, int] = {}
|
|
98
|
+
categories: Dict[str, int] = {}
|
|
99
|
+
theme_count: Dict[str, int] = {}
|
|
100
|
+
total = 0
|
|
101
|
+
active = 0
|
|
102
|
+
|
|
103
|
+
# 解析 layer 参数(字符串 -> 枚举)
|
|
104
|
+
layer_enum = None
|
|
105
|
+
if layer:
|
|
106
|
+
try:
|
|
107
|
+
layer_enum = MemoryLayer.from_string(str(layer))
|
|
108
|
+
except Exception:
|
|
109
|
+
layer_enum = None
|
|
110
|
+
|
|
111
|
+
mems = storage.list_memories(
|
|
112
|
+
category=category, layer=layer_enum, limit=limit)
|
|
113
|
+
for m in mems or []:
|
|
114
|
+
total += 1
|
|
115
|
+
# v6.1.2 修复:原来读的是 `getattr(m, "is_archived", False)`,但
|
|
116
|
+
# MemoryEntry(core/storage.py)没有 is_archived 字段——本项目的「归档」
|
|
117
|
+
# 实为 category == 'trash',而 `list_memories` 早已把它排除。于是这里
|
|
118
|
+
# 恒不成立,`active_memories` 永远等于 `total_memories`,字段失去意义。
|
|
119
|
+
# 改用真实信号:过了 valid_to 的时刻不再计入活跃(valid_to == 0 表示无边界)。
|
|
120
|
+
valid_to = getattr(m, "valid_to", 0) or 0
|
|
121
|
+
if not valid_to or valid_to > now:
|
|
122
|
+
active += 1
|
|
123
|
+
ts = getattr(m, "created_at", 0) or 0
|
|
124
|
+
key = _day_key(ts)
|
|
125
|
+
bucket = buckets.get(key)
|
|
126
|
+
if bucket is None:
|
|
127
|
+
continue
|
|
128
|
+
bucket["count"] += 1
|
|
129
|
+
lname = getattr(m, "layer", "") or "short_term"
|
|
130
|
+
lname = getattr(lname, "value", lname) or "short_term"
|
|
131
|
+
bucket["layers"][lname] = bucket["layers"].get(lname, 0) + 1
|
|
132
|
+
cname = getattr(m, "category", "") or "general"
|
|
133
|
+
bucket["categories"][cname] = bucket["categories"].get(cname, 0) + 1
|
|
134
|
+
layers_dist[lname] = layers_dist.get(lname, 0) + 1
|
|
135
|
+
categories[cname] = categories.get(cname, 0) + 1
|
|
136
|
+
theme = _tag_theme(list(getattr(m, "tags", []) or []))
|
|
137
|
+
if theme:
|
|
138
|
+
theme_count[theme] = theme_count.get(theme, 0) + 1
|
|
139
|
+
|
|
140
|
+
# 偏好趋势:按月窗口聚合 top 主题
|
|
141
|
+
trend_buckets: "OrderedDict[str, Dict[str, int]]" = OrderedDict()
|
|
142
|
+
for m in mems or []:
|
|
143
|
+
theme = _tag_theme(list(getattr(m, "tags", []) or []))
|
|
144
|
+
if not theme:
|
|
145
|
+
continue
|
|
146
|
+
ts = getattr(m, "created_at", 0) or 0
|
|
147
|
+
try:
|
|
148
|
+
month = datetime.fromtimestamp(ts).strftime("%Y-%m")
|
|
149
|
+
except (TypeError, ValueError, OSError):
|
|
150
|
+
continue
|
|
151
|
+
tb = trend_buckets.setdefault(month, {})
|
|
152
|
+
tb[theme] = tb.get(theme, 0) + 1
|
|
153
|
+
|
|
154
|
+
preference_trend = []
|
|
155
|
+
for month, themes in trend_buckets.items():
|
|
156
|
+
top = sorted(themes.items(), key=lambda kv: (-kv[1], kv[0]))[:8]
|
|
157
|
+
preference_trend.append({"date": month, "themes": [
|
|
158
|
+
{"name": name, "count": cnt} for name, cnt in top]})
|
|
159
|
+
|
|
160
|
+
# 知识图谱规模(仅计数,不含实体明文)
|
|
161
|
+
entities = 0
|
|
162
|
+
relations = 0
|
|
163
|
+
try:
|
|
164
|
+
conn = storage._get_conn()
|
|
165
|
+
row = conn.execute("SELECT COUNT(*) FROM knowledge_graph").fetchone()
|
|
166
|
+
entities = int(row[0]) if row and row[0] else 0
|
|
167
|
+
row = conn.execute("SELECT COUNT(*) FROM graph_relations").fetchone()
|
|
168
|
+
relations = int(row[0]) if row and row[0] else 0
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
timeline = []
|
|
173
|
+
for b in buckets.values():
|
|
174
|
+
timeline.append({
|
|
175
|
+
"date": b["date"], "count": b["count"],
|
|
176
|
+
"layers": b["layers"], "categories": b["categories"],
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
four_tier = {}
|
|
180
|
+
for ln in layer_names:
|
|
181
|
+
four_tier[ln] = layers_dist.get(ln, 0)
|
|
182
|
+
|
|
183
|
+
start_key = next(iter(buckets), "")
|
|
184
|
+
end_key = next(reversed(buckets), "") if buckets else ""
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
"success": True,
|
|
188
|
+
"generated_at": round(now, 3),
|
|
189
|
+
"days": days,
|
|
190
|
+
"start_date": start_key,
|
|
191
|
+
"end_date": end_key,
|
|
192
|
+
"total_memories": total,
|
|
193
|
+
"active_memories": active,
|
|
194
|
+
"timeline": timeline,
|
|
195
|
+
"layers_distribution": layers_dist,
|
|
196
|
+
"categories": categories,
|
|
197
|
+
"preferences": {
|
|
198
|
+
"themes": [{"name": k, "count": v}
|
|
199
|
+
for k, v in sorted(
|
|
200
|
+
theme_count.items(), key=lambda kv: (-kv[1], kv[0]))[:20]],
|
|
201
|
+
"trend": preference_trend,
|
|
202
|
+
},
|
|
203
|
+
"graph": {"entities": entities, "relations": relations},
|
|
204
|
+
"four_tier_summary": four_tier,
|
|
205
|
+
"privacy": "aggregate-only",
|
|
206
|
+
}
|