forked from Ikalus1988/MisakaNet
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdedup_engine.py
More file actions
142 lines (122 loc) · 4.36 KB
/
Copy pathdedup_engine.py
File metadata and controls
142 lines (122 loc) · 4.36 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
"""
Dedup Engine - Semantic deduplication for skills
⚠️ DEAD CODE — 语义去重已在 SkillIndexer.register_skill() 内建实现。
保留作为设计参考。
"""
from typing import Tuple, Optional
from enum import Enum
from dataclasses import dataclass
from storage.vector_store import VectorStore
class DedupAction(Enum):
MERGE = "merge" # similarity > 0.92, merge content
LINK = "link" # 0.75 < similarity <= 0.92, create edge
ADD = "add" # similarity <= 0.75, new skill
@dataclass
class DedupResult:
action: DedupAction
target_id: Optional[str] = None
similarity: float = 0.0
merged_sources: list = None
def __post_init__(self):
if self.merged_sources is None:
self.merged_sources = []
class DedupEngine:
"""
Semantic deduplication engine for skill merging.
Implements cosine similarity threshold-based decisions.
"""
# Similarity thresholds
THRESHOLD_MERGE = 0.92
THRESHOLD_LINK = 0.75
def __init__(self, vector_store: VectorStore):
self.vector_store = vector_store
def analyze(self, new_skill: dict, existing_skills: list[dict]) -> DedupResult:
"""
Analyze a new skill against existing ones.
Args:
new_skill: {
"id": str,
"name": str,
"description": str,
"embedding": list[float],
"source": str,
"metadata": dict
}
existing_skills: List of similar structure
Returns:
DedupResult with action and details
"""
best_match_id = None
best_similarity = 0.0
for existing in existing_skills:
similarity = self.vector_store.compute_similarity(
new_skill.get("embedding", []),
existing.get("embedding", [])
)
if similarity > best_similarity:
best_similarity = similarity
best_match_id = existing.get("id")
if best_similarity > self.THRESHOLD_MERGE:
return DedupResult(
action=DedupAction.MERGE,
target_id=best_match_id,
similarity=best_similarity,
merged_sources=[new_skill.get("source"), best_match_id]
)
elif best_similarity > self.THRESHOLD_LINK:
return DedupResult(
action=DedupAction.LINK,
target_id=best_match_id,
similarity=best_similarity,
merged_sources=[new_skill.get("source")]
)
else:
return DedupResult(
action=DedupAction.ADD,
similarity=best_similarity
)
def merge_skills(self, skill1: dict, skill2: dict) -> dict:
"""
Merge two skills into one.
Takes union of metadata, marks sources.
"""
merged = {
"id": skill1.get("id"),
"name": skill1.get("name"),
"description": skill1.get("description"),
"confidence": max(
skill1.get("confidence", 0),
skill2.get("confidence", 0)
),
"sources": [
skill1.get("source", "unknown"),
skill2.get("source", "unknown")
],
"metadata": {
**skill1.get("metadata", {}),
**skill2.get("metadata", {})
}
}
return merged
def resolve_conflict(self, skills: list[dict], strategy: str = "latest") -> dict:
"""
Resolve conflict when multiple versions of same skill exist.
Args:
skills: List of skill versions
strategy: "latest" | "source_priority" | "highest_confidence"
"""
if not skills:
return None
if strategy == "latest":
return max(skills, key=lambda s: s.get("updated_at", ""))
elif strategy == "source_priority":
# Keep the one from preferred source
preferred_source = "hermes-hub" # Hub has priority
for skill in skills:
if skill.get("source") == preferred_source:
return skill
return skills[0]
elif strategy == "highest_confidence":
return max(skills, key=lambda s: s.get("confidence", 0))
else:
return skills[0]