aiecs 1.5.1__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.
Files changed (302) hide show
  1. aiecs/__init__.py +72 -0
  2. aiecs/__main__.py +41 -0
  3. aiecs/aiecs_client.py +469 -0
  4. aiecs/application/__init__.py +10 -0
  5. aiecs/application/executors/__init__.py +10 -0
  6. aiecs/application/executors/operation_executor.py +363 -0
  7. aiecs/application/knowledge_graph/__init__.py +7 -0
  8. aiecs/application/knowledge_graph/builder/__init__.py +37 -0
  9. aiecs/application/knowledge_graph/builder/document_builder.py +375 -0
  10. aiecs/application/knowledge_graph/builder/graph_builder.py +356 -0
  11. aiecs/application/knowledge_graph/builder/schema_mapping.py +531 -0
  12. aiecs/application/knowledge_graph/builder/structured_pipeline.py +443 -0
  13. aiecs/application/knowledge_graph/builder/text_chunker.py +319 -0
  14. aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
  15. aiecs/application/knowledge_graph/extractors/base.py +100 -0
  16. aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +327 -0
  17. aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +349 -0
  18. aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +244 -0
  19. aiecs/application/knowledge_graph/fusion/__init__.py +23 -0
  20. aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +387 -0
  21. aiecs/application/knowledge_graph/fusion/entity_linker.py +343 -0
  22. aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +580 -0
  23. aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +189 -0
  24. aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
  25. aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +344 -0
  26. aiecs/application/knowledge_graph/pattern_matching/query_executor.py +378 -0
  27. aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
  28. aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +199 -0
  29. aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
  30. aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
  31. aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +347 -0
  32. aiecs/application/knowledge_graph/reasoning/inference_engine.py +504 -0
  33. aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +167 -0
  34. aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
  35. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
  36. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +630 -0
  37. aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +654 -0
  38. aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +477 -0
  39. aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +390 -0
  40. aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +217 -0
  41. aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +169 -0
  42. aiecs/application/knowledge_graph/reasoning/query_planner.py +872 -0
  43. aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +554 -0
  44. aiecs/application/knowledge_graph/retrieval/__init__.py +19 -0
  45. aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +596 -0
  46. aiecs/application/knowledge_graph/search/__init__.py +59 -0
  47. aiecs/application/knowledge_graph/search/hybrid_search.py +423 -0
  48. aiecs/application/knowledge_graph/search/reranker.py +295 -0
  49. aiecs/application/knowledge_graph/search/reranker_strategies.py +553 -0
  50. aiecs/application/knowledge_graph/search/text_similarity.py +398 -0
  51. aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
  52. aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +329 -0
  53. aiecs/application/knowledge_graph/traversal/path_scorer.py +269 -0
  54. aiecs/application/knowledge_graph/validators/__init__.py +13 -0
  55. aiecs/application/knowledge_graph/validators/relation_validator.py +189 -0
  56. aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
  57. aiecs/application/knowledge_graph/visualization/graph_visualizer.py +321 -0
  58. aiecs/common/__init__.py +9 -0
  59. aiecs/common/knowledge_graph/__init__.py +17 -0
  60. aiecs/common/knowledge_graph/runnable.py +484 -0
  61. aiecs/config/__init__.py +16 -0
  62. aiecs/config/config.py +498 -0
  63. aiecs/config/graph_config.py +137 -0
  64. aiecs/config/registry.py +23 -0
  65. aiecs/core/__init__.py +46 -0
  66. aiecs/core/interface/__init__.py +34 -0
  67. aiecs/core/interface/execution_interface.py +152 -0
  68. aiecs/core/interface/storage_interface.py +171 -0
  69. aiecs/domain/__init__.py +289 -0
  70. aiecs/domain/agent/__init__.py +189 -0
  71. aiecs/domain/agent/base_agent.py +697 -0
  72. aiecs/domain/agent/exceptions.py +103 -0
  73. aiecs/domain/agent/graph_aware_mixin.py +559 -0
  74. aiecs/domain/agent/hybrid_agent.py +490 -0
  75. aiecs/domain/agent/integration/__init__.py +26 -0
  76. aiecs/domain/agent/integration/context_compressor.py +222 -0
  77. aiecs/domain/agent/integration/context_engine_adapter.py +252 -0
  78. aiecs/domain/agent/integration/retry_policy.py +219 -0
  79. aiecs/domain/agent/integration/role_config.py +213 -0
  80. aiecs/domain/agent/knowledge_aware_agent.py +646 -0
  81. aiecs/domain/agent/lifecycle.py +296 -0
  82. aiecs/domain/agent/llm_agent.py +300 -0
  83. aiecs/domain/agent/memory/__init__.py +12 -0
  84. aiecs/domain/agent/memory/conversation.py +197 -0
  85. aiecs/domain/agent/migration/__init__.py +14 -0
  86. aiecs/domain/agent/migration/conversion.py +160 -0
  87. aiecs/domain/agent/migration/legacy_wrapper.py +90 -0
  88. aiecs/domain/agent/models.py +317 -0
  89. aiecs/domain/agent/observability.py +407 -0
  90. aiecs/domain/agent/persistence.py +289 -0
  91. aiecs/domain/agent/prompts/__init__.py +29 -0
  92. aiecs/domain/agent/prompts/builder.py +161 -0
  93. aiecs/domain/agent/prompts/formatters.py +189 -0
  94. aiecs/domain/agent/prompts/template.py +255 -0
  95. aiecs/domain/agent/registry.py +260 -0
  96. aiecs/domain/agent/tool_agent.py +257 -0
  97. aiecs/domain/agent/tools/__init__.py +12 -0
  98. aiecs/domain/agent/tools/schema_generator.py +221 -0
  99. aiecs/domain/community/__init__.py +155 -0
  100. aiecs/domain/community/agent_adapter.py +477 -0
  101. aiecs/domain/community/analytics.py +481 -0
  102. aiecs/domain/community/collaborative_workflow.py +642 -0
  103. aiecs/domain/community/communication_hub.py +645 -0
  104. aiecs/domain/community/community_builder.py +320 -0
  105. aiecs/domain/community/community_integration.py +800 -0
  106. aiecs/domain/community/community_manager.py +813 -0
  107. aiecs/domain/community/decision_engine.py +879 -0
  108. aiecs/domain/community/exceptions.py +225 -0
  109. aiecs/domain/community/models/__init__.py +33 -0
  110. aiecs/domain/community/models/community_models.py +268 -0
  111. aiecs/domain/community/resource_manager.py +457 -0
  112. aiecs/domain/community/shared_context_manager.py +603 -0
  113. aiecs/domain/context/__init__.py +58 -0
  114. aiecs/domain/context/context_engine.py +989 -0
  115. aiecs/domain/context/conversation_models.py +354 -0
  116. aiecs/domain/context/graph_memory.py +467 -0
  117. aiecs/domain/execution/__init__.py +12 -0
  118. aiecs/domain/execution/model.py +57 -0
  119. aiecs/domain/knowledge_graph/__init__.py +19 -0
  120. aiecs/domain/knowledge_graph/models/__init__.py +52 -0
  121. aiecs/domain/knowledge_graph/models/entity.py +130 -0
  122. aiecs/domain/knowledge_graph/models/evidence.py +194 -0
  123. aiecs/domain/knowledge_graph/models/inference_rule.py +186 -0
  124. aiecs/domain/knowledge_graph/models/path.py +179 -0
  125. aiecs/domain/knowledge_graph/models/path_pattern.py +173 -0
  126. aiecs/domain/knowledge_graph/models/query.py +272 -0
  127. aiecs/domain/knowledge_graph/models/query_plan.py +187 -0
  128. aiecs/domain/knowledge_graph/models/relation.py +136 -0
  129. aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
  130. aiecs/domain/knowledge_graph/schema/entity_type.py +135 -0
  131. aiecs/domain/knowledge_graph/schema/graph_schema.py +271 -0
  132. aiecs/domain/knowledge_graph/schema/property_schema.py +155 -0
  133. aiecs/domain/knowledge_graph/schema/relation_type.py +171 -0
  134. aiecs/domain/knowledge_graph/schema/schema_manager.py +496 -0
  135. aiecs/domain/knowledge_graph/schema/type_enums.py +205 -0
  136. aiecs/domain/task/__init__.py +13 -0
  137. aiecs/domain/task/dsl_processor.py +613 -0
  138. aiecs/domain/task/model.py +62 -0
  139. aiecs/domain/task/task_context.py +268 -0
  140. aiecs/infrastructure/__init__.py +24 -0
  141. aiecs/infrastructure/graph_storage/__init__.py +11 -0
  142. aiecs/infrastructure/graph_storage/base.py +601 -0
  143. aiecs/infrastructure/graph_storage/batch_operations.py +449 -0
  144. aiecs/infrastructure/graph_storage/cache.py +429 -0
  145. aiecs/infrastructure/graph_storage/distributed.py +226 -0
  146. aiecs/infrastructure/graph_storage/error_handling.py +390 -0
  147. aiecs/infrastructure/graph_storage/graceful_degradation.py +306 -0
  148. aiecs/infrastructure/graph_storage/health_checks.py +378 -0
  149. aiecs/infrastructure/graph_storage/in_memory.py +514 -0
  150. aiecs/infrastructure/graph_storage/index_optimization.py +483 -0
  151. aiecs/infrastructure/graph_storage/lazy_loading.py +410 -0
  152. aiecs/infrastructure/graph_storage/metrics.py +357 -0
  153. aiecs/infrastructure/graph_storage/migration.py +413 -0
  154. aiecs/infrastructure/graph_storage/pagination.py +471 -0
  155. aiecs/infrastructure/graph_storage/performance_monitoring.py +466 -0
  156. aiecs/infrastructure/graph_storage/postgres.py +871 -0
  157. aiecs/infrastructure/graph_storage/query_optimizer.py +635 -0
  158. aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
  159. aiecs/infrastructure/graph_storage/sqlite.py +623 -0
  160. aiecs/infrastructure/graph_storage/streaming.py +495 -0
  161. aiecs/infrastructure/messaging/__init__.py +13 -0
  162. aiecs/infrastructure/messaging/celery_task_manager.py +383 -0
  163. aiecs/infrastructure/messaging/websocket_manager.py +298 -0
  164. aiecs/infrastructure/monitoring/__init__.py +34 -0
  165. aiecs/infrastructure/monitoring/executor_metrics.py +174 -0
  166. aiecs/infrastructure/monitoring/global_metrics_manager.py +213 -0
  167. aiecs/infrastructure/monitoring/structured_logger.py +48 -0
  168. aiecs/infrastructure/monitoring/tracing_manager.py +410 -0
  169. aiecs/infrastructure/persistence/__init__.py +24 -0
  170. aiecs/infrastructure/persistence/context_engine_client.py +187 -0
  171. aiecs/infrastructure/persistence/database_manager.py +333 -0
  172. aiecs/infrastructure/persistence/file_storage.py +754 -0
  173. aiecs/infrastructure/persistence/redis_client.py +220 -0
  174. aiecs/llm/__init__.py +86 -0
  175. aiecs/llm/callbacks/__init__.py +11 -0
  176. aiecs/llm/callbacks/custom_callbacks.py +264 -0
  177. aiecs/llm/client_factory.py +420 -0
  178. aiecs/llm/clients/__init__.py +33 -0
  179. aiecs/llm/clients/base_client.py +193 -0
  180. aiecs/llm/clients/googleai_client.py +181 -0
  181. aiecs/llm/clients/openai_client.py +131 -0
  182. aiecs/llm/clients/vertex_client.py +437 -0
  183. aiecs/llm/clients/xai_client.py +184 -0
  184. aiecs/llm/config/__init__.py +51 -0
  185. aiecs/llm/config/config_loader.py +275 -0
  186. aiecs/llm/config/config_validator.py +236 -0
  187. aiecs/llm/config/model_config.py +151 -0
  188. aiecs/llm/utils/__init__.py +10 -0
  189. aiecs/llm/utils/validate_config.py +91 -0
  190. aiecs/main.py +363 -0
  191. aiecs/scripts/__init__.py +3 -0
  192. aiecs/scripts/aid/VERSION_MANAGEMENT.md +97 -0
  193. aiecs/scripts/aid/__init__.py +19 -0
  194. aiecs/scripts/aid/version_manager.py +215 -0
  195. aiecs/scripts/dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md +242 -0
  196. aiecs/scripts/dependance_check/README_DEPENDENCY_CHECKER.md +310 -0
  197. aiecs/scripts/dependance_check/__init__.py +17 -0
  198. aiecs/scripts/dependance_check/dependency_checker.py +938 -0
  199. aiecs/scripts/dependance_check/dependency_fixer.py +391 -0
  200. aiecs/scripts/dependance_check/download_nlp_data.py +396 -0
  201. aiecs/scripts/dependance_check/quick_dependency_check.py +270 -0
  202. aiecs/scripts/dependance_check/setup_nlp_data.sh +217 -0
  203. aiecs/scripts/dependance_patch/__init__.py +7 -0
  204. aiecs/scripts/dependance_patch/fix_weasel/README_WEASEL_PATCH.md +126 -0
  205. aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
  206. aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.py +128 -0
  207. aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.sh +82 -0
  208. aiecs/scripts/dependance_patch/fix_weasel/patch_weasel_library.sh +188 -0
  209. aiecs/scripts/dependance_patch/fix_weasel/run_weasel_patch.sh +41 -0
  210. aiecs/scripts/tools_develop/README.md +449 -0
  211. aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
  212. aiecs/scripts/tools_develop/__init__.py +21 -0
  213. aiecs/scripts/tools_develop/check_type_annotations.py +259 -0
  214. aiecs/scripts/tools_develop/validate_tool_schemas.py +422 -0
  215. aiecs/scripts/tools_develop/verify_tools.py +356 -0
  216. aiecs/tasks/__init__.py +1 -0
  217. aiecs/tasks/worker.py +172 -0
  218. aiecs/tools/__init__.py +299 -0
  219. aiecs/tools/apisource/__init__.py +99 -0
  220. aiecs/tools/apisource/intelligence/__init__.py +19 -0
  221. aiecs/tools/apisource/intelligence/data_fusion.py +381 -0
  222. aiecs/tools/apisource/intelligence/query_analyzer.py +413 -0
  223. aiecs/tools/apisource/intelligence/search_enhancer.py +388 -0
  224. aiecs/tools/apisource/monitoring/__init__.py +9 -0
  225. aiecs/tools/apisource/monitoring/metrics.py +303 -0
  226. aiecs/tools/apisource/providers/__init__.py +115 -0
  227. aiecs/tools/apisource/providers/base.py +664 -0
  228. aiecs/tools/apisource/providers/census.py +401 -0
  229. aiecs/tools/apisource/providers/fred.py +564 -0
  230. aiecs/tools/apisource/providers/newsapi.py +412 -0
  231. aiecs/tools/apisource/providers/worldbank.py +357 -0
  232. aiecs/tools/apisource/reliability/__init__.py +12 -0
  233. aiecs/tools/apisource/reliability/error_handler.py +375 -0
  234. aiecs/tools/apisource/reliability/fallback_strategy.py +391 -0
  235. aiecs/tools/apisource/tool.py +850 -0
  236. aiecs/tools/apisource/utils/__init__.py +9 -0
  237. aiecs/tools/apisource/utils/validators.py +338 -0
  238. aiecs/tools/base_tool.py +201 -0
  239. aiecs/tools/docs/__init__.py +121 -0
  240. aiecs/tools/docs/ai_document_orchestrator.py +599 -0
  241. aiecs/tools/docs/ai_document_writer_orchestrator.py +2403 -0
  242. aiecs/tools/docs/content_insertion_tool.py +1333 -0
  243. aiecs/tools/docs/document_creator_tool.py +1317 -0
  244. aiecs/tools/docs/document_layout_tool.py +1166 -0
  245. aiecs/tools/docs/document_parser_tool.py +994 -0
  246. aiecs/tools/docs/document_writer_tool.py +1818 -0
  247. aiecs/tools/knowledge_graph/__init__.py +17 -0
  248. aiecs/tools/knowledge_graph/graph_reasoning_tool.py +734 -0
  249. aiecs/tools/knowledge_graph/graph_search_tool.py +923 -0
  250. aiecs/tools/knowledge_graph/kg_builder_tool.py +476 -0
  251. aiecs/tools/langchain_adapter.py +542 -0
  252. aiecs/tools/schema_generator.py +275 -0
  253. aiecs/tools/search_tool/__init__.py +100 -0
  254. aiecs/tools/search_tool/analyzers.py +589 -0
  255. aiecs/tools/search_tool/cache.py +260 -0
  256. aiecs/tools/search_tool/constants.py +128 -0
  257. aiecs/tools/search_tool/context.py +216 -0
  258. aiecs/tools/search_tool/core.py +749 -0
  259. aiecs/tools/search_tool/deduplicator.py +123 -0
  260. aiecs/tools/search_tool/error_handler.py +271 -0
  261. aiecs/tools/search_tool/metrics.py +371 -0
  262. aiecs/tools/search_tool/rate_limiter.py +178 -0
  263. aiecs/tools/search_tool/schemas.py +277 -0
  264. aiecs/tools/statistics/__init__.py +80 -0
  265. aiecs/tools/statistics/ai_data_analysis_orchestrator.py +643 -0
  266. aiecs/tools/statistics/ai_insight_generator_tool.py +505 -0
  267. aiecs/tools/statistics/ai_report_orchestrator_tool.py +694 -0
  268. aiecs/tools/statistics/data_loader_tool.py +564 -0
  269. aiecs/tools/statistics/data_profiler_tool.py +658 -0
  270. aiecs/tools/statistics/data_transformer_tool.py +573 -0
  271. aiecs/tools/statistics/data_visualizer_tool.py +495 -0
  272. aiecs/tools/statistics/model_trainer_tool.py +487 -0
  273. aiecs/tools/statistics/statistical_analyzer_tool.py +459 -0
  274. aiecs/tools/task_tools/__init__.py +86 -0
  275. aiecs/tools/task_tools/chart_tool.py +732 -0
  276. aiecs/tools/task_tools/classfire_tool.py +922 -0
  277. aiecs/tools/task_tools/image_tool.py +447 -0
  278. aiecs/tools/task_tools/office_tool.py +684 -0
  279. aiecs/tools/task_tools/pandas_tool.py +635 -0
  280. aiecs/tools/task_tools/report_tool.py +635 -0
  281. aiecs/tools/task_tools/research_tool.py +392 -0
  282. aiecs/tools/task_tools/scraper_tool.py +715 -0
  283. aiecs/tools/task_tools/stats_tool.py +688 -0
  284. aiecs/tools/temp_file_manager.py +130 -0
  285. aiecs/tools/tool_executor/__init__.py +37 -0
  286. aiecs/tools/tool_executor/tool_executor.py +881 -0
  287. aiecs/utils/LLM_output_structor.py +445 -0
  288. aiecs/utils/__init__.py +34 -0
  289. aiecs/utils/base_callback.py +47 -0
  290. aiecs/utils/cache_provider.py +695 -0
  291. aiecs/utils/execution_utils.py +184 -0
  292. aiecs/utils/logging.py +1 -0
  293. aiecs/utils/prompt_loader.py +14 -0
  294. aiecs/utils/token_usage_repository.py +323 -0
  295. aiecs/ws/__init__.py +0 -0
  296. aiecs/ws/socket_server.py +52 -0
  297. aiecs-1.5.1.dist-info/METADATA +608 -0
  298. aiecs-1.5.1.dist-info/RECORD +302 -0
  299. aiecs-1.5.1.dist-info/WHEEL +5 -0
  300. aiecs-1.5.1.dist-info/entry_points.txt +10 -0
  301. aiecs-1.5.1.dist-info/licenses/LICENSE +225 -0
  302. aiecs-1.5.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,220 @@
