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.

Files changed (340) hide show
  1. aiecs/__init__.py +13 -16
  2. aiecs/__main__.py +7 -7
  3. aiecs/aiecs_client.py +269 -75
  4. aiecs/application/executors/operation_executor.py +79 -54
  5. aiecs/application/knowledge_graph/__init__.py +7 -0
  6. aiecs/application/knowledge_graph/builder/__init__.py +37 -0
  7. aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
  8. aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
  9. aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
  10. aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
  11. aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
  12. aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
  13. aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
  14. aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
  15. aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
  16. aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
  17. aiecs/application/knowledge_graph/extractors/base.py +98 -0
  18. aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
  19. aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
  20. aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
  21. aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
  22. aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
  23. aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
  24. aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
  25. aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
  26. aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
  27. aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
  28. aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
  29. aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
  30. aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
  31. aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
  32. aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
  33. aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
  34. aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
  35. aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
  36. aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
  37. aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
  38. aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
  39. aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
  40. aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -0
  41. aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
  42. aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
  43. aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +341 -0
  44. aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
  45. aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -0
  46. aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
  47. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
  48. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +913 -0
  49. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
  50. aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
  51. aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
  52. aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
  53. aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
  54. aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
  55. aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
  56. aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
  57. aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
  58. aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
  59. aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
  60. aiecs/application/knowledge_graph/search/__init__.py +59 -0
  61. aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
  62. aiecs/application/knowledge_graph/search/reranker.py +293 -0
  63. aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
  64. aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
  65. aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
  66. aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
  67. aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
  68. aiecs/application/knowledge_graph/validators/__init__.py +13 -0
  69. aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
  70. aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
  71. aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
  72. aiecs/common/__init__.py +9 -0
  73. aiecs/common/knowledge_graph/__init__.py +17 -0
  74. aiecs/common/knowledge_graph/runnable.py +471 -0
  75. aiecs/config/__init__.py +20 -5
  76. aiecs/config/config.py +762 -31
  77. aiecs/config/graph_config.py +131 -0
  78. aiecs/config/tool_config.py +399 -0
  79. aiecs/core/__init__.py +29 -13
  80. aiecs/core/interface/__init__.py +2 -2
  81. aiecs/core/interface/execution_interface.py +22 -22
  82. aiecs/core/interface/storage_interface.py +37 -88
  83. aiecs/core/registry/__init__.py +31 -0
  84. aiecs/core/registry/service_registry.py +92 -0
  85. aiecs/domain/__init__.py +270 -1
  86. aiecs/domain/agent/__init__.py +191 -0
  87. aiecs/domain/agent/base_agent.py +3870 -0
  88. aiecs/domain/agent/exceptions.py +99 -0
  89. aiecs/domain/agent/graph_aware_mixin.py +569 -0
  90. aiecs/domain/agent/hybrid_agent.py +1435 -0
  91. aiecs/domain/agent/integration/__init__.py +29 -0
  92. aiecs/domain/agent/integration/context_compressor.py +216 -0
  93. aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
  94. aiecs/domain/agent/integration/protocols.py +281 -0
  95. aiecs/domain/agent/integration/retry_policy.py +218 -0
  96. aiecs/domain/agent/integration/role_config.py +213 -0
  97. aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
  98. aiecs/domain/agent/lifecycle.py +291 -0
  99. aiecs/domain/agent/llm_agent.py +692 -0
  100. aiecs/domain/agent/memory/__init__.py +12 -0
  101. aiecs/domain/agent/memory/conversation.py +1124 -0
  102. aiecs/domain/agent/migration/__init__.py +14 -0
  103. aiecs/domain/agent/migration/conversion.py +163 -0
  104. aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
  105. aiecs/domain/agent/models.py +884 -0
  106. aiecs/domain/agent/observability.py +479 -0
  107. aiecs/domain/agent/persistence.py +449 -0
  108. aiecs/domain/agent/prompts/__init__.py +29 -0
  109. aiecs/domain/agent/prompts/builder.py +159 -0
  110. aiecs/domain/agent/prompts/formatters.py +187 -0
  111. aiecs/domain/agent/prompts/template.py +255 -0
  112. aiecs/domain/agent/registry.py +253 -0
  113. aiecs/domain/agent/tool_agent.py +444 -0
  114. aiecs/domain/agent/tools/__init__.py +15 -0
  115. aiecs/domain/agent/tools/schema_generator.py +364 -0
  116. aiecs/domain/community/__init__.py +155 -0
  117. aiecs/domain/community/agent_adapter.py +469 -0
  118. aiecs/domain/community/analytics.py +432 -0
  119. aiecs/domain/community/collaborative_workflow.py +648 -0
  120. aiecs/domain/community/communication_hub.py +634 -0
  121. aiecs/domain/community/community_builder.py +320 -0
  122. aiecs/domain/community/community_integration.py +796 -0
  123. aiecs/domain/community/community_manager.py +803 -0
  124. aiecs/domain/community/decision_engine.py +849 -0
  125. aiecs/domain/community/exceptions.py +231 -0
  126. aiecs/domain/community/models/__init__.py +33 -0
  127. aiecs/domain/community/models/community_models.py +234 -0
  128. aiecs/domain/community/resource_manager.py +461 -0
  129. aiecs/domain/community/shared_context_manager.py +589 -0
  130. aiecs/domain/context/__init__.py +40 -10
  131. aiecs/domain/context/context_engine.py +1910 -0
  132. aiecs/domain/context/conversation_models.py +87 -53
  133. aiecs/domain/context/graph_memory.py +582 -0
  134. aiecs/domain/execution/model.py +12 -4
  135. aiecs/domain/knowledge_graph/__init__.py +19 -0
  136. aiecs/domain/knowledge_graph/models/__init__.py +52 -0
  137. aiecs/domain/knowledge_graph/models/entity.py +148 -0
  138. aiecs/domain/knowledge_graph/models/evidence.py +178 -0
  139. aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
  140. aiecs/domain/knowledge_graph/models/path.py +171 -0
  141. aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
  142. aiecs/domain/knowledge_graph/models/query.py +261 -0
  143. aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
  144. aiecs/domain/knowledge_graph/models/relation.py +202 -0
  145. aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
  146. aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
  147. aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
  148. aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
  149. aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
  150. aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
  151. aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
  152. aiecs/domain/task/dsl_processor.py +172 -56
  153. aiecs/domain/task/model.py +20 -8
  154. aiecs/domain/task/task_context.py +27 -24
  155. aiecs/infrastructure/__init__.py +0 -2
  156. aiecs/infrastructure/graph_storage/__init__.py +11 -0
  157. aiecs/infrastructure/graph_storage/base.py +837 -0
  158. aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
  159. aiecs/infrastructure/graph_storage/cache.py +424 -0
  160. aiecs/infrastructure/graph_storage/distributed.py +223 -0
  161. aiecs/infrastructure/graph_storage/error_handling.py +380 -0
  162. aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
  163. aiecs/infrastructure/graph_storage/health_checks.py +378 -0
  164. aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
  165. aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
  166. aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
  167. aiecs/infrastructure/graph_storage/metrics.py +344 -0
  168. aiecs/infrastructure/graph_storage/migration.py +400 -0
  169. aiecs/infrastructure/graph_storage/pagination.py +483 -0
  170. aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
  171. aiecs/infrastructure/graph_storage/postgres.py +1563 -0
  172. aiecs/infrastructure/graph_storage/property_storage.py +353 -0
  173. aiecs/infrastructure/graph_storage/protocols.py +76 -0
  174. aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
  175. aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
  176. aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
  177. aiecs/infrastructure/graph_storage/streaming.py +487 -0
  178. aiecs/infrastructure/graph_storage/tenant.py +412 -0
  179. aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
  180. aiecs/infrastructure/messaging/websocket_manager.py +51 -35
  181. aiecs/infrastructure/monitoring/__init__.py +22 -0
  182. aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
  183. aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
  184. aiecs/infrastructure/monitoring/structured_logger.py +3 -7
  185. aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
  186. aiecs/infrastructure/persistence/__init__.py +14 -1
  187. aiecs/infrastructure/persistence/context_engine_client.py +184 -0
  188. aiecs/infrastructure/persistence/database_manager.py +67 -43
  189. aiecs/infrastructure/persistence/file_storage.py +180 -103
  190. aiecs/infrastructure/persistence/redis_client.py +74 -21
  191. aiecs/llm/__init__.py +73 -25
  192. aiecs/llm/callbacks/__init__.py +11 -0
  193. aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
  194. aiecs/llm/client_factory.py +224 -36
  195. aiecs/llm/client_resolver.py +155 -0
  196. aiecs/llm/clients/__init__.py +38 -0
  197. aiecs/llm/clients/base_client.py +324 -0
  198. aiecs/llm/clients/google_function_calling_mixin.py +457 -0
  199. aiecs/llm/clients/googleai_client.py +241 -0
  200. aiecs/llm/clients/openai_client.py +158 -0
  201. aiecs/llm/clients/openai_compatible_mixin.py +367 -0
  202. aiecs/llm/clients/vertex_client.py +897 -0
  203. aiecs/llm/clients/xai_client.py +201 -0
  204. aiecs/llm/config/__init__.py +51 -0
  205. aiecs/llm/config/config_loader.py +272 -0
  206. aiecs/llm/config/config_validator.py +206 -0
  207. aiecs/llm/config/model_config.py +143 -0
  208. aiecs/llm/protocols.py +149 -0
  209. aiecs/llm/utils/__init__.py +10 -0
  210. aiecs/llm/utils/validate_config.py +89 -0
  211. aiecs/main.py +140 -121
  212. aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
  213. aiecs/scripts/aid/__init__.py +19 -0
  214. aiecs/scripts/aid/module_checker.py +499 -0
  215. aiecs/scripts/aid/version_manager.py +235 -0
  216. aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
  217. aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
  218. aiecs/scripts/dependance_check/__init__.py +15 -0
  219. aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
  220. aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
  221. aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
  222. aiecs/scripts/dependance_patch/__init__.py +7 -0
  223. aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
  224. aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
  225. aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
  226. aiecs/scripts/knowledge_graph/__init__.py +3 -0
  227. aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
  228. aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
  229. aiecs/scripts/tools_develop/README.md +671 -0
  230. aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
  231. aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
  232. aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
  233. aiecs/scripts/tools_develop/__init__.py +21 -0
  234. aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
  235. aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
  236. aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
  237. aiecs/scripts/tools_develop/schema_coverage.py +511 -0
  238. aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
  239. aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
  240. aiecs/scripts/tools_develop/verify_tools.py +352 -0
  241. aiecs/tasks/__init__.py +0 -1
  242. aiecs/tasks/worker.py +115 -47
  243. aiecs/tools/__init__.py +194 -72
  244. aiecs/tools/apisource/__init__.py +99 -0
  245. aiecs/tools/apisource/intelligence/__init__.py +19 -0
  246. aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
  247. aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
  248. aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
  249. aiecs/tools/apisource/monitoring/__init__.py +9 -0
  250. aiecs/tools/apisource/monitoring/metrics.py +330 -0
  251. aiecs/tools/apisource/providers/__init__.py +112 -0
  252. aiecs/tools/apisource/providers/base.py +671 -0
  253. aiecs/tools/apisource/providers/census.py +397 -0
  254. aiecs/tools/apisource/providers/fred.py +535 -0
  255. aiecs/tools/apisource/providers/newsapi.py +409 -0
  256. aiecs/tools/apisource/providers/worldbank.py +352 -0
  257. aiecs/tools/apisource/reliability/__init__.py +12 -0
  258. aiecs/tools/apisource/reliability/error_handler.py +363 -0
  259. aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
  260. aiecs/tools/apisource/tool.py +832 -0
  261. aiecs/tools/apisource/utils/__init__.py +9 -0
  262. aiecs/tools/apisource/utils/validators.py +334 -0
  263. aiecs/tools/base_tool.py +415 -21
  264. aiecs/tools/docs/__init__.py +121 -0
  265. aiecs/tools/docs/ai_document_orchestrator.py +607 -0
  266. aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
  267. aiecs/tools/docs/content_insertion_tool.py +1320 -0
  268. aiecs/tools/docs/document_creator_tool.py +1323 -0
  269. aiecs/tools/docs/document_layout_tool.py +1160 -0
  270. aiecs/tools/docs/document_parser_tool.py +1011 -0
  271. aiecs/tools/docs/document_writer_tool.py +1829 -0
  272. aiecs/tools/knowledge_graph/__init__.py +17 -0
  273. aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
  274. aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
  275. aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
  276. aiecs/tools/langchain_adapter.py +300 -138
  277. aiecs/tools/schema_generator.py +455 -0
  278. aiecs/tools/search_tool/__init__.py +100 -0
  279. aiecs/tools/search_tool/analyzers.py +581 -0
  280. aiecs/tools/search_tool/cache.py +264 -0
  281. aiecs/tools/search_tool/constants.py +128 -0
  282. aiecs/tools/search_tool/context.py +224 -0
  283. aiecs/tools/search_tool/core.py +778 -0
  284. aiecs/tools/search_tool/deduplicator.py +119 -0
  285. aiecs/tools/search_tool/error_handler.py +242 -0
  286. aiecs/tools/search_tool/metrics.py +343 -0
  287. aiecs/tools/search_tool/rate_limiter.py +172 -0
  288. aiecs/tools/search_tool/schemas.py +275 -0
  289. aiecs/tools/statistics/__init__.py +80 -0
  290. aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
  291. aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
  292. aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
  293. aiecs/tools/statistics/data_loader_tool.py +555 -0
  294. aiecs/tools/statistics/data_profiler_tool.py +638 -0
  295. aiecs/tools/statistics/data_transformer_tool.py +580 -0
  296. aiecs/tools/statistics/data_visualizer_tool.py +498 -0
  297. aiecs/tools/statistics/model_trainer_tool.py +507 -0
  298. aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
  299. aiecs/tools/task_tools/__init__.py +49 -36
  300. aiecs/tools/task_tools/chart_tool.py +200 -184
  301. aiecs/tools/task_tools/classfire_tool.py +268 -267
  302. aiecs/tools/task_tools/image_tool.py +175 -131
  303. aiecs/tools/task_tools/office_tool.py +226 -146
  304. aiecs/tools/task_tools/pandas_tool.py +477 -121
  305. aiecs/tools/task_tools/report_tool.py +390 -142
  306. aiecs/tools/task_tools/research_tool.py +149 -79
  307. aiecs/tools/task_tools/scraper_tool.py +339 -145
  308. aiecs/tools/task_tools/stats_tool.py +448 -209
  309. aiecs/tools/temp_file_manager.py +26 -24
  310. aiecs/tools/tool_executor/__init__.py +18 -16
  311. aiecs/tools/tool_executor/tool_executor.py +364 -52
  312. aiecs/utils/LLM_output_structor.py +74 -48
  313. aiecs/utils/__init__.py +14 -3
  314. aiecs/utils/base_callback.py +0 -3
  315. aiecs/utils/cache_provider.py +696 -0
  316. aiecs/utils/execution_utils.py +50 -31
  317. aiecs/utils/prompt_loader.py +1 -0
  318. aiecs/utils/token_usage_repository.py +37 -11
  319. aiecs/ws/socket_server.py +14 -4
  320. {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/METADATA +52 -15
  321. aiecs-1.7.6.dist-info/RECORD +337 -0
  322. aiecs-1.7.6.dist-info/entry_points.txt +13 -0
  323. aiecs/config/registry.py +0 -19
  324. aiecs/domain/context/content_engine.py +0 -982
  325. aiecs/llm/base_client.py +0 -99
  326. aiecs/llm/openai_client.py +0 -125
  327. aiecs/llm/vertex_client.py +0 -186
  328. aiecs/llm/xai_client.py +0 -184
  329. aiecs/scripts/dependency_checker.py +0 -857
  330. aiecs/scripts/quick_dependency_check.py +0 -269
  331. aiecs/tools/task_tools/search_api.py +0 -7
  332. aiecs-1.0.1.dist-info/RECORD +0 -90
  333. aiecs-1.0.1.dist-info/entry_points.txt +0 -7
  334. /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
  335. /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
  336. /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
  337. /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
  338. {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/WHEEL +0 -0
  339. {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/licenses/LICENSE +0 -0
  340. {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