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,138 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MindForge v5.0 功能模块
|
|
3
|
+
知识图谱 · 记忆演化 · 人格化 · 多模态 · 联邦记忆 · 多Agent记忆空间
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .recall import RecallEngine, RecallConfig
|
|
7
|
+
from .knowledge_graph import KnowledgeGraph, KnowledgeEntity, KnowledgeRelation
|
|
8
|
+
from .evolution import MemoryEvolution, ForgettingCurve
|
|
9
|
+
from .personality import PersonalityEngine, UserProfile
|
|
10
|
+
from .multimodal import MultimodalMemory, MultimodalType
|
|
11
|
+
from .federated import FederatedMemory, FederatedPeer
|
|
12
|
+
from .categorizer import TaxonomyManager
|
|
13
|
+
from .privacy import PrivacyEngine, PrivacyScanResult
|
|
14
|
+
from .integrator import MemoryIntegrator
|
|
15
|
+
from .multi_agent import MultiAgentMemoryManager, AgentSpace, SpaceMember, SpaceRole
|
|
16
|
+
# v5.3.9 新增五大能力
|
|
17
|
+
from .intent_router import IntentRouter, IntentResult, INTENT_DEFS, DEFAULT_INTENT
|
|
18
|
+
from .conflict_detector import ConflictDetector, ConflictPair, DecayAction, ANTONYM_PAIRS
|
|
19
|
+
from .skill_extractor import SkillExtractor, SkillTemplate, SkillSlot
|
|
20
|
+
from .hybrid_search import (QueryExpander, ExpansionResult,
|
|
21
|
+
CrossEncoderReranker, RerankResult, RerankWeights)
|
|
22
|
+
from .session_focus import SessionFocus, TopicCluster, FocusSummary
|
|
23
|
+
# v5.4.2 新增两大能力
|
|
24
|
+
from .federated_acl import FederatedACLManager
|
|
25
|
+
from .share_conflict import SharedConflictResolver
|
|
26
|
+
# v6.0.0 新增:一键导入 / 时间线可视化 / 技能包 / 演变 / 结果学习 / 护照 / 遗忘 / 收尾
|
|
27
|
+
from .importer import import_chat_history, parse_export, detect_platform
|
|
28
|
+
from .timeline_viz import build_timeline_visual
|
|
29
|
+
from .skill_pack import export_skill_pack, validate_skill_pack, import_skill_pack
|
|
30
|
+
from .outcome_learning import apply_outcome, outcome_stats, search_weighted
|
|
31
|
+
from .memory_passport import (
|
|
32
|
+
export_passport, import_passport, revoke_passport, passport_status,
|
|
33
|
+
)
|
|
34
|
+
from .principled_forgetting import (
|
|
35
|
+
explain_forgetting, run_principled_gc, forgetting_stats,
|
|
36
|
+
)
|
|
37
|
+
from .follow_up import (
|
|
38
|
+
create_follow_up, list_due, resolve_follow_up, list_follow_ups,
|
|
39
|
+
)
|
|
40
|
+
# v6.0.0 扩展端口(多 Agent 协作 + 6G AI 记忆预览,正式发布)
|
|
41
|
+
from .v6 import (
|
|
42
|
+
V6_TARGET_VERSION,
|
|
43
|
+
V6Capability,
|
|
44
|
+
V6Status,
|
|
45
|
+
V6Port,
|
|
46
|
+
MultiAgentCollaborationPort,
|
|
47
|
+
MultiAgentAdapterPort,
|
|
48
|
+
MemoryPreviewPort,
|
|
49
|
+
create_multi_agent_adapter,
|
|
50
|
+
register_v6_port,
|
|
51
|
+
unregister_v6_port,
|
|
52
|
+
get_v6_port,
|
|
53
|
+
list_v6_ports,
|
|
54
|
+
v6_status,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"RecallEngine",
|
|
59
|
+
"RecallConfig",
|
|
60
|
+
"KnowledgeGraph",
|
|
61
|
+
"KnowledgeEntity",
|
|
62
|
+
"KnowledgeRelation",
|
|
63
|
+
"MemoryEvolution",
|
|
64
|
+
"ForgettingCurve",
|
|
65
|
+
"PersonalityEngine",
|
|
66
|
+
"UserProfile",
|
|
67
|
+
"MultimodalMemory",
|
|
68
|
+
"MultimodalType",
|
|
69
|
+
"FederatedMemory",
|
|
70
|
+
"FederatedPeer",
|
|
71
|
+
"TaxonomyManager",
|
|
72
|
+
"PrivacyEngine",
|
|
73
|
+
"PrivacyScanResult",
|
|
74
|
+
"MemoryIntegrator",
|
|
75
|
+
"MultiAgentMemoryManager",
|
|
76
|
+
"AgentSpace",
|
|
77
|
+
"SpaceMember",
|
|
78
|
+
"SpaceRole",
|
|
79
|
+
# v5.3.9
|
|
80
|
+
"IntentRouter",
|
|
81
|
+
"IntentResult",
|
|
82
|
+
"INTENT_DEFS",
|
|
83
|
+
"DEFAULT_INTENT",
|
|
84
|
+
"ConflictDetector",
|
|
85
|
+
"ConflictPair",
|
|
86
|
+
"DecayAction",
|
|
87
|
+
"ANTONYM_PAIRS",
|
|
88
|
+
"SkillExtractor",
|
|
89
|
+
"SkillTemplate",
|
|
90
|
+
"SkillSlot",
|
|
91
|
+
"QueryExpander",
|
|
92
|
+
"ExpansionResult",
|
|
93
|
+
"CrossEncoderReranker",
|
|
94
|
+
"RerankResult",
|
|
95
|
+
"RerankWeights",
|
|
96
|
+
"SessionFocus",
|
|
97
|
+
"TopicCluster",
|
|
98
|
+
"FocusSummary",
|
|
99
|
+
# v5.4.2
|
|
100
|
+
"FederatedACLManager",
|
|
101
|
+
"SharedConflictResolver",
|
|
102
|
+
# v6.0.0 功能 1-8
|
|
103
|
+
"import_chat_history",
|
|
104
|
+
"parse_export",
|
|
105
|
+
"detect_platform",
|
|
106
|
+
"build_timeline_visual",
|
|
107
|
+
"export_skill_pack",
|
|
108
|
+
"validate_skill_pack",
|
|
109
|
+
"import_skill_pack",
|
|
110
|
+
"apply_outcome",
|
|
111
|
+
"outcome_stats",
|
|
112
|
+
"search_weighted",
|
|
113
|
+
"export_passport",
|
|
114
|
+
"import_passport",
|
|
115
|
+
"revoke_passport",
|
|
116
|
+
"passport_status",
|
|
117
|
+
"explain_forgetting",
|
|
118
|
+
"run_principled_gc",
|
|
119
|
+
"forgetting_stats",
|
|
120
|
+
"create_follow_up",
|
|
121
|
+
"list_due",
|
|
122
|
+
"resolve_follow_up",
|
|
123
|
+
"list_follow_ups",
|
|
124
|
+
# v6.0.0 端口预留
|
|
125
|
+
"V6_TARGET_VERSION",
|
|
126
|
+
"V6Capability",
|
|
127
|
+
"V6Status",
|
|
128
|
+
"V6Port",
|
|
129
|
+
"MultiAgentCollaborationPort",
|
|
130
|
+
"MultiAgentAdapterPort",
|
|
131
|
+
"MemoryPreviewPort",
|
|
132
|
+
"create_multi_agent_adapter",
|
|
133
|
+
"register_v6_port",
|
|
134
|
+
"unregister_v6_port",
|
|
135
|
+
"get_v6_port",
|
|
136
|
+
"list_v6_ports",
|
|
137
|
+
"v6_status",
|
|
138
|
+
]
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MindForge v5.0 分类管理模块
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import List, Dict, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class CategoryNode:
|
|
12
|
+
name: str
|
|
13
|
+
parent: Optional[str] = None
|
|
14
|
+
children: List[str] = field(default_factory=list)
|
|
15
|
+
keywords: List[str] = field(default_factory=list)
|
|
16
|
+
description: str = ""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
DEFAULT_TAXONOMY = {
|
|
20
|
+
"work": {
|
|
21
|
+
"name": "工作",
|
|
22
|
+
"keywords": ["工作", "项目", "任务", "会议", "报告", "同事", "老板", "公司", "业务"],
|
|
23
|
+
"children": ["tech", "project", "meeting"],
|
|
24
|
+
},
|
|
25
|
+
"tech": {
|
|
26
|
+
"name": "技术",
|
|
27
|
+
"keywords": ["技术", "编程", "代码", "开发", "bug", "算法", "架构", "设计"],
|
|
28
|
+
"parent": "work",
|
|
29
|
+
"children": ["programming", "devops", "design"],
|
|
30
|
+
},
|
|
31
|
+
"programming": {
|
|
32
|
+
"name": "编程开发",
|
|
33
|
+
"keywords": ["python", "java", "javascript", "函数", "类", "接口", "api"],
|
|
34
|
+
"parent": "tech",
|
|
35
|
+
},
|
|
36
|
+
"life": {
|
|
37
|
+
"name": "生活",
|
|
38
|
+
"keywords": ["生活", "吃饭", "睡觉", "家庭", "朋友", "旅行", "美食", "电影"],
|
|
39
|
+
"children": ["health", "hobby"],
|
|
40
|
+
},
|
|
41
|
+
"health": {
|
|
42
|
+
"name": "健康",
|
|
43
|
+
"keywords": ["健康", "运动", "健身", "饮食", "睡眠", "体检", "疾病"],
|
|
44
|
+
"parent": "life",
|
|
45
|
+
},
|
|
46
|
+
"learning": {
|
|
47
|
+
"name": "学习",
|
|
48
|
+
"keywords": ["学习", "研究", "阅读", "书籍", "课程", "笔记", "知识"],
|
|
49
|
+
"children": [],
|
|
50
|
+
},
|
|
51
|
+
"idea": {
|
|
52
|
+
"name": "创意",
|
|
53
|
+
"keywords": ["创意", "想法", "灵感", "设计", "发明", "创新", "产品"],
|
|
54
|
+
"children": [],
|
|
55
|
+
},
|
|
56
|
+
"personal": {
|
|
57
|
+
"name": "个人",
|
|
58
|
+
"keywords": ["我", "自己", "个人", "隐私", "秘密", "想法", "感受"],
|
|
59
|
+
"children": [],
|
|
60
|
+
},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class TaxonomyManager:
|
|
65
|
+
"""分类管理器"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, taxonomy: Optional[Dict] = None):
|
|
68
|
+
self.taxonomy = taxonomy or DEFAULT_TAXONOMY
|
|
69
|
+
self._build_keyword_index()
|
|
70
|
+
|
|
71
|
+
def _build_keyword_index(self):
|
|
72
|
+
self._keyword_map: Dict[str, str] = {}
|
|
73
|
+
for cat_id, cat_data in self.taxonomy.items():
|
|
74
|
+
for keyword in cat_data.get("keywords", []):
|
|
75
|
+
self._keyword_map[keyword.lower()] = cat_id
|
|
76
|
+
|
|
77
|
+
def suggest_category(self, content: str) -> str:
|
|
78
|
+
"""建议分类"""
|
|
79
|
+
content_lower = content.lower()
|
|
80
|
+
scores: Dict[str, int] = {}
|
|
81
|
+
|
|
82
|
+
for keyword, cat_id in self._keyword_map.items():
|
|
83
|
+
if keyword in content_lower:
|
|
84
|
+
scores[cat_id] = scores.get(cat_id, 0) + 1
|
|
85
|
+
|
|
86
|
+
if not scores:
|
|
87
|
+
return "general"
|
|
88
|
+
|
|
89
|
+
best_category = max(scores.items(), key=lambda x: x[1])[0]
|
|
90
|
+
return best_category
|
|
91
|
+
|
|
92
|
+
def suggest_tags(self, content: str, max_tags: int = 5) -> List[str]:
|
|
93
|
+
"""建议标签"""
|
|
94
|
+
content_lower = content.lower()
|
|
95
|
+
tags = []
|
|
96
|
+
|
|
97
|
+
for keyword, cat_id in self._keyword_map.items():
|
|
98
|
+
if keyword in content_lower:
|
|
99
|
+
if keyword not in tags:
|
|
100
|
+
tags.append(keyword)
|
|
101
|
+
|
|
102
|
+
words = re.findall(r'[\w\u4e00-\u9fff]+', content_lower)
|
|
103
|
+
from collections import Counter
|
|
104
|
+
word_counts = Counter(words)
|
|
105
|
+
for word, count in word_counts.most_common(10):
|
|
106
|
+
if len(word) > 2 and word not in tags and count >= 1:
|
|
107
|
+
tags.append(word)
|
|
108
|
+
if len(tags) >= max_tags:
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
return tags[:max_tags]
|
|
112
|
+
|
|
113
|
+
def get_category_info(self, category_id: str) -> Optional[Dict]:
|
|
114
|
+
"""获取分类信息"""
|
|
115
|
+
return self.taxonomy.get(category_id)
|
|
116
|
+
|
|
117
|
+
def get_all_categories(self) -> Dict[str, Dict]:
|
|
118
|
+
"""获取所有分类"""
|
|
119
|
+
return self.taxonomy
|
|
120
|
+
|
|
121
|
+
def get_subcategories(self, parent_id: str) -> List[str]:
|
|
122
|
+
"""获取子分类"""
|
|
123
|
+
parent = self.taxonomy.get(parent_id)
|
|
124
|
+
if parent:
|
|
125
|
+
return parent.get("children", [])
|
|
126
|
+
return []
|
|
127
|
+
|
|
128
|
+
def get_parent_category(self, category_id: str) -> Optional[str]:
|
|
129
|
+
"""获取父分类"""
|
|
130
|
+
cat = self.taxonomy.get(category_id)
|
|
131
|
+
if cat:
|
|
132
|
+
return cat.get("parent")
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
def add_category(self, category_id: str, name: str,
|
|
136
|
+
keywords: Optional[List[str]] = None,
|
|
137
|
+
parent: Optional[str] = None):
|
|
138
|
+
"""添加分类"""
|
|
139
|
+
self.taxonomy[category_id] = {
|
|
140
|
+
"name": name,
|
|
141
|
+
"keywords": keywords or [],
|
|
142
|
+
"parent": parent,
|
|
143
|
+
"children": [],
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if parent and parent in self.taxonomy:
|
|
147
|
+
if category_id not in self.taxonomy[parent]["children"]:
|
|
148
|
+
self.taxonomy[parent]["children"].append(category_id)
|
|
149
|
+
|
|
150
|
+
for keyword in keywords or []:
|
|
151
|
+
self._keyword_map[keyword.lower()] = category_id
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MindForge 矛盾检测 + 自动衰减引擎
|
|
3
|
+
========================================
|
|
4
|
+
三类检测:
|
|
5
|
+
A. 直接反义对(词级别,如 启用 / 禁用)
|
|
6
|
+
B. 属性值冲突("价格是 X" vs "价格是 Y, X≠Y")
|
|
7
|
+
C. 时间线冲突("已于周一完成" vs "预计周三完成")
|
|
8
|
+
|
|
9
|
+
自动衰减策略:
|
|
10
|
+
- 同主体冲突 → 老记忆重要性衰减、tag 标记 conflict
|
|
11
|
+
- 显式 resolve 后,胜出记忆 +0.08 重要性(构造参数
|
|
12
|
+
`auto_boost_resolve_winner`,v6.1.2 校正:文档原写 +0.1,与代码默认值不符),
|
|
13
|
+
失败方 -0.20(`auto_decay_resolve_loser`)
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
20
|
+
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
# 反义词典(内置常见技术/生活反义对)
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
ANTONYM_PAIRS: List[Tuple[str, str]] = [
|
|
26
|
+
("启用", "禁用"), ("开启", "关闭"), ("打开", "关闭"), ("启动", "停止"),
|
|
27
|
+
("允许", "禁止"), ("允许", "拒绝"), ("通过", "驳回"), ("成功", "失败"),
|
|
28
|
+
("新增", "删除"), ("添加", "移除"), ("增加", "减少"), ("上升", "下降"),
|
|
29
|
+
("提高", "降低"), ("支持", "反对"), ("兼容", "不兼容"), ("安全", "危险"),
|
|
30
|
+
("公开", "私有"), ("加密", "明文"), ("同步", "异步"), ("阻塞", "非阻塞"),
|
|
31
|
+
("真", "假"), ("是", "否"), ("对", "错"), ("正确", "错误"),
|
|
32
|
+
("稳定", "不稳定"), ("可用", "不可用"), ("正常", "异常"), ("存在", "不存在"),
|
|
33
|
+
("完成", "未完成"), ("已", "未"), ("可以", "不能"), ("应该", "不该"),
|
|
34
|
+
# v5.4.7 修复:添加常见技术偏好反义对
|
|
35
|
+
("vim", "vscode"), ("vim", "vs code"), ("emacs", "vim"),
|
|
36
|
+
("python", "java"), ("javascript", "typescript"), ("react", "vue"),
|
|
37
|
+
("mac", "windows"), ("linux", "windows"), ("ios", "android"),
|
|
38
|
+
("git", "svn"), ("docker", "vm"), ("kubernetes", "docker swarm"),
|
|
39
|
+
("rest", "graphql"), ("sql", "nosql"), ("mongodb", "postgresql"),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# 属性抽取正则:<属性名> 是/为/=/: <值>
|
|
43
|
+
_ATTR_RE = re.compile(
|
|
44
|
+
r"([\u4e00-\u9fa5A-Za-z][\u4e00-\u9fa5A-Za-z0-9_\+\-\.\*#]{0,19})"
|
|
45
|
+
r"\s*(?:是|为|=|等于|:|:)\s*"
|
|
46
|
+
r"([\u4e00-\u9fa5A-Za-z0-9_\-\+\.\*#/%@\$]{1,40})"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# v5.4.7 修复:添加偏好抽取正则,检测 "喜欢/偏好 X" 模式
|
|
50
|
+
_PREFERENCE_RE = re.compile(
|
|
51
|
+
r"(?:喜欢|偏好|爱用|常用|主要用|习惯用)\s*"
|
|
52
|
+
r"([\u4e00-\u9fa5A-Za-z0-9_\-\+\.\*#/%@\$]{1,40})"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# 时间词
|
|
56
|
+
_TIME_WORDS = {
|
|
57
|
+
"周一": 1, "周二": 2, "周三": 3, "周四": 4, "周五": 5, "周六": 6, "周日": 7, "周天": 7,
|
|
58
|
+
"星期一": 1, "星期二": 2, "星期三": 3, "星期四": 4, "星期五": 5, "星期六": 6, "星期日": 7,
|
|
59
|
+
"一月": 1, "二月": 2, "三月": 3, "四月": 4, "五月": 5, "六月": 6, "七月": 7,
|
|
60
|
+
"八月": 8, "九月": 9, "十月": 10, "十一月": 11, "十二月": 12,
|
|
61
|
+
"今天": 0, "明天": 1, "后天": 2, "昨天": -1, "前天": -2,
|
|
62
|
+
}
|
|
63
|
+
_STATUS_PATTERNS = [
|
|
64
|
+
(re.compile(r"(已|已经|早就|完成了|做完了|搞定了)\s*[^\s,,。;;]{0,10}(完成|做完|上线|发布|提交|合并)"), "done"),
|
|
65
|
+
(re.compile(r"(预计|计划|准备|将于|要|还没|尚未|未完成|待办|准备)\s*[^\s,,。;;]{0,10}(完成|上线|发布|提交|合并|做|弄)"), "pending"),
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# 数据类
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class ConflictPair:
|
|
75
|
+
memory_id_a: str
|
|
76
|
+
memory_id_b: str
|
|
77
|
+
conflict_type: str # antonym / attribute / timeline
|
|
78
|
+
severity: float # 0.0 ~ 1.0
|
|
79
|
+
evidence: Dict[str, Any] = field(default_factory=dict)
|
|
80
|
+
suggestion: str = ""
|
|
81
|
+
resolution: str = "unresolved" # unresolved / a_wins / b_wins / both_deprecated
|
|
82
|
+
|
|
83
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
84
|
+
return {
|
|
85
|
+
"memory_id_a": self.memory_id_a,
|
|
86
|
+
"memory_id_b": self.memory_id_b,
|
|
87
|
+
"conflict_type": self.conflict_type,
|
|
88
|
+
"severity": round(self.severity, 3),
|
|
89
|
+
"evidence": self.evidence,
|
|
90
|
+
"suggestion": self.suggestion,
|
|
91
|
+
"resolution": self.resolution,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class DecayAction:
|
|
97
|
+
memory_id: str
|
|
98
|
+
delta_importance: float # 例如 -0.15
|
|
99
|
+
added_tags: List[str] = field(default_factory=list)
|
|
100
|
+
reason: str = ""
|
|
101
|
+
|
|
102
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
103
|
+
return {
|
|
104
|
+
"memory_id": self.memory_id,
|
|
105
|
+
"delta_importance": round(self.delta_importance, 3),
|
|
106
|
+
"added_tags": self.added_tags,
|
|
107
|
+
"reason": self.reason,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# 引擎
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
class ConflictDetector:
|
|
116
|
+
"""纯 CPU 实现的矛盾检测 + 自动衰减。无外部依赖。"""
|
|
117
|
+
|
|
118
|
+
def __init__(self,
|
|
119
|
+
storage_or_antonym_pairs=None,
|
|
120
|
+
antonym_threshold: int = 1,
|
|
121
|
+
attr_match_min_length: int = 2,
|
|
122
|
+
auto_decay_old: float = 0.12,
|
|
123
|
+
auto_decay_resolve_loser: float = 0.20,
|
|
124
|
+
auto_boost_resolve_winner: float = 0.08):
|
|
125
|
+
# v5.4.7 修复:兼容 README 示例中的 ConflictDetector(storage) 用法
|
|
126
|
+
# 如果第一个参数是 StorageEngine 实例,则忽略它(当前版本不需要)
|
|
127
|
+
# 如果是 list/tuple,则作为反义词对列表
|
|
128
|
+
if storage_or_antonym_pairs is None:
|
|
129
|
+
antonym_pairs = None
|
|
130
|
+
elif isinstance(storage_or_antonym_pairs, (list, tuple)):
|
|
131
|
+
antonym_pairs = storage_or_antonym_pairs
|
|
132
|
+
else:
|
|
133
|
+
# 假设是 storage 或其他对象,忽略
|
|
134
|
+
antonym_pairs = None
|
|
135
|
+
|
|
136
|
+
self.antonyms = list(antonym_pairs or ANTONYM_PAIRS)
|
|
137
|
+
self.antonym_threshold = antonym_threshold
|
|
138
|
+
self.attr_min_len = attr_match_min_length
|
|
139
|
+
self.auto_decay_old = auto_decay_old
|
|
140
|
+
self.auto_decay_loser = auto_decay_resolve_loser
|
|
141
|
+
self.auto_boost_winner = auto_boost_resolve_winner
|
|
142
|
+
|
|
143
|
+
# -- detector primitives -----------------------------------------------
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
def _tokens(text: str) -> set:
|
|
147
|
+
# 简单中文切词:按标点/空白 + 2-gram 组合 + 中文 2~4 字片段
|
|
148
|
+
raw = re.sub(r"[\s,,。;;::!!??、()()\[\]【】\"'《》\-—/\\]+", " ", text)
|
|
149
|
+
parts = [p for p in raw.split() if p]
|
|
150
|
+
bag = set(parts)
|
|
151
|
+
for p in parts:
|
|
152
|
+
if len(p) >= 2:
|
|
153
|
+
for n in (2, 3):
|
|
154
|
+
for i in range(0, len(p) - n + 1):
|
|
155
|
+
bag.add(p[i:i + n])
|
|
156
|
+
return bag
|
|
157
|
+
|
|
158
|
+
def _antonym_hit(self, a: str, b: str) -> Tuple[int, List[str]]:
|
|
159
|
+
count = 0
|
|
160
|
+
pairs_hit: List[str] = []
|
|
161
|
+
ta, tb = self._tokens(a), self._tokens(b)
|
|
162
|
+
for x, y in self.antonyms:
|
|
163
|
+
if (x in ta and y in tb) or (y in ta and x in tb):
|
|
164
|
+
count += 1
|
|
165
|
+
pairs_hit.append(f"{x}↔{y}")
|
|
166
|
+
return count, pairs_hit
|
|
167
|
+
|
|
168
|
+
def _extract_attrs(self, text: str) -> Dict[str, str]:
|
|
169
|
+
attrs: Dict[str, str] = {}
|
|
170
|
+
for m in _ATTR_RE.finditer(text):
|
|
171
|
+
name, value = m.group(1).strip(), m.group(2).strip()
|
|
172
|
+
if len(name) >= self.attr_min_len and len(value) >= 1:
|
|
173
|
+
attrs[name] = value
|
|
174
|
+
return attrs
|
|
175
|
+
|
|
176
|
+
def _extract_preferences(self, text: str) -> List[str]:
|
|
177
|
+
"""v5.4.7 修复:抽取偏好项,如 '喜欢 Vim' -> ['vim']"""
|
|
178
|
+
prefs = []
|
|
179
|
+
for m in _PREFERENCE_RE.finditer(text):
|
|
180
|
+
pref = m.group(1).strip().lower()
|
|
181
|
+
if len(pref) >= 2:
|
|
182
|
+
prefs.append(pref)
|
|
183
|
+
return prefs
|
|
184
|
+
|
|
185
|
+
def _timeline_status(self, text: str) -> Optional[str]:
|
|
186
|
+
for pattern, status in _STATUS_PATTERNS:
|
|
187
|
+
if pattern.search(text):
|
|
188
|
+
return status
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
# -- public API --------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
def detect_antonym(self, text_a: str, text_b: str,
|
|
194
|
+
id_a: str, id_b: str) -> Optional[ConflictPair]:
|
|
195
|
+
cnt, pairs = self._antonym_hit(text_a, text_b)
|
|
196
|
+
if cnt < self.antonym_threshold:
|
|
197
|
+
return None
|
|
198
|
+
severity = min(0.35 + cnt * 0.20, 1.0)
|
|
199
|
+
return ConflictPair(
|
|
200
|
+
memory_id_a=id_a, memory_id_b=id_b,
|
|
201
|
+
conflict_type="antonym", severity=severity,
|
|
202
|
+
evidence={"antonym_pairs": pairs, "hit_count": cnt},
|
|
203
|
+
suggestion="反义词同现,建议人工核验以较新记忆或上下文一致者为准",
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def detect_preference(self, text_a: str, text_b: str,
|
|
207
|
+
id_a: str, id_b: str) -> Optional[ConflictPair]:
|
|
208
|
+
"""v5.4.7 修复:检测偏好冲突,如 '喜欢 Vim' vs '喜欢 VS Code'"""
|
|
209
|
+
prefs_a = self._extract_preferences(text_a)
|
|
210
|
+
prefs_b = self._extract_preferences(text_b)
|
|
211
|
+
if not prefs_a or not prefs_b:
|
|
212
|
+
return None
|
|
213
|
+
# 检查是否有不同的偏好项
|
|
214
|
+
set_a, set_b = set(prefs_a), set(prefs_b)
|
|
215
|
+
if set_a == set_b:
|
|
216
|
+
return None # 相同偏好,不冲突
|
|
217
|
+
# 检查是否有交集(同一类别但不同选择)
|
|
218
|
+
# 例如:都提到编辑器但选择不同
|
|
219
|
+
conflicts = []
|
|
220
|
+
for pa in set_a:
|
|
221
|
+
for pb in set_b:
|
|
222
|
+
if pa != pb:
|
|
223
|
+
# v5.4.8 P3-001 修复:收紧长度差阈值,添加额外启发式
|
|
224
|
+
# 1. 长度差 <= 2(更严格)
|
|
225
|
+
# 2. 首字母相同(同类事物的常见模式)
|
|
226
|
+
# 3. 一个包含另一个(如 vim/vimrc)
|
|
227
|
+
len_diff_ok = abs(len(pa) - len(pb)) <= 2
|
|
228
|
+
same_first_letter = pa[0] == pb[0] if pa and pb else False
|
|
229
|
+
substring_rel = pa in pb or pb in pa
|
|
230
|
+
|
|
231
|
+
if len_diff_ok or same_first_letter or substring_rel:
|
|
232
|
+
conflicts.append((pa, pb))
|
|
233
|
+
if not conflicts:
|
|
234
|
+
return None
|
|
235
|
+
severity = min(0.40 + 0.10 * len(conflicts), 0.90)
|
|
236
|
+
return ConflictPair(
|
|
237
|
+
memory_id_a=id_a, memory_id_b=id_b,
|
|
238
|
+
conflict_type="preference", severity=severity,
|
|
239
|
+
evidence={"preference_conflicts": conflicts, "count": len(conflicts)},
|
|
240
|
+
suggestion=f"偏好不一致:{', '.join(f'{a} vs {b}' for a, b in conflicts[:3])}",
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def detect_attribute(self, text_a: str, text_b: str,
|
|
244
|
+
id_a: str, id_b: str) -> Optional[ConflictPair]:
|
|
245
|
+
a_attrs = self._extract_attrs(text_a)
|
|
246
|
+
b_attrs = self._extract_attrs(text_b)
|
|
247
|
+
shared = [k for k in a_attrs if k in b_attrs and a_attrs[k] != b_attrs[k]]
|
|
248
|
+
if not shared:
|
|
249
|
+
return None
|
|
250
|
+
conflicts = {k: (a_attrs[k], b_attrs[k]) for k in shared}
|
|
251
|
+
severity = min(0.30 + 0.12 * len(shared), 0.95)
|
|
252
|
+
return ConflictPair(
|
|
253
|
+
memory_id_a=id_a, memory_id_b=id_b,
|
|
254
|
+
conflict_type="attribute", severity=severity,
|
|
255
|
+
evidence={"attribute_conflicts": conflicts, "count": len(shared)},
|
|
256
|
+
suggestion=f"以下 {len(shared)} 个属性取值不一致:{', '.join(shared)}",
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
def detect_timeline(self, text_a: str, text_b: str,
|
|
260
|
+
id_a: str, id_b: str) -> Optional[ConflictPair]:
|
|
261
|
+
sa, sb = self._timeline_status(text_a), self._timeline_status(text_b)
|
|
262
|
+
if not sa or not sb or sa == sb:
|
|
263
|
+
return None
|
|
264
|
+
if {sa, sb} == {"done", "pending"}:
|
|
265
|
+
severity = 0.65
|
|
266
|
+
return ConflictPair(
|
|
267
|
+
memory_id_a=id_a, memory_id_b=id_b,
|
|
268
|
+
conflict_type="timeline", severity=severity,
|
|
269
|
+
evidence={"status_a": sa, "status_b": sb},
|
|
270
|
+
suggestion="同一件事出现『已完成』vs『待完成』矛盾,请以最近时间戳或显式更新者为准",
|
|
271
|
+
)
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
# -- batch scan --------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
def scan_memories(self,
|
|
277
|
+
memories: List[Dict[str, Any]],
|
|
278
|
+
max_pairs: int = 1000) -> List[ConflictPair]:
|
|
279
|
+
"""扫描一批记忆并返回所有冲突对。
|
|
280
|
+
memories: 每一项必须至少含 id, content 字段,可选 created_at。
|
|
281
|
+
"""
|
|
282
|
+
results: List[ConflictPair] = []
|
|
283
|
+
n = len(memories)
|
|
284
|
+
# 两两比较,O(n^2),对大样本先按标签/关键词分桶以减少比较
|
|
285
|
+
checked = 0
|
|
286
|
+
for i in range(n):
|
|
287
|
+
mi = memories[i]
|
|
288
|
+
for j in range(i + 1, n):
|
|
289
|
+
mj = memories[j]
|
|
290
|
+
checked += 1
|
|
291
|
+
if checked > max_pairs:
|
|
292
|
+
return results
|
|
293
|
+
ta, tb = str(mi.get("content", "")), str(mj.get("content", ""))
|
|
294
|
+
id_a, id_b = str(mi.get("id", i)), str(mj.get("id", j))
|
|
295
|
+
for detector in (self.detect_antonym, self.detect_attribute, self.detect_timeline, self.detect_preference):
|
|
296
|
+
pair = detector(ta, tb, id_a, id_b)
|
|
297
|
+
if pair is not None:
|
|
298
|
+
results.append(pair)
|
|
299
|
+
# 按严重度排序
|
|
300
|
+
results.sort(key=lambda p: p.severity, reverse=True)
|
|
301
|
+
return results
|
|
302
|
+
|
|
303
|
+
# -- auto decay --------------------------------------------------------
|
|
304
|
+
|
|
305
|
+
def plan_decay(self, conflicts: List[ConflictPair],
|
|
306
|
+
memories_by_id: Dict[str, Dict[str, Any]]) -> List[DecayAction]:
|
|
307
|
+
"""基于冲突结果生成衰减动作。未 resolve 的双方,老的那条减去 auto_decay_old,并打上 conflict 标签。"""
|
|
308
|
+
actions: Dict[str, DecayAction] = {}
|
|
309
|
+
|
|
310
|
+
def _get(mid: str) -> Dict[str, Any]:
|
|
311
|
+
return memories_by_id.get(mid, {"id": mid, "created_at": None})
|
|
312
|
+
|
|
313
|
+
for c in conflicts:
|
|
314
|
+
a, b = c.memory_id_a, c.memory_id_b
|
|
315
|
+
if c.resolution == "unresolved":
|
|
316
|
+
# 谁老谁减
|
|
317
|
+
ca_time = _get(a).get("created_at") or 0
|
|
318
|
+
cb_time = _get(b).get("created_at") or 0
|
|
319
|
+
if ca_time and cb_time:
|
|
320
|
+
older = a if ca_time < cb_time else b
|
|
321
|
+
else:
|
|
322
|
+
older = a if len(a) < len(b) else b
|
|
323
|
+
self._merge_action(actions, older, -self.auto_decay_old, ["conflict"],
|
|
324
|
+
reason=f"与{('b' if older==a else 'a')}冲突类型={c.conflict_type}")
|
|
325
|
+
elif c.resolution == "a_wins":
|
|
326
|
+
self._merge_action(actions, a, +self.auto_boost_winner, [], reason=f"冲突胜出: {c.conflict_type}")
|
|
327
|
+
self._merge_action(actions, b, -self.auto_decay_loser, ["conflict-deprecated"],
|
|
328
|
+
reason=f"冲突失败: {c.conflict_type}")
|
|
329
|
+
elif c.resolution == "b_wins":
|
|
330
|
+
self._merge_action(actions, b, +self.auto_boost_winner, [], reason=f"冲突胜出: {c.conflict_type}")
|
|
331
|
+
self._merge_action(actions, a, -self.auto_decay_loser, ["conflict-deprecated"],
|
|
332
|
+
reason=f"冲突失败: {c.conflict_type}")
|
|
333
|
+
elif c.resolution == "both_deprecated":
|
|
334
|
+
self._merge_action(actions, a, -self.auto_decay_loser, ["conflict-deprecated"],
|
|
335
|
+
reason=f"双方均弃用: {c.conflict_type}")
|
|
336
|
+
self._merge_action(actions, b, -self.auto_decay_loser, ["conflict-deprecated"],
|
|
337
|
+
reason=f"双方均弃用: {c.conflict_type}")
|
|
338
|
+
return list(actions.values())
|
|
339
|
+
|
|
340
|
+
@staticmethod
|
|
341
|
+
def _merge_action(bucket: Dict[str, DecayAction], mid: str,
|
|
342
|
+
delta: float, tags: List[str], reason: str):
|
|
343
|
+
if mid not in bucket:
|
|
344
|
+
bucket[mid] = DecayAction(memory_id=mid, delta_importance=0.0, added_tags=[], reason="")
|
|
345
|
+
a = bucket[mid]
|
|
346
|
+
a.delta_importance += delta
|
|
347
|
+
for t in tags:
|
|
348
|
+
if t not in a.added_tags:
|
|
349
|
+
a.added_tags.append(t)
|
|
350
|
+
if a.reason:
|
|
351
|
+
a.reason += " | " + reason
|
|
352
|
+
else:
|
|
353
|
+
a.reason = reason
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
__all__ = ["ConflictDetector", "ConflictPair", "DecayAction", "ANTONYM_PAIRS"]
|