secagentx 3.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.
Files changed (182) hide show
  1. backend/__init__.py +1 -0
  2. backend/__main__.py +3 -0
  3. backend/_assets/config.yaml +195 -0
  4. backend/_assets/frontend/dist/assets/AgentsView-DFqRplI8.css +1 -0
  5. backend/_assets/frontend/dist/assets/AgentsView-DJTpfQ87.js +1 -0
  6. backend/_assets/frontend/dist/assets/AttackChainView-BVfI-SC6.js +1 -0
  7. backend/_assets/frontend/dist/assets/ChatView-Bg4nIlQ0.css +1 -0
  8. backend/_assets/frontend/dist/assets/ChatView-CRndtmEC.js +38 -0
  9. backend/_assets/frontend/dist/assets/DashboardView-BXs4AWv8.js +1 -0
  10. backend/_assets/frontend/dist/assets/DashboardView-WnHWiqBW.css +1 -0
  11. backend/_assets/frontend/dist/assets/EventDetailView-DQk6Qp7-.js +1 -0
  12. backend/_assets/frontend/dist/assets/EventDetailView-Dj-a-zWr.css +1 -0
  13. backend/_assets/frontend/dist/assets/EventsView-8xh8uKO0.js +1 -0
  14. backend/_assets/frontend/dist/assets/EventsView-BHfFn66K.css +1 -0
  15. backend/_assets/frontend/dist/assets/KnowledgeView-C2N3v9bF.css +1 -0
  16. backend/_assets/frontend/dist/assets/KnowledgeView-D54ODoC9.js +11 -0
  17. backend/_assets/frontend/dist/assets/LoginView-CjvFmo7C.js +1 -0
  18. backend/_assets/frontend/dist/assets/LoginView-D14FzcsP.css +1 -0
  19. backend/_assets/frontend/dist/assets/SettingsView-JOJuoABi.js +1 -0
  20. backend/_assets/frontend/dist/assets/UsersView-DhMXZhir.js +1 -0
  21. backend/_assets/frontend/dist/assets/_plugin-vue_export-helper-BDNMzG2s.js +1 -0
  22. backend/_assets/frontend/dist/assets/index-D9Su3fYY.css +1 -0
  23. backend/_assets/frontend/dist/assets/index-DBEjbyJR.js +4 -0
  24. backend/_assets/frontend/dist/assets/rolldown-runtime-DK3Fl9T5.js +1 -0
  25. backend/_assets/frontend/dist/assets/vendor-charts-Dh1nChzO.js +39 -0
  26. backend/_assets/frontend/dist/assets/vendor-icons-BTNgi7vU.js +1 -0
  27. backend/_assets/frontend/dist/assets/vendor-ui-Cdd65KTN.js +7367 -0
  28. backend/_assets/frontend/dist/assets/vendor-vue-CXKcZAmz.js +1 -0
  29. backend/_assets/frontend/dist/index.html +51 -0
  30. backend/_assets/frontend/index.html +46 -0
  31. backend/_assets/frontend/package-lock.json +2983 -0
  32. backend/_assets/frontend/package.json +37 -0
  33. backend/_assets/frontend/src/App.vue +231 -0
  34. backend/_assets/frontend/src/main.js +12 -0
  35. backend/_assets/frontend/src/router/index.js +117 -0
  36. backend/_assets/frontend/src/stores/chat.js +671 -0
  37. backend/_assets/frontend/src/styles/tokens.css +282 -0
  38. backend/_assets/frontend/src/utils/http.js +191 -0
  39. backend/_assets/frontend/src/views/AgentsView.vue +227 -0
  40. backend/_assets/frontend/src/views/AttackChainView.vue +210 -0
  41. backend/_assets/frontend/src/views/ChatView.vue +1107 -0
  42. backend/_assets/frontend/src/views/DashboardView.vue +514 -0
  43. backend/_assets/frontend/src/views/EventDetailView.vue +387 -0
  44. backend/_assets/frontend/src/views/EventsView.vue +253 -0
  45. backend/_assets/frontend/src/views/KnowledgeView.vue +883 -0
  46. backend/_assets/frontend/src/views/LoginView.vue +314 -0
  47. backend/_assets/frontend/src/views/SettingsView.vue +226 -0
  48. backend/_assets/frontend/src/views/UsersView.vue +235 -0
  49. backend/_assets/frontend/vite.config.js +55 -0
  50. backend/_assets/knowledge_data/compliance/regulations.json +383 -0
  51. backend/_assets/knowledge_data/cve/vulnerabilities.json +70720 -0
  52. backend/_assets/knowledge_data/mitre_attack/techniques.json +14097 -0
  53. backend/_assets/knowledge_data/remediation/remediation.json +574 -0
  54. backend/_assets/knowledge_data/threat_intel/actors.json +1602 -0
  55. backend/_assets/knowledge_data/threat_intel/malware.json +971 -0
  56. backend/agents/__init__.py +1 -0
  57. backend/agents/alert_filter/__init__.py +1 -0
  58. backend/agents/alert_filter/alert_filter_agent.py +88 -0
  59. backend/agents/analyst.py +331 -0
  60. backend/agents/base.py +553 -0
  61. backend/agents/classifier_agent.py +134 -0
  62. backend/agents/cot_engine.py +578 -0
  63. backend/agents/intel.py +46 -0
  64. backend/agents/knowledge/__init__.py +2 -0
  65. backend/agents/knowledge/agentic_rag.py +928 -0
  66. backend/agents/knowledge/knowledge_agent.py +47 -0
  67. backend/agents/registry.py +148 -0
  68. backend/agents/responder.py +51 -0
  69. backend/agents/summary_agent.py +219 -0
  70. backend/auto_ingestor.py +948 -0
  71. backend/auto_patrol.py +452 -0
  72. backend/cli_app.py +523 -0
  73. backend/config/__init__.py +20 -0
  74. backend/config/provider_profiles.py +416 -0
  75. backend/config/runtime_settings.py +84 -0
  76. backend/decision_fusion/__init__.py +32 -0
  77. backend/decision_fusion/base.py +127 -0
  78. backend/decision_fusion/dempster_shafer.py +419 -0
  79. backend/decision_fusion/weighted_average.py +141 -0
  80. backend/email_ingestor.py +588 -0
  81. backend/errors.py +243 -0
  82. backend/escalation.py +453 -0
  83. backend/evaluation/__init__.py +7 -0
  84. backend/evaluation/evaluator.py +197 -0
  85. backend/evaluation/metrics.py +210 -0
  86. backend/federation/__init__.py +22 -0
  87. backend/federation/core.py +743 -0
  88. backend/interface/__init__.py +1 -0
  89. backend/interface/api_server.py +1830 -0
  90. backend/interface/cli.py +671 -0
  91. backend/knowledge/__init__.py +11 -0
  92. backend/knowledge/compliance.py +101 -0
  93. backend/knowledge/cve_db.py +100 -0
  94. backend/knowledge/mitre_attack.py +457 -0
  95. backend/knowledge/remediation.py +84 -0
  96. backend/knowledge/threat_intel_kb.py +246 -0
  97. backend/llm/__init__.py +1 -0
  98. backend/llm/anthropic_compatible.py +191 -0
  99. backend/llm/base.py +81 -0
  100. backend/llm/deepseek.py +221 -0
  101. backend/llm/fallback.py +268 -0
  102. backend/llm/mock.py +204 -0
  103. backend/llm/openai_compatible.py +51 -0
  104. backend/llm/provider.py +217 -0
  105. backend/llm/qwen.py +218 -0
  106. backend/main.py +421 -0
  107. backend/memory/__init__.py +1 -0
  108. backend/memory/episodic_memory.py +132 -0
  109. backend/memory/manager.py +204 -0
  110. backend/memory/semantic_memory.py +156 -0
  111. backend/memory/session_memory.py +102 -0
  112. backend/ml_model/__init__.py +23 -0
  113. backend/ml_model/pipeline.py +191 -0
  114. backend/ml_model/trainer.py +627 -0
  115. backend/models/__init__.py +1 -0
  116. backend/models/event.py +62 -0
  117. backend/models/message.py +58 -0
  118. backend/models/output.py +1216 -0
  119. backend/monitoring/metrics.py +184 -0
  120. backend/orchestrator/__init__.py +17 -0
  121. backend/orchestrator/agent_router.py +145 -0
  122. backend/orchestrator/core.py +159 -0
  123. backend/orchestrator/dynamic_weights.py +287 -0
  124. backend/orchestrator/react_loop.py +1895 -0
  125. backend/orchestrator/risk_model.py +494 -0
  126. backend/orchestrator/risk_scorer.py +356 -0
  127. backend/reasoner/__init__.py +19 -0
  128. backend/reasoner/reasoner.py +316 -0
  129. backend/runtime_assets.py +45 -0
  130. backend/security/__init__.py +24 -0
  131. backend/security/adversarial/__init__.py +13 -0
  132. backend/security/adversarial/guard.py +121 -0
  133. backend/security/adversarial/redteam.py +121 -0
  134. backend/security/adversarial/scanner.py +208 -0
  135. backend/security/audit.py +118 -0
  136. backend/security/auth.py +283 -0
  137. backend/security/circuit_breaker.py +212 -0
  138. backend/security/dispatch.py +18 -0
  139. backend/security/ratelimit.py +237 -0
  140. backend/security/rbac.py +431 -0
  141. backend/security/refresh_sessions.py +193 -0
  142. backend/security/sanitizer.py +32 -0
  143. backend/storage/__init__.py +1 -0
  144. backend/storage/chroma_store.py +79 -0
  145. backend/storage/database.py +296 -0
  146. backend/storage/migrations/env.py +65 -0
  147. backend/storage/migrations/versions/0001_initial_schema.py +101 -0
  148. backend/storage/migrations/versions/0002_add_agent_logs_experiences.py +53 -0
  149. backend/storage/migrations/versions/0003_add_conversation_owner.py +30 -0
  150. backend/storage/models.py +194 -0
  151. backend/storage/repositories/__init__.py +1 -0
  152. backend/storage/repositories/asset_repo.py +86 -0
  153. backend/storage/repositories/conversation_repo.py +216 -0
  154. backend/storage/repositories/event_repo.py +76 -0
  155. backend/storage/repositories/trajectory_repo.py +324 -0
  156. backend/storage/retention.py +117 -0
  157. backend/tools/__init__.py +16 -0
  158. backend/tools/alert_filter.py +208 -0
  159. backend/tools/aliyun_firewall.py +237 -0
  160. backend/tools/aws_firewall.py +369 -0
  161. backend/tools/base.py +445 -0
  162. backend/tools/calling.py +207 -0
  163. backend/tools/cve_search.py +77 -0
  164. backend/tools/execution_engine.py +202 -0
  165. backend/tools/firewall.py +387 -0
  166. backend/tools/geoip.py +168 -0
  167. backend/tools/log_analyzer.py +185 -0
  168. backend/tools/ml_detector.py +226 -0
  169. backend/tools/registry.py +38 -0
  170. backend/tools/tencent_firewall.py +300 -0
  171. backend/tools/threat_intel.py +732 -0
  172. backend/tools/trust_checker.py +76 -0
  173. backend/utils/__init__.py +5 -0
  174. backend/utils/config_loader.py +164 -0
  175. backend/utils/env.py +75 -0
  176. backend/utils/logger.py +150 -0
  177. backend/utils/text.py +32 -0
  178. secagentx-3.0.0.dist-info/METADATA +325 -0
  179. secagentx-3.0.0.dist-info/RECORD +182 -0
  180. secagentx-3.0.0.dist-info/WHEEL +5 -0
  181. secagentx-3.0.0.dist-info/entry_points.txt +2 -0
  182. secagentx-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,419 @@
