attune-ai 2.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.
- attune/__init__.py +358 -0
- attune/adaptive/__init__.py +13 -0
- attune/adaptive/task_complexity.py +127 -0
- attune/agent_monitoring.py +414 -0
- attune/cache/__init__.py +117 -0
- attune/cache/base.py +166 -0
- attune/cache/dependency_manager.py +256 -0
- attune/cache/hash_only.py +251 -0
- attune/cache/hybrid.py +457 -0
- attune/cache/storage.py +285 -0
- attune/cache_monitor.py +356 -0
- attune/cache_stats.py +298 -0
- attune/cli/__init__.py +152 -0
- attune/cli/__main__.py +12 -0
- attune/cli/commands/__init__.py +1 -0
- attune/cli/commands/batch.py +264 -0
- attune/cli/commands/cache.py +248 -0
- attune/cli/commands/help.py +331 -0
- attune/cli/commands/info.py +140 -0
- attune/cli/commands/inspect.py +436 -0
- attune/cli/commands/inspection.py +57 -0
- attune/cli/commands/memory.py +48 -0
- attune/cli/commands/metrics.py +92 -0
- attune/cli/commands/orchestrate.py +184 -0
- attune/cli/commands/patterns.py +207 -0
- attune/cli/commands/profiling.py +202 -0
- attune/cli/commands/provider.py +98 -0
- attune/cli/commands/routing.py +285 -0
- attune/cli/commands/setup.py +96 -0
- attune/cli/commands/status.py +235 -0
- attune/cli/commands/sync.py +166 -0
- attune/cli/commands/tier.py +121 -0
- attune/cli/commands/utilities.py +114 -0
- attune/cli/commands/workflow.py +579 -0
- attune/cli/core.py +32 -0
- attune/cli/parsers/__init__.py +68 -0
- attune/cli/parsers/batch.py +118 -0
- attune/cli/parsers/cache.py +65 -0
- attune/cli/parsers/help.py +41 -0
- attune/cli/parsers/info.py +26 -0
- attune/cli/parsers/inspect.py +66 -0
- attune/cli/parsers/metrics.py +42 -0
- attune/cli/parsers/orchestrate.py +61 -0
- attune/cli/parsers/patterns.py +54 -0
- attune/cli/parsers/provider.py +40 -0
- attune/cli/parsers/routing.py +110 -0
- attune/cli/parsers/setup.py +42 -0
- attune/cli/parsers/status.py +47 -0
- attune/cli/parsers/sync.py +31 -0
- attune/cli/parsers/tier.py +33 -0
- attune/cli/parsers/workflow.py +77 -0
- attune/cli/utils/__init__.py +1 -0
- attune/cli/utils/data.py +242 -0
- attune/cli/utils/helpers.py +68 -0
- attune/cli_legacy.py +3957 -0
- attune/cli_minimal.py +1159 -0
- attune/cli_router.py +437 -0
- attune/cli_unified.py +814 -0
- attune/config/__init__.py +66 -0
- attune/config/xml_config.py +286 -0
- attune/config.py +545 -0
- attune/coordination.py +870 -0
- attune/core.py +1511 -0
- attune/core_modules/__init__.py +15 -0
- attune/cost_tracker.py +626 -0
- attune/dashboard/__init__.py +41 -0
- attune/dashboard/app.py +512 -0
- attune/dashboard/simple_server.py +435 -0
- attune/dashboard/standalone_server.py +547 -0
- attune/discovery.py +306 -0
- attune/emergence.py +306 -0
- attune/exceptions.py +123 -0
- attune/feedback_loops.py +373 -0
- attune/hot_reload/README.md +473 -0
- attune/hot_reload/__init__.py +62 -0
- attune/hot_reload/config.py +83 -0
- attune/hot_reload/integration.py +229 -0
- attune/hot_reload/reloader.py +298 -0
- attune/hot_reload/watcher.py +183 -0
- attune/hot_reload/websocket.py +177 -0
- attune/levels.py +577 -0
- attune/leverage_points.py +441 -0
- attune/logging_config.py +261 -0
- attune/mcp/__init__.py +10 -0
- attune/mcp/server.py +506 -0
- attune/memory/__init__.py +237 -0
- attune/memory/claude_memory.py +469 -0
- attune/memory/config.py +224 -0
- attune/memory/control_panel.py +1290 -0
- attune/memory/control_panel_support.py +145 -0
- attune/memory/cross_session.py +845 -0
- attune/memory/edges.py +179 -0
- attune/memory/encryption.py +159 -0
- attune/memory/file_session.py +770 -0
- attune/memory/graph.py +570 -0
- attune/memory/long_term.py +913 -0
- attune/memory/long_term_types.py +99 -0
- attune/memory/mixins/__init__.py +25 -0
- attune/memory/mixins/backend_init_mixin.py +249 -0
- attune/memory/mixins/capabilities_mixin.py +208 -0
- attune/memory/mixins/handoff_mixin.py +208 -0
- attune/memory/mixins/lifecycle_mixin.py +49 -0
- attune/memory/mixins/long_term_mixin.py +352 -0
- attune/memory/mixins/promotion_mixin.py +109 -0
- attune/memory/mixins/short_term_mixin.py +182 -0
- attune/memory/nodes.py +179 -0
- attune/memory/redis_bootstrap.py +540 -0
- attune/memory/security/__init__.py +31 -0
- attune/memory/security/audit_logger.py +932 -0
- attune/memory/security/pii_scrubber.py +640 -0
- attune/memory/security/secrets_detector.py +678 -0
- attune/memory/short_term.py +2192 -0
- attune/memory/simple_storage.py +302 -0
- attune/memory/storage/__init__.py +15 -0
- attune/memory/storage_backend.py +167 -0
- attune/memory/summary_index.py +583 -0
- attune/memory/types.py +446 -0
- attune/memory/unified.py +182 -0
- attune/meta_workflows/__init__.py +74 -0
- attune/meta_workflows/agent_creator.py +248 -0
- attune/meta_workflows/builtin_templates.py +567 -0
- attune/meta_workflows/cli_commands/__init__.py +56 -0
- attune/meta_workflows/cli_commands/agent_commands.py +321 -0
- attune/meta_workflows/cli_commands/analytics_commands.py +442 -0
- attune/meta_workflows/cli_commands/config_commands.py +232 -0
- attune/meta_workflows/cli_commands/memory_commands.py +182 -0
- attune/meta_workflows/cli_commands/template_commands.py +354 -0
- attune/meta_workflows/cli_commands/workflow_commands.py +382 -0
- attune/meta_workflows/cli_meta_workflows.py +59 -0
- attune/meta_workflows/form_engine.py +292 -0
- attune/meta_workflows/intent_detector.py +409 -0
- attune/meta_workflows/models.py +569 -0
- attune/meta_workflows/pattern_learner.py +738 -0
- attune/meta_workflows/plan_generator.py +384 -0
- attune/meta_workflows/session_context.py +397 -0
- attune/meta_workflows/template_registry.py +229 -0
- attune/meta_workflows/workflow.py +984 -0
- attune/metrics/__init__.py +12 -0
- attune/metrics/collector.py +31 -0
- attune/metrics/prompt_metrics.py +194 -0
- attune/models/__init__.py +172 -0
- attune/models/__main__.py +13 -0
- attune/models/adaptive_routing.py +437 -0
- attune/models/auth_cli.py +444 -0
- attune/models/auth_strategy.py +450 -0
- attune/models/cli.py +655 -0
- attune/models/empathy_executor.py +354 -0
- attune/models/executor.py +257 -0
- attune/models/fallback.py +762 -0
- attune/models/provider_config.py +282 -0
- attune/models/registry.py +472 -0
- attune/models/tasks.py +359 -0
- attune/models/telemetry/__init__.py +71 -0
- attune/models/telemetry/analytics.py +594 -0
- attune/models/telemetry/backend.py +196 -0
- attune/models/telemetry/data_models.py +431 -0
- attune/models/telemetry/storage.py +489 -0
- attune/models/token_estimator.py +420 -0
- attune/models/validation.py +280 -0
- attune/monitoring/__init__.py +52 -0
- attune/monitoring/alerts.py +946 -0
- attune/monitoring/alerts_cli.py +448 -0
- attune/monitoring/multi_backend.py +271 -0
- attune/monitoring/otel_backend.py +362 -0
- attune/optimization/__init__.py +19 -0
- attune/optimization/context_optimizer.py +272 -0
- attune/orchestration/__init__.py +67 -0
- attune/orchestration/agent_templates.py +707 -0
- attune/orchestration/config_store.py +499 -0
- attune/orchestration/execution_strategies.py +2111 -0
- attune/orchestration/meta_orchestrator.py +1168 -0
- attune/orchestration/pattern_learner.py +696 -0
- attune/orchestration/real_tools.py +931 -0
- attune/pattern_cache.py +187 -0
- attune/pattern_library.py +542 -0
- attune/patterns/debugging/all_patterns.json +81 -0
- attune/patterns/debugging/workflow_20260107_1770825e.json +77 -0
- attune/patterns/refactoring_memory.json +89 -0
- attune/persistence.py +564 -0
- attune/platform_utils.py +265 -0
- attune/plugins/__init__.py +28 -0
- attune/plugins/base.py +361 -0
- attune/plugins/registry.py +268 -0
- attune/project_index/__init__.py +32 -0
- attune/project_index/cli.py +335 -0
- attune/project_index/index.py +667 -0
- attune/project_index/models.py +504 -0
- attune/project_index/reports.py +474 -0
- attune/project_index/scanner.py +777 -0
- attune/project_index/scanner_parallel.py +291 -0
- attune/prompts/__init__.py +61 -0
- attune/prompts/config.py +77 -0
- attune/prompts/context.py +177 -0
- attune/prompts/parser.py +285 -0
- attune/prompts/registry.py +313 -0
- attune/prompts/templates.py +208 -0
- attune/redis_config.py +302 -0
- attune/redis_memory.py +799 -0
- attune/resilience/__init__.py +56 -0
- attune/resilience/circuit_breaker.py +256 -0
- attune/resilience/fallback.py +179 -0
- attune/resilience/health.py +300 -0
- attune/resilience/retry.py +209 -0
- attune/resilience/timeout.py +135 -0
- attune/routing/__init__.py +43 -0
- attune/routing/chain_executor.py +433 -0
- attune/routing/classifier.py +217 -0
- attune/routing/smart_router.py +234 -0
- attune/routing/workflow_registry.py +343 -0
- attune/scaffolding/README.md +589 -0
- attune/scaffolding/__init__.py +35 -0
- attune/scaffolding/__main__.py +14 -0
- attune/scaffolding/cli.py +240 -0
- attune/scaffolding/templates/base_wizard.py.jinja2 +121 -0
- attune/scaffolding/templates/coach_wizard.py.jinja2 +321 -0
- attune/scaffolding/templates/domain_wizard.py.jinja2 +408 -0
- attune/scaffolding/templates/linear_flow_wizard.py.jinja2 +203 -0
- attune/socratic/__init__.py +256 -0
- attune/socratic/ab_testing.py +958 -0
- attune/socratic/blueprint.py +533 -0
- attune/socratic/cli.py +703 -0
- attune/socratic/collaboration.py +1114 -0
- attune/socratic/domain_templates.py +924 -0
- attune/socratic/embeddings.py +738 -0
- attune/socratic/engine.py +794 -0
- attune/socratic/explainer.py +682 -0
- attune/socratic/feedback.py +772 -0
- attune/socratic/forms.py +629 -0
- attune/socratic/generator.py +732 -0
- attune/socratic/llm_analyzer.py +637 -0
- attune/socratic/mcp_server.py +702 -0
- attune/socratic/session.py +312 -0
- attune/socratic/storage.py +667 -0
- attune/socratic/success.py +730 -0
- attune/socratic/visual_editor.py +860 -0
- attune/socratic/web_ui.py +958 -0
- attune/telemetry/__init__.py +39 -0
- attune/telemetry/agent_coordination.py +475 -0
- attune/telemetry/agent_tracking.py +367 -0
- attune/telemetry/approval_gates.py +545 -0
- attune/telemetry/cli.py +1231 -0
- attune/telemetry/commands/__init__.py +14 -0
- attune/telemetry/commands/dashboard_commands.py +696 -0
- attune/telemetry/event_streaming.py +409 -0
- attune/telemetry/feedback_loop.py +567 -0
- attune/telemetry/usage_tracker.py +591 -0
- attune/templates.py +754 -0
- attune/test_generator/__init__.py +38 -0
- attune/test_generator/__main__.py +14 -0
- attune/test_generator/cli.py +234 -0
- attune/test_generator/generator.py +355 -0
- attune/test_generator/risk_analyzer.py +216 -0
- attune/test_generator/templates/unit_test.py.jinja2 +272 -0
- attune/tier_recommender.py +384 -0
- attune/tools.py +183 -0
- attune/trust/__init__.py +28 -0
- attune/trust/circuit_breaker.py +579 -0
- attune/trust_building.py +527 -0
- attune/validation/__init__.py +19 -0
- attune/validation/xml_validator.py +281 -0
- attune/vscode_bridge.py +173 -0
- attune/workflow_commands.py +780 -0
- attune/workflow_patterns/__init__.py +33 -0
- attune/workflow_patterns/behavior.py +249 -0
- attune/workflow_patterns/core.py +76 -0
- attune/workflow_patterns/output.py +99 -0
- attune/workflow_patterns/registry.py +255 -0
- attune/workflow_patterns/structural.py +288 -0
- attune/workflows/__init__.py +539 -0
- attune/workflows/autonomous_test_gen.py +1268 -0
- attune/workflows/base.py +2667 -0
- attune/workflows/batch_processing.py +342 -0
- attune/workflows/bug_predict.py +1084 -0
- attune/workflows/builder.py +273 -0
- attune/workflows/caching.py +253 -0
- attune/workflows/code_review.py +1048 -0
- attune/workflows/code_review_adapters.py +312 -0
- attune/workflows/code_review_pipeline.py +722 -0
- attune/workflows/config.py +645 -0
- attune/workflows/dependency_check.py +644 -0
- attune/workflows/document_gen/__init__.py +25 -0
- attune/workflows/document_gen/config.py +30 -0
- attune/workflows/document_gen/report_formatter.py +162 -0
- attune/workflows/document_gen/workflow.py +1426 -0
- attune/workflows/document_manager.py +216 -0
- attune/workflows/document_manager_README.md +134 -0
- attune/workflows/documentation_orchestrator.py +1205 -0
- attune/workflows/history.py +510 -0
- attune/workflows/keyboard_shortcuts/__init__.py +39 -0
- attune/workflows/keyboard_shortcuts/generators.py +391 -0
- attune/workflows/keyboard_shortcuts/parsers.py +416 -0
- attune/workflows/keyboard_shortcuts/prompts.py +295 -0
- attune/workflows/keyboard_shortcuts/schema.py +193 -0
- attune/workflows/keyboard_shortcuts/workflow.py +509 -0
- attune/workflows/llm_base.py +363 -0
- attune/workflows/manage_docs.py +87 -0
- attune/workflows/manage_docs_README.md +134 -0
- attune/workflows/manage_documentation.py +821 -0
- attune/workflows/new_sample_workflow1.py +149 -0
- attune/workflows/new_sample_workflow1_README.md +150 -0
- attune/workflows/orchestrated_health_check.py +849 -0
- attune/workflows/orchestrated_release_prep.py +600 -0
- attune/workflows/output.py +413 -0
- attune/workflows/perf_audit.py +863 -0
- attune/workflows/pr_review.py +762 -0
- attune/workflows/progress.py +785 -0
- attune/workflows/progress_server.py +322 -0
- attune/workflows/progressive/README 2.md +454 -0
- attune/workflows/progressive/README.md +454 -0
- attune/workflows/progressive/__init__.py +82 -0
- attune/workflows/progressive/cli.py +219 -0
- attune/workflows/progressive/core.py +488 -0
- attune/workflows/progressive/orchestrator.py +723 -0
- attune/workflows/progressive/reports.py +520 -0
- attune/workflows/progressive/telemetry.py +274 -0
- attune/workflows/progressive/test_gen.py +495 -0
- attune/workflows/progressive/workflow.py +589 -0
- attune/workflows/refactor_plan.py +694 -0
- attune/workflows/release_prep.py +895 -0
- attune/workflows/release_prep_crew.py +969 -0
- attune/workflows/research_synthesis.py +404 -0
- attune/workflows/routing.py +168 -0
- attune/workflows/secure_release.py +593 -0
- attune/workflows/security_adapters.py +297 -0
- attune/workflows/security_audit.py +1329 -0
- attune/workflows/security_audit_phase3.py +355 -0
- attune/workflows/seo_optimization.py +633 -0
- attune/workflows/step_config.py +234 -0
- attune/workflows/telemetry_mixin.py +269 -0
- attune/workflows/test5.py +125 -0
- attune/workflows/test5_README.md +158 -0
- attune/workflows/test_coverage_boost_crew.py +849 -0
- attune/workflows/test_gen/__init__.py +52 -0
- attune/workflows/test_gen/ast_analyzer.py +249 -0
- attune/workflows/test_gen/config.py +88 -0
- attune/workflows/test_gen/data_models.py +38 -0
- attune/workflows/test_gen/report_formatter.py +289 -0
- attune/workflows/test_gen/test_templates.py +381 -0
- attune/workflows/test_gen/workflow.py +655 -0
- attune/workflows/test_gen.py +54 -0
- attune/workflows/test_gen_behavioral.py +477 -0
- attune/workflows/test_gen_parallel.py +341 -0
- attune/workflows/test_lifecycle.py +526 -0
- attune/workflows/test_maintenance.py +627 -0
- attune/workflows/test_maintenance_cli.py +590 -0
- attune/workflows/test_maintenance_crew.py +840 -0
- attune/workflows/test_runner.py +622 -0
- attune/workflows/tier_tracking.py +531 -0
- attune/workflows/xml_enhanced_crew.py +285 -0
- attune_ai-2.0.0.dist-info/METADATA +1026 -0
- attune_ai-2.0.0.dist-info/RECORD +457 -0
- attune_ai-2.0.0.dist-info/WHEEL +5 -0
- attune_ai-2.0.0.dist-info/entry_points.txt +26 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE +201 -0
- attune_ai-2.0.0.dist-info/licenses/LICENSE_CHANGE_ANNOUNCEMENT.md +101 -0
- attune_ai-2.0.0.dist-info/top_level.txt +5 -0
- attune_healthcare/__init__.py +13 -0
- attune_healthcare/monitors/__init__.py +9 -0
- attune_healthcare/monitors/clinical_protocol_monitor.py +315 -0
- attune_healthcare/monitors/monitoring/__init__.py +44 -0
- attune_healthcare/monitors/monitoring/protocol_checker.py +300 -0
- attune_healthcare/monitors/monitoring/protocol_loader.py +214 -0
- attune_healthcare/monitors/monitoring/sensor_parsers.py +306 -0
- attune_healthcare/monitors/monitoring/trajectory_analyzer.py +389 -0
- attune_llm/README.md +553 -0
- attune_llm/__init__.py +28 -0
- attune_llm/agent_factory/__init__.py +53 -0
- attune_llm/agent_factory/adapters/__init__.py +85 -0
- attune_llm/agent_factory/adapters/autogen_adapter.py +312 -0
- attune_llm/agent_factory/adapters/crewai_adapter.py +483 -0
- attune_llm/agent_factory/adapters/haystack_adapter.py +298 -0
- attune_llm/agent_factory/adapters/langchain_adapter.py +362 -0
- attune_llm/agent_factory/adapters/langgraph_adapter.py +333 -0
- attune_llm/agent_factory/adapters/native.py +228 -0
- attune_llm/agent_factory/adapters/wizard_adapter.py +423 -0
- attune_llm/agent_factory/base.py +305 -0
- attune_llm/agent_factory/crews/__init__.py +67 -0
- attune_llm/agent_factory/crews/code_review.py +1113 -0
- attune_llm/agent_factory/crews/health_check.py +1262 -0
- attune_llm/agent_factory/crews/refactoring.py +1128 -0
- attune_llm/agent_factory/crews/security_audit.py +1018 -0
- attune_llm/agent_factory/decorators.py +287 -0
- attune_llm/agent_factory/factory.py +558 -0
- attune_llm/agent_factory/framework.py +193 -0
- attune_llm/agent_factory/memory_integration.py +328 -0
- attune_llm/agent_factory/resilient.py +320 -0
- attune_llm/agents_md/__init__.py +22 -0
- attune_llm/agents_md/loader.py +218 -0
- attune_llm/agents_md/parser.py +271 -0
- attune_llm/agents_md/registry.py +307 -0
- attune_llm/claude_memory.py +466 -0
- attune_llm/cli/__init__.py +8 -0
- attune_llm/cli/sync_claude.py +487 -0
- attune_llm/code_health.py +1313 -0
- attune_llm/commands/__init__.py +51 -0
- attune_llm/commands/context.py +375 -0
- attune_llm/commands/loader.py +301 -0
- attune_llm/commands/models.py +231 -0
- attune_llm/commands/parser.py +371 -0
- attune_llm/commands/registry.py +429 -0
- attune_llm/config/__init__.py +29 -0
- attune_llm/config/unified.py +291 -0
- attune_llm/context/__init__.py +22 -0
- attune_llm/context/compaction.py +455 -0
- attune_llm/context/manager.py +434 -0
- attune_llm/contextual_patterns.py +361 -0
- attune_llm/core.py +907 -0
- attune_llm/git_pattern_extractor.py +435 -0
- attune_llm/hooks/__init__.py +24 -0
- attune_llm/hooks/config.py +306 -0
- attune_llm/hooks/executor.py +289 -0
- attune_llm/hooks/registry.py +302 -0
- attune_llm/hooks/scripts/__init__.py +39 -0
- attune_llm/hooks/scripts/evaluate_session.py +201 -0
- attune_llm/hooks/scripts/first_time_init.py +285 -0
- attune_llm/hooks/scripts/pre_compact.py +207 -0
- attune_llm/hooks/scripts/session_end.py +183 -0
- attune_llm/hooks/scripts/session_start.py +163 -0
- attune_llm/hooks/scripts/suggest_compact.py +225 -0
- attune_llm/learning/__init__.py +30 -0
- attune_llm/learning/evaluator.py +438 -0
- attune_llm/learning/extractor.py +514 -0
- attune_llm/learning/storage.py +560 -0
- attune_llm/levels.py +227 -0
- attune_llm/pattern_confidence.py +414 -0
- attune_llm/pattern_resolver.py +272 -0
- attune_llm/pattern_summary.py +350 -0
- attune_llm/providers.py +967 -0
- attune_llm/routing/__init__.py +32 -0
- attune_llm/routing/model_router.py +362 -0
- attune_llm/security/IMPLEMENTATION_SUMMARY.md +413 -0
- attune_llm/security/PHASE2_COMPLETE.md +384 -0
- attune_llm/security/PHASE2_SECRETS_DETECTOR_COMPLETE.md +271 -0
- attune_llm/security/QUICK_REFERENCE.md +316 -0
- attune_llm/security/README.md +262 -0
- attune_llm/security/__init__.py +62 -0
- attune_llm/security/audit_logger.py +929 -0
- attune_llm/security/audit_logger_example.py +152 -0
- attune_llm/security/pii_scrubber.py +640 -0
- attune_llm/security/secrets_detector.py +678 -0
- attune_llm/security/secrets_detector_example.py +304 -0
- attune_llm/security/secure_memdocs.py +1192 -0
- attune_llm/security/secure_memdocs_example.py +278 -0
- attune_llm/session_status.py +745 -0
- attune_llm/state.py +246 -0
- attune_llm/utils/__init__.py +5 -0
- attune_llm/utils/tokens.py +349 -0
- attune_software/SOFTWARE_PLUGIN_README.md +57 -0
- attune_software/__init__.py +13 -0
- attune_software/cli/__init__.py +120 -0
- attune_software/cli/inspect.py +362 -0
- attune_software/cli.py +574 -0
- attune_software/plugin.py +188 -0
- workflow_scaffolding/__init__.py +11 -0
- workflow_scaffolding/__main__.py +12 -0
- workflow_scaffolding/cli.py +206 -0
- workflow_scaffolding/generator.py +265 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"""Adaptive Model Routing based on historical telemetry.
|
|
2
|
+
|
|
3
|
+
This module implements Pattern 3 from AGENT_COORDINATION_ARCHITECTURE.md:
|
|
4
|
+
Using telemetry history to learn which models work best for each workflow/stage.
|
|
5
|
+
|
|
6
|
+
Key Features:
|
|
7
|
+
- Analyzes historical performance per model/workflow/stage
|
|
8
|
+
- Recommends best model based on success rate and cost
|
|
9
|
+
- Auto-detects when tier upgrades are needed (>20% failure rate)
|
|
10
|
+
- Respects cost and latency constraints
|
|
11
|
+
|
|
12
|
+
Copyright 2025 Smart-AI-Memory
|
|
13
|
+
Licensed under Fair Source License 0.9
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
# Lazy import to avoid circular dependency
|
|
25
|
+
_model_registry = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _get_registry():
|
|
29
|
+
"""Get ModelRegistry instance (lazy load to avoid circular import)."""
|
|
30
|
+
global _model_registry
|
|
31
|
+
if _model_registry is None:
|
|
32
|
+
from .registry import MODEL_REGISTRY
|
|
33
|
+
_model_registry = MODEL_REGISTRY
|
|
34
|
+
return _model_registry
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class ModelPerformance:
|
|
39
|
+
"""Performance metrics for a model on a specific task.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
model_id: Model identifier (e.g., "claude-sonnet-4.5")
|
|
43
|
+
tier: Model tier (CHEAP, CAPABLE, PREMIUM)
|
|
44
|
+
success_rate: Percentage of successful calls (0.0 - 1.0)
|
|
45
|
+
avg_latency_ms: Average response time in milliseconds
|
|
46
|
+
avg_cost: Average cost per call in USD
|
|
47
|
+
sample_size: Number of calls analyzed
|
|
48
|
+
recent_failures: Number of failures in last 20 calls
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
model_id: str
|
|
52
|
+
tier: str
|
|
53
|
+
success_rate: float
|
|
54
|
+
avg_latency_ms: float
|
|
55
|
+
avg_cost: float
|
|
56
|
+
sample_size: int
|
|
57
|
+
recent_failures: int = 0
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def quality_score(self) -> float:
|
|
61
|
+
"""Calculate quality score for ranking models.
|
|
62
|
+
|
|
63
|
+
Score prioritizes:
|
|
64
|
+
1. Success rate (most important)
|
|
65
|
+
2. Cost (secondary)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Quality score (higher is better)
|
|
69
|
+
"""
|
|
70
|
+
# Success rate contributes 100 points max
|
|
71
|
+
# Lower cost adds bonus points
|
|
72
|
+
return (self.success_rate * 100) - (self.avg_cost * 10)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class AdaptiveModelRouter:
|
|
76
|
+
"""Route tasks to models based on historical telemetry performance.
|
|
77
|
+
|
|
78
|
+
Uses telemetry data to learn which models work best for each workflow/stage
|
|
79
|
+
combination. Automatically recommends tier upgrades when failure rates are high.
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
>>> from attune.telemetry import UsageTracker
|
|
83
|
+
>>> router = AdaptiveModelRouter(UsageTracker.get_instance())
|
|
84
|
+
>>>
|
|
85
|
+
>>> # Get best model for this workflow stage
|
|
86
|
+
>>> model = router.get_best_model(
|
|
87
|
+
... workflow="code-review",
|
|
88
|
+
... stage="analysis",
|
|
89
|
+
... max_cost=0.01
|
|
90
|
+
... )
|
|
91
|
+
>>> print(f"Using {model}")
|
|
92
|
+
Using claude-3-5-haiku-20241022
|
|
93
|
+
>>>
|
|
94
|
+
>>> # Check if we should upgrade tier
|
|
95
|
+
>>> should_upgrade, reason = router.recommend_tier_upgrade(
|
|
96
|
+
... workflow="code-review",
|
|
97
|
+
... stage="analysis"
|
|
98
|
+
... )
|
|
99
|
+
>>> if should_upgrade:
|
|
100
|
+
... print(f"⚠️ {reason}")
|
|
101
|
+
⚠️ High failure rate: 25.0% in last 20 calls
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
# Minimum sample size for making routing decisions
|
|
105
|
+
MIN_SAMPLE_SIZE = 10
|
|
106
|
+
|
|
107
|
+
# Failure rate threshold for tier upgrade recommendation
|
|
108
|
+
FAILURE_RATE_THRESHOLD = 0.2 # 20%
|
|
109
|
+
|
|
110
|
+
# Recent window size for failure detection
|
|
111
|
+
RECENT_WINDOW_SIZE = 20
|
|
112
|
+
|
|
113
|
+
def __init__(self, telemetry: Any):
|
|
114
|
+
"""Initialize adaptive router.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
telemetry: UsageTracker instance for telemetry data access
|
|
118
|
+
"""
|
|
119
|
+
self.telemetry = telemetry
|
|
120
|
+
|
|
121
|
+
def _get_default_model(self, tier: str = "CHEAP") -> str:
|
|
122
|
+
"""Get default Anthropic model for a tier from registry.
|
|
123
|
+
|
|
124
|
+
This dynamically fetches the current Anthropic model for each tier,
|
|
125
|
+
so when new models are released (e.g., Claude 5), they're automatically used.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
tier: Tier name (CHEAP, CAPABLE, or PREMIUM)
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Model ID from registry (e.g., "claude-3-5-haiku-20241022")
|
|
132
|
+
"""
|
|
133
|
+
registry = _get_registry()
|
|
134
|
+
|
|
135
|
+
# Get Anthropic model for this tier
|
|
136
|
+
tier_lower = tier.lower()
|
|
137
|
+
if tier_lower in registry.get("anthropic", {}):
|
|
138
|
+
return registry["anthropic"][tier_lower].id
|
|
139
|
+
|
|
140
|
+
# Fallback to known models if registry lookup fails
|
|
141
|
+
fallbacks = {
|
|
142
|
+
"cheap": "claude-3-5-haiku-20241022",
|
|
143
|
+
"capable": "claude-sonnet-4-5",
|
|
144
|
+
"premium": "claude-opus-4-5-20251101",
|
|
145
|
+
}
|
|
146
|
+
return fallbacks.get(tier_lower, "claude-3-5-haiku-20241022")
|
|
147
|
+
|
|
148
|
+
def get_best_model(
|
|
149
|
+
self,
|
|
150
|
+
workflow: str,
|
|
151
|
+
stage: str,
|
|
152
|
+
max_cost: float | None = None,
|
|
153
|
+
max_latency_ms: int | None = None,
|
|
154
|
+
min_success_rate: float = 0.8,
|
|
155
|
+
) -> str:
|
|
156
|
+
"""Get best model for workflow/stage based on historical performance.
|
|
157
|
+
|
|
158
|
+
Analyzes recent telemetry to find the model with the best quality score
|
|
159
|
+
(success rate + cost efficiency) that meets the specified constraints.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
workflow: Workflow name (e.g., "code-review", "bug-predict")
|
|
163
|
+
stage: Stage name (e.g., "analysis", "synthesis")
|
|
164
|
+
max_cost: Maximum acceptable cost per call (USD)
|
|
165
|
+
max_latency_ms: Maximum acceptable latency (milliseconds)
|
|
166
|
+
min_success_rate: Minimum acceptable success rate (0.0 - 1.0)
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Model ID to use (e.g., "claude-3-5-haiku-20241022")
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
>>> model = router.get_best_model(
|
|
173
|
+
... workflow="code-review",
|
|
174
|
+
... stage="analysis",
|
|
175
|
+
... max_cost=0.01,
|
|
176
|
+
... min_success_rate=0.9
|
|
177
|
+
... )
|
|
178
|
+
>>> print(model)
|
|
179
|
+
claude-3-5-haiku-20241022
|
|
180
|
+
"""
|
|
181
|
+
# Get performance data for all models on this workflow/stage
|
|
182
|
+
performances = self._analyze_model_performance(workflow, stage)
|
|
183
|
+
|
|
184
|
+
if not performances:
|
|
185
|
+
# No historical data, use default Anthropic cheap model from registry
|
|
186
|
+
default_model = self._get_default_model("CHEAP")
|
|
187
|
+
logger.info(
|
|
188
|
+
"adaptive_routing_no_history",
|
|
189
|
+
workflow=workflow,
|
|
190
|
+
stage=stage,
|
|
191
|
+
fallback=default_model,
|
|
192
|
+
)
|
|
193
|
+
return default_model
|
|
194
|
+
|
|
195
|
+
# Filter by constraints
|
|
196
|
+
candidates = []
|
|
197
|
+
for perf in performances:
|
|
198
|
+
# Skip if insufficient data
|
|
199
|
+
if perf.sample_size < self.MIN_SAMPLE_SIZE:
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
# Skip if doesn't meet minimum success rate
|
|
203
|
+
if perf.success_rate < min_success_rate:
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
# Skip if exceeds cost constraint
|
|
207
|
+
if max_cost is not None and perf.avg_cost > max_cost:
|
|
208
|
+
continue
|
|
209
|
+
|
|
210
|
+
# Skip if exceeds latency constraint
|
|
211
|
+
if max_latency_ms is not None and perf.avg_latency_ms > max_latency_ms:
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
candidates.append(perf)
|
|
215
|
+
|
|
216
|
+
if not candidates:
|
|
217
|
+
# All models filtered out, fall back to default Anthropic model
|
|
218
|
+
default_model = self._get_default_model("CHEAP")
|
|
219
|
+
logger.warning(
|
|
220
|
+
"adaptive_routing_no_candidates",
|
|
221
|
+
workflow=workflow,
|
|
222
|
+
stage=stage,
|
|
223
|
+
constraints={"max_cost": max_cost, "max_latency_ms": max_latency_ms},
|
|
224
|
+
fallback=default_model,
|
|
225
|
+
)
|
|
226
|
+
return default_model
|
|
227
|
+
|
|
228
|
+
# Sort by quality score (success rate + cost efficiency)
|
|
229
|
+
candidates.sort(key=lambda p: p.quality_score, reverse=True)
|
|
230
|
+
best = candidates[0]
|
|
231
|
+
|
|
232
|
+
logger.info(
|
|
233
|
+
"adaptive_routing_selected",
|
|
234
|
+
workflow=workflow,
|
|
235
|
+
stage=stage,
|
|
236
|
+
model=best.model_id,
|
|
237
|
+
success_rate=f"{best.success_rate:.1%}",
|
|
238
|
+
avg_cost=f"${best.avg_cost:.4f}",
|
|
239
|
+
sample_size=best.sample_size,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
return best.model_id
|
|
243
|
+
|
|
244
|
+
def recommend_tier_upgrade(
|
|
245
|
+
self, workflow: str, stage: str
|
|
246
|
+
) -> tuple[bool, str]:
|
|
247
|
+
"""Check if tier should be upgraded based on failure rate.
|
|
248
|
+
|
|
249
|
+
Analyzes recent telemetry (last 20 calls) for this workflow/stage.
|
|
250
|
+
If failure rate exceeds threshold (20%), recommends tier upgrade.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
workflow: Workflow name
|
|
254
|
+
stage: Stage name
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
Tuple of (should_upgrade: bool, reason: str)
|
|
258
|
+
|
|
259
|
+
Example:
|
|
260
|
+
>>> should_upgrade, reason = router.recommend_tier_upgrade(
|
|
261
|
+
... workflow="code-review",
|
|
262
|
+
... stage="analysis"
|
|
263
|
+
... )
|
|
264
|
+
>>> if should_upgrade:
|
|
265
|
+
... print(f"⚠️ Upgrading tier: {reason}")
|
|
266
|
+
⚠️ Upgrading tier: High failure rate: 25.0% in last 20 calls
|
|
267
|
+
"""
|
|
268
|
+
# Get recent entries for this workflow/stage
|
|
269
|
+
entries = self._get_workflow_stage_entries(workflow, stage, days=7)
|
|
270
|
+
|
|
271
|
+
if len(entries) < self.MIN_SAMPLE_SIZE:
|
|
272
|
+
return False, f"Insufficient data ({len(entries)} calls, need {self.MIN_SAMPLE_SIZE})"
|
|
273
|
+
|
|
274
|
+
# Analyze recent window (last 20 calls)
|
|
275
|
+
recent = entries[-self.RECENT_WINDOW_SIZE :]
|
|
276
|
+
failures = sum(1 for e in recent if not e.get("success", True))
|
|
277
|
+
failure_rate = failures / len(recent)
|
|
278
|
+
|
|
279
|
+
if failure_rate > self.FAILURE_RATE_THRESHOLD:
|
|
280
|
+
return (
|
|
281
|
+
True,
|
|
282
|
+
f"High failure rate: {failure_rate:.1%} ({failures}/{len(recent)} failed in recent calls)",
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
return False, f"Performance acceptable: {failure_rate:.1%} failure rate"
|
|
286
|
+
|
|
287
|
+
def get_routing_stats(
|
|
288
|
+
self, workflow: str, stage: str | None = None, days: int = 7
|
|
289
|
+
) -> dict[str, Any]:
|
|
290
|
+
"""Get routing statistics for a workflow (or specific stage).
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
workflow: Workflow name
|
|
294
|
+
stage: Optional stage name (None for all stages)
|
|
295
|
+
days: Number of days to analyze
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
Dictionary with routing statistics:
|
|
299
|
+
- models_used: List of models used
|
|
300
|
+
- performance_by_model: Performance metrics per model
|
|
301
|
+
- total_calls: Total number of calls
|
|
302
|
+
- avg_cost: Average cost per call
|
|
303
|
+
- avg_success_rate: Average success rate
|
|
304
|
+
|
|
305
|
+
Example:
|
|
306
|
+
>>> stats = router.get_routing_stats("code-review", days=7)
|
|
307
|
+
>>> print(f"Models used: {stats['models_used']}")
|
|
308
|
+
Models used: ['claude-haiku-3.5', 'claude-sonnet-4.5']
|
|
309
|
+
>>> print(f"Average cost: ${stats['avg_cost']:.4f}")
|
|
310
|
+
Average cost: $0.0023
|
|
311
|
+
"""
|
|
312
|
+
entries = self._get_workflow_stage_entries(workflow, stage, days=days)
|
|
313
|
+
|
|
314
|
+
if not entries:
|
|
315
|
+
return {
|
|
316
|
+
"models_used": [],
|
|
317
|
+
"performance_by_model": {},
|
|
318
|
+
"total_calls": 0,
|
|
319
|
+
"avg_cost": 0.0,
|
|
320
|
+
"avg_success_rate": 0.0,
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
# Calculate stats
|
|
324
|
+
models_used = list({e["model"] for e in entries})
|
|
325
|
+
total_calls = len(entries)
|
|
326
|
+
total_cost = sum(e.get("cost", 0.0) for e in entries)
|
|
327
|
+
successes = sum(1 for e in entries if e.get("success", True))
|
|
328
|
+
|
|
329
|
+
# Per-model performance
|
|
330
|
+
performance_by_model = {}
|
|
331
|
+
for model in models_used:
|
|
332
|
+
model_entries = [e for e in entries if e["model"] == model]
|
|
333
|
+
model_successes = sum(1 for e in model_entries if e.get("success", True))
|
|
334
|
+
|
|
335
|
+
performance_by_model[model] = {
|
|
336
|
+
"calls": len(model_entries),
|
|
337
|
+
"success_rate": model_successes / len(model_entries),
|
|
338
|
+
"avg_cost": sum(e.get("cost", 0.0) for e in model_entries)
|
|
339
|
+
/ len(model_entries),
|
|
340
|
+
"avg_latency_ms": sum(e.get("duration_ms", 0) for e in model_entries)
|
|
341
|
+
/ len(model_entries),
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return {
|
|
345
|
+
"workflow": workflow,
|
|
346
|
+
"stage": stage or "all",
|
|
347
|
+
"days_analyzed": days,
|
|
348
|
+
"models_used": models_used,
|
|
349
|
+
"performance_by_model": performance_by_model,
|
|
350
|
+
"total_calls": total_calls,
|
|
351
|
+
"avg_cost": total_cost / total_calls if total_calls > 0 else 0.0,
|
|
352
|
+
"avg_success_rate": successes / total_calls if total_calls > 0 else 0.0,
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
def _analyze_model_performance(
|
|
356
|
+
self, workflow: str, stage: str, days: int = 7
|
|
357
|
+
) -> list[ModelPerformance]:
|
|
358
|
+
"""Analyze performance of all models for this workflow/stage.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
workflow: Workflow name
|
|
362
|
+
stage: Stage name
|
|
363
|
+
days: Number of days to analyze
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
List of ModelPerformance objects, one per model
|
|
367
|
+
"""
|
|
368
|
+
entries = self._get_workflow_stage_entries(workflow, stage, days=days)
|
|
369
|
+
|
|
370
|
+
if not entries:
|
|
371
|
+
return []
|
|
372
|
+
|
|
373
|
+
# Group by model
|
|
374
|
+
by_model: dict[str, list[dict]] = {}
|
|
375
|
+
for entry in entries:
|
|
376
|
+
model = entry["model"]
|
|
377
|
+
if model not in by_model:
|
|
378
|
+
by_model[model] = []
|
|
379
|
+
by_model[model].append(entry)
|
|
380
|
+
|
|
381
|
+
# Calculate performance metrics per model
|
|
382
|
+
performances = []
|
|
383
|
+
for model, model_entries in by_model.items():
|
|
384
|
+
total = len(model_entries)
|
|
385
|
+
successes = sum(1 for e in model_entries if e.get("success", True))
|
|
386
|
+
success_rate = successes / total
|
|
387
|
+
|
|
388
|
+
avg_latency = (
|
|
389
|
+
sum(e.get("duration_ms", 0) for e in model_entries) / total
|
|
390
|
+
)
|
|
391
|
+
avg_cost = sum(e.get("cost", 0.0) for e in model_entries) / total
|
|
392
|
+
|
|
393
|
+
# Analyze recent failures (last 20 calls)
|
|
394
|
+
recent = model_entries[-self.RECENT_WINDOW_SIZE :]
|
|
395
|
+
recent_failures = sum(1 for e in recent if not e.get("success", True))
|
|
396
|
+
|
|
397
|
+
performances.append(
|
|
398
|
+
ModelPerformance(
|
|
399
|
+
model_id=model,
|
|
400
|
+
tier=model_entries[0].get("tier", "unknown"),
|
|
401
|
+
success_rate=success_rate,
|
|
402
|
+
avg_latency_ms=avg_latency,
|
|
403
|
+
avg_cost=avg_cost,
|
|
404
|
+
sample_size=total,
|
|
405
|
+
recent_failures=recent_failures,
|
|
406
|
+
)
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
return performances
|
|
410
|
+
|
|
411
|
+
def _get_workflow_stage_entries(
|
|
412
|
+
self, workflow: str, stage: str | None, days: int
|
|
413
|
+
) -> list[dict[str, Any]]:
|
|
414
|
+
"""Get telemetry entries for a workflow/stage.
|
|
415
|
+
|
|
416
|
+
Args:
|
|
417
|
+
workflow: Workflow name
|
|
418
|
+
stage: Stage name (None for all stages)
|
|
419
|
+
days: Number of days to retrieve
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
List of telemetry entries
|
|
423
|
+
"""
|
|
424
|
+
# Get recent entries from telemetry tracker
|
|
425
|
+
all_entries = self.telemetry.get_recent_entries(limit=10000, days=days)
|
|
426
|
+
|
|
427
|
+
# Filter to this workflow
|
|
428
|
+
workflow_entries = [e for e in all_entries if e.get("workflow") == workflow]
|
|
429
|
+
|
|
430
|
+
# Filter to this stage if specified
|
|
431
|
+
if stage is not None:
|
|
432
|
+
workflow_entries = [e for e in workflow_entries if e.get("stage") == stage]
|
|
433
|
+
|
|
434
|
+
return workflow_entries
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
__all__ = ["AdaptiveModelRouter", "ModelPerformance"]
|