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,193 @@
|
|
|
1
|
+
"""Framework Enumeration and Detection
|
|
2
|
+
|
|
3
|
+
Defines supported agent frameworks and provides utilities for
|
|
4
|
+
detecting installed frameworks and selecting defaults.
|
|
5
|
+
|
|
6
|
+
Copyright 2025 Smart-AI-Memory
|
|
7
|
+
Licensed under Fair Source License 0.9
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from enum import Enum
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Framework(Enum):
|
|
14
|
+
"""Supported agent frameworks."""
|
|
15
|
+
|
|
16
|
+
# Empathy native (no external deps)
|
|
17
|
+
NATIVE = "native"
|
|
18
|
+
|
|
19
|
+
# LangChain ecosystem
|
|
20
|
+
LANGCHAIN = "langchain"
|
|
21
|
+
LANGGRAPH = "langgraph"
|
|
22
|
+
|
|
23
|
+
# Microsoft AutoGen
|
|
24
|
+
AUTOGEN = "autogen"
|
|
25
|
+
|
|
26
|
+
# deepset Haystack
|
|
27
|
+
HAYSTACK = "haystack"
|
|
28
|
+
|
|
29
|
+
# CrewAI
|
|
30
|
+
CREWAI = "crewai"
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_string(cls, name: str) -> "Framework":
|
|
34
|
+
"""Convert string to Framework enum."""
|
|
35
|
+
name_lower = name.lower().strip()
|
|
36
|
+
mapping = {
|
|
37
|
+
"native": cls.NATIVE,
|
|
38
|
+
"empathy": cls.NATIVE,
|
|
39
|
+
"langchain": cls.LANGCHAIN,
|
|
40
|
+
"langgraph": cls.LANGGRAPH,
|
|
41
|
+
"lang_graph": cls.LANGGRAPH,
|
|
42
|
+
"autogen": cls.AUTOGEN,
|
|
43
|
+
"auto_gen": cls.AUTOGEN,
|
|
44
|
+
"haystack": cls.HAYSTACK,
|
|
45
|
+
"crewai": cls.CREWAI,
|
|
46
|
+
"crew_ai": cls.CREWAI,
|
|
47
|
+
"crew": cls.CREWAI,
|
|
48
|
+
}
|
|
49
|
+
if name_lower in mapping:
|
|
50
|
+
return mapping[name_lower]
|
|
51
|
+
raise ValueError(f"Unknown framework: {name}. Available: {list(mapping.keys())}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def detect_installed_frameworks() -> list[Framework]:
|
|
55
|
+
"""Detect which agent frameworks are installed.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
List of installed frameworks (native is always included)
|
|
59
|
+
|
|
60
|
+
"""
|
|
61
|
+
installed = [Framework.NATIVE]
|
|
62
|
+
|
|
63
|
+
# Check LangChain
|
|
64
|
+
try:
|
|
65
|
+
import langchain # noqa: F401
|
|
66
|
+
|
|
67
|
+
installed.append(Framework.LANGCHAIN)
|
|
68
|
+
except ImportError:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
# Check LangGraph
|
|
72
|
+
try:
|
|
73
|
+
import langgraph # noqa: F401
|
|
74
|
+
|
|
75
|
+
installed.append(Framework.LANGGRAPH)
|
|
76
|
+
except ImportError:
|
|
77
|
+
pass
|
|
78
|
+
|
|
79
|
+
# Check AutoGen
|
|
80
|
+
try:
|
|
81
|
+
import autogen # noqa: F401
|
|
82
|
+
|
|
83
|
+
installed.append(Framework.AUTOGEN)
|
|
84
|
+
except ImportError:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
# Check Haystack
|
|
88
|
+
try:
|
|
89
|
+
import haystack # noqa: F401
|
|
90
|
+
|
|
91
|
+
installed.append(Framework.HAYSTACK)
|
|
92
|
+
except ImportError:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
# Check CrewAI
|
|
96
|
+
try:
|
|
97
|
+
import crewai # noqa: F401
|
|
98
|
+
|
|
99
|
+
installed.append(Framework.CREWAI)
|
|
100
|
+
except (ImportError, AttributeError, ImportWarning):
|
|
101
|
+
# INTENTIONAL: Catch import-time errors from CrewAI dependencies
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
return installed
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_recommended_framework(use_case: str = "general") -> Framework:
|
|
108
|
+
"""Get recommended framework for a use case.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
use_case: One of "general", "rag", "multi_agent", "code_analysis", "workflow"
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Recommended framework based on installed packages and use case
|
|
115
|
+
|
|
116
|
+
"""
|
|
117
|
+
installed = detect_installed_frameworks()
|
|
118
|
+
|
|
119
|
+
recommendations = {
|
|
120
|
+
"general": [Framework.LANGGRAPH, Framework.LANGCHAIN, Framework.NATIVE],
|
|
121
|
+
"rag": [Framework.HAYSTACK, Framework.LANGCHAIN, Framework.NATIVE],
|
|
122
|
+
"multi_agent": [Framework.AUTOGEN, Framework.LANGGRAPH, Framework.NATIVE],
|
|
123
|
+
"code_analysis": [Framework.LANGGRAPH, Framework.LANGCHAIN, Framework.NATIVE],
|
|
124
|
+
"workflow": [Framework.LANGGRAPH, Framework.LANGCHAIN, Framework.NATIVE],
|
|
125
|
+
"conversational": [Framework.AUTOGEN, Framework.LANGCHAIN, Framework.NATIVE],
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
preferred = recommendations.get(use_case, recommendations["general"])
|
|
129
|
+
|
|
130
|
+
for framework in preferred:
|
|
131
|
+
if framework in installed:
|
|
132
|
+
return framework
|
|
133
|
+
|
|
134
|
+
return Framework.NATIVE
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def get_framework_info(framework: Framework) -> dict[str, object]:
|
|
138
|
+
"""Get information about a framework.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Dict with name, description, best_for, install_command
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
info: dict[Framework, dict[str, object]] = {
|
|
145
|
+
Framework.NATIVE: {
|
|
146
|
+
"name": "Empathy Native",
|
|
147
|
+
"description": "Built-in agent system with EmpathyLLM integration",
|
|
148
|
+
"best_for": ["Simple agents", "Cost optimization", "Pattern learning"],
|
|
149
|
+
"install_command": None,
|
|
150
|
+
"docs_url": "https://smartaimemory.com/docs/agents",
|
|
151
|
+
},
|
|
152
|
+
Framework.LANGCHAIN: {
|
|
153
|
+
"name": "LangChain",
|
|
154
|
+
"description": "Composable chains with tools, memory, and retrieval",
|
|
155
|
+
"best_for": ["Tool usage", "RAG", "Chains", "Prompt templates"],
|
|
156
|
+
"install_command": "pip install langchain langchain-anthropic",
|
|
157
|
+
"docs_url": "https://python.langchain.com/docs/",
|
|
158
|
+
},
|
|
159
|
+
Framework.LANGGRAPH: {
|
|
160
|
+
"name": "LangGraph",
|
|
161
|
+
"description": "Stateful, multi-actor workflows with cycles",
|
|
162
|
+
"best_for": ["Complex workflows", "Stateful agents", "Multi-step reasoning"],
|
|
163
|
+
"install_command": "pip install langgraph langchain-anthropic",
|
|
164
|
+
"docs_url": "https://langchain-ai.github.io/langgraph/",
|
|
165
|
+
},
|
|
166
|
+
Framework.AUTOGEN: {
|
|
167
|
+
"name": "AutoGen",
|
|
168
|
+
"description": "Multi-agent conversational systems",
|
|
169
|
+
"best_for": ["Agent teams", "Conversations", "Code execution"],
|
|
170
|
+
"install_command": "pip install pyautogen",
|
|
171
|
+
"docs_url": "https://microsoft.github.io/autogen/",
|
|
172
|
+
},
|
|
173
|
+
Framework.HAYSTACK: {
|
|
174
|
+
"name": "Haystack",
|
|
175
|
+
"description": "Production-ready RAG and NLP pipelines",
|
|
176
|
+
"best_for": ["Document QA", "RAG", "Search", "NLP pipelines"],
|
|
177
|
+
"install_command": "pip install haystack-ai",
|
|
178
|
+
"docs_url": "https://docs.haystack.deepset.ai/",
|
|
179
|
+
},
|
|
180
|
+
Framework.CREWAI: {
|
|
181
|
+
"name": "CrewAI",
|
|
182
|
+
"description": "Role-based multi-agent framework with goal-oriented crews",
|
|
183
|
+
"best_for": [
|
|
184
|
+
"Agent teams",
|
|
185
|
+
"Role-playing",
|
|
186
|
+
"Task delegation",
|
|
187
|
+
"Hierarchical workflows",
|
|
188
|
+
],
|
|
189
|
+
"install_command": "pip install crewai",
|
|
190
|
+
"docs_url": "https://docs.crewai.com/",
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
return info.get(framework, info[Framework.NATIVE])
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Memory-Aware Agent Wrapper
|
|
2
|
+
|
|
3
|
+
Integrates agents with the Memory Graph for cross-agent learning.
|
|
4
|
+
Agents can query for similar past findings and store new findings
|
|
5
|
+
for other agents to benefit from.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
from attune_llm.agent_factory import AgentFactory
|
|
9
|
+
from attune_llm.agent_factory.memory_integration import MemoryAwareAgent
|
|
10
|
+
|
|
11
|
+
factory = AgentFactory()
|
|
12
|
+
agent = factory.create_agent(
|
|
13
|
+
name="bug_hunter",
|
|
14
|
+
role="debugger",
|
|
15
|
+
memory_graph_enabled=True
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Agent will automatically:
|
|
19
|
+
# 1. Query similar past bugs before invocation
|
|
20
|
+
# 2. Store new findings after invocation
|
|
21
|
+
|
|
22
|
+
Copyright 2025 Smart-AI-Memory
|
|
23
|
+
Licensed under Fair Source License 0.9
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from attune_llm.agent_factory.base import BaseAgent
|
|
30
|
+
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class MemoryAwareAgent(BaseAgent):
|
|
35
|
+
"""Agent wrapper that integrates with Memory Graph.
|
|
36
|
+
|
|
37
|
+
Enables cross-agent learning by:
|
|
38
|
+
- Querying similar past findings before invocation
|
|
39
|
+
- Storing new findings after invocation
|
|
40
|
+
- Connecting findings across agents
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
agent: BaseAgent,
|
|
46
|
+
graph_path: str = "patterns/memory_graph.json",
|
|
47
|
+
store_findings: bool = True,
|
|
48
|
+
query_similar: bool = True,
|
|
49
|
+
similarity_threshold: float = 0.4,
|
|
50
|
+
max_similar_results: int = 5,
|
|
51
|
+
):
|
|
52
|
+
"""Initialize memory-aware agent wrapper.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
agent: The underlying agent to wrap
|
|
56
|
+
graph_path: Path to memory graph JSON file
|
|
57
|
+
store_findings: Whether to store findings in memory graph
|
|
58
|
+
query_similar: Whether to query for similar findings
|
|
59
|
+
similarity_threshold: Minimum similarity score for results
|
|
60
|
+
max_similar_results: Maximum similar findings to return
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
super().__init__(agent.config)
|
|
64
|
+
self._wrapped = agent
|
|
65
|
+
self._graph_path = graph_path
|
|
66
|
+
self._store_findings = store_findings
|
|
67
|
+
self._query_similar = query_similar
|
|
68
|
+
self._similarity_threshold = similarity_threshold
|
|
69
|
+
self._max_similar_results = max_similar_results
|
|
70
|
+
self._graph: Any = None
|
|
71
|
+
|
|
72
|
+
self._init_graph()
|
|
73
|
+
|
|
74
|
+
def _init_graph(self) -> None:
|
|
75
|
+
"""Initialize connection to Memory Graph."""
|
|
76
|
+
try:
|
|
77
|
+
from attune.memory import MemoryGraph
|
|
78
|
+
|
|
79
|
+
self._graph = MemoryGraph(path=self._graph_path)
|
|
80
|
+
logger.debug(f"Memory graph loaded from {self._graph_path}")
|
|
81
|
+
except ImportError:
|
|
82
|
+
logger.warning("attune.memory not available, memory integration disabled")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.warning(f"Failed to load memory graph: {e}")
|
|
85
|
+
|
|
86
|
+
async def invoke(self, input_data: str | dict, context: dict | None = None) -> dict:
|
|
87
|
+
"""Invoke the agent with memory graph integration.
|
|
88
|
+
|
|
89
|
+
1. Query for similar past findings
|
|
90
|
+
2. Add context from similar findings
|
|
91
|
+
3. Invoke wrapped agent
|
|
92
|
+
4. Store any new findings
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
input_data: User input or structured data
|
|
96
|
+
context: Optional context (previous results, shared state)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Dict with at least {"output": str, "metadata": dict}
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
context = context or {}
|
|
103
|
+
|
|
104
|
+
# 1. Query for similar findings if enabled
|
|
105
|
+
if self._query_similar and self._graph:
|
|
106
|
+
similar_findings = self._get_similar_findings(input_data)
|
|
107
|
+
if similar_findings:
|
|
108
|
+
context["similar_findings"] = similar_findings
|
|
109
|
+
context["memory_graph_hits"] = len(similar_findings)
|
|
110
|
+
|
|
111
|
+
# 2. Invoke wrapped agent with enhanced context
|
|
112
|
+
result = await self._wrapped.invoke(input_data, context)
|
|
113
|
+
|
|
114
|
+
# 3. Store findings if enabled
|
|
115
|
+
if self._store_findings and self._graph:
|
|
116
|
+
self._store_agent_findings(input_data, result)
|
|
117
|
+
|
|
118
|
+
# 4. Add memory graph metadata
|
|
119
|
+
if "metadata" not in result:
|
|
120
|
+
result["metadata"] = {}
|
|
121
|
+
|
|
122
|
+
result["metadata"]["memory_graph"] = {
|
|
123
|
+
"enabled": self._graph is not None,
|
|
124
|
+
"similar_found": len(context.get("similar_findings", [])),
|
|
125
|
+
"findings_stored": self._store_findings,
|
|
126
|
+
"graph_path": self._graph_path,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return result
|
|
130
|
+
|
|
131
|
+
async def stream(self, input_data: str | dict, context: dict | None = None):
|
|
132
|
+
"""Stream agent response with memory integration.
|
|
133
|
+
|
|
134
|
+
Note: Memory integration for streaming works at the full response
|
|
135
|
+
level - similar findings are added to context before streaming starts.
|
|
136
|
+
"""
|
|
137
|
+
context = context or {}
|
|
138
|
+
|
|
139
|
+
# Query for similar findings
|
|
140
|
+
if self._query_similar and self._graph:
|
|
141
|
+
similar_findings = self._get_similar_findings(input_data)
|
|
142
|
+
if similar_findings:
|
|
143
|
+
context["similar_findings"] = similar_findings
|
|
144
|
+
|
|
145
|
+
# Stream from wrapped agent
|
|
146
|
+
async for chunk in self._wrapped.stream(input_data, context): # type: ignore[attr-defined]
|
|
147
|
+
yield chunk
|
|
148
|
+
|
|
149
|
+
def _get_similar_findings(self, input_data: str | dict) -> list[dict]:
|
|
150
|
+
"""Query memory graph for similar past findings."""
|
|
151
|
+
if not self._graph:
|
|
152
|
+
return []
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
# Build query from input
|
|
156
|
+
if isinstance(input_data, str):
|
|
157
|
+
query = {"name": input_data[:100], "description": input_data}
|
|
158
|
+
else:
|
|
159
|
+
query = {
|
|
160
|
+
"name": str(input_data.get("task", input_data.get("input", "")))[:100],
|
|
161
|
+
"description": str(input_data),
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
# Query graph
|
|
165
|
+
similar = self._graph.find_similar(
|
|
166
|
+
query,
|
|
167
|
+
threshold=self._similarity_threshold,
|
|
168
|
+
limit=self._max_similar_results,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Format results
|
|
172
|
+
results = []
|
|
173
|
+
for node, score in similar:
|
|
174
|
+
if score >= self._similarity_threshold:
|
|
175
|
+
finding = {
|
|
176
|
+
"name": node.name,
|
|
177
|
+
"type": node.type.value if node.type else None,
|
|
178
|
+
"similarity": round(score, 2),
|
|
179
|
+
"source_wizard": node.source_wizard,
|
|
180
|
+
"metadata": node.metadata or {},
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Check for resolutions
|
|
184
|
+
resolutions = self._get_resolutions(node.id)
|
|
185
|
+
if resolutions:
|
|
186
|
+
finding["resolutions"] = resolutions
|
|
187
|
+
|
|
188
|
+
results.append(finding)
|
|
189
|
+
|
|
190
|
+
if results:
|
|
191
|
+
logger.debug(f"Found {len(results)} similar findings for agent {self.name}")
|
|
192
|
+
|
|
193
|
+
return results
|
|
194
|
+
|
|
195
|
+
except Exception as e:
|
|
196
|
+
logger.warning(f"Error querying similar findings: {e}")
|
|
197
|
+
return []
|
|
198
|
+
|
|
199
|
+
def _get_resolutions(self, node_id: str) -> list[dict]:
|
|
200
|
+
"""Get resolutions (fixes) for a finding."""
|
|
201
|
+
if not self._graph:
|
|
202
|
+
return []
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
from attune.memory import EdgeType
|
|
206
|
+
|
|
207
|
+
related = self._graph.find_related(node_id, [EdgeType.FIXED_BY])
|
|
208
|
+
return [
|
|
209
|
+
{
|
|
210
|
+
"name": r.name,
|
|
211
|
+
"description": r.metadata.get("description", ""),
|
|
212
|
+
}
|
|
213
|
+
for r in related
|
|
214
|
+
]
|
|
215
|
+
except Exception: # noqa: BLE001
|
|
216
|
+
# INTENTIONAL: Graceful degradation when graph unavailable
|
|
217
|
+
logger.debug(f"Could not get resolutions for node {node_id}")
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
def _store_agent_findings(self, input_data: str | dict, result: dict) -> None:
|
|
221
|
+
"""Store findings from agent result in memory graph."""
|
|
222
|
+
if not self._graph:
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
try:
|
|
226
|
+
metadata = result.get("metadata", {})
|
|
227
|
+
|
|
228
|
+
# Check for explicit findings in result
|
|
229
|
+
findings = metadata.get("findings", [])
|
|
230
|
+
|
|
231
|
+
# Also check for bug/issue/vulnerability patterns in output
|
|
232
|
+
if not findings and self._contains_finding_patterns(result.get("output", "")):
|
|
233
|
+
# Create implicit finding from result
|
|
234
|
+
if isinstance(input_data, str):
|
|
235
|
+
task_name = input_data[:100]
|
|
236
|
+
else:
|
|
237
|
+
task_name = str(input_data.get("task", input_data.get("input", "")))[:100]
|
|
238
|
+
|
|
239
|
+
findings = [
|
|
240
|
+
{
|
|
241
|
+
"type": self._infer_finding_type(result.get("output", "")),
|
|
242
|
+
"name": f"{self.name}: {task_name}",
|
|
243
|
+
"description": result.get("output", "")[:500],
|
|
244
|
+
},
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
# Store findings
|
|
248
|
+
for finding in findings:
|
|
249
|
+
finding_id = self._graph.add_finding(self.name, finding)
|
|
250
|
+
logger.debug(f"Stored finding {finding_id} from agent {self.name}")
|
|
251
|
+
|
|
252
|
+
# Save graph
|
|
253
|
+
if findings:
|
|
254
|
+
self._graph._save()
|
|
255
|
+
|
|
256
|
+
except Exception as e:
|
|
257
|
+
logger.warning(f"Error storing findings: {e}")
|
|
258
|
+
|
|
259
|
+
def _contains_finding_patterns(self, text: str) -> bool:
|
|
260
|
+
"""Check if output contains patterns indicating a finding."""
|
|
261
|
+
patterns = [
|
|
262
|
+
"found",
|
|
263
|
+
"detected",
|
|
264
|
+
"issue",
|
|
265
|
+
"bug",
|
|
266
|
+
"error",
|
|
267
|
+
"vulnerability",
|
|
268
|
+
"problem",
|
|
269
|
+
"warning",
|
|
270
|
+
"fix",
|
|
271
|
+
]
|
|
272
|
+
text_lower = text.lower()
|
|
273
|
+
return any(pattern in text_lower for pattern in patterns)
|
|
274
|
+
|
|
275
|
+
def _infer_finding_type(self, text: str) -> str:
|
|
276
|
+
"""Infer finding type from output text."""
|
|
277
|
+
text_lower = text.lower()
|
|
278
|
+
|
|
279
|
+
# Check for fix/resolution first (takes precedence)
|
|
280
|
+
if any(x in text_lower for x in ["fix", "fixed", "resolved", "patched"]):
|
|
281
|
+
return "fix"
|
|
282
|
+
if any(x in text_lower for x in ["vulnerability", "security", "injection", "xss"]):
|
|
283
|
+
return "vulnerability"
|
|
284
|
+
if any(x in text_lower for x in ["bug", "error", "exception", "crash"]):
|
|
285
|
+
return "bug"
|
|
286
|
+
if any(x in text_lower for x in ["performance", "slow", "latency", "memory"]):
|
|
287
|
+
return "performance_issue"
|
|
288
|
+
return "pattern"
|
|
289
|
+
|
|
290
|
+
# Delegate other methods to wrapped agent
|
|
291
|
+
def add_tool(self, tool: Any) -> None:
|
|
292
|
+
"""Add a tool to the wrapped agent."""
|
|
293
|
+
self._wrapped.add_tool(tool)
|
|
294
|
+
|
|
295
|
+
def get_conversation_history(self) -> list[dict]:
|
|
296
|
+
"""Get conversation history from wrapped agent."""
|
|
297
|
+
return self._wrapped.get_conversation_history()
|
|
298
|
+
|
|
299
|
+
def clear_history(self) -> None:
|
|
300
|
+
"""Clear conversation history in wrapped agent."""
|
|
301
|
+
self._wrapped.clear_history()
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def model(self) -> str:
|
|
305
|
+
"""Get the model being used by wrapped agent."""
|
|
306
|
+
return self._wrapped.model
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def graph(self):
|
|
310
|
+
"""Get the memory graph instance."""
|
|
311
|
+
return self._graph
|
|
312
|
+
|
|
313
|
+
def get_graph_stats(self) -> dict:
|
|
314
|
+
"""Get statistics from memory graph."""
|
|
315
|
+
if not self._graph:
|
|
316
|
+
return {"enabled": False}
|
|
317
|
+
|
|
318
|
+
try:
|
|
319
|
+
return {
|
|
320
|
+
"enabled": True,
|
|
321
|
+
"node_count": len(self._graph.nodes),
|
|
322
|
+
"edge_count": len(self._graph.edges),
|
|
323
|
+
"path": str(self._graph.path),
|
|
324
|
+
}
|
|
325
|
+
except Exception as e: # noqa: BLE001
|
|
326
|
+
# INTENTIONAL: Stats are optional, don't crash on errors
|
|
327
|
+
logger.debug(f"Could not get graph stats: {e}")
|
|
328
|
+
return {"enabled": True, "error": "Could not get stats"}
|