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
backend/main.py ADDED
@@ -0,0 +1,421 @@
1
+ import os
2
+ import yaml
3
+ import asyncio
4
+ from pathlib import Path
5
+
6
+ # 统一环境初始化 (加载 .env + 设置项目根目录)
7
+ from backend.utils.env import init_environment
8
+ from backend.runtime_assets import config_path
9
+ init_environment()
10
+
11
+ # 数据模型(单数据源,Schema 定义以 models.py 为准)
12
+ from backend.storage.models import SCHEMA_SQL
13
+
14
+ from backend.llm.provider import LLMFactory
15
+ from backend.tools.registry import ToolRegistry
16
+ from backend.tools.threat_intel import ThreatIntelTool
17
+ from backend.tools.firewall import FirewallTool
18
+ from backend.tools.log_analyzer import LogAnalyzerTool
19
+ from backend.tools.alert_filter import AlertFilterTool
20
+ from backend.tools.cve_search import CVESearchTool
21
+ from backend.tools.geoip import GeoIPTool
22
+ from backend.agents.analyst import AnalystAgent
23
+ from backend.agents.intel import IntelAgent
24
+ from backend.agents.responder import ResponderAgent
25
+ from backend.agents.knowledge.knowledge_agent import KnowledgeAgent
26
+ from backend.agents.alert_filter.alert_filter_agent import AlertFilterAgent
27
+ from backend.agents.knowledge.agentic_rag import AgenticRAGEngine
28
+ from backend.orchestrator.core import Orchestrator
29
+ from backend.knowledge.mitre_attack import MitreAttackKnowledge
30
+ from backend.knowledge.compliance import ComplianceKnowledge
31
+ from backend.knowledge.cve_db import CVEDatabase
32
+ from backend.knowledge.threat_intel_kb import ActorKnowledge, MalwareKnowledge
33
+
34
+ # 统一日志
35
+ import logging
36
+ logger = logging.getLogger("secagentx")
37
+
38
+
39
+ def load_config(path: str = None) -> dict:
40
+ if path is None:
41
+ path = config_path()
42
+ path = Path(path)
43
+ if not path.exists():
44
+ logger.info(f"[config] config file not found: {path}")
45
+ return {}
46
+ with open(path, "r", encoding="utf-8") as f:
47
+ return yaml.safe_load(f)
48
+
49
+
50
+ def _run_alembic_migrations():
51
+ """启动时自动执行 Alembic 迁移(PostgreSQL 模式)。"""
52
+ try:
53
+ from alembic.config import Config
54
+ from alembic import command
55
+ migrations_dir = os.path.join(os.path.dirname(__file__), "storage", "migrations")
56
+ alembic_cfg = Config(os.path.join(migrations_dir, "alembic.ini"))
57
+ alembic_cfg.set_main_option("script_location", migrations_dir)
58
+ command.upgrade(alembic_cfg, "head")
59
+ logger.info("[init] Alembic 迁移完成: head")
60
+ except Exception as e:
61
+ logger.warning(f"[init] Alembic 迁移执行失败: {e}")
62
+ logger.warning("[init] 请确认已安装 alembic (pip install alembic) 且数据库连接正常")
63
+
64
+
65
+ def init_db(db_path: str = None, force_sqlite: bool = False):
66
+ """
67
+ 初始化数据库 Schema
68
+
69
+ - PostgreSQL 模式: 自动执行 Alembic 迁移到最新版本
70
+ - SQLite 模式: 直接执行 CREATE TABLE DDL
71
+
72
+ 参数:
73
+ db_path: SQLite 数据库文件路径(仅在 SQLite 模式下使用)
74
+ force_sqlite: 强制使用 SQLite 初始化(测试用)
75
+ """
76
+ from backend.storage.database import _is_postgres
77
+
78
+ # PostgreSQL 模式:自动运行 Alembic 迁移
79
+ if _is_postgres() and not force_sqlite:
80
+ try:
81
+ import alembic
82
+ _run_alembic_migrations()
83
+ except ImportError:
84
+ logger.warning("[init] alembic 未安装 (pip install alembic),跳过 Schema 自动化管理")
85
+ except Exception as e:
86
+ logger.error(f"[init] Alembic 迁移失败: {e}")
87
+ return
88
+
89
+ # SQLite 模式:使用 models.py 中的单数据源 DDL
90
+ import sqlite3
91
+ if db_path is None:
92
+ db_path = os.getenv("SECAGENTX_DB_PATH", "data/secagentx.db")
93
+ os.makedirs(os.path.dirname(db_path) or ".", exist_ok=True)
94
+ conn = sqlite3.connect(db_path)
95
+ conn.executescript(SCHEMA_SQL)
96
+ _migrate_sqlite_schema(conn)
97
+ conn.commit()
98
+ conn.close()
99
+ logger.info(f"[init] SQLite 数据库初始化: {db_path}")
100
+
101
+
102
+ def _migrate_sqlite_schema(conn):
103
+ """SQLite 增量迁移:为已存在的旧表补齐缺失列(幂等,可重复执行)。
104
+
105
+ 历史库(早期版本创建)可能缺少后续版本新增的列,
106
+ 这里检测并 ALTER TABLE 补齐,避免 'no such column' 运行时错误。
107
+ """
108
+ import sqlite3
109
+
110
+ def _ensure_column(table: str, column: str, ddl: str) -> bool:
111
+ try:
112
+ cols = {r[1] for r in conn.execute(f"PRAGMA table_info({table})")}
113
+ except sqlite3.Error:
114
+ return False
115
+ if column not in cols:
116
+ try:
117
+ conn.execute(f"ALTER TABLE {table} ADD COLUMN {column} {ddl}")
118
+ logger.info(f"[migrate] 表 {table} 新增列 {column}")
119
+ return True
120
+ except sqlite3.Error as e:
121
+ logger.warning(f"[migrate] 表 {table} 添加列 {column} 失败: {e}")
122
+ return False
123
+
124
+ # events 表历史版本缺少处置字段
125
+ _ensure_column("events", "resolution", "TEXT DEFAULT ''")
126
+ _ensure_column("events", "resolved_by", "TEXT DEFAULT ''")
127
+ owner_added = _ensure_column(
128
+ "conversations", "owner_id", "TEXT NOT NULL DEFAULT ''"
129
+ )
130
+ if owner_added:
131
+ legacy_owner = os.getenv("SECAGENTX_LEGACY_CONVERSATION_OWNER", "admin")
132
+ conn.execute(
133
+ "UPDATE conversations SET owner_id = ? WHERE owner_id = ''",
134
+ (legacy_owner,),
135
+ )
136
+ conn.execute(
137
+ "CREATE INDEX IF NOT EXISTS idx_conversations_owner "
138
+ "ON conversations(owner_id)"
139
+ )
140
+
141
+
142
+ def _check_api_keys():
143
+ """启动时检查核心模型与可选威胁情报配置。"""
144
+ runtime_provider = os.getenv("SECAGENTX_ACTIVE_PROVIDER", "")
145
+ legacy_model = bool(os.getenv("DEEPSEEK_API_KEY") or os.getenv("QWEN_API_KEY"))
146
+ model_ready = bool(runtime_provider or legacy_model)
147
+ intel = {
148
+ "VirusTotal": bool(os.getenv("VT_API_KEY")),
149
+ "AbuseIPDB": bool(os.getenv("ABUSEIPDB_API_KEY")),
150
+ "AlienVault OTX": bool(os.getenv("OTX_API_KEY")),
151
+ }
152
+
153
+ logger.info("\n" + "=" * 50)
154
+ logger.info(" SecAgentX API Key 检查")
155
+ logger.info("=" * 50)
156
+ if model_ready:
157
+ logger.info(" [OK] 活动模型 Provider 已配置")
158
+ else:
159
+ logger.warning(" [MISSING] 模型 Provider 未配置,请运行 secagentx onboard")
160
+ enabled_intel = [name for name, ready in intel.items() if ready]
161
+ if enabled_intel:
162
+ logger.info(" [OK] 外部威胁情报: %s", ", ".join(enabled_intel))
163
+ else:
164
+ logger.info(" [OPTIONAL] 外部威胁情报未配置;使用本地知识与离线情报适配器")
165
+ logger.info("=" * 50 + "\n")
166
+
167
+
168
+ def init_application(config: dict = None) -> Orchestrator:
169
+ _check_api_keys()
170
+
171
+ if config is None:
172
+ config = load_config()
173
+
174
+ from backend.storage.database import _is_postgres, get_sqlite_path
175
+ if _is_postgres():
176
+ init_db()
177
+ logger.info("[init] database: PostgreSQL")
178
+ else:
179
+ db_path = get_sqlite_path()
180
+ init_db(db_path)
181
+ logger.info(f"[init] database: {db_path}")
182
+
183
+ mitre = MitreAttackKnowledge()
184
+ mitre_count = mitre.count()
185
+ logger.info(f"[init] MITRE ATT&CK: {mitre_count['techniques']} techniques, {mitre_count['sub_techniques']} sub-techniques, {mitre_count['tactics']} tactics")
186
+ compliance = ComplianceKnowledge()
187
+ logger.info(f"[init] Compliance knowledge: {compliance.count()} regulations/standards")
188
+ cve_db = CVEDatabase()
189
+ logger.info(f"[init] CVE database: {cve_db.count()} vulnerabilities")
190
+ actor_kb = ActorKnowledge()
191
+ actor_count = actor_kb.count()
192
+ logger.info(f"[init] Threat actors: {actor_count['actors']} ({', '.join(f'{k}:{v}' for k,v in actor_count.get('countries',{}).items())})")
193
+ malware_kb = MalwareKnowledge()
194
+ logger.info(f"[init] Malware: {malware_kb.count()} samples")
195
+
196
+ tools = ToolRegistry()
197
+ tools.register(ThreatIntelTool())
198
+ # 读取 config 白名单,注入 FirewallTool(支持适配器模式)
199
+ block_cfg = config.get("auto_operation", {}).get("block_protection", {})
200
+ whitelist = block_cfg.get("whitelist_ips", None)
201
+ fw_backend = os.getenv("FIREWALL_BACKEND", "disabled")
202
+ fw_tool = FirewallTool(whitelist=whitelist, backend=fw_backend)
203
+ fw_tool.agent_id = "orch-firewall" # 审计日志标识
204
+ tools.register(fw_tool)
205
+ logger.info(f"[init] 防火墙后端: {fw_backend}")
206
+ tools.register(LogAnalyzerTool())
207
+ tools.register(AlertFilterTool())
208
+ tools.register(CVESearchTool())
209
+ tools.register(GeoIPTool())
210
+ # ML 为可选能力:只有部署了真实训练模型时才注册,避免把未验证模型暴露给 Agent。
211
+ try:
212
+ from backend.tools.ml_detector import MLThreatDetectorTool
213
+ ml_tool = MLThreatDetectorTool()
214
+ if os.path.isfile(ml_tool._model_path):
215
+ tools.register(ml_tool)
216
+ logger.info("[init] ML 威胁检测模型已发现,将在首次调用时加载")
217
+ else:
218
+ logger.info("[init] ML 威胁检测未启用:未部署真实训练模型")
219
+ except ImportError as e:
220
+ logger.warning(f"[init] ML 威胁检测模块加载失败: {e}")
221
+ except Exception as e:
222
+ logger.warning(f"[init] ML 威胁检测初始化失败: {e}")
223
+ logger.info(f"[init] tools: {tools.count()}")
224
+
225
+ llm_configs = config.get("llm", {})
226
+ deepseek_cfg = dict(llm_configs.get("deepseek", {}))
227
+ qwen_cfg = dict(llm_configs.get("qwen", {}))
228
+ # LLM fallback 配置(主 LLM 超时/失败时自动切换备用 LLM)
229
+ _orchestrator_fb = dict(llm_configs.get("fallback", {})) if isinstance(llm_configs.get("fallback"), dict) else None
230
+
231
+ for cfg in [deepseek_cfg, qwen_cfg]:
232
+ key = cfg.get("api_key", "")
233
+ if key.startswith("${") and key.endswith("}"):
234
+ cfg["api_key"] = os.getenv(key[2:-1], "")
235
+
236
+ # ═══ 修复:fallback 段的嵌套 config 也要做 env 插值 ═══
237
+ # 否则 config.yaml fallback.config.api_key 的 "${QWEN_API_KEY}" 会以字面量传给 LLM,
238
+ # 导致 fallback 请求 401/403。
239
+ if _orchestrator_fb and isinstance(_orchestrator_fb, dict):
240
+ _fb_cfg = _orchestrator_fb.get("config")
241
+ if isinstance(_fb_cfg, dict):
242
+ _fb_key = _fb_cfg.get("api_key", "")
243
+ if isinstance(_fb_key, str) and _fb_key.startswith("${") and _fb_key.endswith("}"):
244
+ _fb_cfg["api_key"] = os.getenv(_fb_key[2:-1], "")
245
+
246
+ # 根据 LLM_PROVIDER 环境变量选择 LLM 提供商
247
+ llm_provider = os.getenv(
248
+ "SECAGENTX_ACTIVE_PROVIDER", os.getenv("LLM_PROVIDER", "deepseek")
249
+ ).lower()
250
+ orchestrator_llm_cfg = deepseek_cfg.copy()
251
+ if _orchestrator_fb:
252
+ orchestrator_llm_cfg["fallback"] = _orchestrator_fb
253
+ logger.info(f"[init] LLM 提供商: {llm_provider}")
254
+
255
+ # Agent 也使用同一 fallback 配置(主 LLM 超时/失败时自动切换备用 LLM)
256
+ analyst = AnalystAgent(tools, llm_fallback_config=_orchestrator_fb)
257
+ intel = IntelAgent(tools, llm_fallback_config=_orchestrator_fb)
258
+ responder = ResponderAgent(tools, llm_fallback_config=_orchestrator_fb)
259
+ knowledge = KnowledgeAgent(tools, llm_fallback_config=_orchestrator_fb)
260
+ alert_filter = AlertFilterAgent(tools, llm_fallback_config=_orchestrator_fb)
261
+ # v2.5: 报告生成员(Summary Agent)— 汇总所有专业 Agent 结果生成模板化最终报告
262
+ from backend.agents.summary_agent import SummaryAgent
263
+ summary = SummaryAgent(tools, llm_fallback_config=_orchestrator_fb)
264
+ # v2.5: 事件分类器(Classifier Agent)— 判断安全事件模板分类
265
+ from backend.agents.classifier_agent import ClassifierAgent
266
+ classifier = ClassifierAgent(tools, llm_fallback_config=_orchestrator_fb)
267
+
268
+ try:
269
+ from backend.storage.chroma_store import VectorStore
270
+ vector_store = VectorStore()
271
+ # Qwen RAG 也传入 fallback 配置
272
+ qwen = LLMFactory.get_qwen(qwen_cfg, fallback_config=_orchestrator_fb)
273
+ max_rounds = config.get("agents", {}).get("knowledge", {}).get("max_retrieval_rounds", 3)
274
+ rag_engine = AgenticRAGEngine(llm=qwen, vector_store=vector_store, max_rounds=max_rounds)
275
+ # BGE 模型改为懒加载:首次查询时自动加载(模型已缓存,无需启动时预载)
276
+ knowledge.set_rag_engine(rag_engine)
277
+ logger.info(f"[init] RAG engine: enabled (max {max_rounds} rounds)")
278
+ except Exception as e:
279
+ rag_engine = AgenticRAGEngine(max_rounds=1)
280
+ knowledge.set_rag_engine(rag_engine)
281
+ logger.warning(f"[WARN] [init] RAG engine: FALLBACK MODE - ChromaDB 初始化失败")
282
+ logger.info(f" 原因: {e}")
283
+ logger.info(f" 影响: 知识检索将降级为关键词搜索,语义相关性下降")
284
+ logger.info(f" 修复: 检查 chromadb 安装和 data/chromadb/ 目录权限")
285
+
286
+ orchestrator = Orchestrator({
287
+ **config.get("orchestrator", {}),
288
+ "llm": orchestrator_llm_cfg,
289
+ }, tools=tools)
290
+ orchestrator.register_agent("analyst-001", "安全分析师", analyst,
291
+ "告警分析、日志分析、攻击溯源")
292
+ orchestrator.register_agent("intel-001", "威胁情报员", intel,
293
+ "IOC查询、威胁情报关联")
294
+ orchestrator.register_agent("responder-001", "应急响应员", responder,
295
+ "封禁IP、策略管理")
296
+ orchestrator.register_agent("knowledge-001", "知识智能体", knowledge,
297
+ "MITRE ATT&CK、CVE查询")
298
+ orchestrator.register_agent("alert-filter-001", "告警误报剔除专家", alert_filter,
299
+ "告警误报过滤:规则引擎+AI双层研判,将误报率从90%降至10%以下")
300
+ orchestrator.register_agent("summary-001", "报告生成员", summary,
301
+ "汇总所有专业Agent分析结果,生成模板化最终综合报告")
302
+ orchestrator.register_agent("classifier-001", "事件分类器", classifier,
303
+ "判断安全事件模板分类(漏洞分析/攻击检测/安全配置/威胁情报/应急响应)")
304
+
305
+ stats = orchestrator.get_stats()
306
+ logger.info(f"[init] SecAgentX ready: {stats['agents_count']} agents, {tools.count()} tools")
307
+
308
+ # ═══════════════════════ 初始化跨区域联邦模块 ═══════════════════════
309
+ orchestrator._federation = None
310
+ try:
311
+ from backend.federation import Federation
312
+ federation = Federation(config.get("federation", {}))
313
+ if federation.enabled:
314
+ logger.info(f"[init] 跨区域联邦: 已启用 (region={federation.region_id}, peers={len(federation._peers)})")
315
+ else:
316
+ logger.info("[init] 跨区域联邦: 未启用")
317
+ orchestrator._federation = federation
318
+ except Exception as e:
319
+ logger.warning(f"[init] 跨区域联邦: 加载失败 ({e})")
320
+
321
+ # ═══════════════════════ 初始化零人工干预模块 ═══════════════════════
322
+ _auto_modules = {}
323
+ _background_tasks: list[asyncio.Task] = []
324
+ auto_op_config = config.get("auto_operation", {})
325
+ auto_enabled = auto_op_config.get("enabled", False)
326
+
327
+ if auto_enabled:
328
+ logger.info("\n[auto] 零人工干预模式已启用")
329
+
330
+ # 1. 自动升级通知引擎
331
+ try:
332
+ from backend.escalation import AutoEscalation
333
+ escalator = AutoEscalation(auto_op_config)
334
+ _auto_modules["escalator"] = escalator
335
+ esc_status = escalator.get_status()
336
+ active_channels = [s["type"] for s in esc_status if s["enabled"]]
337
+ logger.info(f"[auto] 升级通知引擎: {', '.join(active_channels) if active_channels else '仅控制台'}")
338
+
339
+ # 将熔断器的自动恢复失败通知接入升级通知引擎
340
+ try:
341
+ from backend.security.circuit_breaker import circuit_breaker
342
+ async def _cb_escalate(msg: str):
343
+ await escalator.escalate(
344
+ incident_id="circuit-breaker-auto-recovery",
345
+ summary=msg,
346
+ confidence=0.0,
347
+ reason="熔断器自动恢复失败,需人工介入",
348
+ )
349
+ circuit_breaker.set_escalate_callback(_cb_escalate)
350
+ logger.info("[auto] 熔断器自动恢复通知: 已接入升级引擎")
351
+ except Exception as e:
352
+ logger.warning(f"[auto] 熔断器通知接入失败: {e}")
353
+ except Exception as e:
354
+ escalator = None
355
+ logger.info(f"[auto] 升级通知引擎: 加载失败 ({e})")
356
+
357
+ # 2. 自动告警接入器
358
+ ingestor = None
359
+ try:
360
+ from backend.auto_ingestor import AutoIngestor
361
+ ingestor = AutoIngestor(orchestrator, escalator, auto_op_config)
362
+ _auto_modules["ingestor"] = ingestor
363
+ logger.info(f"[auto] 告警接入器: 已就绪 (在 api_server 启动时激活)")
364
+ except Exception as e:
365
+ logger.info(f"[auto] 告警接入器: 加载失败 ({e})")
366
+
367
+ # 2.5 邮件安全接入器(可选,依赖 IMAP 配置)
368
+ try:
369
+ from backend.email_ingestor import EmailIngestor
370
+ if ingestor is not None:
371
+ email_ingestor = EmailIngestor(ingestor, auto_op_config)
372
+ _auto_modules["email_ingestor"] = email_ingestor
373
+ if email_ingestor._configured:
374
+ logger.info(f"[auto] 邮件接入器: 已就绪 ({email_ingestor.username}@{email_ingestor.imap_server})")
375
+ else:
376
+ logger.info("[auto] 邮件接入器: 未配置 (设置 EMAIL_IMAP_SERVER/USERNAME/PASSWORD 后启用)")
377
+ else:
378
+ logger.info("[auto] 邮件接入器: 跳过 (告警接入器未就绪)")
379
+ except Exception as e:
380
+ logger.info(f"[auto] 邮件接入器: 加载失败 ({e})")
381
+
382
+ # 3. 自动安全巡检器
383
+ try:
384
+ from backend.auto_patrol import AutoPatrol
385
+ patrol = AutoPatrol(orchestrator, escalator, auto_op_config)
386
+ _auto_modules["patrol"] = patrol
387
+ logger.info(f"[auto] 安全巡检器: 已就绪 (间隔 {auto_op_config.get('patrol', {}).get('interval_seconds', 1800)}s)")
388
+ except Exception as e:
389
+ logger.info(f"[auto] 安全巡检器: 加载失败 ({e})")
390
+
391
+ # 4. 防火墙工具注入置信度阈值
392
+ try:
393
+ block_threshold = auto_op_config.get("thresholds", {}).get("auto_block", 0.70)
394
+ fw_tool = tools.get("firewall_manage")
395
+ if fw_tool and hasattr(fw_tool, "_block_threshold"):
396
+ fw_tool._block_threshold = block_threshold
397
+ logger.info(f"[auto] 防火墙门控阈值: {block_threshold:.0%}")
398
+ except Exception as e:
399
+ logger.info(f"[auto] 防火墙门控: 配置失败 ({e})")
400
+
401
+ logger.info("[auto] 初始化完成\n")
402
+ else:
403
+ logger.info("\n[auto] 零人工干预模式已禁用 (config.yaml: auto_operation.enabled=false)\n")
404
+
405
+ # 将自动模块和后台任务挂载到 orchestrator 上,供 api_server 访问
406
+ orchestrator._auto_modules = _auto_modules
407
+ orchestrator._background_tasks = _background_tasks
408
+ orchestrator._config = config
409
+
410
+ stats = orchestrator.get_stats()
411
+ logger.info(f"[init] SecAgentX ready: {stats['agents_count']} agents, {tools.count()} tools")
412
+ return orchestrator
413
+
414
+
415
+ async def quick_query(orchestrator: Orchestrator, text: str) -> str:
416
+ """快速查询 — 使用统一 TrueReAct 入口"""
417
+ results = []
418
+ async for chunk in orchestrator.process(text):
419
+ if chunk["type"] in ("orchestrator_complete", "true_react_complete"):
420
+ results.append(chunk.get("summary", chunk.get("content", "")))
421
+ return "\n".join(results)
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,132 @@
1
+ """
2
+ EpisodicMemory — 情景记忆(v2.4 M4)
3
+
4
+ 沉淀已完成的安全研判案例(复用 experiences 表):
5
+ - record_episode: 完成一次威胁研判后写入案例
6
+ - recall_similar: 按场景关键词/向量检索召回相似案例
7
+ - 每次召回自增 times_used
8
+
9
+ 字段(对齐 experiences 表):
10
+ scenario / input_summary / actions_taken / outcome / lessons / vector_id / times_used
11
+ """
12
+ import uuid
13
+ import json
14
+ import re
15
+ from datetime import datetime, timezone
16
+ from typing import Optional
17
+ from ..storage.database import Repository
18
+
19
+
20
+ class EpisodicMemory:
21
+ """情景记忆 — 历史研判案例沉淀与召回。"""
22
+
23
+ def __init__(self, db=None):
24
+ self.db = db if db is not None else Repository()
25
+
26
+ async def _execute(self, sql: str, params: tuple = ()):
27
+ if isinstance(self.db, Repository):
28
+ return await self.db.execute(sql, params)
29
+ return self.db.execute(sql, params)
30
+
31
+ async def _fetch_all(self, sql: str, params: tuple = ()) -> list[dict]:
32
+ if isinstance(self.db, Repository):
33
+ return await self.db.fetch_all(sql, params)
34
+ return self.db.fetch_all(sql, params)
35
+
36
+ async def _fetch_one(self, sql: str, params: tuple = ()) -> Optional[dict]:
37
+ if isinstance(self.db, Repository):
38
+ return await self.db.fetch_one(sql, params)
39
+ return self.db.fetch_one(sql, params)
40
+
41
+ async def record_episode(self, scenario: str, input_summary: str = "",
42
+ actions_taken: list = None, outcome: str = "",
43
+ lessons: str = "") -> str:
44
+ """沉淀一个研判案例。返回案例 ID。"""
45
+ eid = uuid.uuid4().hex[:12]
46
+ now = datetime.now(timezone.utc).isoformat()
47
+ await self._execute(
48
+ "INSERT INTO experiences (id, scenario, input_summary, actions_taken, outcome, lessons, created_at, times_used) "
49
+ "VALUES (?, ?, ?, ?, ?, ?, ?, 0)",
50
+ (
51
+ eid, scenario, input_summary,
52
+ json.dumps(actions_taken or [], ensure_ascii=False),
53
+ outcome, lessons, now,
54
+ ),
55
+ )
56
+ return eid
57
+
58
+ async def recall_similar(self, query: str, limit: int = 5) -> list[dict]:
59
+ """按场景关键词召回相似案例(关键词重叠 + 时间倒序)。
60
+
61
+ 关键词提取策略:
62
+ 1. 按分隔符拆词(空格/逗号/句号)
63
+ 2. 整段无分隔时按 2-gram 拆分
64
+ """
65
+ import re
66
+ terms = [t for t in re.split(r'[\s,,。;;、]+', query) if len(t) >= 2]
67
+ if len(terms) <= 1 and len(query) >= 4:
68
+ terms = [query[i:i + 2] for i in range(len(query) - 1)]
69
+ query_terms = list(dict.fromkeys(terms))[:12]
70
+
71
+ rows = await self._fetch_all(
72
+ "SELECT * FROM experiences ORDER BY created_at DESC LIMIT 100", (),
73
+ )
74
+ scored = []
75
+ for r in rows:
76
+ scenario = r.get("scenario") or ""
77
+ input_summary = r.get("input_summary") or ""
78
+ score = 0
79
+ for t in query_terms:
80
+ if len(t) >= 2 and t in scenario:
81
+ score += 2
82
+ elif len(t) >= 2 and t in input_summary:
83
+ score += 1
84
+ if score > 0:
85
+ scored.append((score, r))
86
+ scored.sort(key=lambda x: -x[0])
87
+
88
+ out = []
89
+ for _, r in scored[:limit]:
90
+ eid = r["id"]
91
+ # 自增 times_used(失败静默,不影响召回)
92
+ try:
93
+ await self._execute(
94
+ "UPDATE experiences SET times_used = times_used + 1 WHERE id = ?",
95
+ (eid,),
96
+ )
97
+ except Exception:
98
+ pass
99
+ out.append({
100
+ "id": eid,
101
+ "scenario": r.get("scenario", ""),
102
+ "input_summary": r.get("input_summary", ""),
103
+ "actions_taken": json.loads(r.get("actions_taken") or "[]") or [],
104
+ "outcome": r.get("outcome", ""),
105
+ "lessons": r.get("lessons", ""),
106
+ "times_used": (r.get("times_used") or 0) + 1,
107
+ "created_at": r.get("created_at", ""),
108
+ })
109
+ return out
110
+
111
+ async def list_episodes(self, limit: int = 20) -> list[dict]:
112
+ rows = await self._fetch_all(
113
+ "SELECT * FROM experiences ORDER BY times_used DESC, created_at DESC LIMIT ?",
114
+ (limit,),
115
+ )
116
+ return [{
117
+ "id": r.get("id"),
118
+ "scenario": r.get("scenario"),
119
+ "input_summary": r.get("input_summary", ""),
120
+ "outcome": r.get("outcome", ""),
121
+ "lessons": r.get("lessons", ""),
122
+ "times_used": r.get("times_used", 0),
123
+ "created_at": r.get("created_at", ""),
124
+ } for r in rows]
125
+
126
+ async def count(self) -> int:
127
+ row = await self._fetch_one("SELECT COUNT(*) AS n FROM experiences", ())
128
+ return row.get("n", 0) if row else 0
129
+
130
+
131
+ __all__ = ["EpisodicMemory"]
132
+