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,273 @@
|
|
|
1
|
+
"""Builder pattern for BaseWorkflow construction.
|
|
2
|
+
|
|
3
|
+
Simplifies complex workflow configuration by providing a fluent API
|
|
4
|
+
for setting optional parameters.
|
|
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 collections.abc import Callable
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from attune.cache import BaseCache
|
|
17
|
+
from attune.models import LLMExecutor, TelemetryBackend, UnifiedModelProvider
|
|
18
|
+
from attune.workflows.base import BaseWorkflow
|
|
19
|
+
from attune.workflows.config import WorkflowConfig
|
|
20
|
+
from attune.workflows.progress import ProgressCallback
|
|
21
|
+
from attune.workflows.routing import TierRoutingStrategy
|
|
22
|
+
from attune.workflows.tier_tracking import WorkflowTierTracker
|
|
23
|
+
|
|
24
|
+
T = TypeVar("T", bound="BaseWorkflow")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WorkflowBuilder(Generic[T]):
|
|
28
|
+
"""Builder for complex workflow configuration.
|
|
29
|
+
|
|
30
|
+
Provides a fluent API for constructing workflows with many optional parameters.
|
|
31
|
+
Eliminates the need to pass 12+ constructor arguments.
|
|
32
|
+
|
|
33
|
+
Example:
|
|
34
|
+
>>> from attune.workflows.test_gen import TestGenerationWorkflow
|
|
35
|
+
>>> from attune.workflows.builder import WorkflowBuilder
|
|
36
|
+
>>> from attune.workflows.routing import BalancedRouting
|
|
37
|
+
>>>
|
|
38
|
+
>>> workflow = (
|
|
39
|
+
... WorkflowBuilder(TestGenerationWorkflow)
|
|
40
|
+
... .with_config(my_config)
|
|
41
|
+
... .with_routing(BalancedRouting(budget=10.0))
|
|
42
|
+
... .with_cache_enabled(True)
|
|
43
|
+
... .with_telemetry_enabled(True)
|
|
44
|
+
... .build()
|
|
45
|
+
... )
|
|
46
|
+
|
|
47
|
+
Chaining methods:
|
|
48
|
+
- with_config() - Set workflow configuration
|
|
49
|
+
- with_executor() - Set custom LLM executor
|
|
50
|
+
- with_provider() - Set model provider
|
|
51
|
+
- with_cache() - Set custom cache instance
|
|
52
|
+
- with_cache_enabled() - Enable/disable caching
|
|
53
|
+
- with_telemetry() - Set custom telemetry backend
|
|
54
|
+
- with_telemetry_enabled() - Enable/disable telemetry
|
|
55
|
+
- with_progress_callback() - Set progress callback
|
|
56
|
+
- with_tier_tracker() - Set tier tracker
|
|
57
|
+
- with_routing() - Set routing strategy
|
|
58
|
+
- build() - Construct the workflow
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(self, workflow_class: type[T]):
|
|
62
|
+
"""Initialize builder for a specific workflow class.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
workflow_class: The workflow class to build (e.g., TestGenerationWorkflow)
|
|
66
|
+
"""
|
|
67
|
+
self.workflow_class = workflow_class
|
|
68
|
+
|
|
69
|
+
# Optional configuration
|
|
70
|
+
self._config: WorkflowConfig | None = None
|
|
71
|
+
self._executor: LLMExecutor | None = None
|
|
72
|
+
self._provider: UnifiedModelProvider | None = None
|
|
73
|
+
self._cache: BaseCache | None = None
|
|
74
|
+
self._enable_cache: bool = True
|
|
75
|
+
self._telemetry_backend: TelemetryBackend | None = None
|
|
76
|
+
self._enable_telemetry: bool = True
|
|
77
|
+
self._progress_callback: ProgressCallback | None = None
|
|
78
|
+
self._tier_tracker: WorkflowTierTracker | None = None
|
|
79
|
+
self._routing_strategy: TierRoutingStrategy | None = None
|
|
80
|
+
|
|
81
|
+
def with_config(self, config: WorkflowConfig) -> WorkflowBuilder[T]:
|
|
82
|
+
"""Set workflow configuration.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
config: WorkflowConfig instance with provider, models, etc.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Self for method chaining
|
|
89
|
+
"""
|
|
90
|
+
self._config = config
|
|
91
|
+
return self
|
|
92
|
+
|
|
93
|
+
def with_executor(self, executor: LLMExecutor) -> WorkflowBuilder[T]:
|
|
94
|
+
"""Set custom LLM executor.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
executor: LLMExecutor instance for making LLM calls
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Self for method chaining
|
|
101
|
+
"""
|
|
102
|
+
self._executor = executor
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
def with_provider(self, provider: UnifiedModelProvider) -> WorkflowBuilder[T]:
|
|
106
|
+
"""Set model provider.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
provider: ModelProvider enum (ANTHROPIC, OPENAI, GOOGLE)
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Self for method chaining
|
|
113
|
+
"""
|
|
114
|
+
self._provider = provider
|
|
115
|
+
return self
|
|
116
|
+
|
|
117
|
+
def with_cache(self, cache: BaseCache) -> WorkflowBuilder[T]:
|
|
118
|
+
"""Set custom cache instance.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
cache: BaseCache instance for caching LLM responses
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Self for method chaining
|
|
125
|
+
"""
|
|
126
|
+
self._cache = cache
|
|
127
|
+
return self
|
|
128
|
+
|
|
129
|
+
def with_cache_enabled(self, enabled: bool) -> WorkflowBuilder[T]:
|
|
130
|
+
"""Enable or disable caching.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
enabled: Whether to enable caching (default: True)
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Self for method chaining
|
|
137
|
+
"""
|
|
138
|
+
self._enable_cache = enabled
|
|
139
|
+
return self
|
|
140
|
+
|
|
141
|
+
def with_telemetry(self, backend: TelemetryBackend) -> WorkflowBuilder[T]:
|
|
142
|
+
"""Set custom telemetry backend.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
backend: TelemetryBackend instance for tracking workflow runs
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
Self for method chaining
|
|
149
|
+
"""
|
|
150
|
+
self._telemetry_backend = backend
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
def with_telemetry_enabled(self, enabled: bool) -> WorkflowBuilder[T]:
|
|
154
|
+
"""Enable or disable telemetry.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
enabled: Whether to enable telemetry (default: True)
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Self for method chaining
|
|
161
|
+
"""
|
|
162
|
+
self._enable_telemetry = enabled
|
|
163
|
+
return self
|
|
164
|
+
|
|
165
|
+
def with_progress_callback(
|
|
166
|
+
self, callback: ProgressCallback | Callable[[str, int, int], None]
|
|
167
|
+
) -> WorkflowBuilder[T]:
|
|
168
|
+
"""Set progress callback for workflow execution.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
callback: ProgressCallback instance or callable(stage, current, total)
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Self for method chaining
|
|
175
|
+
"""
|
|
176
|
+
self._progress_callback = callback # type: ignore
|
|
177
|
+
return self
|
|
178
|
+
|
|
179
|
+
def with_tier_tracker(self, tracker: WorkflowTierTracker) -> WorkflowBuilder[T]:
|
|
180
|
+
"""Set tier tracker for learning tier progression.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
tracker: WorkflowTierTracker instance
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Self for method chaining
|
|
187
|
+
"""
|
|
188
|
+
self._tier_tracker = tracker
|
|
189
|
+
return self
|
|
190
|
+
|
|
191
|
+
def with_routing(self, strategy: TierRoutingStrategy) -> WorkflowBuilder[T]:
|
|
192
|
+
"""Set tier routing strategy.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
strategy: TierRoutingStrategy (CostOptimized, PerformanceOptimized, Balanced)
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Self for method chaining
|
|
199
|
+
|
|
200
|
+
Example:
|
|
201
|
+
>>> from attune.workflows.routing import BalancedRouting
|
|
202
|
+
>>> builder.with_routing(BalancedRouting(budget=50.0))
|
|
203
|
+
"""
|
|
204
|
+
self._routing_strategy = strategy
|
|
205
|
+
return self
|
|
206
|
+
|
|
207
|
+
def build(self) -> T:
|
|
208
|
+
"""Build the configured workflow.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Configured workflow instance ready for execution
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
TypeError: If workflow_class constructor doesn't accept the provided parameters
|
|
215
|
+
"""
|
|
216
|
+
# Build kwargs for constructor
|
|
217
|
+
kwargs: dict[str, Any] = {}
|
|
218
|
+
|
|
219
|
+
if self._config is not None:
|
|
220
|
+
kwargs["config"] = self._config
|
|
221
|
+
|
|
222
|
+
if self._executor is not None:
|
|
223
|
+
kwargs["executor"] = self._executor
|
|
224
|
+
|
|
225
|
+
if self._provider is not None:
|
|
226
|
+
kwargs["provider"] = self._provider
|
|
227
|
+
|
|
228
|
+
if self._cache is not None:
|
|
229
|
+
kwargs["cache"] = self._cache
|
|
230
|
+
|
|
231
|
+
kwargs["enable_cache"] = self._enable_cache
|
|
232
|
+
|
|
233
|
+
if self._telemetry_backend is not None:
|
|
234
|
+
kwargs["telemetry_backend"] = self._telemetry_backend
|
|
235
|
+
|
|
236
|
+
kwargs["enable_telemetry"] = self._enable_telemetry
|
|
237
|
+
|
|
238
|
+
if self._progress_callback is not None:
|
|
239
|
+
kwargs["progress_callback"] = self._progress_callback
|
|
240
|
+
|
|
241
|
+
if self._tier_tracker is not None:
|
|
242
|
+
kwargs["tier_tracker"] = self._tier_tracker
|
|
243
|
+
|
|
244
|
+
if self._routing_strategy is not None:
|
|
245
|
+
kwargs["routing_strategy"] = self._routing_strategy
|
|
246
|
+
|
|
247
|
+
# Construct workflow
|
|
248
|
+
return self.workflow_class(**kwargs)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def workflow_builder(workflow_class: type[T]) -> WorkflowBuilder[T]:
|
|
252
|
+
"""Factory function for creating workflow builders.
|
|
253
|
+
|
|
254
|
+
Convenience function for creating builders with cleaner syntax.
|
|
255
|
+
|
|
256
|
+
Args:
|
|
257
|
+
workflow_class: The workflow class to build
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
WorkflowBuilder instance
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
>>> from attune.workflows.builder import workflow_builder
|
|
264
|
+
>>> from attune.workflows.test_gen import TestGenerationWorkflow
|
|
265
|
+
>>>
|
|
266
|
+
>>> workflow = (
|
|
267
|
+
... workflow_builder(TestGenerationWorkflow)
|
|
268
|
+
... .with_cache_enabled(True)
|
|
269
|
+
... .with_telemetry_enabled(False)
|
|
270
|
+
... .build()
|
|
271
|
+
... )
|
|
272
|
+
"""
|
|
273
|
+
return WorkflowBuilder(workflow_class)
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"""Caching Mixin for Workflow LLM Calls
|
|
2
|
+
|
|
3
|
+
Extracted from BaseWorkflow to improve maintainability and reusability.
|
|
4
|
+
Provides caching behavior for LLM calls with automatic cache setup.
|
|
5
|
+
|
|
6
|
+
Copyright 2025 Smart-AI-Memory
|
|
7
|
+
Licensed under Fair Source License 0.9
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from attune.cache import BaseCache
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class CachedResponse:
|
|
24
|
+
"""Cached LLM response data."""
|
|
25
|
+
|
|
26
|
+
content: str
|
|
27
|
+
input_tokens: int
|
|
28
|
+
output_tokens: int
|
|
29
|
+
|
|
30
|
+
def to_dict(self) -> dict[str, Any]:
|
|
31
|
+
"""Convert to dictionary for cache storage."""
|
|
32
|
+
return {
|
|
33
|
+
"content": self.content,
|
|
34
|
+
"input_tokens": self.input_tokens,
|
|
35
|
+
"output_tokens": self.output_tokens,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_dict(cls, data: dict[str, Any]) -> CachedResponse:
|
|
40
|
+
"""Create from dictionary (cache retrieval)."""
|
|
41
|
+
return cls(
|
|
42
|
+
content=data["content"],
|
|
43
|
+
input_tokens=data["input_tokens"],
|
|
44
|
+
output_tokens=data["output_tokens"],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@runtime_checkable
|
|
49
|
+
class CacheAwareWorkflow(Protocol):
|
|
50
|
+
"""Protocol for workflows that support caching."""
|
|
51
|
+
|
|
52
|
+
name: str
|
|
53
|
+
_cache: BaseCache | None
|
|
54
|
+
_enable_cache: bool
|
|
55
|
+
|
|
56
|
+
def get_model_for_tier(self, tier: Any) -> str:
|
|
57
|
+
"""Get model ID for a given tier."""
|
|
58
|
+
...
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class CachingMixin:
|
|
62
|
+
"""Mixin that provides caching behavior for LLM calls.
|
|
63
|
+
|
|
64
|
+
This mixin extracts caching logic from BaseWorkflow to improve
|
|
65
|
+
maintainability and enable reuse in other contexts.
|
|
66
|
+
|
|
67
|
+
Attributes:
|
|
68
|
+
_cache: Optional cache instance
|
|
69
|
+
_enable_cache: Whether caching is enabled
|
|
70
|
+
_cache_setup_attempted: Whether cache setup has been tried
|
|
71
|
+
|
|
72
|
+
Usage:
|
|
73
|
+
class MyWorkflow(CachingMixin, BaseWorkflow):
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
# CachingMixin methods are now available
|
|
77
|
+
workflow._maybe_setup_cache()
|
|
78
|
+
cached = workflow._try_cache_lookup(...)
|
|
79
|
+
workflow._store_in_cache(...)
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
# Instance variables (set by __init__ or subclass)
|
|
83
|
+
_cache: BaseCache | None = None
|
|
84
|
+
_enable_cache: bool = True
|
|
85
|
+
_cache_setup_attempted: bool = False
|
|
86
|
+
|
|
87
|
+
# These must be provided by the class using this mixin
|
|
88
|
+
name: str = "unknown"
|
|
89
|
+
|
|
90
|
+
def _maybe_setup_cache(self) -> None:
|
|
91
|
+
"""Set up cache with one-time user prompt if needed.
|
|
92
|
+
|
|
93
|
+
This is called lazily on first workflow execution to avoid
|
|
94
|
+
blocking workflow initialization.
|
|
95
|
+
"""
|
|
96
|
+
if not self._enable_cache:
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
if self._cache_setup_attempted:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
self._cache_setup_attempted = True
|
|
103
|
+
|
|
104
|
+
# If cache already provided, use it
|
|
105
|
+
if self._cache is not None:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
# Import here to avoid circular imports
|
|
109
|
+
from attune.cache import auto_setup_cache, create_cache
|
|
110
|
+
|
|
111
|
+
# Otherwise, trigger auto-setup (which may prompt user)
|
|
112
|
+
try:
|
|
113
|
+
auto_setup_cache()
|
|
114
|
+
self._cache = create_cache()
|
|
115
|
+
logger.info(f"Cache initialized for workflow: {self.name}")
|
|
116
|
+
except ImportError as e:
|
|
117
|
+
# Hybrid cache dependencies not available, fall back to hash-only
|
|
118
|
+
logger.info(
|
|
119
|
+
f"Using hash-only cache (install empathy-framework[cache] for semantic caching): {e}"
|
|
120
|
+
)
|
|
121
|
+
self._cache = create_cache(cache_type="hash")
|
|
122
|
+
except (OSError, PermissionError) as e:
|
|
123
|
+
# File system errors - disable cache
|
|
124
|
+
logger.warning(f"Cache setup failed (file system error): {e}, continuing without cache")
|
|
125
|
+
self._enable_cache = False
|
|
126
|
+
except (ValueError, TypeError, AttributeError) as e:
|
|
127
|
+
# Configuration errors - disable cache
|
|
128
|
+
logger.warning(f"Cache setup failed (config error): {e}, continuing without cache")
|
|
129
|
+
self._enable_cache = False
|
|
130
|
+
|
|
131
|
+
def _make_cache_key(self, system: str, user_message: str) -> str:
|
|
132
|
+
"""Create cache key from system and user prompts.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
system: System prompt
|
|
136
|
+
user_message: User message
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Combined prompt string for cache key
|
|
140
|
+
"""
|
|
141
|
+
return f"{system}\n\n{user_message}" if system else user_message
|
|
142
|
+
|
|
143
|
+
def _try_cache_lookup(
|
|
144
|
+
self,
|
|
145
|
+
stage: str,
|
|
146
|
+
system: str,
|
|
147
|
+
user_message: str,
|
|
148
|
+
model: str,
|
|
149
|
+
) -> CachedResponse | None:
|
|
150
|
+
"""Try to retrieve a cached response.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
stage: Stage name for cache key
|
|
154
|
+
system: System prompt
|
|
155
|
+
user_message: User message
|
|
156
|
+
model: Model ID
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
CachedResponse if found, None otherwise
|
|
160
|
+
"""
|
|
161
|
+
if not self._enable_cache or self._cache is None:
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
full_prompt = self._make_cache_key(system, user_message)
|
|
166
|
+
cached_data = self._cache.get(self.name, stage, full_prompt, model)
|
|
167
|
+
|
|
168
|
+
if cached_data is not None:
|
|
169
|
+
logger.debug(f"Cache hit for {self.name}:{stage}")
|
|
170
|
+
return CachedResponse.from_dict(cached_data)
|
|
171
|
+
|
|
172
|
+
except (KeyError, TypeError, ValueError) as e:
|
|
173
|
+
# Malformed cache data - continue with LLM call
|
|
174
|
+
logger.debug(f"Cache lookup failed (malformed data): {e}, continuing with LLM call")
|
|
175
|
+
except (OSError, PermissionError) as e:
|
|
176
|
+
# File system errors - continue with LLM call
|
|
177
|
+
logger.debug(f"Cache lookup failed (file system error): {e}, continuing with LLM call")
|
|
178
|
+
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
def _store_in_cache(
|
|
182
|
+
self,
|
|
183
|
+
stage: str,
|
|
184
|
+
system: str,
|
|
185
|
+
user_message: str,
|
|
186
|
+
model: str,
|
|
187
|
+
response: CachedResponse,
|
|
188
|
+
) -> bool:
|
|
189
|
+
"""Store a response in the cache.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
stage: Stage name for cache key
|
|
193
|
+
system: System prompt
|
|
194
|
+
user_message: User message
|
|
195
|
+
model: Model ID
|
|
196
|
+
response: Response to cache
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
True if stored successfully, False otherwise
|
|
200
|
+
"""
|
|
201
|
+
if not self._enable_cache or self._cache is None:
|
|
202
|
+
return False
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
full_prompt = self._make_cache_key(system, user_message)
|
|
206
|
+
self._cache.put(self.name, stage, full_prompt, model, response.to_dict())
|
|
207
|
+
logger.debug(f"Cached response for {self.name}:{stage}")
|
|
208
|
+
return True
|
|
209
|
+
except (OSError, PermissionError) as e:
|
|
210
|
+
# File system errors - log but continue
|
|
211
|
+
logger.debug(f"Failed to cache response (file system error): {e}")
|
|
212
|
+
except (ValueError, TypeError, KeyError) as e:
|
|
213
|
+
# Data serialization errors - log but continue
|
|
214
|
+
logger.debug(f"Failed to cache response (serialization error): {e}")
|
|
215
|
+
|
|
216
|
+
return False
|
|
217
|
+
|
|
218
|
+
def _get_cache_type(self) -> str:
|
|
219
|
+
"""Get the cache type for telemetry tracking.
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Cache type string (e.g., "hash", "semantic")
|
|
223
|
+
"""
|
|
224
|
+
if self._cache is None:
|
|
225
|
+
return "none"
|
|
226
|
+
|
|
227
|
+
if hasattr(self._cache, "cache_type"):
|
|
228
|
+
ct = self._cache.cache_type
|
|
229
|
+
# Ensure it's a string (not a Mock object)
|
|
230
|
+
return str(ct) if ct and isinstance(ct, str) else "hash"
|
|
231
|
+
|
|
232
|
+
return "hash" # Default assumption
|
|
233
|
+
|
|
234
|
+
def _get_cache_stats(self) -> dict[str, Any]:
|
|
235
|
+
"""Get cache statistics for cost reporting.
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Dictionary with cache stats (hits, misses, hit_rate)
|
|
239
|
+
"""
|
|
240
|
+
if self._cache is None:
|
|
241
|
+
return {"hits": 0, "misses": 0, "hit_rate": 0.0}
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
stats = self._cache.get_stats()
|
|
245
|
+
return {
|
|
246
|
+
"hits": stats.hits,
|
|
247
|
+
"misses": stats.misses,
|
|
248
|
+
"hit_rate": stats.hit_rate,
|
|
249
|
+
}
|
|
250
|
+
except (AttributeError, TypeError) as e:
|
|
251
|
+
# Cache doesn't support stats
|
|
252
|
+
logger.debug(f"Cache stats not available: {e}")
|
|
253
|
+
return {"hits": 0, "misses": 0, "hit_rate": 0.0}
|