1
+ import redis.asyncio as redis
2
+ import logging
3
+ from typing import Optional
4
+ import os
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class RedisClient:
10
+ """Redis client singleton for sharing across different caching strategies"""
11
+
12
+ def __init__(self):
13
+ self._client: Optional[redis.Redis] = None
14
+ self._connection_pool: Optional[redis.ConnectionPool] = None
15
+
16
+ async def initialize(self):
17
+ """Initialize Redis client"""
18
+ try:
19
+ # Get Redis configuration from environment variables
20
+ redis_host = os.getenv("REDIS_HOST", "localhost")
21
+ redis_port = int(os.getenv("REDIS_PORT", 6379))
22
+ redis_db = int(os.getenv("REDIS_DB", 0))
23
+ redis_password = os.getenv("REDIS_PASSWORD")
24
+
25
+ # Create connection pool
26
+ self._connection_pool = redis.ConnectionPool(
27
+ host=redis_host,
28
+ port=redis_port,
29
+ db=redis_db,
30
+ password=redis_password,
31
+ decode_responses=True,
32
+ max_connections=20,
33
+ retry_on_timeout=True,
34
+ )
35
+
36
+ # Create Redis client
37
+ self._client = redis.Redis(connection_pool=self._connection_pool)
38
+
39
+ # Test connection
40
+ await self._client.ping()
41
+ logger.info(
42
+ f"Redis client initialized successfully: {redis_host}:{redis_port}/{redis_db}"
43
+ )
44
+
45
+ except Exception as e:
46
+ logger.error(f"Failed to initialize Redis client: {e}")
47
+ raise
48
+
49
+ async def get_client(self) -> redis.Redis:
50
+ """Get Redis client instance"""
51
+ if self._client is None:
52
+ raise RuntimeError("Redis client not initialized. Call initialize() first.")
53
+ return self._client
54
+
55
+ async def close(self):
56
+ """Close Redis connection"""
57
+ if self._client:
58
+ await self._client.close()
59
+ self._client = None
60
+ if self._connection_pool:
61
+ await self._connection_pool.disconnect()
62
+ self._connection_pool = None
63
+ logger.info("Redis client closed")
64
+
65
+ async def hincrby(self, name: str, key: str, amount: int = 1) -> int:
66
+ """Atomically increment hash field"""
67
+ client = await self.get_client()
68
+ return await client.hincrby(name, key, amount)
69
+
70
+ async def hget(self, name: str, key: str) -> Optional[str]:
71
+ """Get hash field value"""
72
+ client = await self.get_client()
73
+ return await client.hget(name, key)
74
+
75
+ async def hgetall(self, name: str) -> dict:
76
+ """Get all hash fields"""
77
+ client = await self.get_client()
78
+ return await client.hgetall(name)
79
+
80
+ async def hset(
81
+ self,
82
+ name: str,
83
+ key: Optional[str] = None,
84
+ value: Optional[str] = None,
85
+ mapping: Optional[dict] = None,
86
+ ) -> int:
87
+ """Set hash fields
88
+
89
+ Supports two calling patterns:
90
+ 1. hset(name, key, value) - Set single field (positional)
91
+ 2. hset(name, key=key, value=value) - Set single field (keyword)
92
+ 3. hset(name, mapping={...}) - Set multiple fields
93
+
94
+ Args:
95
+ name: Redis hash key name
96
+ key: Field name (for single field set)
97
+ value: Field value (for single field set)
98
+ mapping: Dictionary of field-value pairs (for multiple fields)
99
+
100
+ Returns:
101
+ Number of fields that were added
102
+
103
+ Raises:
104
+ ValueError: If neither (key, value) nor mapping is provided
105
+
106
+ Examples:
107
+ # Single field with positional args
108
+ await redis_client.hset("myhash", "field1", "value1")
109
+
110
+ # Single field with keyword args
111
+ await redis_client.hset("myhash", key="field1", value="value1")
112
+
113
+ # Multiple fields with mapping
114
+ await redis_client.hset("myhash", mapping={"field1": "value1", "field2": "value2"})
115
+ """
116
+ client = await self.get_client()
117
+
118
+ if mapping is not None:
119
+ # Multiple fields mode
120
+ return await client.hset(name, mapping=mapping)
121
+ elif key is not None and value is not None:
122
+ # Single field mode
123
+ return await client.hset(name, key=key, value=value)
124
+ else:
125
+ raise ValueError(
126
+ "Either provide (key, value) or mapping parameter. "
127
+ f"Got: key={key}, value={value}, mapping={mapping}"
128
+ )
129
+
130
+ async def expire(self, name: str, time: int) -> bool:
131
+ """Set expiration time"""
132
+ client = await self.get_client()
133
+ return await client.expire(name, time)
134
+
135
+ async def exists(self, name: str) -> bool:
136
+ """Check if key exists"""
137
+ client = await self.get_client()
138
+ return bool(await client.exists(name))
139
+
140
+ async def ping(self) -> bool:
141
+ """Test Redis connection"""
142
+ try:
143
+ client = await self.get_client()
144
+ result = await client.ping()
145
+ return result
146
+ except Exception as e:
147
+ logger.error(f"Redis ping failed: {e}")
148
+ return False
149
+
150
+ async def info(self, section: str = None) -> dict:
151
+ """Get Redis server information"""
152
+ try:
153
+ client = await self.get_client()
154
+ return await client.info(section)
155
+ except Exception as e:
156
+ logger.error(f"Redis info failed: {e}")
157
+ return {}
158
+
159
+ async def delete(self, *keys) -> int:
160
+ """Delete one or more keys"""
161
+ try:
162
+ client = await self.get_client()
163
+ return await client.delete(*keys)
164
+ except Exception as e:
165
+ logger.error(f"Redis delete failed: {e}")
166
+ return 0
167
+
168
+ async def set(self, key: str, value: str, ex: int = None) -> bool:
169
+ """Set a key-value pair with optional expiration"""
170
+ try:
171
+ client = await self.get_client()
172
+ return await client.set(key, value, ex=ex)
173
+ except Exception as e:
174
+ logger.error(f"Redis set failed for key {key}: {e}")
175
+ return False
176
+
177
+ async def get(self, key: str) -> Optional[str]:
178
+ """Get value by key"""
179
+ try:
180
+ client = await self.get_client()
181
+ return await client.get(key)
182
+ except Exception as e:
183
+ logger.error(f"Redis get failed for key {key}: {e}")
184
+ return None
185
+
186
+
187
+ # ✅ Key changes:
188
+ # 1. No longer create instance immediately.
189
+ # 2. Define a global variable with initial value None. This variable will
190
+ # be populated by lifespan.
191
+ redis_client: Optional[RedisClient] = None
192
+
193
+ # 3. Provide an initialization function for lifespan to call
194
+
195
+
196
+ async def initialize_redis_client():
197
+ """Create and initialize global Redis client instance at application startup."""
198
+ global redis_client
199
+ if redis_client is None:
200
+ redis_client = RedisClient()
201
+ await redis_client.initialize()
202
+
203
+
204
+ # 4. Provide a close function for lifespan to call
205
+
206
+
207
+ async def close_redis_client():
208
+ """Close global Redis client instance at application shutdown."""
209
+ if redis_client:
210
+ await redis_client.close()
211
+
212
+
213
+ # For backward compatibility, keep get_redis_client function
214
+
215
+
216
+ async def get_redis_client() -> RedisClient:
217
+ """Get global Redis client instance"""
218
+ if redis_client is None:
219
+ raise RuntimeError("Redis client not initialized. Call initialize_redis_client() first.")
220
+ return redis_client
aiecs/llm/__init__.py ADDED
@@ -0,0 +1,86 @@
1
+ """
2
+ LLM Package - Modular AI Provider Architecture
3
+
4
+ This package provides a unified interface to multiple AI providers through
5
+ individual client implementations and a factory pattern.
6
+
7
+ Package Structure:
8
+ - clients/: LLM client implementations
9
+ - config/: Configuration management
10
+ - callbacks/: Callback handlers
11
+ - utils/: Utility functions and scripts
12
+ """
13
+
14
+ # Import from organized subpackages
15
+ from .clients import (
16
+ BaseLLMClient,
17
+ LLMMessage,
18
+ LLMResponse,
19
+ LLMClientError,
20
+ ProviderNotAvailableError,
21
+ RateLimitError,
22
+ OpenAIClient,
23
+ VertexAIClient,
24
+ GoogleAIClient,
25
+ XAIClient,
26
+ )
27
+
28
+ from .client_factory import (
29
+ AIProvider,
30
+ LLMClientFactory,
31
+ LLMClientManager,
32
+ get_llm_manager,
33
+ generate_text,
34
+ stream_text,
35
+ )
36
+
37
+ from .config import (
38
+ ModelCostConfig,
39
+ ModelCapabilities,
40
+ ModelDefaultParams,
41
+ ModelConfig,
42
+ ProviderConfig,
43
+ LLMModelsConfig,
44
+ LLMConfigLoader,
45
+ get_llm_config_loader,
46
+ get_llm_config,
47
+ reload_llm_config,
48
+ )
49
+
50
+ from .callbacks import CustomAsyncCallbackHandler
51
+
52
+ __all__ = [
53
+ # Base classes and types
54
+ "BaseLLMClient",
55
+ "LLMMessage",
56
+ "LLMResponse",
57
+ "LLMClientError",
58
+ "ProviderNotAvailableError",
59
+ "RateLimitError",
60
+ "AIProvider",
61
+ # Factory and manager
62
+ "LLMClientFactory",
63
+ "LLMClientManager",
64
+ "get_llm_manager",
65
+ # Individual clients
66
+ "OpenAIClient",
67
+ "VertexAIClient",
68
+ "GoogleAIClient",
69
+ "XAIClient",
70
+ # Convenience functions
71
+ "generate_text",
72
+ "stream_text",
73
+ # Configuration management
74
+ "ModelCostConfig",
75
+ "ModelCapabilities",
76
+ "ModelDefaultParams",
77
+ "ModelConfig",
78
+ "ProviderConfig",
79
+ "LLMModelsConfig",
80
+ "LLMConfigLoader",
81
+ "get_llm_config_loader",
82
+ "get_llm_config",
83
+ "reload_llm_config",
84
+ # Callbacks
85
+ "CustomAsyncCallbackHandler",
86
+ ]
@@ -0,0 +1,11 @@
1
+ """
2
+ LLM Callbacks.
3
+
4
+ This package contains callback handlers for LLM operations.
5
+ """
6
+
7
+ from .custom_callbacks import CustomAsyncCallbackHandler
8
+
9
+ __all__ = [
10
+ "CustomAsyncCallbackHandler",
11
+ ]
@@ -0,0 +1,264 @@
1
+ from typing import Any, List, Optional
2
+ import logging
3
+
4
+ # Import the base callback handler from utils
5
+ from aiecs.utils.base_callback import CustomAsyncCallbackHandler
6
+
7
+ # Import LLM types for internal use only
8
+ # Import token usage repository
9
+ from aiecs.utils.token_usage_repository import token_usage_repo
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class RedisTokenCallbackHandler(CustomAsyncCallbackHandler):
15
+ """
16
+ Concrete token recording callback handler.
17
+ Responsible for recording token usage after LLM calls by delegating to the repository.
18
+ """
19
+
20
+ def __init__(self, user_id: str, cycle_start_date: Optional[str] = None):
21
+ if not user_id:
22
+ raise ValueError("user_id must be provided for RedisTokenCallbackHandler")
23
+ self.user_id = user_id
24
+ self.cycle_start_date = cycle_start_date
25
+ self.start_time = None
26
+ self.messages = None
27
+
28
+ async def on_llm_start(self, messages: List[dict], **kwargs: Any) -> None:
29
+ """Triggered when LLM call starts"""
30
+ import time
31
+
32
+ self.start_time = time.time()
33
+ self.messages = messages
34
+
35
+ logger.info(
36
+ f"[Callback] LLM call started for user '{self.user_id}' with {len(messages)} messages"
37
+ )
38
+
39
+ async def on_llm_end(self, response: dict, **kwargs: Any) -> None:
40
+ """Triggered when LLM call ends successfully"""
41
+ try:
42
+ # Record call duration
43
+ if self.start_time:
44
+ import time
45
+
46
+ call_duration = time.time() - self.start_time
47
+ logger.info(
48
+ f"[Callback] LLM call completed for user '{self.user_id}' in {call_duration:.2f}s"
49
+ )
50
+
51
+ # Extract token usage from response dictionary
52
+ tokens_used = response.get("tokens_used")
53
+
54
+ if tokens_used and tokens_used > 0:
55
+ # Delegate recording work to repository
56
+ await token_usage_repo.increment_total_usage(
57
+ self.user_id, tokens_used, self.cycle_start_date
58
+ )
59
+
60
+ logger.info(f"[Callback] Recorded {tokens_used} tokens for user '{self.user_id}'")
61
+ else:
62
+ logger.warning(
63
+ f"[Callback] No token usage data available for user '{self.user_id}'"
64
+ )
65
+
66
+ except Exception as e:
67
+ logger.error(f"[Callback] Failed to record token usage for user '{self.user_id}': {e}")
68
+ # Don't re-raise exception to avoid affecting main LLM call flow
69
+
70
+ async def on_llm_error(self, error: Exception, **kwargs: Any) -> None:
71
+ """Triggered when LLM call encounters an error"""
72
+ if self.start_time:
73
+ import time
74
+
75
+ call_duration = time.time() - self.start_time
76
+ logger.error(
77
+ f"[Callback] LLM call failed for user '{self.user_id}' after {call_duration:.2f}s: {error}"
78
+ )
79
+ else:
80
+ logger.error(f"[Callback] LLM call failed for user '{self.user_id}': {error}")
81
+
82
+
83
+ class DetailedRedisTokenCallbackHandler(CustomAsyncCallbackHandler):
84
+ """
85
+ Detailed token recording callback handler.
86
+ Records separate prompt and completion token usage in addition to total usage.
87
+ """
88
+
89
+ def __init__(self, user_id: str, cycle_start_date: Optional[str] = None):
90
+ if not user_id:
91
+ raise ValueError("user_id must be provided for DetailedRedisTokenCallbackHandler")
92
+ self.user_id = user_id
93
+ self.cycle_start_date = cycle_start_date
94
+ self.start_time = None
95
+ self.messages = None
96
+ self.prompt_tokens = 0
97
+
98
+ async def on_llm_start(self, messages: List[dict], **kwargs: Any) -> None:
99
+ """Triggered when LLM call starts"""
100
+ import time
101
+
102
+ self.start_time = time.time()
103
+ self.messages = messages
104
+
105
+ # Estimate input token count
106
+ self.prompt_tokens = self._estimate_prompt_tokens(messages)
107
+
108
+ logger.info(
109
+ f"[DetailedCallback] LLM call started for user '{self.user_id}' with estimated {self.prompt_tokens} prompt tokens"
110
+ )
111
+
112
+ async def on_llm_end(self, response: dict, **kwargs: Any) -> None:
113
+ """Triggered when LLM call ends successfully"""
114
+ try:
115
+ # Record call duration
116
+ if self.start_time:
117
+ import time
118
+
119
+ call_duration = time.time() - self.start_time
120
+ logger.info(
121
+ f"[DetailedCallback] LLM call completed for user '{self.user_id}' in {call_duration:.2f}s"
122
+ )
123
+
124
+ # Extract detailed token information from response
125
+ prompt_tokens, completion_tokens = self._extract_detailed_tokens(response)
126
+
127
+ # Ensure we have valid integers (not None)
128
+ prompt_tokens = prompt_tokens or 0
129
+ completion_tokens = completion_tokens or 0
130
+
131
+ if prompt_tokens > 0 or completion_tokens > 0:
132
+ # Use detailed token recording method
133
+ await token_usage_repo.increment_detailed_usage(
134
+ self.user_id,
135
+ prompt_tokens,
136
+ completion_tokens,
137
+ self.cycle_start_date,
138
+ )
139
+
140
+ logger.info(
141
+ f"[DetailedCallback] Recorded detailed tokens for user '{self.user_id}': prompt={prompt_tokens}, completion={completion_tokens}"
142
+ )
143
+ else:
144
+ logger.warning(
145
+ f"[DetailedCallback] No detailed token usage data available for user '{self.user_id}'"
146
+ )
147
+
148
+ except Exception as e:
149
+ logger.error(
150
+ f"[DetailedCallback] Failed to record detailed token usage for user '{self.user_id}': {e}"
151
+ )
152
+ # Don't re-raise exception to avoid affecting main LLM call flow
153
+
154
+ async def on_llm_error(self, error: Exception, **kwargs: Any) -> None:
155
+ """Triggered when LLM call encounters an error"""
156
+ if self.start_time:
157
+ import time
158
+
159
+ call_duration = time.time() - self.start_time
160
+ logger.error(
161
+ f"[DetailedCallback] LLM call failed for user '{self.user_id}' after {call_duration:.2f}s: {error}"
162
+ )
163
+ else:
164
+ logger.error(f"[DetailedCallback] LLM call failed for user '{self.user_id}': {error}")
165
+
166
+ def _estimate_prompt_tokens(self, messages: List[dict]) -> int:
167
+ """Estimate token count for input messages"""
168
+ total_chars = sum(len(msg.get("content", "")) for msg in messages)
169
+ # Rough estimation: 4 characters ≈ 1 token
170
+ return total_chars // 4
171
+
172
+ def _extract_detailed_tokens(self, response: dict) -> tuple[int, int]:
173
+ """
174
+ Extract detailed token information from response dictionary
175
+
176
+ Returns:
177
+ tuple: (prompt_tokens, completion_tokens)
178
+ """
179
+ # If response has detailed token information, use it first
180
+ prompt_tokens = response.get("prompt_tokens") or 0
181
+ completion_tokens = response.get("completion_tokens") or 0
182
+
183
+ if prompt_tokens > 0 and completion_tokens > 0:
184
+ return prompt_tokens, completion_tokens
185
+
186
+ # If only total token count is available, try to allocate
187
+ tokens_used = response.get("tokens_used") or 0
188
+ if tokens_used > 0:
189
+ # Use previously estimated prompt tokens
190
+ prompt_tokens = self.prompt_tokens
191
+ completion_tokens = max(0, tokens_used - prompt_tokens)
192
+ return prompt_tokens, completion_tokens
193
+
194
+ # If no token information, try to estimate from response content
195
+ content = response.get("content", "")
196
+ if content:
197
+ completion_tokens = len(content) // 4
198
+ prompt_tokens = self.prompt_tokens
199
+ return prompt_tokens, completion_tokens
200
+
201
+ return 0, 0
202
+
203
+
204
+ class CompositeCallbackHandler(CustomAsyncCallbackHandler):
205
+ """
206
+ Composite callback handler that can execute multiple callback handlers simultaneously
207
+ """
208
+
209
+ def __init__(self, handlers: List[CustomAsyncCallbackHandler]):
210
+ self.handlers = handlers or []
211
+
212
+ def add_handler(self, handler: CustomAsyncCallbackHandler):
213
+ """Add a callback handler"""
214
+ self.handlers.append(handler)
215
+
216
+ async def on_llm_start(self, messages: List[dict], **kwargs: Any) -> None:
217
+ """Execute start callbacks for all handlers"""
218
+ for handler in self.handlers:
219
+ try:
220
+ await handler.on_llm_start(messages, **kwargs)
221
+ except Exception as e:
222
+ logger.error(
223
+ f"Error in callback handler {type(handler).__name__}.on_llm_start: {e}"
224
+ )
225
+
226
+ async def on_llm_end(self, response: dict, **kwargs: Any) -> None:
227
+ """Execute end callbacks for all handlers"""
228
+ for handler in self.handlers:
229
+ try:
230
+ await handler.on_llm_end(response, **kwargs)
231
+ except Exception as e:
232
+ logger.error(f"Error in callback handler {type(handler).__name__}.on_llm_end: {e}")
233
+
234
+ async def on_llm_error(self, error: Exception, **kwargs: Any) -> None:
235
+ """Execute error callbacks for all handlers"""
236
+ for handler in self.handlers:
237
+ try:
238
+ await handler.on_llm_error(error, **kwargs)
239
+ except Exception as e:
240
+ logger.error(
241
+ f"Error in callback handler {type(handler).__name__}.on_llm_error: {e}"
242
+ )
243
+
244
+
245
+ # Convenience functions for creating common callback handlers
246
+ def create_token_callback(
247
+ user_id: str, cycle_start_date: Optional[str] = None
248
+ ) -> RedisTokenCallbackHandler:
249
+ """Create a basic token recording callback handler"""
250
+ return RedisTokenCallbackHandler(user_id, cycle_start_date)
251
+
252
+
253
+ def create_detailed_token_callback(
254
+ user_id: str, cycle_start_date: Optional[str] = None
255
+ ) -> DetailedRedisTokenCallbackHandler:
256
+ """Create a detailed token recording callback handler"""
257
+ return DetailedRedisTokenCallbackHandler(user_id, cycle_start_date)
258
+
259
+
260
+ def create_composite_callback(
261
+ *handlers: CustomAsyncCallbackHandler,
262
+ ) -> CompositeCallbackHandler:
263
+ """Create a composite callback handler"""
264
+ return CompositeCallbackHandler(list(handlers))