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,404 @@
|
|
|
1
|
+
"""Research Synthesis Workflow
|
|
2
|
+
|
|
3
|
+
A cost-optimized pipeline for research and analysis tasks:
|
|
4
|
+
1. Haiku: Summarize each source (cheap, parallel)
|
|
5
|
+
2. Sonnet: Identify patterns across summaries
|
|
6
|
+
3. Opus: Synthesize final insights (conditional on complexity)
|
|
7
|
+
|
|
8
|
+
Integration with attune.models:
|
|
9
|
+
- Supports LLMExecutor for unified execution (optional)
|
|
10
|
+
- WorkflowStepConfig for declarative step definitions
|
|
11
|
+
- Automatic telemetry emission when executor is used
|
|
12
|
+
- Falls back to direct API calls when executor not provided
|
|
13
|
+
|
|
14
|
+
Copyright 2025 Smart-AI-Memory
|
|
15
|
+
Licensed under Fair Source License 0.9
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from .base import BaseWorkflow, ModelTier
|
|
21
|
+
from .step_config import WorkflowStepConfig
|
|
22
|
+
|
|
23
|
+
# Step configurations for executor-based execution
|
|
24
|
+
SUMMARIZE_STEP = WorkflowStepConfig(
|
|
25
|
+
name="summarize",
|
|
26
|
+
task_type="summarize", # Routes to cheap tier
|
|
27
|
+
description="Summarize each source document",
|
|
28
|
+
max_tokens=2048,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
ANALYZE_STEP = WorkflowStepConfig(
|
|
32
|
+
name="analyze",
|
|
33
|
+
task_type="analyze_patterns", # Routes to capable tier
|
|
34
|
+
description="Identify patterns across summaries",
|
|
35
|
+
max_tokens=2048,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
SYNTHESIZE_STEP = WorkflowStepConfig(
|
|
39
|
+
name="synthesize",
|
|
40
|
+
task_type="complex_reasoning", # Routes to premium tier
|
|
41
|
+
description="Synthesize final insights",
|
|
42
|
+
max_tokens=4096,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
SYNTHESIZE_STEP_CAPABLE = WorkflowStepConfig(
|
|
46
|
+
name="synthesize",
|
|
47
|
+
task_type="generate_content", # Routes to capable tier
|
|
48
|
+
tier_hint="capable", # Force capable tier
|
|
49
|
+
description="Synthesize final insights (lower complexity)",
|
|
50
|
+
max_tokens=4096,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ResearchSynthesisWorkflow(BaseWorkflow):
|
|
55
|
+
"""Multi-tier research synthesis workflow for comparing multiple documents.
|
|
56
|
+
|
|
57
|
+
Uses cheap models for initial summarization, capable models for
|
|
58
|
+
pattern analysis, and optionally premium models for final synthesis
|
|
59
|
+
when the analysis reveals high complexity.
|
|
60
|
+
|
|
61
|
+
IMPORTANT: This workflow requires at least 2 source documents.
|
|
62
|
+
For single research questions without sources, use a direct LLM call instead.
|
|
63
|
+
|
|
64
|
+
Usage (legacy - direct API calls):
|
|
65
|
+
workflow = ResearchSynthesisWorkflow()
|
|
66
|
+
result = await workflow.execute(
|
|
67
|
+
sources=["doc1.md", "doc2.md"],
|
|
68
|
+
question="What are the key patterns?"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
Usage (executor-based - unified execution with telemetry):
|
|
72
|
+
from attune.models import MockLLMExecutor
|
|
73
|
+
|
|
74
|
+
executor = MockLLMExecutor() # or EmpathyLLMExecutor for real calls
|
|
75
|
+
workflow = ResearchSynthesisWorkflow(executor=executor)
|
|
76
|
+
result = await workflow.execute(
|
|
77
|
+
sources=["doc1.md", "doc2.md"],
|
|
78
|
+
question="What are the key patterns?"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
When using executor:
|
|
82
|
+
- Automatic task-based routing via WorkflowStepConfig
|
|
83
|
+
- Automatic telemetry emission (LLMCallRecord per call)
|
|
84
|
+
- Automatic workflow telemetry (WorkflowRunRecord at end)
|
|
85
|
+
- Fallback and retry policies applied automatically
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
ValueError: If fewer than 2 sources are provided
|
|
89
|
+
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
name = "research"
|
|
93
|
+
description = "Cost-optimized research synthesis for multi-document analysis"
|
|
94
|
+
MIN_SOURCES = 2 # Minimum required sources
|
|
95
|
+
stages = ["summarize", "analyze", "synthesize"]
|
|
96
|
+
tier_map = {
|
|
97
|
+
"summarize": ModelTier.CHEAP,
|
|
98
|
+
"analyze": ModelTier.CAPABLE,
|
|
99
|
+
"synthesize": ModelTier.PREMIUM,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
def __init__(self, complexity_threshold: float = 0.7, **kwargs: Any):
|
|
103
|
+
"""Initialize workflow.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
complexity_threshold: Threshold (0-1) above which premium
|
|
107
|
+
synthesis is used. Below this, capable tier is used.
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
super().__init__(**kwargs)
|
|
111
|
+
self.complexity_threshold = complexity_threshold
|
|
112
|
+
self._detected_complexity: float = 0.0
|
|
113
|
+
|
|
114
|
+
async def _call_with_step(
|
|
115
|
+
self,
|
|
116
|
+
step: WorkflowStepConfig,
|
|
117
|
+
system: str,
|
|
118
|
+
user_message: str,
|
|
119
|
+
) -> tuple[str, int, int]:
|
|
120
|
+
"""Make an LLM call using WorkflowStepConfig and executor (if available).
|
|
121
|
+
|
|
122
|
+
This is the recommended approach for new workflows. It provides:
|
|
123
|
+
- Automatic task-based routing
|
|
124
|
+
- Automatic telemetry emission
|
|
125
|
+
- Fallback and retry policies
|
|
126
|
+
|
|
127
|
+
If no executor is configured, falls back to direct API call.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
step: WorkflowStepConfig defining the step
|
|
131
|
+
system: System prompt
|
|
132
|
+
user_message: User message
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
(response_text, input_tokens, output_tokens)
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
if self._executor is not None:
|
|
139
|
+
# Use executor-based execution with telemetry
|
|
140
|
+
prompt = f"{system}\n\n{user_message}" if system else user_message
|
|
141
|
+
content, input_tokens, output_tokens, _cost = await self.run_step_with_executor(
|
|
142
|
+
step=step,
|
|
143
|
+
prompt=prompt,
|
|
144
|
+
system=system,
|
|
145
|
+
)
|
|
146
|
+
return content, input_tokens, output_tokens
|
|
147
|
+
# Fall back to direct API call
|
|
148
|
+
tier_value = step.effective_tier
|
|
149
|
+
tier = ModelTier(tier_value)
|
|
150
|
+
return await self._call_llm(
|
|
151
|
+
tier,
|
|
152
|
+
system,
|
|
153
|
+
user_message,
|
|
154
|
+
max_tokens=step.max_tokens or 4096,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
def validate_sources(self, sources: list) -> None:
|
|
158
|
+
"""Validate that sufficient sources are provided.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
sources: List of source documents/content
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
ValueError: If fewer than MIN_SOURCES are provided
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
if not sources or len(sources) < self.MIN_SOURCES:
|
|
168
|
+
raise ValueError(
|
|
169
|
+
f"ResearchSynthesisWorkflow requires at least {self.MIN_SOURCES} source documents. "
|
|
170
|
+
f"Got {len(sources) if sources else 0}. "
|
|
171
|
+
"For single research questions without sources, use a direct LLM call instead.",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
async def execute(self, **kwargs: Any) -> Any:
|
|
175
|
+
"""Execute the research synthesis workflow.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
sources: List of source documents (required, minimum 2)
|
|
179
|
+
question: Research question to answer
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
WorkflowResult with synthesis output
|
|
183
|
+
|
|
184
|
+
Raises:
|
|
185
|
+
ValueError: If fewer than 2 sources are provided
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
sources = kwargs.get("sources", [])
|
|
189
|
+
self.validate_sources(sources)
|
|
190
|
+
return await super().execute(**kwargs)
|
|
191
|
+
|
|
192
|
+
def should_skip_stage(self, stage_name: str, input_data: Any) -> tuple[bool, str | None]:
|
|
193
|
+
"""Skip premium synthesis if complexity is low."""
|
|
194
|
+
if stage_name == "synthesize" and self._detected_complexity < self.complexity_threshold:
|
|
195
|
+
# Downgrade to capable tier instead of skipping
|
|
196
|
+
self.tier_map["synthesize"] = ModelTier.CAPABLE
|
|
197
|
+
return False, None
|
|
198
|
+
return False, None
|
|
199
|
+
|
|
200
|
+
async def run_stage(
|
|
201
|
+
self,
|
|
202
|
+
stage_name: str,
|
|
203
|
+
tier: ModelTier,
|
|
204
|
+
input_data: Any,
|
|
205
|
+
) -> tuple[Any, int, int]:
|
|
206
|
+
"""Execute a research workflow stage.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
stage_name: Stage to run
|
|
210
|
+
tier: Model tier to use
|
|
211
|
+
input_data: Input data
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
(output, input_tokens, output_tokens)
|
|
215
|
+
|
|
216
|
+
"""
|
|
217
|
+
if stage_name == "summarize":
|
|
218
|
+
return await self._summarize(input_data, tier)
|
|
219
|
+
if stage_name == "analyze":
|
|
220
|
+
return await self._analyze(input_data, tier)
|
|
221
|
+
if stage_name == "synthesize":
|
|
222
|
+
return await self._synthesize(input_data, tier)
|
|
223
|
+
raise ValueError(f"Unknown stage: {stage_name}")
|
|
224
|
+
|
|
225
|
+
async def _summarize(self, input_data: dict, tier: ModelTier) -> tuple[dict, int, int]:
|
|
226
|
+
"""Summarize each source document.
|
|
227
|
+
|
|
228
|
+
Note: Sources are validated in execute() before this method is called.
|
|
229
|
+
"""
|
|
230
|
+
sources = input_data.get("sources", [])
|
|
231
|
+
question = input_data.get("question", "Summarize the content")
|
|
232
|
+
|
|
233
|
+
total_input = 0
|
|
234
|
+
total_output = 0
|
|
235
|
+
|
|
236
|
+
# Process each source
|
|
237
|
+
summaries = []
|
|
238
|
+
system = """You are a research assistant. Summarize the given content,
|
|
239
|
+
extracting key points relevant to the research question. Be thorough but concise."""
|
|
240
|
+
|
|
241
|
+
for source in sources:
|
|
242
|
+
user_message = f"Research question: {question}\n\nSource content:\n{source}"
|
|
243
|
+
|
|
244
|
+
response, inp_tokens, out_tokens = await self._call_llm(
|
|
245
|
+
tier,
|
|
246
|
+
system,
|
|
247
|
+
user_message,
|
|
248
|
+
max_tokens=1024,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
summaries.append(
|
|
252
|
+
{
|
|
253
|
+
"source": str(source)[:100],
|
|
254
|
+
"summary": response,
|
|
255
|
+
"key_points": [],
|
|
256
|
+
},
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
total_input += inp_tokens
|
|
260
|
+
total_output += out_tokens
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
{
|
|
264
|
+
"summaries": summaries,
|
|
265
|
+
"question": question,
|
|
266
|
+
"source_count": len(sources),
|
|
267
|
+
},
|
|
268
|
+
total_input,
|
|
269
|
+
total_output,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
async def _analyze(self, input_data: dict, tier: ModelTier) -> tuple[dict, int, int]:
|
|
273
|
+
"""Analyze patterns across summaries from multiple sources."""
|
|
274
|
+
summaries = input_data.get("summaries", [])
|
|
275
|
+
question = input_data.get("question", "")
|
|
276
|
+
|
|
277
|
+
# Combine summaries for analysis
|
|
278
|
+
combined = "\n\n".join(
|
|
279
|
+
[f"Source: {s.get('source', 'unknown')}\n{s.get('summary', '')}" for s in summaries],
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
system = """You are a research analyst. Analyze the summaries to identify:
|
|
283
|
+
1. Common patterns and themes
|
|
284
|
+
2. Contradictions or disagreements
|
|
285
|
+
3. Key insights
|
|
286
|
+
4. Complexity level (simple, moderate, complex)
|
|
287
|
+
|
|
288
|
+
Provide a structured analysis."""
|
|
289
|
+
|
|
290
|
+
user_message = f"Research question: {question}\n\nSummaries to analyze:\n{combined}"
|
|
291
|
+
|
|
292
|
+
response, input_tokens, output_tokens = await self._call_llm(
|
|
293
|
+
tier,
|
|
294
|
+
system,
|
|
295
|
+
user_message,
|
|
296
|
+
max_tokens=2048,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# Estimate complexity from response length and content
|
|
300
|
+
self._detected_complexity = min(len(response) / 2000, 1.0)
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
{
|
|
304
|
+
"patterns": [{"pattern": response, "sources": [], "confidence": 0.85}],
|
|
305
|
+
"complexity": self._detected_complexity,
|
|
306
|
+
"question": question,
|
|
307
|
+
"summary_count": len(summaries),
|
|
308
|
+
"analysis": response,
|
|
309
|
+
},
|
|
310
|
+
input_tokens,
|
|
311
|
+
output_tokens,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
async def _synthesize(self, input_data: dict, tier: ModelTier) -> tuple[dict, int, int]:
|
|
315
|
+
"""Synthesize final insights from multi-source analysis.
|
|
316
|
+
|
|
317
|
+
Supports XML-enhanced prompts when enabled in workflow config.
|
|
318
|
+
"""
|
|
319
|
+
_patterns = input_data.get("patterns", [])
|
|
320
|
+
complexity = input_data.get("complexity", 0.5)
|
|
321
|
+
question = input_data.get("question", "")
|
|
322
|
+
analysis = input_data.get("analysis", "")
|
|
323
|
+
|
|
324
|
+
# Build input payload
|
|
325
|
+
input_payload = f"""Research question: {question}
|
|
326
|
+
|
|
327
|
+
Analysis to synthesize:
|
|
328
|
+
{analysis}
|
|
329
|
+
|
|
330
|
+
Complexity level: {complexity:.2f}"""
|
|
331
|
+
|
|
332
|
+
# Check if XML prompts are enabled
|
|
333
|
+
if self._is_xml_enabled():
|
|
334
|
+
user_message = self._render_xml_prompt(
|
|
335
|
+
role="expert research synthesizer",
|
|
336
|
+
goal="Synthesize analysis into comprehensive answer with key insights",
|
|
337
|
+
instructions=[
|
|
338
|
+
"Provide a comprehensive answer to the research question",
|
|
339
|
+
"Highlight key insights and takeaways",
|
|
340
|
+
"Note any caveats or areas needing further research",
|
|
341
|
+
"Structure your response clearly with sections if appropriate",
|
|
342
|
+
"Provide 1-2 concrete next steps or decisions",
|
|
343
|
+
],
|
|
344
|
+
constraints=[
|
|
345
|
+
"Be thorough, insightful, and actionable",
|
|
346
|
+
"Focus on practical implications",
|
|
347
|
+
"3-5 paragraphs for main answer",
|
|
348
|
+
],
|
|
349
|
+
input_type="research_analysis",
|
|
350
|
+
input_payload=input_payload,
|
|
351
|
+
extra={
|
|
352
|
+
"complexity_score": complexity,
|
|
353
|
+
},
|
|
354
|
+
)
|
|
355
|
+
system = None
|
|
356
|
+
else:
|
|
357
|
+
system = """You are an expert synthesizer. Based on the analysis provided:
|
|
358
|
+
1. Provide a comprehensive answer to the research question
|
|
359
|
+
2. Highlight key insights and takeaways
|
|
360
|
+
3. Note any caveats or areas needing further research
|
|
361
|
+
4. Structure your response clearly with sections if appropriate
|
|
362
|
+
|
|
363
|
+
Be thorough, insightful, and actionable."""
|
|
364
|
+
|
|
365
|
+
user_message = f"""{input_payload}
|
|
366
|
+
|
|
367
|
+
Provide a comprehensive synthesis and answer."""
|
|
368
|
+
|
|
369
|
+
response, input_tokens, output_tokens = await self._call_llm(
|
|
370
|
+
tier,
|
|
371
|
+
system or "",
|
|
372
|
+
user_message,
|
|
373
|
+
max_tokens=4096,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Parse XML response if enforcement is enabled
|
|
377
|
+
parsed_data = self._parse_xml_response(response)
|
|
378
|
+
|
|
379
|
+
synthesis = {
|
|
380
|
+
"answer": response,
|
|
381
|
+
"key_insights": [],
|
|
382
|
+
"confidence": 0.85,
|
|
383
|
+
"model_tier_used": tier.value,
|
|
384
|
+
"complexity_score": complexity,
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
# Merge parsed XML data if available
|
|
388
|
+
if parsed_data.get("xml_parsed"):
|
|
389
|
+
synthesis.update(
|
|
390
|
+
{
|
|
391
|
+
"xml_parsed": True,
|
|
392
|
+
"summary": parsed_data.get("summary"),
|
|
393
|
+
"findings": parsed_data.get("findings", []),
|
|
394
|
+
"checklist": parsed_data.get("checklist", []),
|
|
395
|
+
},
|
|
396
|
+
)
|
|
397
|
+
# Extract key insights from parsed response
|
|
398
|
+
extra = parsed_data.get("_parsed_response")
|
|
399
|
+
if extra and hasattr(extra, "extra"):
|
|
400
|
+
key_insights = extra.extra.get("key_insights", [])
|
|
401
|
+
if key_insights:
|
|
402
|
+
synthesis["key_insights"] = key_insights
|
|
403
|
+
|
|
404
|
+
return synthesis, input_tokens, output_tokens
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Tier routing strategies for workflow execution.
|
|
2
|
+
|
|
3
|
+
Provides pluggable routing algorithms to determine which model tier
|
|
4
|
+
should handle each workflow stage.
|
|
5
|
+
|
|
6
|
+
Copyright 2025 Smart-AI-Memory
|
|
7
|
+
Licensed under Fair Source License 0.9
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import TYPE_CHECKING
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from attune.workflows.base import ModelTier
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class RoutingContext:
|
|
22
|
+
"""Context information for routing decisions.
|
|
23
|
+
|
|
24
|
+
Attributes:
|
|
25
|
+
task_type: Type of task (analyze, generate, review, etc.)
|
|
26
|
+
input_size: Estimated input tokens
|
|
27
|
+
complexity: Task complexity (simple, moderate, complex)
|
|
28
|
+
budget_remaining: Remaining budget in USD
|
|
29
|
+
latency_sensitivity: Latency requirements (low, medium, high)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
task_type: str
|
|
33
|
+
input_size: int
|
|
34
|
+
complexity: str # "simple" | "moderate" | "complex"
|
|
35
|
+
budget_remaining: float
|
|
36
|
+
latency_sensitivity: str # "low" | "medium" | "high"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TierRoutingStrategy(ABC):
|
|
40
|
+
"""Abstract base class for tier routing strategies.
|
|
41
|
+
|
|
42
|
+
Subclasses implement different routing algorithms:
|
|
43
|
+
- CostOptimizedRouting: Minimize cost
|
|
44
|
+
- PerformanceOptimizedRouting: Minimize latency
|
|
45
|
+
- BalancedRouting: Balance cost and performance
|
|
46
|
+
- HybridRouting: User-configured tier mappings
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def route(self, context: RoutingContext) -> ModelTier:
|
|
51
|
+
"""Route task to appropriate tier.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
context: Routing context with task information
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
ModelTier to use for this task
|
|
58
|
+
"""
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def can_fallback(self, tier: ModelTier) -> bool:
|
|
63
|
+
"""Whether fallback to cheaper tier is allowed.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
tier: The tier that failed or exceeded budget
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
True if fallback is allowed, False otherwise
|
|
70
|
+
"""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class CostOptimizedRouting(TierRoutingStrategy):
|
|
75
|
+
"""Route to cheapest tier that can handle the task.
|
|
76
|
+
|
|
77
|
+
Default strategy. Prioritizes cost savings over speed.
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
>>> strategy = CostOptimizedRouting()
|
|
81
|
+
>>> tier = strategy.route(context) # CHEAP for simple tasks
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def route(self, context: RoutingContext) -> ModelTier:
|
|
85
|
+
"""Route based on task complexity, preferring cheaper tiers."""
|
|
86
|
+
from attune.workflows.base import ModelTier
|
|
87
|
+
|
|
88
|
+
if context.complexity == "simple":
|
|
89
|
+
return ModelTier.CHEAP
|
|
90
|
+
elif context.complexity == "complex":
|
|
91
|
+
return ModelTier.PREMIUM
|
|
92
|
+
return ModelTier.CAPABLE
|
|
93
|
+
|
|
94
|
+
def can_fallback(self, tier: ModelTier) -> bool:
|
|
95
|
+
"""Allow fallback except for CHEAP tier."""
|
|
96
|
+
from attune.workflows.base import ModelTier
|
|
97
|
+
|
|
98
|
+
return tier != ModelTier.CHEAP
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class PerformanceOptimizedRouting(TierRoutingStrategy):
|
|
102
|
+
"""Route to fastest tier regardless of cost.
|
|
103
|
+
|
|
104
|
+
Use for latency-sensitive workflows like interactive tools.
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
>>> strategy = PerformanceOptimizedRouting()
|
|
108
|
+
>>> tier = strategy.route(context) # PREMIUM for high latency sensitivity
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def route(self, context: RoutingContext) -> ModelTier:
|
|
112
|
+
"""Route based on latency requirements."""
|
|
113
|
+
from attune.workflows.base import ModelTier
|
|
114
|
+
|
|
115
|
+
if context.latency_sensitivity == "high":
|
|
116
|
+
return ModelTier.PREMIUM
|
|
117
|
+
return ModelTier.CAPABLE
|
|
118
|
+
|
|
119
|
+
def can_fallback(self, tier: ModelTier) -> bool:
|
|
120
|
+
"""Never fallback - performance is priority."""
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class BalancedRouting(TierRoutingStrategy):
|
|
125
|
+
"""Balance cost and performance with budget awareness.
|
|
126
|
+
|
|
127
|
+
Adjusts tier selection based on remaining budget and task complexity.
|
|
128
|
+
|
|
129
|
+
Example:
|
|
130
|
+
>>> strategy = BalancedRouting(total_budget=50.0)
|
|
131
|
+
>>> tier = strategy.route(context) # Adapts based on budget
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def __init__(self, total_budget: float):
|
|
135
|
+
"""Initialize with total budget.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
total_budget: Total budget in USD for this workflow execution
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
ValueError: If total_budget is not positive
|
|
142
|
+
"""
|
|
143
|
+
if total_budget <= 0:
|
|
144
|
+
raise ValueError("total_budget must be positive")
|
|
145
|
+
self.total_budget = total_budget
|
|
146
|
+
|
|
147
|
+
def route(self, context: RoutingContext) -> ModelTier:
|
|
148
|
+
"""Route based on budget ratio and complexity."""
|
|
149
|
+
from attune.workflows.base import ModelTier
|
|
150
|
+
|
|
151
|
+
budget_ratio = context.budget_remaining / self.total_budget
|
|
152
|
+
|
|
153
|
+
# Low budget - use cheap tier
|
|
154
|
+
if budget_ratio < 0.2:
|
|
155
|
+
return ModelTier.CHEAP
|
|
156
|
+
|
|
157
|
+
# High budget + complex task - use premium
|
|
158
|
+
if budget_ratio > 0.7 and context.complexity == "complex":
|
|
159
|
+
return ModelTier.PREMIUM
|
|
160
|
+
|
|
161
|
+
# Default to capable
|
|
162
|
+
return ModelTier.CAPABLE
|
|
163
|
+
|
|
164
|
+
def can_fallback(self, tier: ModelTier) -> bool:
|
|
165
|
+
"""Allow fallback when budget-constrained."""
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
|