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,56 @@
|
|
|
1
|
+
"""Empathy Framework Resilience Module
|
|
2
|
+
|
|
3
|
+
Provides reliability patterns for fault-tolerant workflow operations.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
from attune.resilience import retry, circuit_breaker, timeout, fallback
|
|
7
|
+
|
|
8
|
+
@retry(max_attempts=3, backoff_factor=2.0)
|
|
9
|
+
async def call_llm(prompt: str) -> str:
|
|
10
|
+
...
|
|
11
|
+
|
|
12
|
+
@circuit_breaker(failure_threshold=5, reset_timeout=60)
|
|
13
|
+
async def external_api_call():
|
|
14
|
+
...
|
|
15
|
+
|
|
16
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
17
|
+
Licensed under Fair Source 0.9
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .circuit_breaker import (
|
|
21
|
+
CircuitBreaker,
|
|
22
|
+
CircuitOpenError,
|
|
23
|
+
CircuitState,
|
|
24
|
+
circuit_breaker,
|
|
25
|
+
get_circuit_breaker,
|
|
26
|
+
)
|
|
27
|
+
from .fallback import Fallback, fallback, with_fallback
|
|
28
|
+
from .health import HealthCheck, HealthStatus, SystemHealth
|
|
29
|
+
from .retry import RetryConfig, retry, retry_with_backoff
|
|
30
|
+
from .timeout import TimeoutError as ResilienceTimeoutError
|
|
31
|
+
from .timeout import timeout, with_timeout
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"CircuitBreaker",
|
|
35
|
+
"CircuitOpenError",
|
|
36
|
+
"CircuitState",
|
|
37
|
+
"Fallback",
|
|
38
|
+
# Health
|
|
39
|
+
"HealthCheck",
|
|
40
|
+
"HealthStatus",
|
|
41
|
+
"ResilienceTimeoutError",
|
|
42
|
+
"RetryConfig",
|
|
43
|
+
"SystemHealth",
|
|
44
|
+
# Circuit Breaker
|
|
45
|
+
"circuit_breaker",
|
|
46
|
+
# Fallback
|
|
47
|
+
"fallback",
|
|
48
|
+
"get_circuit_breaker",
|
|
49
|
+
# Retry
|
|
50
|
+
"retry",
|
|
51
|
+
"retry_with_backoff",
|
|
52
|
+
# Timeout
|
|
53
|
+
"timeout",
|
|
54
|
+
"with_fallback",
|
|
55
|
+
"with_timeout",
|
|
56
|
+
]
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""Circuit Breaker Pattern Implementation
|
|
2
|
+
|
|
3
|
+
Prevents cascading failures by stopping calls to failing services.
|
|
4
|
+
|
|
5
|
+
States:
|
|
6
|
+
- CLOSED: Normal operation, calls pass through
|
|
7
|
+
- OPEN: Failures exceeded threshold, calls fail immediately
|
|
8
|
+
- HALF_OPEN: Testing if service recovered
|
|
9
|
+
|
|
10
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
11
|
+
Licensed under Fair Source 0.9
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import logging
|
|
16
|
+
import time
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from functools import wraps
|
|
21
|
+
from typing import Any, TypeVar
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
T = TypeVar("T")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CircuitState(Enum):
|
|
29
|
+
"""Circuit breaker states."""
|
|
30
|
+
|
|
31
|
+
CLOSED = "closed" # Normal operation
|
|
32
|
+
OPEN = "open" # Failing fast
|
|
33
|
+
HALF_OPEN = "half_open" # Testing recovery
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CircuitOpenError(Exception):
|
|
37
|
+
"""Raised when circuit breaker is open."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, name: str, reset_time: float):
|
|
40
|
+
self.name = name
|
|
41
|
+
self.reset_time = reset_time
|
|
42
|
+
super().__init__(f"Circuit breaker '{name}' is open. Resets in {reset_time:.1f}s")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class CircuitBreaker:
|
|
47
|
+
"""Circuit breaker implementation.
|
|
48
|
+
|
|
49
|
+
Tracks failures and opens circuit when threshold is exceeded.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
failure_threshold: int = 5
|
|
54
|
+
reset_timeout: float = 60.0
|
|
55
|
+
half_open_max_calls: int = 3
|
|
56
|
+
excluded_exceptions: tuple[type[Exception], ...] = field(default_factory=tuple)
|
|
57
|
+
|
|
58
|
+
# State tracking
|
|
59
|
+
_state: CircuitState = field(default=CircuitState.CLOSED, init=False)
|
|
60
|
+
_failure_count: int = field(default=0, init=False)
|
|
61
|
+
_success_count: int = field(default=0, init=False)
|
|
62
|
+
_last_failure_time: float | None = field(default=None, init=False)
|
|
63
|
+
_half_open_calls: int = field(default=0, init=False)
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def state(self) -> CircuitState:
|
|
67
|
+
"""Get current circuit state, checking for timeout."""
|
|
68
|
+
if self._state == CircuitState.OPEN:
|
|
69
|
+
if self._should_reset():
|
|
70
|
+
self._transition_to_half_open()
|
|
71
|
+
return self._state
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def is_open(self) -> bool:
|
|
75
|
+
"""Check if circuit is open (failing fast)."""
|
|
76
|
+
return self.state == CircuitState.OPEN
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def is_closed(self) -> bool:
|
|
80
|
+
"""Check if circuit is closed (normal operation)."""
|
|
81
|
+
return self.state == CircuitState.CLOSED
|
|
82
|
+
|
|
83
|
+
def _should_reset(self) -> bool:
|
|
84
|
+
"""Check if enough time has passed to try recovery."""
|
|
85
|
+
if self._last_failure_time is None:
|
|
86
|
+
return True
|
|
87
|
+
return time.time() - self._last_failure_time >= self.reset_timeout
|
|
88
|
+
|
|
89
|
+
def _transition_to_half_open(self) -> None:
|
|
90
|
+
"""Move to half-open state to test recovery."""
|
|
91
|
+
logger.info(f"Circuit breaker '{self.name}' transitioning to HALF_OPEN")
|
|
92
|
+
self._state = CircuitState.HALF_OPEN
|
|
93
|
+
self._half_open_calls = 0
|
|
94
|
+
self._success_count = 0 # Reset success counter for recovery tracking
|
|
95
|
+
|
|
96
|
+
def _transition_to_open(self) -> None:
|
|
97
|
+
"""Open the circuit after too many failures."""
|
|
98
|
+
logger.warning(f"Circuit breaker '{self.name}' OPEN after {self._failure_count} failures")
|
|
99
|
+
self._state = CircuitState.OPEN
|
|
100
|
+
self._last_failure_time = time.time()
|
|
101
|
+
|
|
102
|
+
def _transition_to_closed(self) -> None:
|
|
103
|
+
"""Close the circuit after successful recovery."""
|
|
104
|
+
logger.info(f"Circuit breaker '{self.name}' CLOSED - service recovered")
|
|
105
|
+
self._state = CircuitState.CLOSED
|
|
106
|
+
self._failure_count = 0
|
|
107
|
+
self._success_count = 0
|
|
108
|
+
self._half_open_calls = 0
|
|
109
|
+
|
|
110
|
+
def record_success(self) -> None:
|
|
111
|
+
"""Record a successful call."""
|
|
112
|
+
# Use property to trigger OPEN -> HALF_OPEN transition if timeout elapsed
|
|
113
|
+
current_state = self.state
|
|
114
|
+
if current_state == CircuitState.HALF_OPEN:
|
|
115
|
+
self._success_count += 1
|
|
116
|
+
if self._success_count >= self.half_open_max_calls:
|
|
117
|
+
self._transition_to_closed()
|
|
118
|
+
elif current_state == CircuitState.CLOSED:
|
|
119
|
+
# Reset failure count on success
|
|
120
|
+
self._failure_count = 0
|
|
121
|
+
|
|
122
|
+
def record_failure(self, exception: Exception) -> None:
|
|
123
|
+
"""Record a failed call."""
|
|
124
|
+
# Don't count excluded exceptions
|
|
125
|
+
if isinstance(exception, self.excluded_exceptions):
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
self._failure_count += 1
|
|
129
|
+
self._last_failure_time = time.time()
|
|
130
|
+
|
|
131
|
+
# Use property to trigger OPEN -> HALF_OPEN transition if timeout elapsed
|
|
132
|
+
current_state = self.state
|
|
133
|
+
if current_state == CircuitState.HALF_OPEN:
|
|
134
|
+
# Any failure in half-open immediately opens
|
|
135
|
+
self._transition_to_open()
|
|
136
|
+
elif current_state == CircuitState.CLOSED:
|
|
137
|
+
if self._failure_count >= self.failure_threshold:
|
|
138
|
+
self._transition_to_open()
|
|
139
|
+
|
|
140
|
+
def get_time_until_reset(self) -> float:
|
|
141
|
+
"""Get seconds until circuit might reset."""
|
|
142
|
+
if self._last_failure_time is None:
|
|
143
|
+
return 0.0
|
|
144
|
+
elapsed = time.time() - self._last_failure_time
|
|
145
|
+
return max(0.0, self.reset_timeout - elapsed)
|
|
146
|
+
|
|
147
|
+
def reset(self) -> None:
|
|
148
|
+
"""Manually reset the circuit breaker."""
|
|
149
|
+
self._transition_to_closed()
|
|
150
|
+
|
|
151
|
+
def get_stats(self) -> dict[str, Any]:
|
|
152
|
+
"""Get circuit breaker statistics."""
|
|
153
|
+
return {
|
|
154
|
+
"name": self.name,
|
|
155
|
+
"state": self.state.value,
|
|
156
|
+
"failure_count": self._failure_count,
|
|
157
|
+
"success_count": self._success_count,
|
|
158
|
+
"time_until_reset": self.get_time_until_reset(),
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# Global registry of circuit breakers
|
|
163
|
+
_circuit_breakers: dict[str, CircuitBreaker] = {}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def get_circuit_breaker(name: str) -> CircuitBreaker | None:
|
|
167
|
+
"""Get a circuit breaker by name."""
|
|
168
|
+
return _circuit_breakers.get(name)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def circuit_breaker(
|
|
172
|
+
name: str | None = None,
|
|
173
|
+
failure_threshold: int = 5,
|
|
174
|
+
reset_timeout: float = 60.0,
|
|
175
|
+
half_open_max_calls: int = 3,
|
|
176
|
+
excluded_exceptions: tuple[type[Exception], ...] | None = None,
|
|
177
|
+
fallback: Callable[..., T] | None = None,
|
|
178
|
+
) -> Callable:
|
|
179
|
+
"""Decorator to wrap a function with circuit breaker protection.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
name: Circuit breaker name (defaults to function name)
|
|
183
|
+
failure_threshold: Number of failures before opening
|
|
184
|
+
reset_timeout: Seconds before attempting recovery
|
|
185
|
+
half_open_max_calls: Successful calls needed to close
|
|
186
|
+
excluded_exceptions: Exceptions that don't count as failures
|
|
187
|
+
fallback: Function to call when circuit is open
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
@circuit_breaker(failure_threshold=3, reset_timeout=30)
|
|
191
|
+
async def call_external_api():
|
|
192
|
+
...
|
|
193
|
+
|
|
194
|
+
@circuit_breaker(fallback=lambda: {"status": "degraded"})
|
|
195
|
+
async def get_status():
|
|
196
|
+
...
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
if excluded_exceptions is None:
|
|
200
|
+
excluded_exceptions = ()
|
|
201
|
+
|
|
202
|
+
def decorator(func: Callable[..., T]) -> Callable[..., T]:
|
|
203
|
+
cb_name = name or func.__name__
|
|
204
|
+
|
|
205
|
+
# Create or get existing circuit breaker
|
|
206
|
+
if cb_name not in _circuit_breakers:
|
|
207
|
+
_circuit_breakers[cb_name] = CircuitBreaker(
|
|
208
|
+
name=cb_name,
|
|
209
|
+
failure_threshold=failure_threshold,
|
|
210
|
+
reset_timeout=reset_timeout,
|
|
211
|
+
half_open_max_calls=half_open_max_calls,
|
|
212
|
+
excluded_exceptions=excluded_exceptions,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
cb = _circuit_breakers[cb_name]
|
|
216
|
+
|
|
217
|
+
@wraps(func)
|
|
218
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> T:
|
|
219
|
+
if cb.is_open:
|
|
220
|
+
if fallback:
|
|
221
|
+
logger.info(f"Circuit '{cb_name}' open, using fallback")
|
|
222
|
+
if asyncio.iscoroutinefunction(fallback):
|
|
223
|
+
fb_result: T = await fallback(*args, **kwargs)
|
|
224
|
+
return fb_result
|
|
225
|
+
return fallback(*args, **kwargs)
|
|
226
|
+
raise CircuitOpenError(cb_name, cb.get_time_until_reset())
|
|
227
|
+
|
|
228
|
+
try:
|
|
229
|
+
result: T = await func(*args, **kwargs) # type: ignore[misc]
|
|
230
|
+
cb.record_success()
|
|
231
|
+
return result
|
|
232
|
+
except Exception as e:
|
|
233
|
+
cb.record_failure(e)
|
|
234
|
+
raise
|
|
235
|
+
|
|
236
|
+
@wraps(func)
|
|
237
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> T:
|
|
238
|
+
if cb.is_open:
|
|
239
|
+
if fallback:
|
|
240
|
+
logger.info(f"Circuit '{cb_name}' open, using fallback")
|
|
241
|
+
return fallback(*args, **kwargs)
|
|
242
|
+
raise CircuitOpenError(cb_name, cb.get_time_until_reset())
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
result = func(*args, **kwargs)
|
|
246
|
+
cb.record_success()
|
|
247
|
+
return result
|
|
248
|
+
except Exception as e:
|
|
249
|
+
cb.record_failure(e)
|
|
250
|
+
raise
|
|
251
|
+
|
|
252
|
+
if asyncio.iscoroutinefunction(func):
|
|
253
|
+
return async_wrapper # type: ignore[return-value]
|
|
254
|
+
return sync_wrapper
|
|
255
|
+
|
|
256
|
+
return decorator
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Fallback Pattern Implementation
|
|
2
|
+
|
|
3
|
+
Provides graceful degradation when primary operations fail.
|
|
4
|
+
|
|
5
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
6
|
+
Licensed under Fair Source 0.9
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import logging
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from functools import wraps
|
|
14
|
+
from typing import Any, TypeVar
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
T = TypeVar("T")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class Fallback:
|
|
23
|
+
"""Fallback chain for graceful degradation.
|
|
24
|
+
|
|
25
|
+
Tries each function in order until one succeeds.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
name: str
|
|
29
|
+
functions: list[Callable] = field(default_factory=list)
|
|
30
|
+
default_value: Any | None = None
|
|
31
|
+
|
|
32
|
+
def add(self, func: Callable) -> "Fallback":
|
|
33
|
+
"""Add a fallback function to the chain."""
|
|
34
|
+
self.functions.append(func)
|
|
35
|
+
return self
|
|
36
|
+
|
|
37
|
+
async def execute(self, *args: Any, **kwargs: Any) -> Any:
|
|
38
|
+
"""Execute fallback chain until success."""
|
|
39
|
+
last_exception: Exception | None = None
|
|
40
|
+
|
|
41
|
+
for i, func in enumerate(self.functions):
|
|
42
|
+
try:
|
|
43
|
+
logger.debug(f"Fallback '{self.name}': trying function {i + 1}")
|
|
44
|
+
if asyncio.iscoroutinefunction(func):
|
|
45
|
+
return await func(*args, **kwargs)
|
|
46
|
+
return func(*args, **kwargs)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
last_exception = e
|
|
49
|
+
logger.warning(f"Fallback '{self.name}': function {i + 1} failed: {e}")
|
|
50
|
+
continue
|
|
51
|
+
|
|
52
|
+
# All functions failed
|
|
53
|
+
if self.default_value is not None:
|
|
54
|
+
logger.info(f"Fallback '{self.name}': using default value")
|
|
55
|
+
return self.default_value
|
|
56
|
+
|
|
57
|
+
if last_exception:
|
|
58
|
+
raise last_exception
|
|
59
|
+
raise RuntimeError(f"Fallback '{self.name}': no functions to execute")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def fallback(
|
|
63
|
+
*fallback_funcs: Callable,
|
|
64
|
+
default: Any | None = None,
|
|
65
|
+
log_failures: bool = True,
|
|
66
|
+
) -> Callable:
|
|
67
|
+
"""Decorator to add fallback behavior to a function.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
*fallback_funcs: Functions to try if primary fails
|
|
71
|
+
default: Default value if all functions fail
|
|
72
|
+
log_failures: Whether to log failed attempts
|
|
73
|
+
|
|
74
|
+
Example:
|
|
75
|
+
def get_from_cache():
|
|
76
|
+
return cache.get("key")
|
|
77
|
+
|
|
78
|
+
def get_from_db():
|
|
79
|
+
return db.query("SELECT ...")
|
|
80
|
+
|
|
81
|
+
@fallback(get_from_cache, default=None)
|
|
82
|
+
async def get_data():
|
|
83
|
+
return await api.fetch()
|
|
84
|
+
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
def decorator(func: Callable[..., T]) -> Callable[..., T]:
|
|
88
|
+
@wraps(func)
|
|
89
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> T:
|
|
90
|
+
# Try primary function
|
|
91
|
+
try:
|
|
92
|
+
if asyncio.iscoroutinefunction(func):
|
|
93
|
+
result: T = await func(*args, **kwargs)
|
|
94
|
+
return result
|
|
95
|
+
return func(*args, **kwargs)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
if log_failures:
|
|
98
|
+
logger.warning(f"Primary function {func.__name__} failed: {e}")
|
|
99
|
+
|
|
100
|
+
# Try fallback functions
|
|
101
|
+
for fallback_func in fallback_funcs:
|
|
102
|
+
try:
|
|
103
|
+
if asyncio.iscoroutinefunction(fallback_func):
|
|
104
|
+
result = await fallback_func(*args, **kwargs)
|
|
105
|
+
return result
|
|
106
|
+
return fallback_func(*args, **kwargs) # type: ignore[no-any-return]
|
|
107
|
+
except Exception as e:
|
|
108
|
+
if log_failures:
|
|
109
|
+
logger.warning(f"Fallback {fallback_func.__name__} failed: {e}")
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
# All failed, use default
|
|
113
|
+
if default is not None:
|
|
114
|
+
return default # type: ignore[no-any-return]
|
|
115
|
+
|
|
116
|
+
raise RuntimeError(f"All fallbacks failed for {func.__name__}")
|
|
117
|
+
|
|
118
|
+
@wraps(func)
|
|
119
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> T:
|
|
120
|
+
try:
|
|
121
|
+
return func(*args, **kwargs)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
if log_failures:
|
|
124
|
+
logger.warning(f"Primary function {func.__name__} failed: {e}")
|
|
125
|
+
|
|
126
|
+
for fallback_func in fallback_funcs:
|
|
127
|
+
try:
|
|
128
|
+
return fallback_func(*args, **kwargs) # type: ignore[no-any-return]
|
|
129
|
+
except Exception as e:
|
|
130
|
+
if log_failures:
|
|
131
|
+
logger.warning(f"Fallback {fallback_func.__name__} failed: {e}")
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
if default is not None:
|
|
135
|
+
return default # type: ignore[no-any-return]
|
|
136
|
+
|
|
137
|
+
raise RuntimeError(f"All fallbacks failed for {func.__name__}")
|
|
138
|
+
|
|
139
|
+
if asyncio.iscoroutinefunction(func):
|
|
140
|
+
return async_wrapper # type: ignore[return-value]
|
|
141
|
+
return sync_wrapper
|
|
142
|
+
|
|
143
|
+
return decorator
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def with_fallback(
|
|
147
|
+
primary: Callable[..., T],
|
|
148
|
+
fallbacks: list[Callable[..., T]],
|
|
149
|
+
default: T | None = None,
|
|
150
|
+
) -> Callable[..., T]:
|
|
151
|
+
"""Create a function that tries primary then fallbacks.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
primary: Primary function to try
|
|
155
|
+
fallbacks: List of fallback functions
|
|
156
|
+
default: Default value if all fail
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Wrapped function with fallback behavior
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
get_user = with_fallback(
|
|
163
|
+
get_user_from_api,
|
|
164
|
+
[get_user_from_cache, get_user_from_db],
|
|
165
|
+
default={"id": "unknown"}
|
|
166
|
+
)
|
|
167
|
+
user = await get_user(user_id)
|
|
168
|
+
|
|
169
|
+
"""
|
|
170
|
+
fb = Fallback(name=primary.__name__, default_value=default)
|
|
171
|
+
fb.add(primary)
|
|
172
|
+
for f in fallbacks:
|
|
173
|
+
fb.add(f)
|
|
174
|
+
|
|
175
|
+
async def wrapper(*args: Any, **kwargs: Any) -> T:
|
|
176
|
+
result: T = await fb.execute(*args, **kwargs)
|
|
177
|
+
return result
|
|
178
|
+
|
|
179
|
+
return wrapper # type: ignore[return-value]
|