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,208 @@
|
|
|
1
|
+
"""Prompt Templates
|
|
2
|
+
|
|
3
|
+
Provides protocol and implementations for prompt templates,
|
|
4
|
+
including XML-structured prompts.
|
|
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 dataclasses import dataclass, field
|
|
13
|
+
from typing import Any, Protocol
|
|
14
|
+
|
|
15
|
+
from .context import PromptContext
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PromptTemplate(Protocol):
|
|
19
|
+
"""Protocol for prompt templates."""
|
|
20
|
+
|
|
21
|
+
def render(self, context: PromptContext) -> str:
|
|
22
|
+
"""Render the template with given context.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
context: The prompt context containing role, goal, instructions, etc.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
The rendered prompt string.
|
|
29
|
+
|
|
30
|
+
"""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class PlainTextPromptTemplate:
|
|
36
|
+
"""Simple plain text prompt template.
|
|
37
|
+
|
|
38
|
+
Renders prompts in a straightforward text format without XML structure.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
name: str
|
|
42
|
+
include_role: bool = True
|
|
43
|
+
include_constraints: bool = True
|
|
44
|
+
|
|
45
|
+
def render(self, context: PromptContext) -> str:
|
|
46
|
+
"""Render as plain text prompt."""
|
|
47
|
+
parts = []
|
|
48
|
+
|
|
49
|
+
if self.include_role:
|
|
50
|
+
parts.append(f"You are a {context.role}.")
|
|
51
|
+
parts.append("")
|
|
52
|
+
|
|
53
|
+
parts.append(f"Goal: {context.goal}")
|
|
54
|
+
parts.append("")
|
|
55
|
+
|
|
56
|
+
if context.instructions:
|
|
57
|
+
parts.append("Instructions:")
|
|
58
|
+
for i, inst in enumerate(context.instructions, 1):
|
|
59
|
+
parts.append(f"{i}. {inst}")
|
|
60
|
+
parts.append("")
|
|
61
|
+
|
|
62
|
+
if self.include_constraints and context.constraints:
|
|
63
|
+
parts.append("Guidelines:")
|
|
64
|
+
for constraint in context.constraints:
|
|
65
|
+
parts.append(f"- {constraint}")
|
|
66
|
+
parts.append("")
|
|
67
|
+
|
|
68
|
+
if context.input_payload:
|
|
69
|
+
parts.append(f"Input ({context.input_type}):")
|
|
70
|
+
parts.append(context.input_payload)
|
|
71
|
+
|
|
72
|
+
return "\n".join(parts)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class XmlPromptTemplate:
|
|
77
|
+
"""XML-structured prompt template.
|
|
78
|
+
|
|
79
|
+
Renders prompts in XML format for consistent parsing and
|
|
80
|
+
structured LLM interactions.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
name: str
|
|
84
|
+
schema_version: str = "1.0"
|
|
85
|
+
response_format: str | None = None
|
|
86
|
+
extra_tags: dict[str, str] = field(default_factory=dict)
|
|
87
|
+
|
|
88
|
+
def render(self, context: PromptContext) -> str:
|
|
89
|
+
"""Render XML prompt from context.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
context: The prompt context.
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
XML-formatted prompt string.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
# Build instructions XML
|
|
99
|
+
instructions_xml = self._render_instructions(context.instructions)
|
|
100
|
+
|
|
101
|
+
# Build constraints XML
|
|
102
|
+
constraints_xml = self._render_constraints(context.constraints)
|
|
103
|
+
|
|
104
|
+
# Build extra context XML if present
|
|
105
|
+
extra_xml = self._render_extra(context.extra)
|
|
106
|
+
|
|
107
|
+
# Build input section with CDATA for safety
|
|
108
|
+
input_content = self._escape_cdata(context.input_payload)
|
|
109
|
+
|
|
110
|
+
prompt = f"""<request schema="{self.schema_version}">
|
|
111
|
+
<role>{self._escape_xml(context.role)}</role>
|
|
112
|
+
<goal>{self._escape_xml(context.goal)}</goal>
|
|
113
|
+
<instructions>
|
|
114
|
+
{instructions_xml}
|
|
115
|
+
</instructions>
|
|
116
|
+
<constraints>
|
|
117
|
+
{constraints_xml}
|
|
118
|
+
</constraints>{extra_xml}
|
|
119
|
+
<input type="{context.input_type}">
|
|
120
|
+
<![CDATA[
|
|
121
|
+
{input_content}
|
|
122
|
+
]]>
|
|
123
|
+
</input>
|
|
124
|
+
</request>"""
|
|
125
|
+
|
|
126
|
+
# Add response format instructions if specified
|
|
127
|
+
if self.response_format:
|
|
128
|
+
prompt += f"\n\n{self._response_instructions()}"
|
|
129
|
+
|
|
130
|
+
return prompt
|
|
131
|
+
|
|
132
|
+
def _render_instructions(self, instructions: list[str]) -> str:
|
|
133
|
+
"""Render instructions as XML steps."""
|
|
134
|
+
if not instructions:
|
|
135
|
+
return " <!-- No specific instructions -->"
|
|
136
|
+
lines = []
|
|
137
|
+
for i, inst in enumerate(instructions, 1):
|
|
138
|
+
escaped = self._escape_xml(inst)
|
|
139
|
+
lines.append(f" <step>{i}. {escaped}</step>")
|
|
140
|
+
return "\n".join(lines)
|
|
141
|
+
|
|
142
|
+
def _render_constraints(self, constraints: list[str]) -> str:
|
|
143
|
+
"""Render constraints as XML rules."""
|
|
144
|
+
if not constraints:
|
|
145
|
+
return " <!-- No specific constraints -->"
|
|
146
|
+
lines = []
|
|
147
|
+
for constraint in constraints:
|
|
148
|
+
escaped = self._escape_xml(constraint)
|
|
149
|
+
lines.append(f" <rule>{escaped}</rule>")
|
|
150
|
+
return "\n".join(lines)
|
|
151
|
+
|
|
152
|
+
def _render_extra(self, extra: dict[str, Any]) -> str:
|
|
153
|
+
"""Render extra context as XML if present."""
|
|
154
|
+
if not extra:
|
|
155
|
+
return ""
|
|
156
|
+
|
|
157
|
+
lines = ["\n <context>"]
|
|
158
|
+
for key, value in extra.items():
|
|
159
|
+
if value: # Only include non-empty values
|
|
160
|
+
escaped_key = self._escape_xml(str(key))
|
|
161
|
+
escaped_value = self._escape_xml(str(value))
|
|
162
|
+
lines.append(f" <{escaped_key}>{escaped_value}</{escaped_key}>")
|
|
163
|
+
lines.append(" </context>")
|
|
164
|
+
|
|
165
|
+
return "\n".join(lines)
|
|
166
|
+
|
|
167
|
+
def _response_instructions(self) -> str:
|
|
168
|
+
"""Generate response format instructions."""
|
|
169
|
+
return f"""Please respond using ONLY this XML format (no other text before or after):
|
|
170
|
+
|
|
171
|
+
{self.response_format}
|
|
172
|
+
|
|
173
|
+
Important:
|
|
174
|
+
- Use the exact XML structure shown above
|
|
175
|
+
- Include all required tags even if empty
|
|
176
|
+
- Use severity values: critical, high, medium, low, info
|
|
177
|
+
- Wrap code examples in CDATA sections"""
|
|
178
|
+
|
|
179
|
+
def _escape_xml(self, text: str) -> str:
|
|
180
|
+
"""Escape special XML characters."""
|
|
181
|
+
if not text:
|
|
182
|
+
return ""
|
|
183
|
+
return (
|
|
184
|
+
text.replace("&", "&")
|
|
185
|
+
.replace("<", "<")
|
|
186
|
+
.replace(">", ">")
|
|
187
|
+
.replace('"', """)
|
|
188
|
+
.replace("'", "'")
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
def _escape_cdata(self, text: str) -> str:
|
|
192
|
+
"""Escape text for use in CDATA section.
|
|
193
|
+
|
|
194
|
+
CDATA sections can contain anything except the closing sequence ]]>
|
|
195
|
+
"""
|
|
196
|
+
if not text:
|
|
197
|
+
return ""
|
|
198
|
+
# Replace ]]> with ]]]]><![CDATA[> to escape it
|
|
199
|
+
return text.replace("]]>", "]]]]><![CDATA[>")
|
|
200
|
+
|
|
201
|
+
def with_response_format(self, response_format: str) -> XmlPromptTemplate:
|
|
202
|
+
"""Return a new template with the specified response format."""
|
|
203
|
+
return XmlPromptTemplate(
|
|
204
|
+
name=self.name,
|
|
205
|
+
schema_version=self.schema_version,
|
|
206
|
+
response_format=response_format,
|
|
207
|
+
extra_tags=self.extra_tags.copy(),
|
|
208
|
+
)
|
attune/redis_config.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"""Redis Configuration for Empathy Framework
|
|
2
|
+
|
|
3
|
+
Handles connection to Redis from environment variables.
|
|
4
|
+
Supports Railway, redis.com, local Docker, managed Redis, or mock mode.
|
|
5
|
+
|
|
6
|
+
Environment Variables:
|
|
7
|
+
REDIS_URL: Full Redis URL (redis://user:pass@host:port)
|
|
8
|
+
REDIS_HOST: Redis host (default: localhost)
|
|
9
|
+
REDIS_PORT: Redis port (default: 6379)
|
|
10
|
+
REDIS_PASSWORD: Redis password (optional)
|
|
11
|
+
REDIS_DB: Redis database number (default: 0)
|
|
12
|
+
EMPATHY_REDIS_MOCK: Set to "true" to use mock mode
|
|
13
|
+
|
|
14
|
+
# SSL/TLS (for managed Redis services)
|
|
15
|
+
REDIS_SSL: Set to "true" to enable SSL
|
|
16
|
+
REDIS_SSL_CERT_REQS: Certificate requirement ("required", "optional", "none")
|
|
17
|
+
REDIS_SSL_CA_CERTS: Path to CA certificate file
|
|
18
|
+
REDIS_SSL_CERTFILE: Path to client certificate
|
|
19
|
+
REDIS_SSL_KEYFILE: Path to client key
|
|
20
|
+
|
|
21
|
+
# Connection settings
|
|
22
|
+
REDIS_SOCKET_TIMEOUT: Socket timeout in seconds (default: 5.0)
|
|
23
|
+
REDIS_MAX_CONNECTIONS: Connection pool size (default: 10)
|
|
24
|
+
|
|
25
|
+
# Retry settings
|
|
26
|
+
REDIS_RETRY_MAX_ATTEMPTS: Max retry attempts (default: 3)
|
|
27
|
+
REDIS_RETRY_BASE_DELAY: Base retry delay in seconds (default: 0.1)
|
|
28
|
+
REDIS_RETRY_MAX_DELAY: Max retry delay in seconds (default: 2.0)
|
|
29
|
+
|
|
30
|
+
# Sentinel (for high availability)
|
|
31
|
+
REDIS_SENTINEL_HOSTS: Comma-separated host:port pairs
|
|
32
|
+
REDIS_SENTINEL_MASTER: Sentinel master name
|
|
33
|
+
|
|
34
|
+
Railway Auto-Detection:
|
|
35
|
+
When deployed on Railway, REDIS_URL is automatically set.
|
|
36
|
+
For Railway Redis with SSL, the URL starts with "rediss://"
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
from attune.redis_config import get_redis_memory
|
|
40
|
+
|
|
41
|
+
# Automatically uses environment variables
|
|
42
|
+
memory = get_redis_memory()
|
|
43
|
+
|
|
44
|
+
# Or with explicit URL (SSL auto-detected from rediss://)
|
|
45
|
+
memory = get_redis_memory(url="rediss://user:pass@managed-redis.com:6379")
|
|
46
|
+
|
|
47
|
+
# Or with explicit config
|
|
48
|
+
from attune.memory.short_term import RedisConfig
|
|
49
|
+
config = RedisConfig(host="localhost", ssl=True)
|
|
50
|
+
memory = get_redis_memory(config=config)
|
|
51
|
+
|
|
52
|
+
Copyright 2025 Smart AI Memory, LLC
|
|
53
|
+
Licensed under Fair Source 0.9
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
import os
|
|
57
|
+
from urllib.parse import urlparse
|
|
58
|
+
|
|
59
|
+
from .memory.short_term import RedisConfig, RedisShortTermMemory
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def parse_redis_url(url: str) -> dict:
|
|
63
|
+
"""Parse Redis URL into connection parameters.
|
|
64
|
+
|
|
65
|
+
Supports:
|
|
66
|
+
- redis://user:pass@host:port/db (standard)
|
|
67
|
+
- rediss://user:pass@host:port/db (SSL enabled)
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
url: Redis URL (redis:// or rediss://)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dict with host, port, password, db, ssl
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
parsed = urlparse(url)
|
|
77
|
+
|
|
78
|
+
# Detect SSL from scheme
|
|
79
|
+
ssl = parsed.scheme == "rediss"
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
"host": parsed.hostname or "localhost",
|
|
83
|
+
"port": parsed.port or 6379,
|
|
84
|
+
"password": parsed.password,
|
|
85
|
+
"db": int(parsed.path.lstrip("/") or 0) if parsed.path else 0,
|
|
86
|
+
"ssl": ssl,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_redis_config() -> RedisConfig:
|
|
91
|
+
"""Get Redis configuration from environment variables.
|
|
92
|
+
|
|
93
|
+
Priority:
|
|
94
|
+
1. REDIS_URL (full URL, used by Railway/Heroku/managed services)
|
|
95
|
+
2. Individual env vars (REDIS_HOST, REDIS_PORT, etc.)
|
|
96
|
+
3. Defaults (localhost:6379)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
RedisConfig with all connection parameters
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
# Check for mock mode
|
|
103
|
+
if os.getenv("EMPATHY_REDIS_MOCK", "").lower() == "true":
|
|
104
|
+
return RedisConfig(use_mock=True)
|
|
105
|
+
|
|
106
|
+
# Check for full URL (Railway, Heroku, managed services)
|
|
107
|
+
redis_url = os.getenv("REDIS_URL") or os.getenv("REDIS_PRIVATE_URL")
|
|
108
|
+
if redis_url:
|
|
109
|
+
url_config = parse_redis_url(redis_url)
|
|
110
|
+
return RedisConfig(
|
|
111
|
+
host=url_config["host"],
|
|
112
|
+
port=url_config["port"],
|
|
113
|
+
password=url_config["password"],
|
|
114
|
+
db=url_config["db"],
|
|
115
|
+
ssl=url_config.get("ssl", False),
|
|
116
|
+
use_mock=False,
|
|
117
|
+
# Apply additional env var overrides
|
|
118
|
+
socket_timeout=float(os.getenv("REDIS_SOCKET_TIMEOUT", "5.0")),
|
|
119
|
+
max_connections=int(os.getenv("REDIS_MAX_CONNECTIONS", "10")),
|
|
120
|
+
retry_max_attempts=int(os.getenv("REDIS_RETRY_MAX_ATTEMPTS", "3")),
|
|
121
|
+
retry_base_delay=float(os.getenv("REDIS_RETRY_BASE_DELAY", "0.1")),
|
|
122
|
+
retry_max_delay=float(os.getenv("REDIS_RETRY_MAX_DELAY", "2.0")),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Build config from individual env vars
|
|
126
|
+
return RedisConfig(
|
|
127
|
+
host=os.getenv("REDIS_HOST", "localhost"),
|
|
128
|
+
port=int(os.getenv("REDIS_PORT", "6379")),
|
|
129
|
+
password=os.getenv("REDIS_PASSWORD"),
|
|
130
|
+
db=int(os.getenv("REDIS_DB", "0")),
|
|
131
|
+
use_mock=False,
|
|
132
|
+
# SSL settings
|
|
133
|
+
ssl=os.getenv("REDIS_SSL", "").lower() == "true",
|
|
134
|
+
ssl_cert_reqs=os.getenv("REDIS_SSL_CERT_REQS"),
|
|
135
|
+
ssl_ca_certs=os.getenv("REDIS_SSL_CA_CERTS"),
|
|
136
|
+
ssl_certfile=os.getenv("REDIS_SSL_CERTFILE"),
|
|
137
|
+
ssl_keyfile=os.getenv("REDIS_SSL_KEYFILE"),
|
|
138
|
+
# Connection settings
|
|
139
|
+
socket_timeout=float(os.getenv("REDIS_SOCKET_TIMEOUT", "5.0")),
|
|
140
|
+
socket_connect_timeout=float(os.getenv("REDIS_SOCKET_CONNECT_TIMEOUT", "5.0")),
|
|
141
|
+
max_connections=int(os.getenv("REDIS_MAX_CONNECTIONS", "10")),
|
|
142
|
+
# Retry settings
|
|
143
|
+
retry_on_timeout=os.getenv("REDIS_RETRY_ON_TIMEOUT", "true").lower() == "true",
|
|
144
|
+
retry_max_attempts=int(os.getenv("REDIS_RETRY_MAX_ATTEMPTS", "3")),
|
|
145
|
+
retry_base_delay=float(os.getenv("REDIS_RETRY_BASE_DELAY", "0.1")),
|
|
146
|
+
retry_max_delay=float(os.getenv("REDIS_RETRY_MAX_DELAY", "2.0")),
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def get_redis_config_dict() -> dict:
|
|
151
|
+
"""Get Redis configuration as a dictionary (legacy compatibility).
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Dict with connection parameters
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
config = get_redis_config()
|
|
158
|
+
return {
|
|
159
|
+
"host": config.host,
|
|
160
|
+
"port": config.port,
|
|
161
|
+
"password": config.password,
|
|
162
|
+
"db": config.db,
|
|
163
|
+
"use_mock": config.use_mock,
|
|
164
|
+
"ssl": config.ssl,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_redis_memory(
|
|
169
|
+
url: str | None = None,
|
|
170
|
+
use_mock: bool | None = None,
|
|
171
|
+
config: RedisConfig | None = None,
|
|
172
|
+
) -> RedisShortTermMemory:
|
|
173
|
+
"""Create a RedisShortTermMemory instance with environment-based config.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
url: Optional explicit Redis URL (overrides env vars)
|
|
177
|
+
use_mock: Optional explicit mock mode (overrides env vars)
|
|
178
|
+
config: Optional explicit RedisConfig (overrides all other options)
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
Configured RedisShortTermMemory instance
|
|
182
|
+
|
|
183
|
+
Examples:
|
|
184
|
+
# Auto-configure from environment
|
|
185
|
+
memory = get_redis_memory()
|
|
186
|
+
|
|
187
|
+
# Explicit URL (SSL auto-detected from rediss://)
|
|
188
|
+
memory = get_redis_memory(url="rediss://user:pass@managed-redis.com:6379")
|
|
189
|
+
|
|
190
|
+
# Force mock mode
|
|
191
|
+
memory = get_redis_memory(use_mock=True)
|
|
192
|
+
|
|
193
|
+
# Explicit config with all options
|
|
194
|
+
from attune.memory.short_term import RedisConfig
|
|
195
|
+
config = RedisConfig(
|
|
196
|
+
host="redis.example.com",
|
|
197
|
+
port=6379,
|
|
198
|
+
ssl=True,
|
|
199
|
+
retry_max_attempts=5,
|
|
200
|
+
)
|
|
201
|
+
memory = get_redis_memory(config=config)
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
# Explicit config takes highest priority
|
|
205
|
+
if config is not None:
|
|
206
|
+
return RedisShortTermMemory(config=config)
|
|
207
|
+
|
|
208
|
+
# Explicit mock mode
|
|
209
|
+
if use_mock is True:
|
|
210
|
+
return RedisShortTermMemory(use_mock=True)
|
|
211
|
+
|
|
212
|
+
# Explicit URL
|
|
213
|
+
if url:
|
|
214
|
+
url_config = parse_redis_url(url)
|
|
215
|
+
redis_config = RedisConfig(
|
|
216
|
+
host=url_config["host"],
|
|
217
|
+
port=url_config["port"],
|
|
218
|
+
password=url_config["password"],
|
|
219
|
+
db=url_config["db"],
|
|
220
|
+
ssl=url_config.get("ssl", False),
|
|
221
|
+
use_mock=False,
|
|
222
|
+
)
|
|
223
|
+
return RedisShortTermMemory(config=redis_config)
|
|
224
|
+
|
|
225
|
+
# Environment-based config
|
|
226
|
+
env_config = get_redis_config()
|
|
227
|
+
return RedisShortTermMemory(config=env_config)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def check_redis_connection() -> dict:
|
|
231
|
+
"""Check Redis connection and return status.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
Dict with connection status and info
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
>>> status = check_redis_connection()
|
|
238
|
+
>>> if status["connected"]:
|
|
239
|
+
... print(f"Connected to {status['host']}:{status['port']}")
|
|
240
|
+
|
|
241
|
+
"""
|
|
242
|
+
config = get_redis_config()
|
|
243
|
+
|
|
244
|
+
result = {
|
|
245
|
+
"config_source": "environment",
|
|
246
|
+
"use_mock": config.use_mock,
|
|
247
|
+
"host": config.host,
|
|
248
|
+
"port": config.port,
|
|
249
|
+
"has_password": bool(config.password),
|
|
250
|
+
"db": config.db,
|
|
251
|
+
"connected": False,
|
|
252
|
+
"error": None,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
# Determine config source
|
|
256
|
+
if os.getenv("REDIS_URL"):
|
|
257
|
+
result["config_source"] = "REDIS_URL"
|
|
258
|
+
elif os.getenv("REDIS_PRIVATE_URL"):
|
|
259
|
+
result["config_source"] = "REDIS_PRIVATE_URL"
|
|
260
|
+
elif os.getenv("REDIS_HOST"):
|
|
261
|
+
result["config_source"] = "REDIS_HOST"
|
|
262
|
+
|
|
263
|
+
if result["use_mock"]:
|
|
264
|
+
result["connected"] = True
|
|
265
|
+
result["config_source"] = "mock_mode"
|
|
266
|
+
return result
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
memory = get_redis_memory()
|
|
270
|
+
result["connected"] = memory.ping()
|
|
271
|
+
if result["connected"]:
|
|
272
|
+
stats = memory.get_stats()
|
|
273
|
+
result["memory_used"] = stats.get("used_memory")
|
|
274
|
+
result["total_keys"] = stats.get("total_keys")
|
|
275
|
+
except Exception as e:
|
|
276
|
+
result["error"] = str(e)
|
|
277
|
+
|
|
278
|
+
return result
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# Convenience function for Railway deployments
|
|
282
|
+
def get_railway_redis() -> RedisShortTermMemory:
|
|
283
|
+
"""Get Redis configured for Railway deployment.
|
|
284
|
+
|
|
285
|
+
Railway automatically sets REDIS_URL when you add a Redis service.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
RedisShortTermMemory configured for Railway
|
|
289
|
+
|
|
290
|
+
Raises:
|
|
291
|
+
EnvironmentError: If REDIS_URL is not set
|
|
292
|
+
|
|
293
|
+
"""
|
|
294
|
+
redis_url = os.getenv("REDIS_URL") or os.getenv("REDIS_PRIVATE_URL")
|
|
295
|
+
|
|
296
|
+
if not redis_url:
|
|
297
|
+
raise OSError(
|
|
298
|
+
"REDIS_URL not found. Make sure Redis is added to your Railway project.\n"
|
|
299
|
+
"Run: railway add --database redis",
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
return get_redis_memory(url=redis_url)
|