claude-mpm 4.7.4__py3-none-any.whl → 4.18.2__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 (308) hide show
  1. claude_mpm/VERSION +1 -1
  2. claude_mpm/agents/BASE_AGENT_TEMPLATE.md +118 -0
  3. claude_mpm/agents/BASE_ENGINEER.md +286 -0
  4. claude_mpm/agents/BASE_PM.md +106 -1
  5. claude_mpm/agents/OUTPUT_STYLE.md +329 -11
  6. claude_mpm/agents/PM_INSTRUCTIONS.md +397 -459
  7. claude_mpm/agents/agent_loader.py +17 -5
  8. claude_mpm/agents/frontmatter_validator.py +284 -253
  9. claude_mpm/agents/templates/README.md +465 -0
  10. claude_mpm/agents/templates/agent-manager.json +4 -1
  11. claude_mpm/agents/templates/agentic-coder-optimizer.json +13 -3
  12. claude_mpm/agents/templates/api_qa.json +11 -2
  13. claude_mpm/agents/templates/circuit_breakers.md +638 -0
  14. claude_mpm/agents/templates/clerk-ops.json +12 -2
  15. claude_mpm/agents/templates/code_analyzer.json +8 -2
  16. claude_mpm/agents/templates/content-agent.json +358 -0
  17. claude_mpm/agents/templates/dart_engineer.json +15 -2
  18. claude_mpm/agents/templates/data_engineer.json +15 -2
  19. claude_mpm/agents/templates/documentation.json +10 -2
  20. claude_mpm/agents/templates/engineer.json +21 -1
  21. claude_mpm/agents/templates/gcp_ops_agent.json +12 -2
  22. claude_mpm/agents/templates/git_file_tracking.md +584 -0
  23. claude_mpm/agents/templates/golang_engineer.json +270 -0
  24. claude_mpm/agents/templates/imagemagick.json +4 -1
  25. claude_mpm/agents/templates/java_engineer.json +346 -0
  26. claude_mpm/agents/templates/local_ops_agent.json +1227 -6
  27. claude_mpm/agents/templates/memory_manager.json +4 -1
  28. claude_mpm/agents/templates/nextjs_engineer.json +141 -133
  29. claude_mpm/agents/templates/ops.json +12 -2
  30. claude_mpm/agents/templates/php-engineer.json +270 -174
  31. claude_mpm/agents/templates/pm_examples.md +474 -0
  32. claude_mpm/agents/templates/pm_red_flags.md +240 -0
  33. claude_mpm/agents/templates/product_owner.json +338 -0
  34. claude_mpm/agents/templates/project_organizer.json +14 -4
  35. claude_mpm/agents/templates/prompt-engineer.json +13 -2
  36. claude_mpm/agents/templates/python_engineer.json +174 -81
  37. claude_mpm/agents/templates/qa.json +11 -2
  38. claude_mpm/agents/templates/react_engineer.json +16 -3
  39. claude_mpm/agents/templates/refactoring_engineer.json +12 -2
  40. claude_mpm/agents/templates/research.json +34 -21
  41. claude_mpm/agents/templates/response_format.md +583 -0
  42. claude_mpm/agents/templates/ruby-engineer.json +129 -192
  43. claude_mpm/agents/templates/rust_engineer.json +270 -0
  44. claude_mpm/agents/templates/security.json +10 -2
  45. claude_mpm/agents/templates/svelte-engineer.json +225 -0
  46. claude_mpm/agents/templates/ticketing.json +10 -2
  47. claude_mpm/agents/templates/typescript_engineer.json +116 -125
  48. claude_mpm/agents/templates/validation_templates.md +312 -0
  49. claude_mpm/agents/templates/vercel_ops_agent.json +12 -2
  50. claude_mpm/agents/templates/version_control.json +12 -2
  51. claude_mpm/agents/templates/web_qa.json +11 -2
  52. claude_mpm/agents/templates/web_ui.json +15 -2
  53. claude_mpm/cli/__init__.py +34 -614
  54. claude_mpm/cli/commands/agent_manager.py +25 -12
  55. claude_mpm/cli/commands/agent_state_manager.py +186 -0
  56. claude_mpm/cli/commands/agents.py +235 -148
  57. claude_mpm/cli/commands/agents_detect.py +380 -0
  58. claude_mpm/cli/commands/agents_recommend.py +309 -0
  59. claude_mpm/cli/commands/aggregate.py +7 -3
  60. claude_mpm/cli/commands/analyze.py +9 -4
  61. claude_mpm/cli/commands/analyze_code.py +7 -2
  62. claude_mpm/cli/commands/auto_configure.py +570 -0
  63. claude_mpm/cli/commands/config.py +47 -13
  64. claude_mpm/cli/commands/configure.py +419 -1571
  65. claude_mpm/cli/commands/configure_agent_display.py +261 -0
  66. claude_mpm/cli/commands/configure_behavior_manager.py +204 -0
  67. claude_mpm/cli/commands/configure_hook_manager.py +225 -0
  68. claude_mpm/cli/commands/configure_models.py +18 -0
  69. claude_mpm/cli/commands/configure_navigation.py +167 -0
  70. claude_mpm/cli/commands/configure_paths.py +104 -0
  71. claude_mpm/cli/commands/configure_persistence.py +254 -0
  72. claude_mpm/cli/commands/configure_startup_manager.py +646 -0
  73. claude_mpm/cli/commands/configure_template_editor.py +497 -0
  74. claude_mpm/cli/commands/configure_validators.py +73 -0
  75. claude_mpm/cli/commands/local_deploy.py +537 -0
  76. claude_mpm/cli/commands/memory.py +54 -20
  77. claude_mpm/cli/commands/mpm_init.py +585 -196
  78. claude_mpm/cli/commands/mpm_init_handler.py +37 -3
  79. claude_mpm/cli/commands/search.py +170 -4
  80. claude_mpm/cli/commands/upgrade.py +152 -0
  81. claude_mpm/cli/executor.py +202 -0
  82. claude_mpm/cli/helpers.py +105 -0
  83. claude_mpm/cli/interactive/__init__.py +3 -0
  84. claude_mpm/cli/interactive/skills_wizard.py +491 -0
  85. claude_mpm/cli/parsers/__init__.py +7 -1
  86. claude_mpm/cli/parsers/agents_parser.py +9 -0
  87. claude_mpm/cli/parsers/auto_configure_parser.py +245 -0
  88. claude_mpm/cli/parsers/base_parser.py +110 -3
  89. claude_mpm/cli/parsers/local_deploy_parser.py +227 -0
  90. claude_mpm/cli/parsers/mpm_init_parser.py +65 -5
  91. claude_mpm/cli/shared/output_formatters.py +28 -19
  92. claude_mpm/cli/startup.py +481 -0
  93. claude_mpm/cli/utils.py +52 -1
  94. claude_mpm/commands/mpm-agents-detect.md +168 -0
  95. claude_mpm/commands/mpm-agents-recommend.md +214 -0
  96. claude_mpm/commands/mpm-agents.md +75 -1
  97. claude_mpm/commands/mpm-auto-configure.md +217 -0
  98. claude_mpm/commands/mpm-help.md +163 -0
  99. claude_mpm/commands/mpm-init.md +148 -3
  100. claude_mpm/commands/mpm-version.md +113 -0
  101. claude_mpm/commands/mpm.md +1 -0
  102. claude_mpm/config/agent_config.py +2 -2
  103. claude_mpm/config/model_config.py +428 -0
  104. claude_mpm/constants.py +1 -0
  105. claude_mpm/core/base_service.py +13 -12
  106. claude_mpm/core/enums.py +452 -0
  107. claude_mpm/core/factories.py +1 -1
  108. claude_mpm/core/instruction_reinforcement_hook.py +2 -1
  109. claude_mpm/core/interactive_session.py +9 -3
  110. claude_mpm/core/log_manager.py +2 -0
  111. claude_mpm/core/logging_config.py +6 -2
  112. claude_mpm/core/oneshot_session.py +8 -4
  113. claude_mpm/core/optimized_agent_loader.py +3 -3
  114. claude_mpm/core/output_style_manager.py +12 -192
  115. claude_mpm/core/service_registry.py +5 -1
  116. claude_mpm/core/types.py +2 -9
  117. claude_mpm/core/typing_utils.py +7 -6
  118. claude_mpm/dashboard/static/js/dashboard.js +0 -14
  119. claude_mpm/dashboard/templates/index.html +3 -41
  120. claude_mpm/hooks/__init__.py +20 -0
  121. claude_mpm/hooks/claude_hooks/event_handlers.py +4 -2
  122. claude_mpm/hooks/claude_hooks/response_tracking.py +35 -1
  123. claude_mpm/hooks/claude_hooks/services/connection_manager_http.py +23 -2
  124. claude_mpm/hooks/failure_learning/__init__.py +60 -0
  125. claude_mpm/hooks/failure_learning/failure_detection_hook.py +235 -0
  126. claude_mpm/hooks/failure_learning/fix_detection_hook.py +217 -0
  127. claude_mpm/hooks/failure_learning/learning_extraction_hook.py +286 -0
  128. claude_mpm/hooks/instruction_reinforcement.py +7 -2
  129. claude_mpm/hooks/kuzu_enrichment_hook.py +263 -0
  130. claude_mpm/hooks/kuzu_memory_hook.py +37 -12
  131. claude_mpm/hooks/kuzu_response_hook.py +183 -0
  132. claude_mpm/models/resume_log.py +340 -0
  133. claude_mpm/services/agents/__init__.py +18 -5
  134. claude_mpm/services/agents/auto_config_manager.py +796 -0
  135. claude_mpm/services/agents/deployment/agent_configuration_manager.py +1 -1
  136. claude_mpm/services/agents/deployment/agent_record_service.py +1 -1
  137. claude_mpm/services/agents/deployment/agent_validator.py +17 -1
  138. claude_mpm/services/agents/deployment/async_agent_deployment.py +1 -1
  139. claude_mpm/services/agents/deployment/interface_adapter.py +3 -2
  140. claude_mpm/services/agents/deployment/local_template_deployment.py +1 -1
  141. claude_mpm/services/agents/deployment/pipeline/steps/agent_processing_step.py +7 -6
  142. claude_mpm/services/agents/deployment/pipeline/steps/base_step.py +7 -16
  143. claude_mpm/services/agents/deployment/pipeline/steps/configuration_step.py +4 -3
  144. claude_mpm/services/agents/deployment/pipeline/steps/target_directory_step.py +5 -3
  145. claude_mpm/services/agents/deployment/pipeline/steps/validation_step.py +6 -5
  146. claude_mpm/services/agents/deployment/refactored_agent_deployment_service.py +9 -6
  147. claude_mpm/services/agents/deployment/validation/__init__.py +3 -1
  148. claude_mpm/services/agents/deployment/validation/validation_result.py +1 -9
  149. claude_mpm/services/agents/local_template_manager.py +1 -1
  150. claude_mpm/services/agents/memory/agent_memory_manager.py +5 -2
  151. claude_mpm/services/agents/observers.py +547 -0
  152. claude_mpm/services/agents/recommender.py +568 -0
  153. claude_mpm/services/agents/registry/modification_tracker.py +5 -2
  154. claude_mpm/services/command_handler_service.py +11 -5
  155. claude_mpm/services/core/__init__.py +33 -1
  156. claude_mpm/services/core/interfaces/__init__.py +90 -3
  157. claude_mpm/services/core/interfaces/agent.py +184 -0
  158. claude_mpm/services/core/interfaces/health.py +172 -0
  159. claude_mpm/services/core/interfaces/model.py +281 -0
  160. claude_mpm/services/core/interfaces/process.py +372 -0
  161. claude_mpm/services/core/interfaces/project.py +121 -0
  162. claude_mpm/services/core/interfaces/restart.py +307 -0
  163. claude_mpm/services/core/interfaces/stability.py +260 -0
  164. claude_mpm/services/core/memory_manager.py +11 -24
  165. claude_mpm/services/core/models/__init__.py +79 -0
  166. claude_mpm/services/core/models/agent_config.py +381 -0
  167. claude_mpm/services/core/models/health.py +162 -0
  168. claude_mpm/services/core/models/process.py +235 -0
  169. claude_mpm/services/core/models/restart.py +302 -0
  170. claude_mpm/services/core/models/stability.py +264 -0
  171. claude_mpm/services/core/models/toolchain.py +306 -0
  172. claude_mpm/services/core/path_resolver.py +23 -7
  173. claude_mpm/services/diagnostics/__init__.py +2 -2
  174. claude_mpm/services/diagnostics/checks/agent_check.py +25 -24
  175. claude_mpm/services/diagnostics/checks/claude_code_check.py +24 -23
  176. claude_mpm/services/diagnostics/checks/common_issues_check.py +25 -24
  177. claude_mpm/services/diagnostics/checks/configuration_check.py +24 -23
  178. claude_mpm/services/diagnostics/checks/filesystem_check.py +18 -17
  179. claude_mpm/services/diagnostics/checks/installation_check.py +30 -29
  180. claude_mpm/services/diagnostics/checks/instructions_check.py +20 -19
  181. claude_mpm/services/diagnostics/checks/mcp_check.py +50 -36
  182. claude_mpm/services/diagnostics/checks/mcp_services_check.py +38 -33
  183. claude_mpm/services/diagnostics/checks/monitor_check.py +23 -22
  184. claude_mpm/services/diagnostics/checks/startup_log_check.py +9 -8
  185. claude_mpm/services/diagnostics/diagnostic_runner.py +6 -5
  186. claude_mpm/services/diagnostics/doctor_reporter.py +28 -25
  187. claude_mpm/services/diagnostics/models.py +19 -24
  188. claude_mpm/services/infrastructure/monitoring/__init__.py +1 -1
  189. claude_mpm/services/infrastructure/monitoring/aggregator.py +12 -12
  190. claude_mpm/services/infrastructure/monitoring/base.py +5 -13
  191. claude_mpm/services/infrastructure/monitoring/network.py +7 -6
  192. claude_mpm/services/infrastructure/monitoring/process.py +13 -12
  193. claude_mpm/services/infrastructure/monitoring/resources.py +7 -6
  194. claude_mpm/services/infrastructure/monitoring/service.py +16 -15
  195. claude_mpm/services/infrastructure/resume_log_generator.py +439 -0
  196. claude_mpm/services/local_ops/__init__.py +163 -0
  197. claude_mpm/services/local_ops/crash_detector.py +257 -0
  198. claude_mpm/services/local_ops/health_checks/__init__.py +28 -0
  199. claude_mpm/services/local_ops/health_checks/http_check.py +224 -0
  200. claude_mpm/services/local_ops/health_checks/process_check.py +236 -0
  201. claude_mpm/services/local_ops/health_checks/resource_check.py +255 -0
  202. claude_mpm/services/local_ops/health_manager.py +430 -0
  203. claude_mpm/services/local_ops/log_monitor.py +396 -0
  204. claude_mpm/services/local_ops/memory_leak_detector.py +294 -0
  205. claude_mpm/services/local_ops/process_manager.py +595 -0
  206. claude_mpm/services/local_ops/resource_monitor.py +331 -0
  207. claude_mpm/services/local_ops/restart_manager.py +401 -0
  208. claude_mpm/services/local_ops/restart_policy.py +387 -0
  209. claude_mpm/services/local_ops/state_manager.py +372 -0
  210. claude_mpm/services/local_ops/unified_manager.py +600 -0
  211. claude_mpm/services/mcp_config_manager.py +9 -4
  212. claude_mpm/services/mcp_gateway/core/__init__.py +1 -2
  213. claude_mpm/services/mcp_gateway/core/base.py +18 -31
  214. claude_mpm/services/mcp_gateway/main.py +30 -0
  215. claude_mpm/services/mcp_gateway/tools/external_mcp_services.py +206 -32
  216. claude_mpm/services/mcp_gateway/tools/health_check_tool.py +30 -28
  217. claude_mpm/services/mcp_gateway/tools/kuzu_memory_service.py +25 -5
  218. claude_mpm/services/mcp_service_verifier.py +1 -1
  219. claude_mpm/services/memory/failure_tracker.py +563 -0
  220. claude_mpm/services/memory_hook_service.py +165 -4
  221. claude_mpm/services/model/__init__.py +147 -0
  222. claude_mpm/services/model/base_provider.py +365 -0
  223. claude_mpm/services/model/claude_provider.py +412 -0
  224. claude_mpm/services/model/model_router.py +453 -0
  225. claude_mpm/services/model/ollama_provider.py +415 -0
  226. claude_mpm/services/monitor/daemon_manager.py +3 -2
  227. claude_mpm/services/monitor/handlers/dashboard.py +2 -1
  228. claude_mpm/services/monitor/handlers/hooks.py +2 -1
  229. claude_mpm/services/monitor/management/lifecycle.py +3 -2
  230. claude_mpm/services/monitor/server.py +2 -1
  231. claude_mpm/services/project/__init__.py +23 -0
  232. claude_mpm/services/project/detection_strategies.py +719 -0
  233. claude_mpm/services/project/toolchain_analyzer.py +581 -0
  234. claude_mpm/services/self_upgrade_service.py +342 -0
  235. claude_mpm/services/session_management_service.py +3 -2
  236. claude_mpm/services/session_manager.py +205 -1
  237. claude_mpm/services/shared/async_service_base.py +16 -27
  238. claude_mpm/services/shared/lifecycle_service_base.py +1 -14
  239. claude_mpm/services/socketio/handlers/__init__.py +5 -2
  240. claude_mpm/services/socketio/handlers/hook.py +13 -2
  241. claude_mpm/services/socketio/handlers/registry.py +4 -2
  242. claude_mpm/services/socketio/server/main.py +10 -8
  243. claude_mpm/services/subprocess_launcher_service.py +14 -5
  244. claude_mpm/services/unified/analyzer_strategies/code_analyzer.py +8 -7
  245. claude_mpm/services/unified/analyzer_strategies/dependency_analyzer.py +6 -5
  246. claude_mpm/services/unified/analyzer_strategies/performance_analyzer.py +8 -7
  247. claude_mpm/services/unified/analyzer_strategies/security_analyzer.py +7 -6
  248. claude_mpm/services/unified/analyzer_strategies/structure_analyzer.py +5 -4
  249. claude_mpm/services/unified/config_strategies/validation_strategy.py +13 -9
  250. claude_mpm/services/unified/deployment_strategies/cloud_strategies.py +10 -3
  251. claude_mpm/services/unified/deployment_strategies/local.py +6 -5
  252. claude_mpm/services/unified/deployment_strategies/utils.py +6 -5
  253. claude_mpm/services/unified/deployment_strategies/vercel.py +7 -6
  254. claude_mpm/services/unified/interfaces.py +3 -1
  255. claude_mpm/services/unified/unified_analyzer.py +14 -10
  256. claude_mpm/services/unified/unified_config.py +2 -1
  257. claude_mpm/services/unified/unified_deployment.py +9 -4
  258. claude_mpm/services/version_service.py +104 -1
  259. claude_mpm/skills/__init__.py +21 -0
  260. claude_mpm/skills/bundled/__init__.py +6 -0
  261. claude_mpm/skills/bundled/api-documentation.md +393 -0
  262. claude_mpm/skills/bundled/async-testing.md +571 -0
  263. claude_mpm/skills/bundled/code-review.md +143 -0
  264. claude_mpm/skills/bundled/database-migration.md +199 -0
  265. claude_mpm/skills/bundled/docker-containerization.md +194 -0
  266. claude_mpm/skills/bundled/express-local-dev.md +1429 -0
  267. claude_mpm/skills/bundled/fastapi-local-dev.md +1199 -0
  268. claude_mpm/skills/bundled/git-workflow.md +414 -0
  269. claude_mpm/skills/bundled/imagemagick.md +204 -0
  270. claude_mpm/skills/bundled/json-data-handling.md +223 -0
  271. claude_mpm/skills/bundled/nextjs-local-dev.md +807 -0
  272. claude_mpm/skills/bundled/pdf.md +141 -0
  273. claude_mpm/skills/bundled/performance-profiling.md +567 -0
  274. claude_mpm/skills/bundled/refactoring-patterns.md +180 -0
  275. claude_mpm/skills/bundled/security-scanning.md +327 -0
  276. claude_mpm/skills/bundled/systematic-debugging.md +473 -0
  277. claude_mpm/skills/bundled/test-driven-development.md +378 -0
  278. claude_mpm/skills/bundled/vite-local-dev.md +1061 -0
  279. claude_mpm/skills/bundled/web-performance-optimization.md +2305 -0
  280. claude_mpm/skills/bundled/xlsx.md +157 -0
  281. claude_mpm/skills/registry.py +286 -0
  282. claude_mpm/skills/skill_manager.py +310 -0
  283. claude_mpm/storage/state_storage.py +15 -15
  284. claude_mpm/tools/code_tree_analyzer.py +177 -141
  285. claude_mpm/tools/code_tree_events.py +4 -2
  286. claude_mpm/utils/agent_dependency_loader.py +40 -20
  287. claude_mpm/utils/display_helper.py +260 -0
  288. claude_mpm/utils/git_analyzer.py +407 -0
  289. claude_mpm/utils/robust_installer.py +73 -19
  290. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/METADATA +129 -12
  291. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/RECORD +295 -193
  292. claude_mpm/dashboard/static/css/code-tree.css +0 -1639
  293. claude_mpm/dashboard/static/index-hub-backup.html +0 -713
  294. claude_mpm/dashboard/static/js/components/code-tree/tree-breadcrumb.js +0 -353
  295. claude_mpm/dashboard/static/js/components/code-tree/tree-constants.js +0 -235
  296. claude_mpm/dashboard/static/js/components/code-tree/tree-search.js +0 -409
  297. claude_mpm/dashboard/static/js/components/code-tree/tree-utils.js +0 -435
  298. claude_mpm/dashboard/static/js/components/code-tree.js +0 -5869
  299. claude_mpm/dashboard/static/js/components/code-viewer.js +0 -1386
  300. claude_mpm/hooks/claude_hooks/hook_handler_eventbus.py +0 -425
  301. claude_mpm/hooks/claude_hooks/hook_handler_original.py +0 -1041
  302. claude_mpm/hooks/claude_hooks/hook_handler_refactored.py +0 -347
  303. claude_mpm/services/agents/deployment/agent_lifecycle_manager_refactored.py +0 -575
  304. claude_mpm/services/project/analyzer_refactored.py +0 -450
  305. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/WHEEL +0 -0
  306. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/entry_points.txt +0 -0
  307. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/licenses/LICENSE +0 -0
  308. {claude_mpm-4.7.4.dist-info → claude_mpm-4.18.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,453 @@
1
+ """
2
+ Model Router Implementation for Claude MPM Framework
3
+ ====================================================
4
+
5
+ WHY: Provides intelligent routing between local (Ollama) and cloud (Claude)
6
+ models with automatic fallback, enabling hybrid operation that balances
7
+ privacy, cost, and reliability.
8
+
9
+ DESIGN DECISION: Chain of Responsibility pattern - tries providers in priority
10
+ order until one succeeds. Configuration controls routing strategy (auto, local-only,
11
+ cloud-only, privacy-first).
12
+
13
+ ROUTING STRATEGIES:
14
+ - AUTO: Try Ollama first, fallback to Claude on error (default)
15
+ - OLLAMA: Local-only, fail if unavailable (privacy mode)
16
+ - CLAUDE: Cloud-only, always use Claude
17
+ - PRIVACY: Like OLLAMA but with better error messages
18
+
19
+ ARCHITECTURE:
20
+ - Manages provider lifecycle (initialization, shutdown)
21
+ - Routes requests based on strategy and availability
22
+ - Tracks routing decisions and fallbacks for monitoring
23
+ - Provides unified interface hiding provider complexity
24
+ """
25
+
26
+ from enum import Enum
27
+ from typing import Any, Dict, Optional
28
+
29
+ from claude_mpm.core.logger import get_logger
30
+ from claude_mpm.services.core.base import BaseService
31
+ from claude_mpm.services.core.interfaces.model import (
32
+ IModelRouter,
33
+ ModelCapability,
34
+ ModelResponse,
35
+ )
36
+ from claude_mpm.services.model.claude_provider import ClaudeProvider
37
+ from claude_mpm.services.model.ollama_provider import OllamaProvider
38
+
39
+
40
+ class RoutingStrategy(Enum):
41
+ """
42
+ Routing strategies for model selection.
43
+
44
+ WHY: Provides different operational modes based on user preferences
45
+ for privacy, cost, and reliability.
46
+ """
47
+
48
+ AUTO = "auto" # Try Ollama first, fallback to Claude
49
+ OLLAMA_ONLY = "ollama" # Local only, fail if unavailable
50
+ CLAUDE_ONLY = "claude" # Cloud only, always use Claude
51
+ PRIVACY_FIRST = "privacy" # Like OLLAMA_ONLY but explicit about privacy
52
+
53
+
54
+ class ModelRouter(BaseService, IModelRouter):
55
+ """
56
+ Intelligent model router with automatic fallback.
57
+
58
+ WHY: Provides seamless switching between local and cloud models based on
59
+ availability and configuration. Enables privacy-preserving operation with
60
+ cloud fallback when needed.
61
+
62
+ Configuration:
63
+ strategy: Routing strategy (auto/ollama/claude/privacy)
64
+ ollama_config: Configuration for Ollama provider
65
+ claude_config: Configuration for Claude provider
66
+ fallback_enabled: Allow fallback to cloud (default: True for AUTO)
67
+ max_retries: Maximum retry attempts per provider (default: 2)
68
+
69
+ Usage:
70
+ router = ModelRouter(config={
71
+ "strategy": "auto",
72
+ "ollama_config": {"host": "http://localhost:11434"}
73
+ })
74
+
75
+ await router.initialize()
76
+
77
+ response = await router.analyze_content(
78
+ content="Your content",
79
+ task=ModelCapability.SEO_ANALYSIS
80
+ )
81
+
82
+ Routing Logic:
83
+ AUTO: Ollama available? → Use Ollama → On error → Try Claude
84
+ OLLAMA_ONLY: Ollama available? → Use Ollama → On error → Fail
85
+ CLAUDE_ONLY: Always use Claude
86
+ PRIVACY_FIRST: Like OLLAMA_ONLY but with privacy-focused messages
87
+ """
88
+
89
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
90
+ """
91
+ Initialize model router.
92
+
93
+ Args:
94
+ config: Router configuration
95
+ """
96
+ super().__init__(service_name="model_router", config=config or {})
97
+
98
+ self.logger = get_logger("model.router")
99
+
100
+ # Parse strategy
101
+ strategy_str = self.get_config("strategy", "auto")
102
+ try:
103
+ self.strategy = RoutingStrategy(strategy_str.lower())
104
+ except ValueError:
105
+ self.log_warning(f"Invalid strategy '{strategy_str}', defaulting to AUTO")
106
+ self.strategy = RoutingStrategy.AUTO
107
+
108
+ # Fallback configuration
109
+ self.fallback_enabled = self.get_config(
110
+ "fallback_enabled",
111
+ self.strategy == RoutingStrategy.AUTO,
112
+ )
113
+ self.max_retries = self.get_config("max_retries", 2)
114
+
115
+ # Initialize providers
116
+ ollama_config = self.get_config("ollama_config", {})
117
+ claude_config = self.get_config("claude_config", {})
118
+
119
+ self.ollama_provider = OllamaProvider(config=ollama_config)
120
+ self.claude_provider = ClaudeProvider(config=claude_config)
121
+
122
+ # Routing metrics
123
+ self._route_count: Dict[str, int] = {"ollama": 0, "claude": 0}
124
+ self._fallback_count = 0
125
+ self._active_provider: Optional[str] = None
126
+
127
+ async def initialize(self) -> bool:
128
+ """
129
+ Initialize router and providers.
130
+
131
+ Returns:
132
+ True if at least one provider initialized successfully
133
+ """
134
+ self.log_info(f"Initializing model router with strategy: {self.strategy.value}")
135
+
136
+ success = False
137
+
138
+ # Initialize providers based on strategy
139
+ if self.strategy in (
140
+ RoutingStrategy.AUTO,
141
+ RoutingStrategy.OLLAMA_ONLY,
142
+ RoutingStrategy.PRIVACY_FIRST,
143
+ ):
144
+ self.log_info("Initializing Ollama provider...")
145
+ if await self.ollama_provider.initialize():
146
+ self.log_info("Ollama provider initialized successfully")
147
+ success = True
148
+ else:
149
+ self.log_warning("Ollama provider initialization failed")
150
+
151
+ if self.strategy in (RoutingStrategy.AUTO, RoutingStrategy.CLAUDE_ONLY):
152
+ self.log_info("Initializing Claude provider...")
153
+ if await self.claude_provider.initialize():
154
+ self.log_info("Claude provider initialized successfully")
155
+ success = True
156
+ else:
157
+ self.log_warning("Claude provider initialization failed")
158
+
159
+ if not success:
160
+ self.log_error("No providers initialized successfully")
161
+ return False
162
+
163
+ self._initialized = True
164
+ return True
165
+
166
+ async def shutdown(self) -> None:
167
+ """Shutdown router and all providers."""
168
+ self.log_info("Shutting down model router")
169
+
170
+ # Shutdown providers
171
+ if self.ollama_provider:
172
+ await self.ollama_provider.shutdown()
173
+
174
+ if self.claude_provider:
175
+ await self.claude_provider.shutdown()
176
+
177
+ self._shutdown = True
178
+
179
+ def get_active_provider(self) -> Optional[str]:
180
+ """
181
+ Get name of currently active provider.
182
+
183
+ Returns:
184
+ Provider name or None
185
+ """
186
+ return self._active_provider
187
+
188
+ async def get_provider_status(self) -> Dict[str, Dict[str, Any]]:
189
+ """
190
+ Get status of all configured providers.
191
+
192
+ Returns:
193
+ Dictionary mapping provider names to status info
194
+ """
195
+ status = {}
196
+
197
+ # Check Ollama
198
+ if self.strategy in (
199
+ RoutingStrategy.AUTO,
200
+ RoutingStrategy.OLLAMA_ONLY,
201
+ RoutingStrategy.PRIVACY_FIRST,
202
+ ):
203
+ ollama_available = await self.ollama_provider.is_available()
204
+ ollama_models = (
205
+ await self.ollama_provider.get_available_models()
206
+ if ollama_available
207
+ else []
208
+ )
209
+
210
+ status["ollama"] = {
211
+ "available": ollama_available,
212
+ "initialized": self.ollama_provider.is_initialized,
213
+ "models_count": len(ollama_models),
214
+ "metrics": self.ollama_provider.get_metrics(),
215
+ }
216
+
217
+ # Check Claude
218
+ if self.strategy in (RoutingStrategy.AUTO, RoutingStrategy.CLAUDE_ONLY):
219
+ claude_available = await self.claude_provider.is_available()
220
+
221
+ status["claude"] = {
222
+ "available": claude_available,
223
+ "initialized": self.claude_provider.is_initialized,
224
+ "metrics": self.claude_provider.get_metrics(),
225
+ }
226
+
227
+ # Add routing metrics
228
+ status["router"] = {
229
+ "strategy": self.strategy.value,
230
+ "fallback_enabled": self.fallback_enabled,
231
+ "route_count": self._route_count.copy(),
232
+ "fallback_count": self._fallback_count,
233
+ "active_provider": self._active_provider,
234
+ }
235
+
236
+ return status
237
+
238
+ async def analyze_content(
239
+ self,
240
+ content: str,
241
+ task: ModelCapability,
242
+ model: Optional[str] = None,
243
+ **kwargs,
244
+ ) -> ModelResponse:
245
+ """
246
+ Route content analysis to optimal provider.
247
+
248
+ WHY: Single entry point for all content analysis. Handles provider
249
+ selection, fallback, and error recovery transparently.
250
+
251
+ Args:
252
+ content: Text content to analyze
253
+ task: Type of analysis
254
+ model: Optional specific model
255
+ **kwargs: Provider-specific options
256
+
257
+ Returns:
258
+ ModelResponse from successful provider
259
+ """
260
+ if not self._initialized:
261
+ return self._create_error_response(
262
+ task,
263
+ model,
264
+ "Router not initialized",
265
+ )
266
+
267
+ # Route based on strategy
268
+ if self.strategy == RoutingStrategy.CLAUDE_ONLY:
269
+ return await self._route_to_claude(content, task, model, **kwargs)
270
+
271
+ if self.strategy in (
272
+ RoutingStrategy.OLLAMA_ONLY,
273
+ RoutingStrategy.PRIVACY_FIRST,
274
+ ):
275
+ return await self._route_to_ollama(
276
+ content,
277
+ task,
278
+ model,
279
+ require_success=True,
280
+ **kwargs,
281
+ )
282
+
283
+ # AUTO strategy
284
+ return await self._route_auto(content, task, model, **kwargs)
285
+
286
+ async def _route_auto(
287
+ self,
288
+ content: str,
289
+ task: ModelCapability,
290
+ model: Optional[str],
291
+ **kwargs,
292
+ ) -> ModelResponse:
293
+ """
294
+ Auto routing: Try Ollama first, fallback to Claude.
295
+
296
+ Args:
297
+ content: Content to analyze
298
+ task: Task to perform
299
+ model: Optional model
300
+ **kwargs: Additional options
301
+
302
+ Returns:
303
+ ModelResponse from successful provider
304
+ """
305
+ # Try Ollama first
306
+ if await self.ollama_provider.is_available():
307
+ self.log_debug("Routing to Ollama (primary)")
308
+ response = await self._route_to_ollama(
309
+ content,
310
+ task,
311
+ model,
312
+ require_success=False,
313
+ **kwargs,
314
+ )
315
+
316
+ if response.success:
317
+ return response
318
+
319
+ # Ollama failed, try fallback
320
+ self.log_warning(
321
+ f"Ollama analysis failed: {response.error}, trying Claude fallback"
322
+ )
323
+ self._fallback_count += 1
324
+
325
+ # Ollama unavailable or failed - fallback to Claude
326
+ if self.fallback_enabled:
327
+ self.log_info("Falling back to Claude")
328
+ return await self._route_to_claude(content, task, model, **kwargs)
329
+ return self._create_error_response(
330
+ task,
331
+ model,
332
+ "Ollama unavailable and fallback disabled",
333
+ )
334
+
335
+ async def _route_to_ollama(
336
+ self,
337
+ content: str,
338
+ task: ModelCapability,
339
+ model: Optional[str],
340
+ require_success: bool = False,
341
+ **kwargs,
342
+ ) -> ModelResponse:
343
+ """
344
+ Route to Ollama provider.
345
+
346
+ Args:
347
+ content: Content to analyze
348
+ task: Task to perform
349
+ model: Optional model
350
+ require_success: If True, check availability first
351
+ **kwargs: Additional options
352
+
353
+ Returns:
354
+ ModelResponse
355
+ """
356
+ if require_success and not await self.ollama_provider.is_available():
357
+ if self.strategy == RoutingStrategy.PRIVACY_FIRST:
358
+ error_msg = (
359
+ "Ollama not available. Privacy mode enabled - "
360
+ "not sending to cloud."
361
+ )
362
+ else:
363
+ error_msg = "Ollama not available and required by configuration"
364
+
365
+ return self._create_error_response(task, model, error_msg)
366
+
367
+ self._active_provider = "ollama"
368
+ self._route_count["ollama"] += 1
369
+
370
+ return await self.ollama_provider.analyze_content(
371
+ content, task, model, **kwargs
372
+ )
373
+
374
+ async def _route_to_claude(
375
+ self,
376
+ content: str,
377
+ task: ModelCapability,
378
+ model: Optional[str],
379
+ **kwargs,
380
+ ) -> ModelResponse:
381
+ """
382
+ Route to Claude provider.
383
+
384
+ Args:
385
+ content: Content to analyze
386
+ task: Task to perform
387
+ model: Optional model
388
+ **kwargs: Additional options
389
+
390
+ Returns:
391
+ ModelResponse
392
+ """
393
+ self._active_provider = "claude"
394
+ self._route_count["claude"] += 1
395
+
396
+ return await self.claude_provider.analyze_content(
397
+ content, task, model, **kwargs
398
+ )
399
+
400
+ def _create_error_response(
401
+ self,
402
+ task: ModelCapability,
403
+ model: Optional[str],
404
+ error: str,
405
+ ) -> ModelResponse:
406
+ """
407
+ Create error response.
408
+
409
+ Args:
410
+ task: Task that was attempted
411
+ model: Model that was requested
412
+ error: Error message
413
+
414
+ Returns:
415
+ ModelResponse with error
416
+ """
417
+ return ModelResponse(
418
+ success=False,
419
+ provider="router",
420
+ model=model or "unknown",
421
+ task=task.value,
422
+ result="",
423
+ error=error,
424
+ )
425
+
426
+ def get_routing_metrics(self) -> Dict[str, Any]:
427
+ """
428
+ Get routing performance metrics.
429
+
430
+ Returns:
431
+ Dictionary of routing metrics
432
+ """
433
+ total_routes = sum(self._route_count.values())
434
+ ollama_percentage = (
435
+ (self._route_count["ollama"] / total_routes * 100)
436
+ if total_routes > 0
437
+ else 0
438
+ )
439
+
440
+ return {
441
+ "strategy": self.strategy.value,
442
+ "total_routes": total_routes,
443
+ "ollama_routes": self._route_count["ollama"],
444
+ "claude_routes": self._route_count["claude"],
445
+ "ollama_percentage": ollama_percentage,
446
+ "fallback_count": self._fallback_count,
447
+ "fallback_rate": (
448
+ (self._fallback_count / total_routes * 100) if total_routes > 0 else 0
449
+ ),
450
+ }
451
+
452
+
453
+ __all__ = ["ModelRouter", "RoutingStrategy"]