aiecs 1.0.1__py3-none-any.whl → 1.7.6__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.
Potentially problematic release.
This version of aiecs might be problematic. Click here for more details.
- aiecs/__init__.py +13 -16
- aiecs/__main__.py +7 -7
- aiecs/aiecs_client.py +269 -75
- aiecs/application/executors/operation_executor.py +79 -54
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
- aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
- aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
- aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +98 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
- aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
- aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
- aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
- aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
- aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
- aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
- aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
- aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
- aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
- aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +341 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +913 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
- aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
- aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
- aiecs/application/knowledge_graph/search/reranker.py +293 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +471 -0
- aiecs/config/__init__.py +20 -5
- aiecs/config/config.py +762 -31
- aiecs/config/graph_config.py +131 -0
- aiecs/config/tool_config.py +399 -0
- aiecs/core/__init__.py +29 -13
- aiecs/core/interface/__init__.py +2 -2
- aiecs/core/interface/execution_interface.py +22 -22
- aiecs/core/interface/storage_interface.py +37 -88
- aiecs/core/registry/__init__.py +31 -0
- aiecs/core/registry/service_registry.py +92 -0
- aiecs/domain/__init__.py +270 -1
- aiecs/domain/agent/__init__.py +191 -0
- aiecs/domain/agent/base_agent.py +3870 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1435 -0
- aiecs/domain/agent/integration/__init__.py +29 -0
- aiecs/domain/agent/integration/context_compressor.py +216 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
- aiecs/domain/agent/integration/protocols.py +281 -0
- aiecs/domain/agent/integration/retry_policy.py +218 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
- aiecs/domain/agent/lifecycle.py +291 -0
- aiecs/domain/agent/llm_agent.py +692 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +1124 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +163 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
- aiecs/domain/agent/models.py +884 -0
- aiecs/domain/agent/observability.py +479 -0
- aiecs/domain/agent/persistence.py +449 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +159 -0
- aiecs/domain/agent/prompts/formatters.py +187 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +253 -0
- aiecs/domain/agent/tool_agent.py +444 -0
- aiecs/domain/agent/tools/__init__.py +15 -0
- aiecs/domain/agent/tools/schema_generator.py +364 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +469 -0
- aiecs/domain/community/analytics.py +432 -0
- aiecs/domain/community/collaborative_workflow.py +648 -0
- aiecs/domain/community/communication_hub.py +634 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +796 -0
- aiecs/domain/community/community_manager.py +803 -0
- aiecs/domain/community/decision_engine.py +849 -0
- aiecs/domain/community/exceptions.py +231 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +234 -0
- aiecs/domain/community/resource_manager.py +461 -0
- aiecs/domain/community/shared_context_manager.py +589 -0
- aiecs/domain/context/__init__.py +40 -10
- aiecs/domain/context/context_engine.py +1910 -0
- aiecs/domain/context/conversation_models.py +87 -53
- aiecs/domain/context/graph_memory.py +582 -0
- aiecs/domain/execution/model.py +12 -4
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +148 -0
- aiecs/domain/knowledge_graph/models/evidence.py +178 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
- aiecs/domain/knowledge_graph/models/path.py +171 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
- aiecs/domain/knowledge_graph/models/query.py +261 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
- aiecs/domain/knowledge_graph/models/relation.py +202 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
- aiecs/domain/task/dsl_processor.py +172 -56
- aiecs/domain/task/model.py +20 -8
- aiecs/domain/task/task_context.py +27 -24
- aiecs/infrastructure/__init__.py +0 -2
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +837 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
- aiecs/infrastructure/graph_storage/cache.py +424 -0
- aiecs/infrastructure/graph_storage/distributed.py +223 -0
- aiecs/infrastructure/graph_storage/error_handling.py +380 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
- aiecs/infrastructure/graph_storage/metrics.py +344 -0
- aiecs/infrastructure/graph_storage/migration.py +400 -0
- aiecs/infrastructure/graph_storage/pagination.py +483 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
- aiecs/infrastructure/graph_storage/postgres.py +1563 -0
- aiecs/infrastructure/graph_storage/property_storage.py +353 -0
- aiecs/infrastructure/graph_storage/protocols.py +76 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
- aiecs/infrastructure/graph_storage/streaming.py +487 -0
- aiecs/infrastructure/graph_storage/tenant.py +412 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
- aiecs/infrastructure/messaging/websocket_manager.py +51 -35
- aiecs/infrastructure/monitoring/__init__.py +22 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
- aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
- aiecs/infrastructure/monitoring/structured_logger.py +3 -7
- aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
- aiecs/infrastructure/persistence/__init__.py +14 -1
- aiecs/infrastructure/persistence/context_engine_client.py +184 -0
- aiecs/infrastructure/persistence/database_manager.py +67 -43
- aiecs/infrastructure/persistence/file_storage.py +180 -103
- aiecs/infrastructure/persistence/redis_client.py +74 -21
- aiecs/llm/__init__.py +73 -25
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
- aiecs/llm/client_factory.py +224 -36
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +324 -0
- aiecs/llm/clients/google_function_calling_mixin.py +457 -0
- aiecs/llm/clients/googleai_client.py +241 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +897 -0
- aiecs/llm/clients/xai_client.py +201 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +272 -0
- aiecs/llm/config/config_validator.py +206 -0
- aiecs/llm/config/model_config.py +143 -0
- aiecs/llm/protocols.py +149 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +89 -0
- aiecs/main.py +140 -121
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/module_checker.py +499 -0
- aiecs/scripts/aid/version_manager.py +235 -0
- aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
- aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
- aiecs/scripts/dependance_check/__init__.py +15 -0
- aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
- aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
- aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
- aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
- aiecs/scripts/knowledge_graph/__init__.py +3 -0
- aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
- aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
- aiecs/scripts/tools_develop/README.md +671 -0
- aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
- aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
- aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
- aiecs/scripts/tools_develop/schema_coverage.py +511 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
- aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
- aiecs/scripts/tools_develop/verify_tools.py +352 -0
- aiecs/tasks/__init__.py +0 -1
- aiecs/tasks/worker.py +115 -47
- aiecs/tools/__init__.py +194 -72
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +330 -0
- aiecs/tools/apisource/providers/__init__.py +112 -0
- aiecs/tools/apisource/providers/base.py +671 -0
- aiecs/tools/apisource/providers/census.py +397 -0
- aiecs/tools/apisource/providers/fred.py +535 -0
- aiecs/tools/apisource/providers/newsapi.py +409 -0
- aiecs/tools/apisource/providers/worldbank.py +352 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +363 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
- aiecs/tools/apisource/tool.py +832 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +334 -0
- aiecs/tools/base_tool.py +415 -21
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +607 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
- aiecs/tools/docs/content_insertion_tool.py +1320 -0
- aiecs/tools/docs/document_creator_tool.py +1323 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1011 -0
- aiecs/tools/docs/document_writer_tool.py +1829 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
- aiecs/tools/langchain_adapter.py +300 -138
- aiecs/tools/schema_generator.py +455 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +581 -0
- aiecs/tools/search_tool/cache.py +264 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +224 -0
- aiecs/tools/search_tool/core.py +778 -0
- aiecs/tools/search_tool/deduplicator.py +119 -0
- aiecs/tools/search_tool/error_handler.py +242 -0
- aiecs/tools/search_tool/metrics.py +343 -0
- aiecs/tools/search_tool/rate_limiter.py +172 -0
- aiecs/tools/search_tool/schemas.py +275 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
- aiecs/tools/statistics/data_loader_tool.py +555 -0
- aiecs/tools/statistics/data_profiler_tool.py +638 -0
- aiecs/tools/statistics/data_transformer_tool.py +580 -0
- aiecs/tools/statistics/data_visualizer_tool.py +498 -0
- aiecs/tools/statistics/model_trainer_tool.py +507 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
- aiecs/tools/task_tools/__init__.py +49 -36
- aiecs/tools/task_tools/chart_tool.py +200 -184
- aiecs/tools/task_tools/classfire_tool.py +268 -267
- aiecs/tools/task_tools/image_tool.py +175 -131
- aiecs/tools/task_tools/office_tool.py +226 -146
- aiecs/tools/task_tools/pandas_tool.py +477 -121
- aiecs/tools/task_tools/report_tool.py +390 -142
- aiecs/tools/task_tools/research_tool.py +149 -79
- aiecs/tools/task_tools/scraper_tool.py +339 -145
- aiecs/tools/task_tools/stats_tool.py +448 -209
- aiecs/tools/temp_file_manager.py +26 -24
- aiecs/tools/tool_executor/__init__.py +18 -16
- aiecs/tools/tool_executor/tool_executor.py +364 -52
- aiecs/utils/LLM_output_structor.py +74 -48
- aiecs/utils/__init__.py +14 -3
- aiecs/utils/base_callback.py +0 -3
- aiecs/utils/cache_provider.py +696 -0
- aiecs/utils/execution_utils.py +50 -31
- aiecs/utils/prompt_loader.py +1 -0
- aiecs/utils/token_usage_repository.py +37 -11
- aiecs/ws/socket_server.py +14 -4
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/METADATA +52 -15
- aiecs-1.7.6.dist-info/RECORD +337 -0
- aiecs-1.7.6.dist-info/entry_points.txt +13 -0
- aiecs/config/registry.py +0 -19
- aiecs/domain/context/content_engine.py +0 -982
- aiecs/llm/base_client.py +0 -99
- aiecs/llm/openai_client.py +0 -125
- aiecs/llm/vertex_client.py +0 -186
- aiecs/llm/xai_client.py +0 -184
- aiecs/scripts/dependency_checker.py +0 -857
- aiecs/scripts/quick_dependency_check.py +0 -269
- aiecs/tools/task_tools/search_api.py +0 -7
- aiecs-1.0.1.dist-info/RECORD +0 -90
- aiecs-1.0.1.dist-info/entry_points.txt +0 -7
- /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
- /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
- /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
- /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,1124 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Conversation Memory
|
|
3
|
+
|
|
4
|
+
Multi-turn conversation handling with session management.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Dict, List, Optional, TYPE_CHECKING, Any
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
from aiecs.llm import LLMMessage
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from aiecs.domain.context.context_engine import ContextEngine
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class Session:
|
|
22
|
+
"""
|
|
23
|
+
Conversation session with lifecycle management and metrics tracking.
|
|
24
|
+
|
|
25
|
+
Supports both in-memory and ContextEngine-backed storage for session state.
|
|
26
|
+
|
|
27
|
+
**Lifecycle States:**
|
|
28
|
+
- active: Session is active and receiving requests
|
|
29
|
+
- completed: Session ended successfully
|
|
30
|
+
- failed: Session ended due to error
|
|
31
|
+
- expired: Session expired due to inactivity
|
|
32
|
+
|
|
33
|
+
**Metrics Tracking:**
|
|
34
|
+
- request_count: Number of requests processed
|
|
35
|
+
- error_count: Number of errors encountered
|
|
36
|
+
- total_processing_time: Total processing time in seconds
|
|
37
|
+
- message_count: Number of messages in conversation
|
|
38
|
+
|
|
39
|
+
Examples:
|
|
40
|
+
# Example 1: Basic session creation and usage
|
|
41
|
+
session = Session(
|
|
42
|
+
session_id="session-123",
|
|
43
|
+
agent_id="agent-1"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Add messages
|
|
47
|
+
session.add_message("user", "Hello")
|
|
48
|
+
session.add_message("assistant", "Hi there!")
|
|
49
|
+
|
|
50
|
+
# Track request
|
|
51
|
+
session.track_request(processing_time=1.5, is_error=False)
|
|
52
|
+
|
|
53
|
+
# Get metrics
|
|
54
|
+
metrics = session.get_metrics()
|
|
55
|
+
print(f"Request count: {metrics['request_count']}")
|
|
56
|
+
print(f"Message count: {metrics['message_count']}")
|
|
57
|
+
|
|
58
|
+
# Example 2: Session lifecycle management
|
|
59
|
+
session = Session(
|
|
60
|
+
session_id="session-123",
|
|
61
|
+
agent_id="agent-1"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Session starts as active
|
|
65
|
+
assert session.is_active() # True
|
|
66
|
+
assert session.status == "active"
|
|
67
|
+
|
|
68
|
+
# Process requests
|
|
69
|
+
for i in range(5):
|
|
70
|
+
session.track_request(processing_time=0.5, is_error=False)
|
|
71
|
+
|
|
72
|
+
# Track an error
|
|
73
|
+
session.track_request(processing_time=0.1, is_error=True)
|
|
74
|
+
|
|
75
|
+
# Get metrics
|
|
76
|
+
metrics = session.get_metrics()
|
|
77
|
+
print(f"Total requests: {metrics['request_count']}") # 6
|
|
78
|
+
print(f"Errors: {metrics['error_count']}") # 1
|
|
79
|
+
print(f"Average processing time: {metrics['average_processing_time']}s")
|
|
80
|
+
|
|
81
|
+
# End session
|
|
82
|
+
session.end(status="completed")
|
|
83
|
+
assert session.status == "completed"
|
|
84
|
+
assert not session.is_active() # False
|
|
85
|
+
|
|
86
|
+
# Example 3: Session expiration checking
|
|
87
|
+
session = Session(
|
|
88
|
+
session_id="session-123",
|
|
89
|
+
agent_id="agent-1"
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Check if expired (default: 30 minutes idle)
|
|
93
|
+
if session.is_expired(max_idle_seconds=1800):
|
|
94
|
+
session.end(status="expired")
|
|
95
|
+
print("Session expired due to inactivity")
|
|
96
|
+
|
|
97
|
+
# Example 4: Session with ContextEngine integration
|
|
98
|
+
from aiecs.domain.context import ContextEngine
|
|
99
|
+
|
|
100
|
+
context_engine = ContextEngine()
|
|
101
|
+
await context_engine.initialize()
|
|
102
|
+
|
|
103
|
+
# Create session via ContextEngine
|
|
104
|
+
session_metrics = await context_engine.create_session(
|
|
105
|
+
session_id="session-123",
|
|
106
|
+
user_id="user-456",
|
|
107
|
+
metadata={"source": "web"}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Update session metrics
|
|
111
|
+
await context_engine.update_session(
|
|
112
|
+
session_id="session-123",
|
|
113
|
+
increment_requests=True,
|
|
114
|
+
add_processing_time=1.5,
|
|
115
|
+
mark_error=False
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Get session from ContextEngine
|
|
119
|
+
session_metrics = await context_engine.get_session("session-123")
|
|
120
|
+
if session_metrics:
|
|
121
|
+
print(f"Request count: {session_metrics.request_count}")
|
|
122
|
+
print(f"Error count: {session_metrics.error_count}")
|
|
123
|
+
|
|
124
|
+
# End session
|
|
125
|
+
await context_engine.end_session("session-123", status="completed")
|
|
126
|
+
|
|
127
|
+
# Example 5: Session metrics aggregation
|
|
128
|
+
session = Session(
|
|
129
|
+
session_id="session-123",
|
|
130
|
+
agent_id="agent-1"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Simulate multiple requests
|
|
134
|
+
processing_times = [0.5, 0.8, 1.2, 0.3, 0.9]
|
|
135
|
+
for time in processing_times:
|
|
136
|
+
session.track_request(processing_time=time, is_error=False)
|
|
137
|
+
|
|
138
|
+
# Track one error
|
|
139
|
+
session.track_request(processing_time=0.1, is_error=True)
|
|
140
|
+
|
|
141
|
+
# Get comprehensive metrics
|
|
142
|
+
metrics = session.get_metrics()
|
|
143
|
+
print(f"Total requests: {metrics['request_count']}") # 6
|
|
144
|
+
print(f"Errors: {metrics['error_count']}") # 1
|
|
145
|
+
print(f"Total processing time: {metrics['total_processing_time']}s")
|
|
146
|
+
print(f"Average processing time: {metrics['average_processing_time']}s")
|
|
147
|
+
print(f"Duration: {metrics['duration_seconds']}s")
|
|
148
|
+
print(f"Status: {metrics['status']}")
|
|
149
|
+
|
|
150
|
+
# Example 6: Session cleanup based on expiration
|
|
151
|
+
sessions = [
|
|
152
|
+
Session(session_id=f"session-{i}", agent_id="agent-1")
|
|
153
|
+
for i in range(10)
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
# Mark some sessions as expired
|
|
157
|
+
expired_sessions = []
|
|
158
|
+
for session in sessions:
|
|
159
|
+
if session.is_expired(max_idle_seconds=1800):
|
|
160
|
+
session.end(status="expired")
|
|
161
|
+
expired_sessions.append(session)
|
|
162
|
+
|
|
163
|
+
print(f"Found {len(expired_sessions)} expired sessions")
|
|
164
|
+
|
|
165
|
+
# Example 7: Session with message history
|
|
166
|
+
session = Session(
|
|
167
|
+
session_id="session-123",
|
|
168
|
+
agent_id="agent-1"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Add conversation messages
|
|
172
|
+
session.add_message("user", "What's the weather?")
|
|
173
|
+
session.add_message("assistant", "It's sunny and 72°F.")
|
|
174
|
+
session.add_message("user", "What about tomorrow?")
|
|
175
|
+
session.add_message("assistant", "Tomorrow will be partly cloudy, 68°F.")
|
|
176
|
+
|
|
177
|
+
# Get recent messages
|
|
178
|
+
recent = session.get_recent_messages(limit=2)
|
|
179
|
+
print(f"Recent messages: {len(recent)}") # 2
|
|
180
|
+
|
|
181
|
+
# Get all messages
|
|
182
|
+
all_messages = session.get_recent_messages(limit=None)
|
|
183
|
+
print(f"Total messages: {len(all_messages)}") # 4
|
|
184
|
+
|
|
185
|
+
# Clear messages
|
|
186
|
+
session.clear()
|
|
187
|
+
assert len(session.messages) == 0
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
session_id: str
|
|
191
|
+
agent_id: str
|
|
192
|
+
created_at: datetime = field(default_factory=datetime.utcnow)
|
|
193
|
+
last_activity: datetime = field(default_factory=datetime.utcnow)
|
|
194
|
+
messages: List[LLMMessage] = field(default_factory=list)
|
|
195
|
+
metadata: Dict = field(default_factory=dict)
|
|
196
|
+
|
|
197
|
+
# Lifecycle management fields
|
|
198
|
+
status: str = field(default="active") # active, completed, failed, expired
|
|
199
|
+
ended_at: Optional[datetime] = field(default=None)
|
|
200
|
+
|
|
201
|
+
# Session metrics fields
|
|
202
|
+
request_count: int = field(default=0)
|
|
203
|
+
error_count: int = field(default=0)
|
|
204
|
+
total_processing_time: float = field(default=0.0) # in seconds
|
|
205
|
+
|
|
206
|
+
def add_message(self, role: str, content: str) -> None:
|
|
207
|
+
"""Add message to session."""
|
|
208
|
+
self.messages.append(LLMMessage(role=role, content=content))
|
|
209
|
+
self.last_activity = datetime.utcnow()
|
|
210
|
+
|
|
211
|
+
def get_recent_messages(self, limit: int) -> List[LLMMessage]:
|
|
212
|
+
"""Get recent messages."""
|
|
213
|
+
return self.messages[-limit:] if limit else self.messages
|
|
214
|
+
|
|
215
|
+
def clear(self) -> None:
|
|
216
|
+
"""Clear session messages."""
|
|
217
|
+
self.messages.clear()
|
|
218
|
+
|
|
219
|
+
def track_request(self, processing_time: float = 0.0, is_error: bool = False) -> None:
|
|
220
|
+
"""
|
|
221
|
+
Track a request in this session.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
processing_time: Processing time in seconds
|
|
225
|
+
is_error: Whether the request resulted in an error
|
|
226
|
+
"""
|
|
227
|
+
self.request_count += 1
|
|
228
|
+
self.total_processing_time += processing_time
|
|
229
|
+
if is_error:
|
|
230
|
+
self.error_count += 1
|
|
231
|
+
self.last_activity = datetime.utcnow()
|
|
232
|
+
|
|
233
|
+
def end(self, status: str = "completed") -> None:
|
|
234
|
+
"""
|
|
235
|
+
End the session.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
status: Final session status (completed, failed, expired)
|
|
239
|
+
"""
|
|
240
|
+
self.status = status
|
|
241
|
+
self.ended_at = datetime.utcnow()
|
|
242
|
+
|
|
243
|
+
def get_metrics(self) -> Dict:
|
|
244
|
+
"""
|
|
245
|
+
Get session metrics.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Dictionary with session metrics
|
|
249
|
+
"""
|
|
250
|
+
duration = ((self.ended_at or datetime.utcnow()) - self.created_at).total_seconds()
|
|
251
|
+
avg_processing_time = self.total_processing_time / self.request_count if self.request_count > 0 else 0.0
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
"session_id": self.session_id,
|
|
255
|
+
"agent_id": self.agent_id,
|
|
256
|
+
"status": self.status,
|
|
257
|
+
"request_count": self.request_count,
|
|
258
|
+
"error_count": self.error_count,
|
|
259
|
+
"total_processing_time": self.total_processing_time,
|
|
260
|
+
"average_processing_time": avg_processing_time,
|
|
261
|
+
"duration_seconds": duration,
|
|
262
|
+
"message_count": len(self.messages),
|
|
263
|
+
"created_at": self.created_at.isoformat(),
|
|
264
|
+
"last_activity": self.last_activity.isoformat(),
|
|
265
|
+
"ended_at": self.ended_at.isoformat() if self.ended_at else None,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
def is_active(self) -> bool:
|
|
269
|
+
"""Check if session is active."""
|
|
270
|
+
return self.status == "active"
|
|
271
|
+
|
|
272
|
+
def is_expired(self, max_idle_seconds: int = 1800) -> bool:
|
|
273
|
+
"""
|
|
274
|
+
Check if session has expired due to inactivity.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
max_idle_seconds: Maximum idle time in seconds (default: 30 minutes)
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
True if session is expired, False otherwise
|
|
281
|
+
"""
|
|
282
|
+
if not self.is_active():
|
|
283
|
+
return False
|
|
284
|
+
|
|
285
|
+
idle_time = (datetime.utcnow() - self.last_activity).total_seconds()
|
|
286
|
+
return idle_time > max_idle_seconds
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class ConversationMemory:
|
|
290
|
+
"""
|
|
291
|
+
Manages multi-turn conversations with session isolation.
|
|
292
|
+
|
|
293
|
+
Supports optional ContextEngine integration for persistent conversation history
|
|
294
|
+
across agent restarts. Falls back to in-memory storage when ContextEngine is not provided.
|
|
295
|
+
|
|
296
|
+
**Storage Modes:**
|
|
297
|
+
- In-memory: Default mode, sessions stored in memory (lost on restart)
|
|
298
|
+
- ContextEngine: Persistent storage with Redis backend (survives restarts)
|
|
299
|
+
|
|
300
|
+
**Key Features:**
|
|
301
|
+
- Session lifecycle management (create, update, end)
|
|
302
|
+
- Conversation history with ContextEngine persistence
|
|
303
|
+
- Session metrics tracking (request count, errors, processing time)
|
|
304
|
+
- Automatic cleanup of inactive sessions
|
|
305
|
+
- Both sync and async methods for flexibility
|
|
306
|
+
|
|
307
|
+
Examples:
|
|
308
|
+
# Example 1: In-memory mode (default)
|
|
309
|
+
memory = ConversationMemory(agent_id="agent-1")
|
|
310
|
+
session_id = memory.create_session()
|
|
311
|
+
memory.add_message(session_id, "user", "Hello")
|
|
312
|
+
memory.add_message(session_id, "assistant", "Hi there!")
|
|
313
|
+
history = memory.get_history(session_id)
|
|
314
|
+
|
|
315
|
+
# Example 2: ContextEngine integration for persistent storage
|
|
316
|
+
from aiecs.domain.context import ContextEngine
|
|
317
|
+
|
|
318
|
+
context_engine = ContextEngine()
|
|
319
|
+
await context_engine.initialize()
|
|
320
|
+
|
|
321
|
+
memory = ConversationMemory(agent_id="agent-1", context_engine=context_engine)
|
|
322
|
+
|
|
323
|
+
# Create session with ContextEngine (async)
|
|
324
|
+
session_id = await memory.acreate_session_with_context(
|
|
325
|
+
user_id="user-123",
|
|
326
|
+
metadata={"source": "web"}
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Add messages with ContextEngine (async)
|
|
330
|
+
await memory.aadd_conversation_message(
|
|
331
|
+
session_id=session_id,
|
|
332
|
+
role="user",
|
|
333
|
+
content="Hello, I need help with my order"
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
await memory.aadd_conversation_message(
|
|
337
|
+
session_id=session_id,
|
|
338
|
+
role="assistant",
|
|
339
|
+
content="I'd be happy to help! What's your order number?",
|
|
340
|
+
metadata={"confidence": 0.95}
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# Get conversation history from ContextEngine (async)
|
|
344
|
+
history = await memory.aget_conversation_history(session_id, limit=50)
|
|
345
|
+
|
|
346
|
+
# Format history for LLM prompts
|
|
347
|
+
formatted = await memory.aformat_conversation_history(session_id)
|
|
348
|
+
|
|
349
|
+
# Example 3: Session lifecycle management with ContextEngine
|
|
350
|
+
memory = ConversationMemory(agent_id="agent-1", context_engine=context_engine)
|
|
351
|
+
|
|
352
|
+
# Create session
|
|
353
|
+
session_id = await memory.acreate_session_with_context(
|
|
354
|
+
user_id="user-123"
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Track requests and metrics
|
|
358
|
+
await memory.update_session_with_context(
|
|
359
|
+
session_id=session_id,
|
|
360
|
+
increment_requests=True,
|
|
361
|
+
add_processing_time=1.5,
|
|
362
|
+
mark_error=False
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Get session metrics
|
|
366
|
+
session = await memory.aget_session_with_context(session_id)
|
|
367
|
+
metrics = session.get_metrics()
|
|
368
|
+
print(f"Request count: {metrics['request_count']}")
|
|
369
|
+
print(f"Average processing time: {metrics['average_processing_time']}s")
|
|
370
|
+
|
|
371
|
+
# End session
|
|
372
|
+
await memory.end_session_with_context(session_id, status="completed")
|
|
373
|
+
|
|
374
|
+
# Example 4: Automatic cleanup of inactive sessions
|
|
375
|
+
memory = ConversationMemory(agent_id="agent-1", context_engine=context_engine)
|
|
376
|
+
|
|
377
|
+
# Clean up sessions inactive for more than 30 minutes
|
|
378
|
+
cleaned_count = await memory.cleanup_inactive_sessions(max_idle_seconds=1800)
|
|
379
|
+
print(f"Cleaned up {cleaned_count} inactive sessions")
|
|
380
|
+
|
|
381
|
+
# Example 5: Conversation history persistence across restarts
|
|
382
|
+
# First run: Create agent with ContextEngine
|
|
383
|
+
context_engine = ContextEngine()
|
|
384
|
+
await context_engine.initialize()
|
|
385
|
+
|
|
386
|
+
memory1 = ConversationMemory(agent_id="agent-1", context_engine=context_engine)
|
|
387
|
+
session_id = await memory1.acreate_session_with_context(user_id="user-123")
|
|
388
|
+
|
|
389
|
+
await memory1.aadd_conversation_message(
|
|
390
|
+
session_id=session_id,
|
|
391
|
+
role="user",
|
|
392
|
+
content="What's the weather today?"
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
await memory1.aadd_conversation_message(
|
|
396
|
+
session_id=session_id,
|
|
397
|
+
role="assistant",
|
|
398
|
+
content="It's sunny and 72°F."
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# Agent restarts...
|
|
402
|
+
|
|
403
|
+
# Second run: Conversation history persists!
|
|
404
|
+
memory2 = ConversationMemory(agent_id="agent-1", context_engine=context_engine)
|
|
405
|
+
|
|
406
|
+
# Retrieve existing session
|
|
407
|
+
session = await memory2.aget_session_with_context(session_id)
|
|
408
|
+
if session:
|
|
409
|
+
# Get conversation history
|
|
410
|
+
history = await memory2.aget_conversation_history(session_id)
|
|
411
|
+
print(f"Retrieved {len(history)} messages from previous session")
|
|
412
|
+
|
|
413
|
+
# Example 6: Using with agents
|
|
414
|
+
from aiecs.domain.agent import HybridAgent
|
|
415
|
+
|
|
416
|
+
context_engine = ContextEngine()
|
|
417
|
+
await context_engine.initialize()
|
|
418
|
+
|
|
419
|
+
agent = HybridAgent(
|
|
420
|
+
agent_id="agent-1",
|
|
421
|
+
name="My Agent",
|
|
422
|
+
llm_client=OpenAIClient(),
|
|
423
|
+
tools=["search"],
|
|
424
|
+
config=config,
|
|
425
|
+
context_engine=context_engine # Agent uses ContextEngine
|
|
426
|
+
)
|
|
427
|
+
|
|
428
|
+
# Agent automatically uses ContextEngine for conversation history
|
|
429
|
+
# when context_engine is provided
|
|
430
|
+
|
|
431
|
+
# Example 7: Sync fallback methods (for compatibility)
|
|
432
|
+
memory = ConversationMemory(agent_id="agent-1", context_engine=context_engine)
|
|
433
|
+
|
|
434
|
+
# Sync methods fall back to in-memory storage
|
|
435
|
+
# (logs warning if ContextEngine is configured)
|
|
436
|
+
session_id = memory.create_session() # Falls back to in-memory
|
|
437
|
+
memory.add_message(session_id, "user", "Hello") # Falls back to in-memory
|
|
438
|
+
|
|
439
|
+
# Use async methods for ContextEngine integration
|
|
440
|
+
session_id = await memory.acreate_session_with_context(user_id="user-123")
|
|
441
|
+
await memory.aadd_conversation_message(session_id, "user", "Hello")
|
|
442
|
+
"""
|
|
443
|
+
|
|
444
|
+
def __init__(
|
|
445
|
+
self,
|
|
446
|
+
agent_id: str,
|
|
447
|
+
max_sessions: int = 100,
|
|
448
|
+
context_engine: Optional["ContextEngine"] = None,
|
|
449
|
+
):
|
|
450
|
+
"""
|
|
451
|
+
Initialize conversation memory.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
agent_id: Agent identifier
|
|
455
|
+
max_sessions: Maximum number of sessions to keep (for in-memory storage)
|
|
456
|
+
context_engine: Optional ContextEngine instance for persistent storage.
|
|
457
|
+
If provided, conversation history will be stored persistently.
|
|
458
|
+
If None, falls back to in-memory storage.
|
|
459
|
+
"""
|
|
460
|
+
self.agent_id = agent_id
|
|
461
|
+
self.max_sessions = max_sessions
|
|
462
|
+
self.context_engine = context_engine
|
|
463
|
+
self._sessions: Dict[str, Session] = {}
|
|
464
|
+
|
|
465
|
+
if context_engine:
|
|
466
|
+
logger.info(f"ConversationMemory initialized for agent {agent_id} with ContextEngine integration")
|
|
467
|
+
else:
|
|
468
|
+
logger.info(f"ConversationMemory initialized for agent {agent_id} (in-memory mode)")
|
|
469
|
+
|
|
470
|
+
def create_session(self, session_id: Optional[str] = None) -> str:
|
|
471
|
+
"""
|
|
472
|
+
Create a new conversation session.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
session_id: Optional custom session ID
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
Session ID
|
|
479
|
+
"""
|
|
480
|
+
if session_id is None:
|
|
481
|
+
session_id = f"session_{datetime.utcnow().timestamp()}"
|
|
482
|
+
|
|
483
|
+
if session_id in self._sessions:
|
|
484
|
+
logger.warning(f"Session {session_id} already exists")
|
|
485
|
+
return session_id
|
|
486
|
+
|
|
487
|
+
self._sessions[session_id] = Session(session_id=session_id, agent_id=self.agent_id)
|
|
488
|
+
|
|
489
|
+
# Cleanup old sessions if limit exceeded
|
|
490
|
+
if len(self._sessions) > self.max_sessions:
|
|
491
|
+
self._cleanup_old_sessions()
|
|
492
|
+
|
|
493
|
+
logger.debug(f"Session {session_id} created")
|
|
494
|
+
return session_id
|
|
495
|
+
|
|
496
|
+
def add_message(self, session_id: str, role: str, content: str) -> None:
|
|
497
|
+
"""
|
|
498
|
+
Add message to session.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
session_id: Session ID
|
|
502
|
+
role: Message role
|
|
503
|
+
content: Message content
|
|
504
|
+
"""
|
|
505
|
+
if session_id not in self._sessions:
|
|
506
|
+
logger.warning(f"Session {session_id} not found, creating it")
|
|
507
|
+
self.create_session(session_id)
|
|
508
|
+
|
|
509
|
+
self._sessions[session_id].add_message(role, content)
|
|
510
|
+
|
|
511
|
+
def get_history(self, session_id: str, limit: Optional[int] = None) -> List[LLMMessage]:
|
|
512
|
+
"""
|
|
513
|
+
Get conversation history for session.
|
|
514
|
+
|
|
515
|
+
Args:
|
|
516
|
+
session_id: Session ID
|
|
517
|
+
limit: Optional limit on number of messages
|
|
518
|
+
|
|
519
|
+
Returns:
|
|
520
|
+
List of messages
|
|
521
|
+
"""
|
|
522
|
+
if session_id not in self._sessions:
|
|
523
|
+
return []
|
|
524
|
+
|
|
525
|
+
session = self._sessions[session_id]
|
|
526
|
+
return session.get_recent_messages(limit) if limit else session.messages.copy()
|
|
527
|
+
|
|
528
|
+
def format_history(self, session_id: str, limit: Optional[int] = None) -> str:
|
|
529
|
+
"""
|
|
530
|
+
Format conversation history as string.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
session_id: Session ID
|
|
534
|
+
limit: Optional limit on number of messages
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
Formatted history string
|
|
538
|
+
"""
|
|
539
|
+
history = self.get_history(session_id, limit)
|
|
540
|
+
lines = []
|
|
541
|
+
for msg in history:
|
|
542
|
+
lines.append(f"{msg.role.upper()}: {msg.content}")
|
|
543
|
+
return "\n".join(lines)
|
|
544
|
+
|
|
545
|
+
def clear_session(self, session_id: str) -> None:
|
|
546
|
+
"""
|
|
547
|
+
Clear session messages.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
session_id: Session ID
|
|
551
|
+
"""
|
|
552
|
+
if session_id in self._sessions:
|
|
553
|
+
self._sessions[session_id].clear()
|
|
554
|
+
logger.debug(f"Session {session_id} cleared")
|
|
555
|
+
|
|
556
|
+
def delete_session(self, session_id: str) -> None:
|
|
557
|
+
"""
|
|
558
|
+
Delete session.
|
|
559
|
+
|
|
560
|
+
Args:
|
|
561
|
+
session_id: Session ID
|
|
562
|
+
"""
|
|
563
|
+
if session_id in self._sessions:
|
|
564
|
+
del self._sessions[session_id]
|
|
565
|
+
logger.debug(f"Session {session_id} deleted")
|
|
566
|
+
|
|
567
|
+
def get_session(self, session_id: str) -> Optional[Session]:
|
|
568
|
+
"""
|
|
569
|
+
Get session object.
|
|
570
|
+
|
|
571
|
+
Args:
|
|
572
|
+
session_id: Session ID
|
|
573
|
+
|
|
574
|
+
Returns:
|
|
575
|
+
Session or None
|
|
576
|
+
"""
|
|
577
|
+
return self._sessions.get(session_id)
|
|
578
|
+
|
|
579
|
+
def list_sessions(self) -> List[str]:
|
|
580
|
+
"""List all session IDs."""
|
|
581
|
+
return list(self._sessions.keys())
|
|
582
|
+
|
|
583
|
+
def _cleanup_old_sessions(self) -> None:
|
|
584
|
+
"""Remove oldest sessions to maintain limit."""
|
|
585
|
+
# Sort by last activity
|
|
586
|
+
sorted_sessions = sorted(self._sessions.items(), key=lambda x: x[1].last_activity)
|
|
587
|
+
|
|
588
|
+
# Remove oldest sessions
|
|
589
|
+
num_to_remove = len(self._sessions) - self.max_sessions
|
|
590
|
+
for session_id, _ in sorted_sessions[:num_to_remove]:
|
|
591
|
+
del self._sessions[session_id]
|
|
592
|
+
logger.debug(f"Removed old session {session_id}")
|
|
593
|
+
|
|
594
|
+
def get_stats(self) -> Dict:
|
|
595
|
+
"""Get memory statistics."""
|
|
596
|
+
return {
|
|
597
|
+
"agent_id": self.agent_id,
|
|
598
|
+
"total_sessions": len(self._sessions),
|
|
599
|
+
"total_messages": sum(len(s.messages) for s in self._sessions.values()),
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
# ==================== ContextEngine Integration Methods ====================
|
|
603
|
+
|
|
604
|
+
def add_conversation_message(self, session_id: str, role: str, content: str, metadata: Optional[Dict] = None) -> None:
|
|
605
|
+
"""
|
|
606
|
+
Add conversation message (sync version with ContextEngine fallback).
|
|
607
|
+
|
|
608
|
+
This is a synchronous wrapper that falls back to in-memory storage.
|
|
609
|
+
For ContextEngine integration, use aadd_conversation_message() instead.
|
|
610
|
+
|
|
611
|
+
Args:
|
|
612
|
+
session_id: Session ID
|
|
613
|
+
role: Message role (user, assistant, system)
|
|
614
|
+
content: Message content
|
|
615
|
+
metadata: Optional message metadata
|
|
616
|
+
|
|
617
|
+
Note:
|
|
618
|
+
If ContextEngine is configured, this method will log a warning
|
|
619
|
+
and fall back to in-memory storage. Use the async version
|
|
620
|
+
aadd_conversation_message() for ContextEngine integration.
|
|
621
|
+
"""
|
|
622
|
+
if self.context_engine:
|
|
623
|
+
logger.warning("add_conversation_message called with ContextEngine configured. " "Use aadd_conversation_message() for persistent storage. " "Falling back to in-memory storage.")
|
|
624
|
+
|
|
625
|
+
# Fall back to existing add_message method
|
|
626
|
+
self.add_message(session_id, role, content)
|
|
627
|
+
|
|
628
|
+
async def aadd_conversation_message(self, session_id: str, role: str, content: str, metadata: Optional[Dict] = None) -> bool:
|
|
629
|
+
"""
|
|
630
|
+
Add conversation message (async version with ContextEngine integration).
|
|
631
|
+
|
|
632
|
+
Args:
|
|
633
|
+
session_id: Session ID
|
|
634
|
+
role: Message role (user, assistant, system)
|
|
635
|
+
content: Message content
|
|
636
|
+
metadata: Optional message metadata
|
|
637
|
+
|
|
638
|
+
Returns:
|
|
639
|
+
True if message was added successfully, False otherwise
|
|
640
|
+
"""
|
|
641
|
+
if self.context_engine:
|
|
642
|
+
try:
|
|
643
|
+
# Use ContextEngine for persistent storage
|
|
644
|
+
success = await self.context_engine.add_conversation_message(session_id=session_id, role=role, content=content, metadata=metadata or {})
|
|
645
|
+
logger.debug(f"Added message to session {session_id} via ContextEngine (role={role})")
|
|
646
|
+
return success
|
|
647
|
+
except Exception as e:
|
|
648
|
+
logger.error(f"Failed to add message to ContextEngine for session {session_id}: {e}")
|
|
649
|
+
logger.warning("Falling back to in-memory storage")
|
|
650
|
+
# Fall back to in-memory
|
|
651
|
+
self.add_message(session_id, role, content)
|
|
652
|
+
return False
|
|
653
|
+
else:
|
|
654
|
+
# Use in-memory storage
|
|
655
|
+
self.add_message(session_id, role, content)
|
|
656
|
+
return True
|
|
657
|
+
|
|
658
|
+
def get_conversation_history(self, session_id: str, limit: Optional[int] = None) -> List[LLMMessage]:
|
|
659
|
+
"""
|
|
660
|
+
Get conversation history (sync version with ContextEngine fallback).
|
|
661
|
+
|
|
662
|
+
This is a synchronous wrapper that falls back to in-memory storage.
|
|
663
|
+
For ContextEngine integration, use aget_conversation_history() instead.
|
|
664
|
+
|
|
665
|
+
Args:
|
|
666
|
+
session_id: Session ID
|
|
667
|
+
limit: Optional limit on number of messages
|
|
668
|
+
|
|
669
|
+
Returns:
|
|
670
|
+
List of LLM messages
|
|
671
|
+
|
|
672
|
+
Note:
|
|
673
|
+
If ContextEngine is configured, this method will log a warning
|
|
674
|
+
and fall back to in-memory storage. Use the async version
|
|
675
|
+
aget_conversation_history() for ContextEngine integration.
|
|
676
|
+
"""
|
|
677
|
+
if self.context_engine:
|
|
678
|
+
logger.warning("get_conversation_history called with ContextEngine configured. " "Use aget_conversation_history() for persistent storage. " "Falling back to in-memory storage.")
|
|
679
|
+
|
|
680
|
+
# Fall back to existing get_history method
|
|
681
|
+
return self.get_history(session_id, limit)
|
|
682
|
+
|
|
683
|
+
async def aget_conversation_history(self, session_id: str, limit: Optional[int] = None) -> List[LLMMessage]:
|
|
684
|
+
"""
|
|
685
|
+
Get conversation history (async version with ContextEngine integration).
|
|
686
|
+
|
|
687
|
+
Args:
|
|
688
|
+
session_id: Session ID
|
|
689
|
+
limit: Optional limit on number of messages
|
|
690
|
+
|
|
691
|
+
Returns:
|
|
692
|
+
List of LLM messages
|
|
693
|
+
"""
|
|
694
|
+
if self.context_engine:
|
|
695
|
+
try:
|
|
696
|
+
# Use ContextEngine for persistent storage
|
|
697
|
+
messages = await self.context_engine.get_conversation_history(session_id=session_id, limit=limit or 50)
|
|
698
|
+
|
|
699
|
+
# Convert ConversationMessage to LLMMessage
|
|
700
|
+
llm_messages = []
|
|
701
|
+
for msg in messages:
|
|
702
|
+
# messages is List[Dict[str, Any]] from ContextEngine
|
|
703
|
+
if isinstance(msg, dict):
|
|
704
|
+
llm_messages.append(LLMMessage(role=msg.get("role", "user"), content=msg.get("content", "")))
|
|
705
|
+
else:
|
|
706
|
+
# Fallback for ConversationMessage objects
|
|
707
|
+
llm_messages.append(LLMMessage(role=msg.role, content=msg.content))
|
|
708
|
+
|
|
709
|
+
logger.debug(f"Retrieved {len(llm_messages)} messages from session {session_id} via ContextEngine")
|
|
710
|
+
return llm_messages
|
|
711
|
+
except Exception as e:
|
|
712
|
+
logger.error(f"Failed to get conversation history from ContextEngine for session {session_id}: {e}")
|
|
713
|
+
logger.warning("Falling back to in-memory storage")
|
|
714
|
+
# Fall back to in-memory
|
|
715
|
+
return self.get_history(session_id, limit)
|
|
716
|
+
else:
|
|
717
|
+
# Use in-memory storage
|
|
718
|
+
return self.get_history(session_id, limit)
|
|
719
|
+
|
|
720
|
+
def format_conversation_history(self, session_id: str, limit: Optional[int] = None) -> str:
|
|
721
|
+
"""
|
|
722
|
+
Format conversation history as string for LLM prompt formatting.
|
|
723
|
+
|
|
724
|
+
This is a synchronous wrapper that falls back to in-memory storage.
|
|
725
|
+
For ContextEngine integration, use aformat_conversation_history() instead.
|
|
726
|
+
|
|
727
|
+
Args:
|
|
728
|
+
session_id: Session ID
|
|
729
|
+
limit: Optional limit on number of messages
|
|
730
|
+
|
|
731
|
+
Returns:
|
|
732
|
+
Formatted history string
|
|
733
|
+
|
|
734
|
+
Note:
|
|
735
|
+
If ContextEngine is configured, this method will log a warning
|
|
736
|
+
and fall back to in-memory storage. Use the async version
|
|
737
|
+
aformat_conversation_history() for ContextEngine integration.
|
|
738
|
+
"""
|
|
739
|
+
if self.context_engine:
|
|
740
|
+
logger.warning("format_conversation_history called with ContextEngine configured. " "Use aformat_conversation_history() for persistent storage. " "Falling back to in-memory storage.")
|
|
741
|
+
|
|
742
|
+
# Fall back to existing format_history method
|
|
743
|
+
return self.format_history(session_id, limit)
|
|
744
|
+
|
|
745
|
+
async def aformat_conversation_history(self, session_id: str, limit: Optional[int] = None) -> str:
|
|
746
|
+
"""
|
|
747
|
+
Format conversation history as string (async version with ContextEngine integration).
|
|
748
|
+
|
|
749
|
+
Args:
|
|
750
|
+
session_id: Session ID
|
|
751
|
+
limit: Optional limit on number of messages
|
|
752
|
+
|
|
753
|
+
Returns:
|
|
754
|
+
Formatted history string
|
|
755
|
+
"""
|
|
756
|
+
# Get conversation history (uses ContextEngine if available)
|
|
757
|
+
history = await self.aget_conversation_history(session_id, limit)
|
|
758
|
+
|
|
759
|
+
# Format messages
|
|
760
|
+
lines = []
|
|
761
|
+
for msg in history:
|
|
762
|
+
lines.append(f"{msg.role.upper()}: {msg.content}")
|
|
763
|
+
|
|
764
|
+
return "\n".join(lines)
|
|
765
|
+
|
|
766
|
+
async def clear_conversation_history(self, session_id: str) -> bool:
|
|
767
|
+
"""
|
|
768
|
+
Clear conversation history with ContextEngine cleanup.
|
|
769
|
+
|
|
770
|
+
This method clears both in-memory and ContextEngine storage.
|
|
771
|
+
|
|
772
|
+
Args:
|
|
773
|
+
session_id: Session ID
|
|
774
|
+
|
|
775
|
+
Returns:
|
|
776
|
+
True if cleared successfully, False otherwise
|
|
777
|
+
"""
|
|
778
|
+
success = True
|
|
779
|
+
|
|
780
|
+
# Clear in-memory storage
|
|
781
|
+
self.clear_session(session_id)
|
|
782
|
+
|
|
783
|
+
# Clear ContextEngine storage if available
|
|
784
|
+
if self.context_engine:
|
|
785
|
+
try:
|
|
786
|
+
# ContextEngine doesn't have a direct clear_conversation method,
|
|
787
|
+
# so we'll need to end the session which will clean up associated data
|
|
788
|
+
await self.context_engine.end_session(session_id, status="cleared")
|
|
789
|
+
logger.debug(f"Cleared conversation history for session {session_id} in ContextEngine")
|
|
790
|
+
except Exception as e:
|
|
791
|
+
logger.error(f"Failed to clear conversation history in ContextEngine for session {session_id}: {e}")
|
|
792
|
+
success = False
|
|
793
|
+
|
|
794
|
+
return success
|
|
795
|
+
|
|
796
|
+
# ==================== Session Management Methods ====================
|
|
797
|
+
|
|
798
|
+
def create_session_with_context(
|
|
799
|
+
self,
|
|
800
|
+
session_id: Optional[str] = None,
|
|
801
|
+
user_id: Optional[str] = None,
|
|
802
|
+
metadata: Optional[Dict] = None,
|
|
803
|
+
) -> str:
|
|
804
|
+
"""
|
|
805
|
+
Create a new session (sync version with ContextEngine fallback).
|
|
806
|
+
|
|
807
|
+
This is a synchronous wrapper that falls back to in-memory storage.
|
|
808
|
+
For ContextEngine integration, use acreate_session_with_context() instead.
|
|
809
|
+
|
|
810
|
+
Args:
|
|
811
|
+
session_id: Optional custom session ID
|
|
812
|
+
user_id: Optional user ID for ContextEngine
|
|
813
|
+
metadata: Optional session metadata
|
|
814
|
+
|
|
815
|
+
Returns:
|
|
816
|
+
Session ID
|
|
817
|
+
|
|
818
|
+
Note:
|
|
819
|
+
If ContextEngine is configured, this method will log a warning
|
|
820
|
+
and fall back to in-memory storage. Use the async version
|
|
821
|
+
acreate_session_with_context() for ContextEngine integration.
|
|
822
|
+
"""
|
|
823
|
+
if self.context_engine:
|
|
824
|
+
logger.warning("create_session_with_context called with ContextEngine configured. " "Use acreate_session_with_context() for persistent storage. " "Falling back to in-memory storage.")
|
|
825
|
+
|
|
826
|
+
# Fall back to existing create_session method
|
|
827
|
+
return self.create_session(session_id)
|
|
828
|
+
|
|
829
|
+
async def acreate_session_with_context(
|
|
830
|
+
self,
|
|
831
|
+
session_id: Optional[str] = None,
|
|
832
|
+
user_id: Optional[str] = None,
|
|
833
|
+
metadata: Optional[Dict] = None,
|
|
834
|
+
) -> str:
|
|
835
|
+
"""
|
|
836
|
+
Create a new session (async version with ContextEngine integration).
|
|
837
|
+
|
|
838
|
+
Args:
|
|
839
|
+
session_id: Optional custom session ID
|
|
840
|
+
user_id: Optional user ID for ContextEngine (defaults to agent_id)
|
|
841
|
+
metadata: Optional session metadata
|
|
842
|
+
|
|
843
|
+
Returns:
|
|
844
|
+
Session ID
|
|
845
|
+
"""
|
|
846
|
+
if session_id is None:
|
|
847
|
+
session_id = f"session_{datetime.utcnow().timestamp()}"
|
|
848
|
+
|
|
849
|
+
if self.context_engine:
|
|
850
|
+
try:
|
|
851
|
+
# Use ContextEngine for persistent storage
|
|
852
|
+
user_id = user_id or self.agent_id
|
|
853
|
+
await self.context_engine.create_session(session_id=session_id, user_id=user_id, metadata=metadata or {})
|
|
854
|
+
logger.debug(f"Created session {session_id} via ContextEngine")
|
|
855
|
+
|
|
856
|
+
# Also create in-memory session for compatibility
|
|
857
|
+
if session_id not in self._sessions:
|
|
858
|
+
self._sessions[session_id] = Session(session_id=session_id, agent_id=self.agent_id, metadata=metadata or {})
|
|
859
|
+
except Exception as e:
|
|
860
|
+
logger.error(f"Failed to create session in ContextEngine: {e}")
|
|
861
|
+
logger.warning("Falling back to in-memory storage")
|
|
862
|
+
# Fall back to in-memory
|
|
863
|
+
return self.create_session(session_id)
|
|
864
|
+
else:
|
|
865
|
+
# Use in-memory storage
|
|
866
|
+
return self.create_session(session_id)
|
|
867
|
+
|
|
868
|
+
return session_id
|
|
869
|
+
|
|
870
|
+
def get_session_with_context(self, session_id: str) -> Optional[Session]:
|
|
871
|
+
"""
|
|
872
|
+
Get session (sync version with ContextEngine fallback).
|
|
873
|
+
|
|
874
|
+
This is a synchronous wrapper that falls back to in-memory storage.
|
|
875
|
+
For ContextEngine integration, use aget_session_with_context() instead.
|
|
876
|
+
|
|
877
|
+
Args:
|
|
878
|
+
session_id: Session ID
|
|
879
|
+
|
|
880
|
+
Returns:
|
|
881
|
+
Session object or None
|
|
882
|
+
|
|
883
|
+
Note:
|
|
884
|
+
If ContextEngine is configured, this method will log a warning
|
|
885
|
+
and fall back to in-memory storage. Use the async version
|
|
886
|
+
aget_session_with_context() for ContextEngine integration.
|
|
887
|
+
"""
|
|
888
|
+
if self.context_engine:
|
|
889
|
+
logger.warning("get_session_with_context called with ContextEngine configured. " "Use aget_session_with_context() for persistent storage. " "Falling back to in-memory storage.")
|
|
890
|
+
|
|
891
|
+
# Fall back to existing get_session method
|
|
892
|
+
return self.get_session(session_id)
|
|
893
|
+
|
|
894
|
+
async def aget_session_with_context(self, session_id: str) -> Optional[Session]:
|
|
895
|
+
"""
|
|
896
|
+
Get session (async version with ContextEngine integration).
|
|
897
|
+
|
|
898
|
+
Args:
|
|
899
|
+
session_id: Session ID
|
|
900
|
+
|
|
901
|
+
Returns:
|
|
902
|
+
Session object or None
|
|
903
|
+
"""
|
|
904
|
+
if self.context_engine:
|
|
905
|
+
try:
|
|
906
|
+
# Get from ContextEngine
|
|
907
|
+
session_metrics = await self.context_engine.get_session(session_id)
|
|
908
|
+
|
|
909
|
+
if session_metrics:
|
|
910
|
+
# session_metrics is Dict[str, Any] from ContextEngine
|
|
911
|
+
# Check if we have it in memory
|
|
912
|
+
if session_id in self._sessions:
|
|
913
|
+
# Update in-memory session with ContextEngine metrics
|
|
914
|
+
session = self._sessions[session_id]
|
|
915
|
+
if isinstance(session_metrics, dict):
|
|
916
|
+
session.request_count = session_metrics.get("request_count", 0)
|
|
917
|
+
session.error_count = session_metrics.get("error_count", 0)
|
|
918
|
+
session.total_processing_time = session_metrics.get("total_processing_time", 0.0)
|
|
919
|
+
session.status = session_metrics.get("status", "active")
|
|
920
|
+
else:
|
|
921
|
+
session.request_count = session_metrics.request_count
|
|
922
|
+
session.error_count = session_metrics.error_count
|
|
923
|
+
session.total_processing_time = session_metrics.total_processing_time
|
|
924
|
+
session.status = session_metrics.status
|
|
925
|
+
return session
|
|
926
|
+
else:
|
|
927
|
+
# Create in-memory session from ContextEngine data
|
|
928
|
+
if isinstance(session_metrics, dict):
|
|
929
|
+
# Convert datetime strings to datetime objects if needed
|
|
930
|
+
created_at_val = session_metrics.get("created_at")
|
|
931
|
+
if isinstance(created_at_val, str):
|
|
932
|
+
try:
|
|
933
|
+
created_at_val = datetime.fromisoformat(created_at_val.replace("Z", "+00:00"))
|
|
934
|
+
except (ValueError, AttributeError):
|
|
935
|
+
created_at_val = datetime.utcnow()
|
|
936
|
+
elif created_at_val is None:
|
|
937
|
+
created_at_val = datetime.utcnow()
|
|
938
|
+
|
|
939
|
+
last_activity_val = session_metrics.get("last_activity")
|
|
940
|
+
if isinstance(last_activity_val, str):
|
|
941
|
+
try:
|
|
942
|
+
last_activity_val = datetime.fromisoformat(last_activity_val.replace("Z", "+00:00"))
|
|
943
|
+
except (ValueError, AttributeError):
|
|
944
|
+
last_activity_val = datetime.utcnow()
|
|
945
|
+
elif last_activity_val is None:
|
|
946
|
+
last_activity_val = datetime.utcnow()
|
|
947
|
+
|
|
948
|
+
session = Session(
|
|
949
|
+
session_id=session_id,
|
|
950
|
+
agent_id=self.agent_id,
|
|
951
|
+
created_at=created_at_val if isinstance(created_at_val, datetime) else datetime.utcnow(),
|
|
952
|
+
last_activity=last_activity_val if isinstance(last_activity_val, datetime) else datetime.utcnow(),
|
|
953
|
+
status=session_metrics.get("status", "active"),
|
|
954
|
+
request_count=session_metrics.get("request_count", 0),
|
|
955
|
+
error_count=session_metrics.get("error_count", 0),
|
|
956
|
+
total_processing_time=session_metrics.get("total_processing_time", 0.0),
|
|
957
|
+
)
|
|
958
|
+
else:
|
|
959
|
+
session = Session(
|
|
960
|
+
session_id=session_id,
|
|
961
|
+
agent_id=self.agent_id,
|
|
962
|
+
created_at=session_metrics.created_at,
|
|
963
|
+
last_activity=session_metrics.last_activity,
|
|
964
|
+
status=session_metrics.status,
|
|
965
|
+
request_count=session_metrics.request_count,
|
|
966
|
+
error_count=session_metrics.error_count,
|
|
967
|
+
total_processing_time=session_metrics.total_processing_time,
|
|
968
|
+
)
|
|
969
|
+
self._sessions[session_id] = session
|
|
970
|
+
return session
|
|
971
|
+
|
|
972
|
+
logger.debug(f"Session {session_id} not found in ContextEngine")
|
|
973
|
+
return None
|
|
974
|
+
except Exception as e:
|
|
975
|
+
logger.error(f"Failed to get session from ContextEngine: {e}")
|
|
976
|
+
logger.warning("Falling back to in-memory storage")
|
|
977
|
+
# Fall back to in-memory
|
|
978
|
+
return self.get_session(session_id)
|
|
979
|
+
else:
|
|
980
|
+
# Use in-memory storage
|
|
981
|
+
return self.get_session(session_id)
|
|
982
|
+
|
|
983
|
+
async def update_session_with_context(
|
|
984
|
+
self,
|
|
985
|
+
session_id: str,
|
|
986
|
+
increment_requests: bool = False,
|
|
987
|
+
add_processing_time: float = 0.0,
|
|
988
|
+
mark_error: bool = False,
|
|
989
|
+
updates: Optional[Dict[str, Any]] = None,
|
|
990
|
+
) -> bool:
|
|
991
|
+
"""
|
|
992
|
+
Update session with activity and metrics.
|
|
993
|
+
|
|
994
|
+
Args:
|
|
995
|
+
session_id: Session ID
|
|
996
|
+
increment_requests: Whether to increment request count
|
|
997
|
+
add_processing_time: Processing time to add (in seconds)
|
|
998
|
+
mark_error: Whether to mark an error
|
|
999
|
+
updates: Optional dictionary of additional updates
|
|
1000
|
+
|
|
1001
|
+
Returns:
|
|
1002
|
+
True if updated successfully, False otherwise
|
|
1003
|
+
"""
|
|
1004
|
+
# Update in-memory session
|
|
1005
|
+
session = self.get_session(session_id)
|
|
1006
|
+
if session:
|
|
1007
|
+
if increment_requests or add_processing_time > 0 or mark_error:
|
|
1008
|
+
session.track_request(processing_time=add_processing_time, is_error=mark_error)
|
|
1009
|
+
|
|
1010
|
+
# Apply custom updates
|
|
1011
|
+
if updates:
|
|
1012
|
+
for key, value in updates.items():
|
|
1013
|
+
if hasattr(session, key):
|
|
1014
|
+
setattr(session, key, value)
|
|
1015
|
+
|
|
1016
|
+
# Update ContextEngine if available
|
|
1017
|
+
if self.context_engine:
|
|
1018
|
+
try:
|
|
1019
|
+
await self.context_engine.update_session(
|
|
1020
|
+
session_id=session_id,
|
|
1021
|
+
increment_requests=increment_requests,
|
|
1022
|
+
add_processing_time=add_processing_time,
|
|
1023
|
+
mark_error=mark_error,
|
|
1024
|
+
updates=updates or {},
|
|
1025
|
+
)
|
|
1026
|
+
logger.debug(f"Updated session {session_id} in ContextEngine")
|
|
1027
|
+
return True
|
|
1028
|
+
except Exception as e:
|
|
1029
|
+
logger.error(f"Failed to update session in ContextEngine: {e}")
|
|
1030
|
+
return False
|
|
1031
|
+
|
|
1032
|
+
return session is not None
|
|
1033
|
+
|
|
1034
|
+
async def end_session_with_context(self, session_id: str, status: str = "completed") -> bool:
|
|
1035
|
+
"""
|
|
1036
|
+
End a session and update metrics.
|
|
1037
|
+
|
|
1038
|
+
Args:
|
|
1039
|
+
session_id: Session ID
|
|
1040
|
+
status: Final session status (completed, failed, expired)
|
|
1041
|
+
|
|
1042
|
+
Returns:
|
|
1043
|
+
True if ended successfully, False otherwise
|
|
1044
|
+
"""
|
|
1045
|
+
# End in-memory session
|
|
1046
|
+
session = self.get_session(session_id)
|
|
1047
|
+
if session:
|
|
1048
|
+
session.end(status=status)
|
|
1049
|
+
|
|
1050
|
+
# End ContextEngine session if available
|
|
1051
|
+
if self.context_engine:
|
|
1052
|
+
try:
|
|
1053
|
+
await self.context_engine.end_session(session_id, status=status)
|
|
1054
|
+
logger.debug(f"Ended session {session_id} in ContextEngine with status: {status}")
|
|
1055
|
+
return True
|
|
1056
|
+
except Exception as e:
|
|
1057
|
+
logger.error(f"Failed to end session in ContextEngine: {e}")
|
|
1058
|
+
return False
|
|
1059
|
+
|
|
1060
|
+
return session is not None
|
|
1061
|
+
|
|
1062
|
+
def track_session_request(self, session_id: str, processing_time: float = 0.0, is_error: bool = False) -> None:
|
|
1063
|
+
"""
|
|
1064
|
+
Track a request in a session.
|
|
1065
|
+
|
|
1066
|
+
Args:
|
|
1067
|
+
session_id: Session ID
|
|
1068
|
+
processing_time: Processing time in seconds
|
|
1069
|
+
is_error: Whether the request resulted in an error
|
|
1070
|
+
"""
|
|
1071
|
+
session = self.get_session(session_id)
|
|
1072
|
+
if session:
|
|
1073
|
+
session.track_request(processing_time=processing_time, is_error=is_error)
|
|
1074
|
+
else:
|
|
1075
|
+
logger.warning(f"Session {session_id} not found for request tracking")
|
|
1076
|
+
|
|
1077
|
+
def get_session_metrics(self, session_id: str) -> Optional[Dict]:
|
|
1078
|
+
"""
|
|
1079
|
+
Get aggregated metrics for a session.
|
|
1080
|
+
|
|
1081
|
+
Args:
|
|
1082
|
+
session_id: Session ID
|
|
1083
|
+
|
|
1084
|
+
Returns:
|
|
1085
|
+
Dictionary with session metrics or None if session not found
|
|
1086
|
+
"""
|
|
1087
|
+
session = self.get_session(session_id)
|
|
1088
|
+
if session:
|
|
1089
|
+
return session.get_metrics()
|
|
1090
|
+
return None
|
|
1091
|
+
|
|
1092
|
+
async def cleanup_inactive_sessions(self, max_idle_seconds: int = 1800) -> int:
|
|
1093
|
+
"""
|
|
1094
|
+
Clean up inactive sessions.
|
|
1095
|
+
|
|
1096
|
+
Args:
|
|
1097
|
+
max_idle_seconds: Maximum idle time in seconds (default: 30 minutes)
|
|
1098
|
+
|
|
1099
|
+
Returns:
|
|
1100
|
+
Number of sessions cleaned up
|
|
1101
|
+
"""
|
|
1102
|
+
cleaned_count = 0
|
|
1103
|
+
|
|
1104
|
+
# Find expired sessions
|
|
1105
|
+
expired_sessions = []
|
|
1106
|
+
for session_id, session in list(self._sessions.items()):
|
|
1107
|
+
if session.is_expired(max_idle_seconds):
|
|
1108
|
+
expired_sessions.append(session_id)
|
|
1109
|
+
|
|
1110
|
+
# End and remove expired sessions
|
|
1111
|
+
for session_id in expired_sessions:
|
|
1112
|
+
# End session with expired status
|
|
1113
|
+
await self.end_session_with_context(session_id, status="expired")
|
|
1114
|
+
|
|
1115
|
+
# Remove from in-memory storage
|
|
1116
|
+
if session_id in self._sessions:
|
|
1117
|
+
del self._sessions[session_id]
|
|
1118
|
+
cleaned_count += 1
|
|
1119
|
+
logger.debug(f"Cleaned up expired session {session_id}")
|
|
1120
|
+
|
|
1121
|
+
if cleaned_count > 0:
|
|
1122
|
+
logger.info(f"Cleaned up {cleaned_count} inactive sessions")
|
|
1123
|
+
|
|
1124
|
+
return cleaned_count
|