1
+ """
2
+ DempsterShaferEngine — Dempster-Shafer 证据理论融合引擎(默认实现)
3
+
4
+ 为什么选 DS 而非加权平均:
5
+ - 显式建模"未知 (unknown)":情报源缺失 / Agent 未裁决 → mass 进入 unknown,
6
+ 绝不因"没查到"而把信念分给恶意或良性(项目"缺失≠干净"纪律)
7
+ - 显式量化"冲突":Dempster 冲突系数 K 直接反映证据间打架程度,
8
+ K 高 → 自动标记 needs_human + 输出冲突记录(可审计)
9
+
10
+ 识别框架:
11
+ Θ = { malicious, benign }
12
+ 幂集 2^Θ = { ∅, {malicious}, {benign}, Θ } (Θ = unknown)
13
+
14
+ 每个证据包 → mass function:
15
+ m({malicious}) = w * lean_mal
16
+ m({benign}) = w * lean_benign
17
+ m(Θ) = 1 - m(malicious) - m(benign) (未知质量)
18
+ 其中 w 为归一化权重;lean 由 leaning / findings 推导
19
+
20
+ Dempster 组合规则(两两组合):
21
+ m12(A) = Σ_{B∩C=A} m1(B)·m2(C) / (1 - K)
22
+ K = Σ_{B∩C=∅} m1(B)·m2(C) (冲突系数)
23
+
24
+ 决策映射:
25
+ belief(malicious) ≥ 0.7 → malicious
26
+ belief(benign) ≥ 0.7 → benign
27
+ belief(malicious) ≥ 0.4 → suspicious
28
+ 其余 → unknown + needs_human
29
+ """
30
+ import logging
31
+ from typing import Optional
32
+
33
+ from ..models.output import (
34
+ EvidencePackage, Finding, FusionResult, FusionVerdict, EvidenceMass,
35
+ FusionConflict, build_fusion_result,
36
+ )
37
+ from .base import DecisionFusionEngine, FusionEngineFactory
38
+
39
+ logger = logging.getLogger("secagentx.decision_fusion")
40
+
41
+ # 决策阈值(与既有置信度口径对齐:0.7/0.4)
42
+ BELIEF_MALICIOUS_HIGH = 0.7
43
+ BELIEF_MALICIOUS_MID = 0.4
44
+ BELIEF_BENIGN_HIGH = 0.7
45
+ # 冲突系数阈值:K 超阈值 → 需人工介入
46
+ DEFAULT_CONFLICT_THRESHOLD = 0.6
47
+ # 证据可靠度下限:低于此值视为"证据薄弱",置信度打折
48
+ MIN_EVIDENCE_CONF = 0.3
49
+ # 无任何 Agent 有效裁决时,最终置信度上限
50
+ MIN_CONF_NO_AGENT = 0.3
51
+ # 裁决为 unknown 时的置信度上限(语义一致性:未知 ≠ 高置信,防止 LLM 误读/前端误标)
52
+ UNKNOWN_VERDICT_CONF_CAP = 0.4
53
+
54
+
55
+ class DempsterShaferEngine(DecisionFusionEngine):
56
+ """Dempster-Shafer 证据理论融合引擎。"""
57
+
58
+ name = "dempster_shafer"
59
+
60
+ def __init__(self, config: Optional[dict] = None):
61
+ super().__init__(config)
62
+ self.conflict_threshold = float(
63
+ self.config.get("conflict_threshold", DEFAULT_CONFLICT_THRESHOLD)
64
+ )
65
+
66
+ # ═══════════════════════ 主入口 ═══════════════════════
67
+
68
+ def fuse(self, evidence_packages: list[EvidencePackage]) -> FusionResult:
69
+ if not evidence_packages:
70
+ return self._empty_result()
71
+
72
+ # 1. 权重归一化(基础权重 × 证据可靠度)
73
+ scored = self._normalize_weights(evidence_packages)
74
+ active = [(p, w) for p, w in scored if w > 0]
75
+ if not active:
76
+ # 全部失败/无权重 → 未知 + 需人工
77
+ return self._empty_result(
78
+ summary="所有 Agent 证据均无效,无法融合,需人工介入"
79
+ )
80
+
81
+ # 2. 为每个证据包构建 mass
82
+ masses = []
83
+ for p, w in active:
84
+ masses.append(self._build_mass(p, w))
85
+
86
+ # 3. 全局冲突系数 K(两两冲突之和 / 组合数)
87
+ K_total, pairwise = self._conflict_coefficient(masses)
88
+
89
+ # 4. Dempster 组合
90
+ belief = self._combine(masses, K_total)
91
+
92
+ # 5. 决策映射
93
+ verdict = self._decide(belief, K_total)
94
+
95
+ # 6. 组装结果 + 决策依据链
96
+ evidence_masses = [
97
+ EvidenceMass(
98
+ agent_id=p.agent_id, agent_name=p.agent_name,
99
+ weight=round(w, 4),
100
+ belief=round(self._lean_belief(p), 4),
101
+ plausibility=round(min(1.0, self._lean_belief(p) + 0.0), 4),
102
+ leaning=p.leaning.value if hasattr(p.leaning, "value") else str(p.leaning),
103
+ degraded=p.degraded, failed=p.failed,
104
+ )
105
+ for p, w in active
106
+ ]
107
+ conflicts = [
108
+ FusionConflict(
109
+ between=f"{a} vs {b}",
110
+ coefficient=round(k, 4),
111
+ leaning_a=la, leaning_b=lb,
112
+ resolution=("冲突显著,建议人工复核" if k >= self.conflict_threshold
113
+ else "取信念较高者"),
114
+ )
115
+ for (a, b, k, la, lb) in pairwise
116
+ ]
117
+
118
+ path = self._build_decision_path(active, masses, belief, verdict)
119
+
120
+ fr = build_fusion_result(
121
+ engine=self.name,
122
+ method=self.name,
123
+ status="completed",
124
+ verdict=verdict,
125
+ conflict_coefficient=round(K_total, 4),
126
+ conflicts=conflicts,
127
+ evidence_masses=evidence_masses,
128
+ decision_path=path,
129
+ agent_count=len(active),
130
+ evidence_count=sum(len(p.findings or []) for p, _ in active),
131
+ risk_score=0,
132
+ )
133
+ return fr
134
+
135
+ # ═══════════════════════ mass 构建 ═══════════════════════
136
+
137
+ @staticmethod
138
+ def _lean_belief(p: EvidencePackage) -> float:
139
+ """从 evidence_package 推导"恶意"信念基线(供证据质量展示)。"""
140
+ if p.failed:
141
+ return 0.0
142
+ # 优先用 leaning(含从 verdict 兜底)
143
+ try:
144
+ lean = str(p.leaning.value) if hasattr(p.leaning, "value") else str(p.leaning)
145
+ except Exception:
146
+ lean = "unknown"
147
+ conf = p.leaning_confidence or p.evidence_confidence or 0.5
148
+ if lean == "malicious":
149
+ return conf
150
+ if lean == "suspicious":
151
+ return conf * 0.6
152
+ if lean == "benign":
153
+ return 0.0
154
+ # unknown:从 findings 类型推断
155
+ mal_findings = [f for f in (p.findings or [])
156
+ if "malicious" in str(f.type) or "intel_hit" in str(f.type)]
157
+ if mal_findings:
158
+ return max(f.evidence_confidence for f in mal_findings) * 0.5
159
+ return 0.5 * conf * 0.3
160
+
161
+ def _build_mass(self, p: EvidencePackage, w: float) -> dict:
162
+ """
163
+ 构造单个证据包的 mass 分配(Shafer 折扣)。
164
+
165
+ 关键设计(修正:不把跨 Agent 归一化权重塞进 mass,避免稀释一致证据):
166
+ - 原始 mass 反映证据本身的信念强度:m_mal = leaning_confidence
167
+ - Shafer 折扣 α = evidence_confidence × (降级?0.5:1) × 覆盖惩罚
168
+ - mass'_mal = α × m_mal;未知质量吸收被折扣掉的信念
169
+ - 归一化权重 w 只用于审计展示与 failed 过滤,不参与 mass 稀释
170
+ """
171
+ # 原始 mass(基于 leaning 信念强度)
172
+ try:
173
+ lean_str = str(p.leaning.value) if hasattr(p.leaning, "value") else str(p.leaning)
174
+ except Exception:
175
+ lean_str = "unknown"
176
+ base_conf = p.leaning_confidence or p.evidence_confidence or 0.5
177
+ if lean_str == "malicious":
178
+ m_mal, m_ben = base_conf, 0.0
179
+ elif lean_str == "benign":
180
+ m_mal, m_ben = 0.0, base_conf
181
+ elif lean_str == "suspicious":
182
+ m_mal, m_ben = base_conf * 0.6, 0.0
183
+ else: # unknown / 其他:不给恶意/良性任何正质量,全部归入未知
184
+ m_mal, m_ben = 0.0, 0.0
185
+
186
+ # Shafer 折扣因子(不跨 Agent 归一化,避免稀释)
187
+ discount = max(p.evidence_confidence, 0.0)
188
+ if p.degraded:
189
+ discount *= 0.5
190
+ # 覆盖度惩罚:情报覆盖不足 → 打折扣(缺失≠干净)
191
+ if p.agent_id == "intel-001" and p.coverage is not None:
192
+ discount *= max(0.3, p.coverage)
193
+ # 证据可靠度过低 → 额外衰减
194
+ if p.evidence_confidence < MIN_EVIDENCE_CONF:
195
+ discount *= 0.5
196
+
197
+ m_mal = m_mal * discount
198
+ m_ben = m_ben * discount
199
+ m_unknown = max(0.0, 1.0 - m_mal - m_ben)
200
+ return {
201
+ "agent_id": p.agent_id,
202
+ "weight": w,
203
+ "m_mal": round(m_mal, 6),
204
+ "m_ben": round(m_ben, 6),
205
+ "m_unknown": round(m_unknown, 6),
206
+ }
207
+
208
+ # ═══════════════════════ 组合 ═══════════════════════
209
+
210
+ def _combine(self, masses: list[dict], K_total: float) -> dict:
211
+ """Dempster 组合:按顺序两两组合全部证据。"""
212
+ if not masses:
213
+ return {"malicious": 0.0, "benign": 0.0, "unknown": 1.0}
214
+ acc = {"malicious": masses[0]["m_mal"],
215
+ "benign": masses[0]["m_ben"],
216
+ "unknown": masses[0]["m_unknown"]}
217
+ for m in masses[1:]:
218
+ # _build_mass 产出 m_mal/m_ben/m_unknown,统一成 internal keys
219
+ m_norm = {
220
+ "malicious": m["m_mal"],
221
+ "benign": m["m_ben"],
222
+ "unknown": m["m_unknown"],
223
+ }
224
+ acc = self._pairwise_combine(acc, m_norm)
225
+ # 若冲突过大导致分母为 0,退化为归一化求和(防御)
226
+ norm = acc["malicious"] + acc["benign"] + acc["unknown"]
227
+ if norm <= 0:
228
+ acc = {"malicious": 0.0, "benign": 0.0, "unknown": 1.0}
229
+ break
230
+ return {
231
+ "malicious": round(acc["malicious"], 6),
232
+ "benign": round(acc["benign"], 6),
233
+ "unknown": round(acc["unknown"], 6),
234
+ }
235
+
236
+ @staticmethod
237
+ def _pairwise_combine(m1: dict, m2: dict) -> dict:
238
+ """两个 mass 的 Dempster 组合(含冲突归一)。"""
239
+ # 联合质量
240
+ mal_mal = m1["malicious"] * m2["malicious"]
241
+ mal_unk = m1["malicious"] * m2["unknown"]
242
+ unk_mal = m1["unknown"] * m2["malicious"]
243
+ ben_ben = m1["benign"] * m2["benign"]
244
+ ben_unk = m1["benign"] * m2["unknown"]
245
+ unk_ben = m1["unknown"] * m2["benign"]
246
+ unk_unk = m1["unknown"] * m2["unknown"]
247
+ # 冲突(交集为空)
248
+ conflict = (
249
+ m1["malicious"] * m2["benign"]
250
+ + m1["benign"] * m2["malicious"]
251
+ )
252
+ norm = 1.0 - conflict
253
+ if norm <= 0:
254
+ return {"malicious": 0.0, "benign": 0.0, "unknown": 1.0}
255
+ return {
256
+ "malicious": round((mal_mal + mal_unk + unk_mal) / norm, 6),
257
+ "benign": round((ben_ben + ben_unk + unk_ben) / norm, 6),
258
+ "unknown": round(unk_unk / norm, 6),
259
+ }
260
+
261
+ @staticmethod
262
+ def _conflict_coefficient(masses: list[dict]) -> tuple[float, list]:
263
+ """全局冲突系数 K 与两两冲突列表。"""
264
+ if len(masses) < 2:
265
+ return 0.0, []
266
+ pairwise = []
267
+ total_k = 0.0
268
+ count = 0
269
+ for i in range(len(masses)):
270
+ for j in range(i + 1, len(masses)):
271
+ a, b = masses[i], masses[j]
272
+ k = a["m_mal"] * b["m_ben"] + a["m_ben"] * b["m_mal"]
273
+ total_k += k
274
+ count += 1
275
+ pairwise.append((a["agent_id"], b["agent_id"], k,
276
+ DempsterShaferEngine._lean_label(a),
277
+ DempsterShaferEngine._lean_label(b)))
278
+ avg_k = total_k / count if count else 0.0
279
+ return avg_k, pairwise
280
+
281
+ @staticmethod
282
+ def _lean_label(m: dict) -> str:
283
+ """mass 对应的倾向标签(供冲突记录展示)。"""
284
+ if m["m_mal"] > m["m_ben"]:
285
+ return "malicious"
286
+ if m["m_ben"] > m["m_mal"]:
287
+ return "benign"
288
+ return "unknown"
289
+
290
+ # ═══════════════════════ 决策映射 ═══════════════════════
291
+
292
+ def _decide(self, belief: dict, K_total: float) -> FusionVerdict:
293
+ b_mal = belief["malicious"]
294
+ b_ben = belief["benign"]
295
+ b_unk = belief["unknown"]
296
+ needs_human = bool(K_total >= self.conflict_threshold)
297
+
298
+ if b_mal >= BELIEF_MALICIOUS_HIGH:
299
+ verdict = "malicious"
300
+ risk = "高危"
301
+ action = "block"
302
+ elif b_ben >= BELIEF_BENIGN_HIGH:
303
+ verdict = "benign"
304
+ risk = "低危"
305
+ action = "none"
306
+ elif b_mal >= BELIEF_MALICIOUS_MID:
307
+ verdict = "suspicious"
308
+ risk = "中危"
309
+ action = "escalate"
310
+ else:
311
+ # 未知主导:不放任"低危",需人工
312
+ verdict = "unknown"
313
+ risk = "低危"
314
+ action = "escalate"
315
+ needs_human = True
316
+
317
+ # ── v2.6: 风险概率与置信度分离(两个独立维度) ──
318
+ # risk_probability = 事件为恶意的可能性 = belief_malicious(0~1)
319
+ # confidence = 判断的确定性 = 证据集中度 = 1 - belief_unknown(0~1)
320
+ # 二者独立:概率高≠确定性高(如只有1条模糊情报 → 概率0.6但未知0.4 → 确定性仅0.6)
321
+ risk_probability = round(b_mal, 4)
322
+
323
+ # 无有效证据 → 强制低置信度
324
+ if b_mal + b_ben + b_unk >= 0.999 and b_unk > 0.95:
325
+ confidence = MIN_CONF_NO_AGENT
326
+ else:
327
+ # 确定性 = 已知部分(1 - unknown),反映证据是否充分
328
+ confidence = round(1.0 - b_unk, 4)
329
+ # 良性判定:风险概率清零(非恶意),但确定性仍来自证据集中度
330
+ if verdict == "benign":
331
+ risk_probability = 0.0
332
+ # 若证据极不充分(未知主导),置信度收敛到低值
333
+ if b_unk > 0.95:
334
+ confidence = min(confidence, MIN_CONF_NO_AGENT)
335
+ # unknown 且证据不足时,置信度不越过"可疑"阈值(未知 ≠ 高置信)
336
+ if verdict == "unknown":
337
+ confidence = min(confidence, UNKNOWN_VERDICT_CONF_CAP)
338
+
339
+ return FusionVerdict(
340
+ verdict=verdict,
341
+ belief_malicious=round(b_mal, 4),
342
+ belief_benign=round(b_ben, 4),
343
+ belief_unknown=round(b_unk, 4),
344
+ risk_probability=risk_probability,
345
+ confidence=confidence,
346
+ risk_level=risk,
347
+ recommended_action=action,
348
+ needs_human=needs_human,
349
+ )
350
+
351
+ # ═══════════════════════ 决策依据链 ═══════════════════════
352
+
353
+ def _build_decision_path(self, active, masses, belief, verdict) -> list[dict]:
354
+ path = []
355
+ # 1. 证据收集
356
+ for (p, w), m in zip(active, masses):
357
+ findings_desc = "; ".join(
358
+ (f.fact[:40] if isinstance(f, Finding) else str(f.get("fact", ""))[:40])
359
+ for f in (p.findings or [])[:2]
360
+ ) or f"证据包({p.agent_id})"
361
+ path.append({
362
+ "step": len(path) + 1,
363
+ "desc": (f"收集 {p.agent_name or p.agent_id} 证据:{findings_desc} "
364
+ f"(可靠度 {p.evidence_confidence:.0%},权重 {w:.0%})"),
365
+ "tag": "evidence",
366
+ })
367
+ # 2. 冲突检测
368
+ if verdict.needs_human and self.conflict_coefficient_global(masses) >= self.conflict_threshold:
369
+ path.append({
370
+ "step": len(path) + 1,
371
+ "desc": f"证据间存在显著冲突(K={self.conflict_coefficient_global(masses):.0%}),标记需人工介入",
372
+ "tag": "conflict",
373
+ })
374
+ # 3. 融合结果
375
+ path.append({
376
+ "step": len(path) + 1,
377
+ "desc": (f"Dempster 融合:恶意信念 {verdict.belief_malicious:.0%},"
378
+ f"良性信念 {verdict.belief_benign:.0%},未知 {verdict.belief_unknown:.0%}"),
379
+ "tag": "fusion",
380
+ })
381
+ # 4. 最终裁决
382
+ path.append({
383
+ "step": len(path) + 1,
384
+ "desc": f"最终裁决:{verdict.verdict}(风险 {verdict.risk_level},"
385
+ f"置信度 {verdict.confidence:.0%},动作 {verdict.recommended_action})",
386
+ "tag": "decision",
387
+ })
388
+ return path
389
+
390
+ def conflict_coefficient_global(self, masses: list[dict]) -> float:
391
+ """供 decision_path 展示全局 K。"""
392
+ K, _ = self._conflict_coefficient(masses)
393
+ return K
394
+
395
+ def _empty_result(self, summary: str = "") -> FusionResult:
396
+ """无有效证据时的兜底结果(未知 + 需人工)。"""
397
+ verdict = FusionVerdict(
398
+ verdict="unknown", belief_malicious=0.0, belief_benign=0.0,
399
+ belief_unknown=1.0, risk_probability=0.0, confidence=MIN_CONF_NO_AGENT,
400
+ risk_level="低危", recommended_action="escalate", needs_human=True,
401
+ )
402
+ return build_fusion_result(
403
+ engine=self.name, method=self.name, status="completed",
404
+ verdict=verdict, conflict_coefficient=0.0,
405
+ conflicts=[], evidence_masses=[],
406
+ decision_path=[{
407
+ "step": 1, "desc": summary or "无有效证据,判定未知,需人工介入",
408
+ "tag": "decision",
409
+ }],
410
+ risk_score=0, agent_count=0, evidence_count=0,
411
+ )
412
+
413
+
414
+ # ── 注册到工厂(默认引擎) ──
415
+ FusionEngineFactory.register(DempsterShaferEngine.name, DempsterShaferEngine)
416
+
417
+
418
+ __all__ = ["DempsterShaferEngine"]
419
+
@@ -0,0 +1,141 @@
1
+ """
2
+ WeightedAverageEngine — 加权平均融合引擎(可替换模块的参考实现 / 兜底)
3
+
4
+ 与 Dempster-Shafer 的差异:
5
+ - 不显式建模"未知":直接对 leaning 数值加权平均
6
+ - 适用场景:作为 DS 引擎异常时的降级,或对照实验
7
+ - 输出结构完全一致(FusionResult),上层无需改动
8
+ """
9
+ import logging
10
+ from typing import Optional
11
+
12
+ from ..models.output import (
13
+ EvidencePackage, FusionResult, FusionVerdict, EvidenceMass,
14
+ FusionConflict, build_fusion_result,
15
+ )
16
+ from .base import DecisionFusionEngine, FusionEngineFactory
17
+
18
+ logger = logging.getLogger("secagentx.decision_fusion")
19
+
20
+ VERDICT_WEIGHTS = {"malicious": 1.0, "suspicious": 0.6, "unknown": 0.4, "benign": 0.0}
21
+ HIGH = 0.7
22
+ MID = 0.4
23
+
24
+
25
+ class WeightedAverageEngine(DecisionFusionEngine):
26
+ """加权平均融合引擎 — 参考实现。"""
27
+
28
+ name = "weighted_average"
29
+
30
+ def fuse(self, evidence_packages: list[EvidencePackage]) -> FusionResult:
31
+ if not evidence_packages:
32
+ return self._empty_result()
33
+
34
+ scored = self._normalize_weights(evidence_packages)
35
+ active = [(p, w) for p, w in scored if w > 0]
36
+ if not active:
37
+ return self._empty_result()
38
+
39
+ weighted = 0.0
40
+ total_w = 0.0
41
+ mass_list = []
42
+ for p, w in active:
43
+ try:
44
+ lean_str = str(p.leaning.value) if hasattr(p.leaning, "value") else str(p.leaning)
45
+ except Exception:
46
+ lean_str = "unknown"
47
+ val = VERDICT_WEIGHTS.get(lean_str, 0.4)
48
+ weighted += val * w
49
+ total_w += w
50
+ mass_list.append({
51
+ "agent_id": p.agent_id, "weight": w,
52
+ "m_mal": (val * w), "m_ben": (1 - val) * w * 0, "m_unknown": (1 - val) * w,
53
+ })
54
+
55
+ avg = weighted / total_w if total_w else 0.0
56
+ if avg >= HIGH:
57
+ verdict, risk, action = "malicious", "高危", "block"
58
+ elif avg <= 1 - HIGH:
59
+ verdict, risk, action = "benign", "低危", "none"
60
+ elif avg >= MID:
61
+ verdict, risk, action = "suspicious", "中危", "escalate"
62
+ else:
63
+ verdict, risk, action = "unknown", "低危", "escalate"
64
+
65
+ # 检测冲突(leaning 矛盾)
66
+ conflicts = []
67
+ leans = [str(p.leaning.value) if hasattr(p.leaning, "value") else str(p.leaning)
68
+ for p, _ in active]
69
+ mal = [i for i, l in enumerate(leans) if l == "malicious"]
70
+ ben = [i for i, l in enumerate(leans) if l == "benign"]
71
+ if mal and ben:
72
+ conflicts.append(FusionConflict(
73
+ between=f"{active[mal[0]][0].agent_id} vs {active[ben[0]][0].agent_id}",
74
+ coefficient=0.8,
75
+ leaning_a="malicious", leaning_b="benign",
76
+ resolution="malicious 与 benign 冲突,需人工复核",
77
+ ))
78
+
79
+ evidence_masses = [
80
+ EvidenceMass(
81
+ agent_id=p.agent_id, agent_name=p.agent_name,
82
+ weight=round(w, 4),
83
+ belief=round(VERDICT_WEIGHTS.get(
84
+ str(p.leaning.value) if hasattr(p.leaning, "value") else str(p.leaning),
85
+ 0.4) * w, 4),
86
+ plausibility=round(VERDICT_WEIGHTS.get(
87
+ str(p.leaning.value) if hasattr(p.leaning, "value") else str(p.leaning),
88
+ 0.4) * w, 4),
89
+ leaning=str(p.leaning.value) if hasattr(p.leaning, "value") else str(p.leaning),
90
+ degraded=p.degraded, failed=p.failed,
91
+ )
92
+ for p, w in active
93
+ ]
94
+
95
+ path = [
96
+ {"step": 1, "desc": f"加权平均融合 {len(active)} 个证据包", "tag": "evidence"},
97
+ {"step": 2, "desc": f"加权得分 {avg:.0%} → 裁决 {verdict}", "tag": "fusion"},
98
+ ]
99
+ if conflicts:
100
+ path.append({"step": 3, "desc": "检测到冲突,标记需人工", "tag": "conflict"})
101
+
102
+ needs_human = bool(conflicts) or verdict == "unknown"
103
+ return build_fusion_result(
104
+ engine=self.name, method=self.name, status="completed",
105
+ verdict=FusionVerdict(
106
+ verdict=verdict, belief_malicious=round(avg, 4),
107
+ belief_benign=round(1 - avg, 4), belief_unknown=0.0,
108
+ risk_probability=round(avg, 4), # 加权平均:概率=平均恶意得分
109
+ confidence=round(avg, 4), risk_level=risk,
110
+ recommended_action=action, needs_human=needs_human,
111
+ ),
112
+ conflict_coefficient=round(0.8 if conflicts else 0.0, 4),
113
+ conflicts=conflicts,
114
+ evidence_masses=evidence_masses,
115
+ decision_path=path,
116
+ risk_score=0,
117
+ agent_count=len(active),
118
+ evidence_count=sum(len(p.findings or []) for p, _ in active),
119
+ )
120
+
121
+ def _empty_result(self) -> FusionResult:
122
+ verdict = FusionVerdict(
123
+ verdict="unknown", belief_malicious=0.0, belief_benign=0.0,
124
+ belief_unknown=1.0, risk_probability=0.0, confidence=0.3,
125
+ risk_level="低危",
126
+ recommended_action="escalate", needs_human=True,
127
+ )
128
+ return build_fusion_result(
129
+ engine=self.name, method=self.name, status="completed",
130
+ verdict=verdict, conflict_coefficient=0.0,
131
+ conflicts=[], evidence_masses=[],
132
+ decision_path=[{"step": 1, "desc": "无有效证据", "tag": "decision"}],
133
+ risk_score=0, agent_count=0, evidence_count=0,
134
+ )
135
+
136
+
137
+ FusionEngineFactory.register(WeightedAverageEngine.name, WeightedAverageEngine)
138
+
139
+
140
+ __all__ = ["WeightedAverageEngine"]
141
+