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,414 @@
|
|
|
1
|
+
"""Agent Monitoring for Distributed Memory Networks
|
|
2
|
+
|
|
3
|
+
Provides monitoring and metrics collection for multi-agent systems.
|
|
4
|
+
Tracks individual agent performance, pattern contributions, and
|
|
5
|
+
team-wide collaboration metrics.
|
|
6
|
+
|
|
7
|
+
Key metrics tracked:
|
|
8
|
+
- Agent interaction counts and response times
|
|
9
|
+
- Pattern discovery and reuse rates
|
|
10
|
+
- Cross-agent collaboration efficiency
|
|
11
|
+
- System health and performance
|
|
12
|
+
|
|
13
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
14
|
+
Licensed under Fair Source 0.9
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from .pattern_library import PatternLibrary
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class AgentMetrics:
|
|
26
|
+
"""Metrics for a single agent"""
|
|
27
|
+
|
|
28
|
+
agent_id: str
|
|
29
|
+
total_interactions: int = 0
|
|
30
|
+
total_response_time_ms: float = 0.0
|
|
31
|
+
patterns_discovered: int = 0
|
|
32
|
+
patterns_used: int = 0
|
|
33
|
+
successful_pattern_uses: int = 0
|
|
34
|
+
failed_pattern_uses: int = 0
|
|
35
|
+
first_seen: datetime = field(default_factory=datetime.now)
|
|
36
|
+
last_active: datetime = field(default_factory=datetime.now)
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def avg_response_time_ms(self) -> float:
|
|
40
|
+
"""Average response time in milliseconds"""
|
|
41
|
+
if self.total_interactions == 0:
|
|
42
|
+
return 0.0
|
|
43
|
+
return self.total_response_time_ms / self.total_interactions
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def success_rate(self) -> float:
|
|
47
|
+
"""Pattern usage success rate"""
|
|
48
|
+
total = self.successful_pattern_uses + self.failed_pattern_uses
|
|
49
|
+
if total == 0:
|
|
50
|
+
return 0.0
|
|
51
|
+
return self.successful_pattern_uses / total
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def pattern_contribution_rate(self) -> float:
|
|
55
|
+
"""Rate of pattern discovery per interaction"""
|
|
56
|
+
if self.total_interactions == 0:
|
|
57
|
+
return 0.0
|
|
58
|
+
return self.patterns_discovered / self.total_interactions
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class TeamMetrics:
|
|
63
|
+
"""Aggregated metrics for an agent team"""
|
|
64
|
+
|
|
65
|
+
active_agents: int = 0
|
|
66
|
+
total_agents: int = 0
|
|
67
|
+
shared_patterns: int = 0
|
|
68
|
+
total_interactions: int = 0
|
|
69
|
+
pattern_reuse_count: int = 0
|
|
70
|
+
cross_agent_reuses: int = 0
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def pattern_reuse_rate(self) -> float:
|
|
74
|
+
"""Rate at which patterns are reused"""
|
|
75
|
+
if self.shared_patterns == 0:
|
|
76
|
+
return 0.0
|
|
77
|
+
return self.pattern_reuse_count / self.shared_patterns
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def collaboration_efficiency(self) -> float:
|
|
81
|
+
"""Measure of how effectively agents collaborate.
|
|
82
|
+
|
|
83
|
+
Higher values indicate more cross-agent pattern reuse,
|
|
84
|
+
meaning agents are learning from each other.
|
|
85
|
+
"""
|
|
86
|
+
if self.pattern_reuse_count == 0:
|
|
87
|
+
return 0.0
|
|
88
|
+
return self.cross_agent_reuses / self.pattern_reuse_count
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class AgentMonitor:
|
|
92
|
+
"""Monitors and tracks metrics for multi-agent systems.
|
|
93
|
+
|
|
94
|
+
Provides insights into:
|
|
95
|
+
- Individual agent performance
|
|
96
|
+
- Pattern discovery and sharing
|
|
97
|
+
- Team collaboration effectiveness
|
|
98
|
+
- System health
|
|
99
|
+
|
|
100
|
+
Example:
|
|
101
|
+
>>> monitor = AgentMonitor()
|
|
102
|
+
>>>
|
|
103
|
+
>>> # Record agent activity
|
|
104
|
+
>>> monitor.record_interaction("code_reviewer", response_time_ms=150.0)
|
|
105
|
+
>>> monitor.record_pattern_discovery("code_reviewer")
|
|
106
|
+
>>> monitor.record_pattern_use("test_gen", pattern_agent="code_reviewer", success=True)
|
|
107
|
+
>>>
|
|
108
|
+
>>> # Get individual stats
|
|
109
|
+
>>> stats = monitor.get_agent_stats("code_reviewer")
|
|
110
|
+
>>> print(f"Interactions: {stats['total_interactions']}")
|
|
111
|
+
>>> print(f"Patterns discovered: {stats['patterns_discovered']}")
|
|
112
|
+
>>>
|
|
113
|
+
>>> # Get team stats
|
|
114
|
+
>>> team = monitor.get_team_stats()
|
|
115
|
+
>>> print(f"Collaboration efficiency: {team['collaboration_efficiency']:.0%}")
|
|
116
|
+
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def __init__(self, pattern_library: PatternLibrary | None = None):
|
|
120
|
+
"""Initialize the AgentMonitor.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
pattern_library: Optional pattern library to track for shared patterns
|
|
124
|
+
|
|
125
|
+
"""
|
|
126
|
+
self.agents: dict[str, AgentMetrics] = {}
|
|
127
|
+
self.pattern_library = pattern_library
|
|
128
|
+
|
|
129
|
+
# Track pattern reuse events
|
|
130
|
+
self.pattern_uses: list[dict[str, Any]] = []
|
|
131
|
+
|
|
132
|
+
# Track alerts
|
|
133
|
+
self.alerts: list[dict[str, Any]] = []
|
|
134
|
+
|
|
135
|
+
def record_interaction(
|
|
136
|
+
self,
|
|
137
|
+
agent_id: str,
|
|
138
|
+
response_time_ms: float = 0.0,
|
|
139
|
+
):
|
|
140
|
+
"""Record an agent interaction.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
agent_id: ID of the agent
|
|
144
|
+
response_time_ms: Response time in milliseconds
|
|
145
|
+
|
|
146
|
+
"""
|
|
147
|
+
agent = self._get_or_create_agent(agent_id)
|
|
148
|
+
agent.total_interactions += 1
|
|
149
|
+
agent.total_response_time_ms += response_time_ms
|
|
150
|
+
agent.last_active = datetime.now()
|
|
151
|
+
|
|
152
|
+
# Check for performance alerts
|
|
153
|
+
if response_time_ms > 5000: # Over 5 seconds
|
|
154
|
+
self._add_alert(
|
|
155
|
+
agent_id=agent_id,
|
|
156
|
+
alert_type="slow_response",
|
|
157
|
+
message=f"Agent {agent_id} response time: {response_time_ms:.0f}ms",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def record_pattern_discovery(self, agent_id: str, pattern_id: str | None = None):
|
|
161
|
+
"""Record that an agent discovered a new pattern.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
agent_id: ID of the agent that discovered the pattern
|
|
165
|
+
pattern_id: Optional pattern ID for tracking
|
|
166
|
+
|
|
167
|
+
"""
|
|
168
|
+
agent = self._get_or_create_agent(agent_id)
|
|
169
|
+
agent.patterns_discovered += 1
|
|
170
|
+
agent.last_active = datetime.now()
|
|
171
|
+
|
|
172
|
+
def record_pattern_use(
|
|
173
|
+
self,
|
|
174
|
+
agent_id: str,
|
|
175
|
+
pattern_id: str | None = None,
|
|
176
|
+
pattern_agent: str | None = None,
|
|
177
|
+
success: bool = True,
|
|
178
|
+
):
|
|
179
|
+
"""Record that an agent used a pattern.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
agent_id: ID of the agent using the pattern
|
|
183
|
+
pattern_id: ID of the pattern being used
|
|
184
|
+
pattern_agent: ID of the agent that contributed the pattern
|
|
185
|
+
success: Whether the pattern use was successful
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
agent = self._get_or_create_agent(agent_id)
|
|
189
|
+
agent.patterns_used += 1
|
|
190
|
+
|
|
191
|
+
if success:
|
|
192
|
+
agent.successful_pattern_uses += 1
|
|
193
|
+
else:
|
|
194
|
+
agent.failed_pattern_uses += 1
|
|
195
|
+
|
|
196
|
+
agent.last_active = datetime.now()
|
|
197
|
+
|
|
198
|
+
# Track cross-agent pattern reuse
|
|
199
|
+
is_cross_agent = pattern_agent is not None and pattern_agent != agent_id
|
|
200
|
+
self.pattern_uses.append(
|
|
201
|
+
{
|
|
202
|
+
"user_agent": agent_id,
|
|
203
|
+
"pattern_agent": pattern_agent,
|
|
204
|
+
"pattern_id": pattern_id,
|
|
205
|
+
"cross_agent": is_cross_agent,
|
|
206
|
+
"success": success,
|
|
207
|
+
"timestamp": datetime.now(),
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def get_agent_stats(self, agent_id: str) -> dict[str, Any]:
|
|
212
|
+
"""Get statistics for a specific agent.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
agent_id: ID of the agent
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Dictionary with agent statistics
|
|
219
|
+
|
|
220
|
+
"""
|
|
221
|
+
agent = self.agents.get(agent_id)
|
|
222
|
+
|
|
223
|
+
if agent is None:
|
|
224
|
+
return {
|
|
225
|
+
"agent_id": agent_id,
|
|
226
|
+
"total_interactions": 0,
|
|
227
|
+
"avg_response_time_ms": 0.0,
|
|
228
|
+
"patterns_discovered": 0,
|
|
229
|
+
"patterns_used": 0,
|
|
230
|
+
"success_rate": 0.0,
|
|
231
|
+
"status": "unknown",
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
# Determine agent status
|
|
235
|
+
inactive_threshold = 3600 # 1 hour
|
|
236
|
+
seconds_inactive = (datetime.now() - agent.last_active).total_seconds()
|
|
237
|
+
status = "active" if seconds_inactive < inactive_threshold else "inactive"
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
"agent_id": agent_id,
|
|
241
|
+
"total_interactions": agent.total_interactions,
|
|
242
|
+
"avg_response_time_ms": agent.avg_response_time_ms,
|
|
243
|
+
"patterns_discovered": agent.patterns_discovered,
|
|
244
|
+
"patterns_used": agent.patterns_used,
|
|
245
|
+
"success_rate": agent.success_rate,
|
|
246
|
+
"pattern_contribution_rate": agent.pattern_contribution_rate,
|
|
247
|
+
"first_seen": agent.first_seen.isoformat(),
|
|
248
|
+
"last_active": agent.last_active.isoformat(),
|
|
249
|
+
"status": status,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
def get_team_stats(self) -> dict[str, Any]:
|
|
253
|
+
"""Get aggregated statistics for the entire agent team.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Dictionary with team-wide statistics
|
|
257
|
+
|
|
258
|
+
"""
|
|
259
|
+
if not self.agents:
|
|
260
|
+
# Get shared patterns count from library even if no agents
|
|
261
|
+
shared_patterns = 0
|
|
262
|
+
if self.pattern_library:
|
|
263
|
+
shared_patterns = len(self.pattern_library.patterns)
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
"active_agents": 0,
|
|
267
|
+
"total_agents": 0,
|
|
268
|
+
"shared_patterns": shared_patterns,
|
|
269
|
+
"total_interactions": 0,
|
|
270
|
+
"total_patterns_discovered": 0,
|
|
271
|
+
"pattern_reuse_count": 0,
|
|
272
|
+
"cross_agent_reuses": 0,
|
|
273
|
+
"pattern_reuse_rate": 0.0,
|
|
274
|
+
"collaboration_efficiency": 0.0,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# Count active agents (active in last hour)
|
|
278
|
+
inactive_threshold = 3600
|
|
279
|
+
now = datetime.now()
|
|
280
|
+
active_count = sum(
|
|
281
|
+
1
|
|
282
|
+
for agent in self.agents.values()
|
|
283
|
+
if (now - agent.last_active).total_seconds() < inactive_threshold
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Calculate totals
|
|
287
|
+
total_interactions = sum(a.total_interactions for a in self.agents.values())
|
|
288
|
+
total_patterns_discovered = sum(a.patterns_discovered for a in self.agents.values())
|
|
289
|
+
|
|
290
|
+
# Calculate pattern reuse metrics
|
|
291
|
+
pattern_reuse_count = len(self.pattern_uses)
|
|
292
|
+
cross_agent_reuses = sum(1 for use in self.pattern_uses if use["cross_agent"])
|
|
293
|
+
|
|
294
|
+
# Get shared patterns count from library if available
|
|
295
|
+
shared_patterns = 0
|
|
296
|
+
if self.pattern_library:
|
|
297
|
+
shared_patterns = len(self.pattern_library.patterns)
|
|
298
|
+
else:
|
|
299
|
+
shared_patterns = total_patterns_discovered
|
|
300
|
+
|
|
301
|
+
# Calculate rates
|
|
302
|
+
reuse_rate = pattern_reuse_count / shared_patterns if shared_patterns > 0 else 0.0
|
|
303
|
+
collab_efficiency = (
|
|
304
|
+
cross_agent_reuses / pattern_reuse_count if pattern_reuse_count > 0 else 0.0
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
"active_agents": active_count,
|
|
309
|
+
"total_agents": len(self.agents),
|
|
310
|
+
"shared_patterns": shared_patterns,
|
|
311
|
+
"total_interactions": total_interactions,
|
|
312
|
+
"total_patterns_discovered": total_patterns_discovered,
|
|
313
|
+
"pattern_reuse_count": pattern_reuse_count,
|
|
314
|
+
"cross_agent_reuses": cross_agent_reuses,
|
|
315
|
+
"pattern_reuse_rate": reuse_rate,
|
|
316
|
+
"collaboration_efficiency": collab_efficiency,
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
def get_top_contributors(self, n: int = 5) -> list[dict[str, Any]]:
|
|
320
|
+
"""Get the top pattern-contributing agents.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
n: Number of agents to return
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
List of agent stats, sorted by patterns discovered
|
|
327
|
+
|
|
328
|
+
"""
|
|
329
|
+
sorted_agents = sorted(
|
|
330
|
+
self.agents.values(),
|
|
331
|
+
key=lambda a: a.patterns_discovered,
|
|
332
|
+
reverse=True,
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
return [self.get_agent_stats(agent.agent_id) for agent in sorted_agents[:n]]
|
|
336
|
+
|
|
337
|
+
def get_alerts(self, limit: int = 100) -> list[dict[str, Any]]:
|
|
338
|
+
"""Get recent alerts.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
limit: Maximum number of alerts to return
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
List of alert dictionaries
|
|
345
|
+
|
|
346
|
+
"""
|
|
347
|
+
return self.alerts[-limit:]
|
|
348
|
+
|
|
349
|
+
def check_health(self) -> dict[str, Any]:
|
|
350
|
+
"""Check overall system health.
|
|
351
|
+
|
|
352
|
+
Returns:
|
|
353
|
+
Health status dictionary
|
|
354
|
+
|
|
355
|
+
"""
|
|
356
|
+
team_stats = self.get_team_stats()
|
|
357
|
+
recent_alerts = [
|
|
358
|
+
a for a in self.alerts if (datetime.now() - a["timestamp"]).total_seconds() < 3600
|
|
359
|
+
]
|
|
360
|
+
|
|
361
|
+
# Determine health status
|
|
362
|
+
issues = []
|
|
363
|
+
if team_stats["active_agents"] == 0:
|
|
364
|
+
issues.append("No active agents")
|
|
365
|
+
if team_stats["collaboration_efficiency"] < 0.1 and team_stats["pattern_reuse_count"] > 10:
|
|
366
|
+
issues.append("Low collaboration efficiency")
|
|
367
|
+
if len(recent_alerts) > 10:
|
|
368
|
+
issues.append("High alert volume")
|
|
369
|
+
|
|
370
|
+
status = "healthy"
|
|
371
|
+
if issues:
|
|
372
|
+
status = "degraded" if len(issues) == 1 else "unhealthy"
|
|
373
|
+
|
|
374
|
+
return {
|
|
375
|
+
"status": status,
|
|
376
|
+
"issues": issues,
|
|
377
|
+
"active_agents": team_stats["active_agents"],
|
|
378
|
+
"recent_alerts": len(recent_alerts),
|
|
379
|
+
"timestamp": datetime.now().isoformat(),
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
def _get_or_create_agent(self, agent_id: str) -> AgentMetrics:
|
|
383
|
+
"""Get existing agent metrics or create new"""
|
|
384
|
+
if agent_id not in self.agents:
|
|
385
|
+
self.agents[agent_id] = AgentMetrics(agent_id=agent_id)
|
|
386
|
+
return self.agents[agent_id]
|
|
387
|
+
|
|
388
|
+
def _add_alert(
|
|
389
|
+
self,
|
|
390
|
+
agent_id: str,
|
|
391
|
+
alert_type: str,
|
|
392
|
+
message: str,
|
|
393
|
+
severity: str = "warning",
|
|
394
|
+
):
|
|
395
|
+
"""Add an alert to the monitoring system"""
|
|
396
|
+
self.alerts.append(
|
|
397
|
+
{
|
|
398
|
+
"agent_id": agent_id,
|
|
399
|
+
"alert_type": alert_type,
|
|
400
|
+
"message": message,
|
|
401
|
+
"severity": severity,
|
|
402
|
+
"timestamp": datetime.now(),
|
|
403
|
+
},
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# Keep alerts bounded
|
|
407
|
+
if len(self.alerts) > 1000:
|
|
408
|
+
self.alerts = self.alerts[-500:]
|
|
409
|
+
|
|
410
|
+
def reset(self):
|
|
411
|
+
"""Reset all monitoring data"""
|
|
412
|
+
self.agents = {}
|
|
413
|
+
self.pattern_uses = []
|
|
414
|
+
self.alerts = []
|
attune/cache/__init__.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Response caching for Empathy Framework workflows.
|
|
2
|
+
|
|
3
|
+
Provides hybrid hash + semantic similarity caching to reduce API costs by 70%.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
from attune.cache import create_cache
|
|
7
|
+
|
|
8
|
+
# Auto-detect best cache (hybrid if deps available, hash-only otherwise)
|
|
9
|
+
cache = create_cache()
|
|
10
|
+
|
|
11
|
+
# Manual cache selection
|
|
12
|
+
from attune.cache import HashOnlyCache, HybridCache
|
|
13
|
+
|
|
14
|
+
cache = HashOnlyCache() # Always available
|
|
15
|
+
cache = HybridCache() # Requires sentence-transformers
|
|
16
|
+
|
|
17
|
+
Copyright 2025 Smart-AI-Memory
|
|
18
|
+
Licensed under Fair Source License 0.9
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import logging
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
from .base import BaseCache, CacheEntry, CacheStats
|
|
25
|
+
from .hash_only import HashOnlyCache
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
# Try to import HybridCache (requires optional dependencies)
|
|
30
|
+
try:
|
|
31
|
+
from .hybrid import HybridCache
|
|
32
|
+
|
|
33
|
+
HYBRID_AVAILABLE = True
|
|
34
|
+
except ImportError:
|
|
35
|
+
HYBRID_AVAILABLE = False
|
|
36
|
+
HybridCache = None # type: ignore
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_cache(
|
|
40
|
+
cache_type: str | None = None,
|
|
41
|
+
**kwargs,
|
|
42
|
+
) -> BaseCache:
|
|
43
|
+
"""Create appropriate cache based on available dependencies.
|
|
44
|
+
|
|
45
|
+
Auto-detects if sentence-transformers is available and creates
|
|
46
|
+
HybridCache if possible, otherwise falls back to HashOnlyCache.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
cache_type: Force specific cache type ("hash" | "hybrid" | None for auto).
|
|
50
|
+
**kwargs: Additional arguments passed to cache constructor.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
BaseCache instance (HybridCache or HashOnlyCache).
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
# Auto-detect (recommended)
|
|
57
|
+
cache = create_cache()
|
|
58
|
+
|
|
59
|
+
# Force hash-only
|
|
60
|
+
cache = create_cache(cache_type="hash")
|
|
61
|
+
|
|
62
|
+
# Force hybrid (raises ImportError if deps missing)
|
|
63
|
+
cache = create_cache(cache_type="hybrid")
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
# Force hash-only
|
|
67
|
+
if cache_type == "hash":
|
|
68
|
+
logger.info("Using hash-only cache (explicit)")
|
|
69
|
+
return HashOnlyCache(**kwargs)
|
|
70
|
+
|
|
71
|
+
# Force hybrid
|
|
72
|
+
if cache_type == "hybrid":
|
|
73
|
+
if not HYBRID_AVAILABLE:
|
|
74
|
+
raise ImportError(
|
|
75
|
+
"HybridCache requires sentence-transformers. "
|
|
76
|
+
"Install with: pip install empathy-framework[cache]"
|
|
77
|
+
)
|
|
78
|
+
logger.info("Using hybrid cache (explicit)")
|
|
79
|
+
return HybridCache(**kwargs)
|
|
80
|
+
|
|
81
|
+
# Auto-detect (default)
|
|
82
|
+
if HYBRID_AVAILABLE:
|
|
83
|
+
logger.info("Using hybrid cache (auto-detected)")
|
|
84
|
+
return HybridCache(**kwargs)
|
|
85
|
+
else:
|
|
86
|
+
logger.info(
|
|
87
|
+
"Using hash-only cache (sentence-transformers not available). "
|
|
88
|
+
"For 70% cost savings, install with: pip install empathy-framework[cache]"
|
|
89
|
+
)
|
|
90
|
+
return HashOnlyCache(**kwargs)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def auto_setup_cache() -> None:
|
|
94
|
+
"""Auto-setup cache with one-time prompt if dependencies missing.
|
|
95
|
+
|
|
96
|
+
Called automatically by BaseWorkflow on first run.
|
|
97
|
+
Prompts user to install cache dependencies if not available.
|
|
98
|
+
|
|
99
|
+
"""
|
|
100
|
+
from .dependency_manager import DependencyManager
|
|
101
|
+
|
|
102
|
+
manager = DependencyManager()
|
|
103
|
+
|
|
104
|
+
if manager.should_prompt_cache_install():
|
|
105
|
+
manager.prompt_cache_install()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
__all__ = [
|
|
109
|
+
"BaseCache",
|
|
110
|
+
"CacheEntry",
|
|
111
|
+
"CacheStats",
|
|
112
|
+
"HashOnlyCache",
|
|
113
|
+
"HybridCache",
|
|
114
|
+
"create_cache",
|
|
115
|
+
"auto_setup_cache",
|
|
116
|
+
"HYBRID_AVAILABLE",
|
|
117
|
+
]
|
attune/cache/base.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""Base cache interface for Empathy Framework response caching.
|
|
2
|
+
|
|
3
|
+
Copyright 2025 Smart-AI-Memory
|
|
4
|
+
Licensed under Fair Source License 0.9
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class CacheEntry:
|
|
14
|
+
"""Cached LLM response with metadata."""
|
|
15
|
+
|
|
16
|
+
key: str
|
|
17
|
+
response: Any
|
|
18
|
+
workflow: str
|
|
19
|
+
stage: str
|
|
20
|
+
model: str
|
|
21
|
+
prompt_hash: str
|
|
22
|
+
timestamp: float
|
|
23
|
+
ttl: int | None = None # Time-to-live in seconds
|
|
24
|
+
|
|
25
|
+
def is_expired(self, current_time: float) -> bool:
|
|
26
|
+
"""Check if entry has expired."""
|
|
27
|
+
if self.ttl is None:
|
|
28
|
+
return False
|
|
29
|
+
return (current_time - self.timestamp) > self.ttl
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class CacheStats:
|
|
34
|
+
"""Cache hit/miss statistics."""
|
|
35
|
+
|
|
36
|
+
hits: int = 0
|
|
37
|
+
misses: int = 0
|
|
38
|
+
evictions: int = 0
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def total(self) -> int:
|
|
42
|
+
"""Total cache lookups."""
|
|
43
|
+
return self.hits + self.misses
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def hit_rate(self) -> float:
|
|
47
|
+
"""Cache hit rate percentage."""
|
|
48
|
+
if self.total == 0:
|
|
49
|
+
return 0.0
|
|
50
|
+
return (self.hits / self.total) * 100.0
|
|
51
|
+
|
|
52
|
+
def to_dict(self) -> dict[str, Any]:
|
|
53
|
+
"""Convert to dictionary for logging."""
|
|
54
|
+
return {
|
|
55
|
+
"hits": self.hits,
|
|
56
|
+
"misses": self.misses,
|
|
57
|
+
"evictions": self.evictions,
|
|
58
|
+
"total": self.total,
|
|
59
|
+
"hit_rate": round(self.hit_rate, 1),
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class BaseCache(ABC):
|
|
64
|
+
"""Abstract base class for LLM response caching."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, max_size_mb: int = 500, default_ttl: int = 86400):
|
|
67
|
+
"""Initialize cache.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
max_size_mb: Maximum cache size in megabytes.
|
|
71
|
+
default_ttl: Default time-to-live in seconds (default: 24 hours).
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
self.max_size_mb = max_size_mb
|
|
75
|
+
self.default_ttl = default_ttl
|
|
76
|
+
self.stats = CacheStats()
|
|
77
|
+
|
|
78
|
+
@abstractmethod
|
|
79
|
+
def get(
|
|
80
|
+
self,
|
|
81
|
+
workflow: str,
|
|
82
|
+
stage: str,
|
|
83
|
+
prompt: str,
|
|
84
|
+
model: str,
|
|
85
|
+
) -> Any | None:
|
|
86
|
+
"""Get cached response.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
workflow: Workflow name (e.g., "code-review").
|
|
90
|
+
stage: Stage name (e.g., "scan").
|
|
91
|
+
prompt: Prompt text.
|
|
92
|
+
model: Model identifier (e.g., "claude-3-5-sonnet-20241022").
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Cached response if found, None otherwise.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def put(
|
|
102
|
+
self,
|
|
103
|
+
workflow: str,
|
|
104
|
+
stage: str,
|
|
105
|
+
prompt: str,
|
|
106
|
+
model: str,
|
|
107
|
+
response: Any,
|
|
108
|
+
ttl: int | None = None,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Store response in cache.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
workflow: Workflow name.
|
|
114
|
+
stage: Stage name.
|
|
115
|
+
prompt: Prompt text.
|
|
116
|
+
model: Model identifier.
|
|
117
|
+
response: LLM response to cache.
|
|
118
|
+
ttl: Optional custom TTL (uses default if None).
|
|
119
|
+
|
|
120
|
+
"""
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def clear(self) -> None:
|
|
125
|
+
"""Clear all cached entries."""
|
|
126
|
+
pass
|
|
127
|
+
|
|
128
|
+
@abstractmethod
|
|
129
|
+
def get_stats(self) -> CacheStats:
|
|
130
|
+
"""Get cache statistics.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
CacheStats with hit/miss counts.
|
|
134
|
+
|
|
135
|
+
"""
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
def _create_cache_key(
|
|
139
|
+
self,
|
|
140
|
+
workflow: str,
|
|
141
|
+
stage: str,
|
|
142
|
+
prompt: str,
|
|
143
|
+
model: str,
|
|
144
|
+
) -> str:
|
|
145
|
+
"""Create cache key from workflow, stage, prompt, and model.
|
|
146
|
+
|
|
147
|
+
Uses SHA256 hash of concatenated values.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
workflow: Workflow name.
|
|
151
|
+
stage: Stage name.
|
|
152
|
+
prompt: Prompt text.
|
|
153
|
+
model: Model identifier.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Cache key (SHA256 hash).
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
import hashlib
|
|
160
|
+
|
|
161
|
+
# Combine all inputs for cache key
|
|
162
|
+
key_parts = [workflow, stage, prompt, model]
|
|
163
|
+
key_string = "|".join(key_parts)
|
|
164
|
+
|
|
165
|
+
# SHA256 hash for consistent key length
|
|
166
|
+
return hashlib.sha256(key_string.encode()).hexdigest()
|