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,184 @@
1
+ import json
2
+ import asyncio
3
+ import threading
4
+ import time
5
+ from typing import Any, Callable, Dict, Optional
6
+ from cachetools import LRUCache
7
+ from contextlib import contextmanager
8
+ import logging
9
+ from tenacity import retry, stop_after_attempt, wait_exponential
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class ExecutionUtils:
15
+ """
16
+ Provides common utility set for execution layer, including caching and retry logic.
17
+ """
18
+
19
+ def __init__(
20
+ self,
21
+ cache_size: int = 100,
22
+ cache_ttl: int = 3600,
23
+ retry_attempts: int = 3,
24
+ retry_backoff: float = 1.0,
25
+ ):
26
+ """
27
+ Initialize execution utility class.
28
+
29
+ Args:
30
+ cache_size (int): Maximum number of cache entries
31
+ cache_ttl (int): Cache time-to-live (seconds)
32
+ retry_attempts (int): Number of retry attempts
33
+ retry_backoff (float): Retry backoff factor
34
+ """
35
+ self.cache_size = cache_size
36
+ self.cache_ttl = cache_ttl
37
+ self.retry_attempts = retry_attempts
38
+ self.retry_backoff = retry_backoff
39
+ self._cache = LRUCache(maxsize=self.cache_size) if cache_size > 0 else None
40
+ self._cache_lock = threading.Lock()
41
+ self._cache_ttl_dict: Dict[str, float] = {}
42
+
43
+ def generate_cache_key(
44
+ self,
45
+ func_name: str,
46
+ user_id: str,
47
+ task_id: str,
48
+ args: tuple,
49
+ kwargs: Dict[str, Any],
50
+ ) -> str:
51
+ """
52
+ Generate context-based cache key including user ID, task ID, function name and parameters.
53
+
54
+ Args:
55
+ func_name (str): Function name
56
+ user_id (str): User ID
57
+ task_id (str): Task ID
58
+ args (tuple): Positional arguments
59
+ kwargs (Dict[str, Any]): Keyword arguments
60
+
61
+ Returns:
62
+ str: Cache key
63
+ """
64
+ key_dict = {
65
+ "func": func_name,
66
+ "user_id": user_id,
67
+ "task_id": task_id,
68
+ "args": args,
69
+ "kwargs": {k: v for k, v in kwargs.items() if k != "self"},
70
+ }
71
+ try:
72
+ key_str = json.dumps(key_dict, sort_keys=True)
73
+ except (TypeError, ValueError):
74
+ key_str = str(key_dict)
75
+ return hash(key_str).__str__()
76
+
77
+ def get_from_cache(self, cache_key: str) -> Optional[Any]:
78
+ """
79
+ Get result from cache if it exists and is not expired.
80
+
81
+ Args:
82
+ cache_key (str): Cache key
83
+
84
+ Returns:
85
+ Optional[Any]: Cached result or None
86
+ """
87
+ if self._cache is None:
88
+ return None
89
+ with self._cache_lock:
90
+ if cache_key in self._cache:
91
+ if (
92
+ cache_key in self._cache_ttl_dict
93
+ and time.time() > self._cache_ttl_dict[cache_key]
94
+ ):
95
+ del self._cache[cache_key]
96
+ del self._cache_ttl_dict[cache_key]
97
+ return None
98
+ return self._cache[cache_key]
99
+ return None
100
+
101
+ def add_to_cache(self, cache_key: str, result: Any, ttl: Optional[int] = None) -> None:
102
+ """
103
+ Add result to cache with optional time-to-live setting.
104
+
105
+ Args:
106
+ cache_key (str): Cache key
107
+ result (Any): Cached result
108
+ ttl (Optional[int]): Time-to-live (seconds)
109
+ """
110
+ if self._cache is None:
111
+ return
112
+ with self._cache_lock:
113
+ self._cache[cache_key] = result
114
+ ttl = ttl if ttl is not None else self.cache_ttl
115
+ if ttl > 0:
116
+ self._cache_ttl_dict[cache_key] = time.time() + ttl
117
+
118
+ def create_retry_strategy(self, metric_name: Optional[str] = None) -> Callable:
119
+ """
120
+ Create retry strategy for execution operations.
121
+
122
+ Args:
123
+ metric_name (Optional[str]): Metric name for logging
124
+
125
+ Returns:
126
+ Callable: Retry decorator
127
+ """
128
+
129
+ def after_retry(retry_state):
130
+ logger.warning(
131
+ f"Retry {retry_state.attempt_number}/{self.retry_attempts} for {metric_name or 'operation'} after {retry_state.idle_for}s: {retry_state.outcome.exception()}"
132
+ )
133
+
134
+ return retry(
135
+ stop=stop_after_attempt(self.retry_attempts),
136
+ wait=wait_exponential(multiplier=self.retry_backoff, min=1, max=10),
137
+ after=after_retry,
138
+ )
139
+
140
+ @contextmanager
141
+ def timeout_context(self, seconds: int):
142
+ """
143
+ Context manager for enforcing operation timeout.
144
+
145
+ Args:
146
+ seconds (int): Timeout duration (seconds)
147
+
148
+ Raises:
149
+ TimeoutError: If operation exceeds timeout duration
150
+ """
151
+ loop = asyncio.get_event_loop()
152
+ future = asyncio.Future()
153
+ handle = loop.call_later(
154
+ seconds,
155
+ lambda: future.set_exception(TimeoutError(f"Operation timed out after {seconds}s")),
156
+ )
157
+ try:
158
+ yield future
159
+ finally:
160
+ handle.cancel()
161
+
162
+ async def execute_with_retry_and_timeout(
163
+ self, func: Callable, timeout: int, *args, **kwargs
164
+ ) -> Any:
165
+ """
166
+ Execute operation with retry and timeout mechanism.
167
+
168
+ Args:
169
+ func (Callable): Function to execute
170
+ timeout (int): Timeout duration (seconds)
171
+ *args: Positional arguments
172
+ **kwargs: Keyword arguments
173
+
174
+ Returns:
175
+ Any: Operation result
176
+
177
+ Raises:
178
+ OperationError: If all retry attempts fail
179
+ """
180
+ retry_strategy = self.create_retry_strategy(func.__name__)
181
+ try:
182
+ return await asyncio.wait_for(retry_strategy(func)(*args, **kwargs), timeout=timeout)
183
+ except asyncio.TimeoutError:
184
+ raise TimeoutError(f"Operation timed out after {timeout}s")
aiecs/utils/logging.py ADDED
@@ -0,0 +1 @@
1
+ # Placeholder for logging.py
@@ -0,0 +1,14 @@
1
+ import yaml
2
+ import os
3
+
4
+
5
+ def get_prompt(mode: str, service: str) -> str:
6
+ """
7
+ Load the prompt for the specified service from services/{mode}/prompts.yaml.
8
+ """
9
+ path = f"app/services/{mode}/prompts.yaml"
10
+ if not os.path.exists(path):
11
+ return "[Default prompt]"
12
+ with open(path, "r", encoding="utf-8") as f:
13
+ data = yaml.safe_load(f)
14
+ return data.get(service, "[No specific prompt found]")
@@ -0,0 +1,323 @@
1
+ from datetime import datetime
2
+ from typing import Optional, Dict, Any
3
+ import logging
4
+ from aiecs.infrastructure.persistence.redis_client import get_redis_client
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+
9
+ class TokenUsageRepository:
10
+ """Encapsulates all Redis operations related to user token usage"""
11
+
12
+ def _get_key_for_current_period(
13
+ self, user_id: str, cycle_start_date: Optional[str] = None
14
+ ) -> str:
15
+ """
16
+ Generate Redis key for current billing period
17
+
18
+ Args:
19
+ user_id: User ID
20
+ cycle_start_date: Cycle start date in YYYY-MM-DD format, defaults to current month if not provided
21
+
22
+ Returns:
23
+ Redis key string
24
+ """
25
+ if cycle_start_date:
26
+ # Use provided cycle start date
27
+ period = cycle_start_date
28
+ else:
29
+ # Use current month as default period
30
+ period = datetime.now().strftime("%Y-%m-%d")
31
+
32
+ return f"token_usage:{user_id}:{period}"
33
+
34
+ async def increment_prompt_tokens(
35
+ self,
36
+ user_id: str,
37
+ prompt_tokens: int,
38
+ cycle_start_date: Optional[str] = None,
39
+ ):
40
+ """
41
+ Increment prompt token usage for specified user
42
+
43
+ Args:
44
+ user_id: User ID
45
+ prompt_tokens: Number of input tokens
46
+ cycle_start_date: Cycle start date
47
+ """
48
+ if not user_id or prompt_tokens <= 0:
49
+ return
50
+
51
+ redis_key = self._get_key_for_current_period(user_id, cycle_start_date)
52
+
53
+ try:
54
+ # Use HINCRBY for atomic increment
55
+ client = await get_redis_client()
56
+ await client.hincrby(redis_key, "prompt_tokens", prompt_tokens)
57
+ logger.info(
58
+ f"[Repository] User '{user_id}' prompt tokens incremented by {prompt_tokens} in key '{redis_key}'."
59
+ )
60
+ except Exception as e:
61
+ logger.error(f"Failed to increment prompt tokens for user {user_id}: {e}")
62
+ raise
63
+
64
+ async def increment_completion_tokens(
65
+ self,
66
+ user_id: str,
67
+ completion_tokens: int,
68
+ cycle_start_date: Optional[str] = None,
69
+ ):
70
+ """
71
+ Increment completion token usage for specified user
72
+
73
+ Args:
74
+ user_id: User ID
75
+ completion_tokens: Number of output tokens
76
+ cycle_start_date: Cycle start date
77
+ """
78
+ if not user_id or completion_tokens <= 0:
79
+ return
80
+
81
+ redis_key = self._get_key_for_current_period(user_id, cycle_start_date)
82
+
83
+ try:
84
+ # Use HINCRBY for atomic increment
85
+ client = await get_redis_client()
86
+ await client.hincrby(redis_key, "completion_tokens", completion_tokens)
87
+ logger.info(
88
+ f"[Repository] User '{user_id}' completion tokens incremented by {completion_tokens} in key '{redis_key}'."
89
+ )
90
+ except Exception as e:
91
+ logger.error(f"Failed to increment completion tokens for user {user_id}: {e}")
92
+ raise
93
+
94
+ async def increment_total_usage(
95
+ self,
96
+ user_id: str,
97
+ total_tokens: int,
98
+ cycle_start_date: Optional[str] = None,
99
+ ):
100
+ """
101
+ Increment total token usage for specified user
102
+
103
+ Args:
104
+ user_id: User ID
105
+ total_tokens: Total number of tokens
106
+ cycle_start_date: Cycle start date
107
+ """
108
+ if not user_id or total_tokens <= 0:
109
+ return
110
+
111
+ redis_key = self._get_key_for_current_period(user_id, cycle_start_date)
112
+
113
+ try:
114
+ # Use HINCRBY for atomic increment
115
+ client = await get_redis_client()
116
+ await client.hincrby(redis_key, "total_tokens", total_tokens)
117
+ logger.info(
118
+ f"[Repository] User '{user_id}' total usage incremented by {total_tokens} tokens in key '{redis_key}'."
119
+ )
120
+ except Exception as e:
121
+ logger.error(f"Failed to increment total tokens for user {user_id}: {e}")
122
+ raise
123
+
124
+ async def increment_detailed_usage(
125
+ self,
126
+ user_id: str,
127
+ prompt_tokens: int,
128
+ completion_tokens: int,
129
+ cycle_start_date: Optional[str] = None,
130
+ ):
131
+ """
132
+ Increment both prompt and completion token usage for specified user
133
+
134
+ Args:
135
+ user_id: User ID
136
+ prompt_tokens: Number of input tokens
137
+ completion_tokens: Number of output tokens
138
+ cycle_start_date: Cycle start date
139
+ """
140
+ if not user_id or (prompt_tokens <= 0 and completion_tokens <= 0):
141
+ return
142
+
143
+ redis_key = self._get_key_for_current_period(user_id, cycle_start_date)
144
+
145
+ try:
146
+ # Batch update multiple fields
147
+ updates = {}
148
+ if prompt_tokens > 0:
149
+ updates["prompt_tokens"] = prompt_tokens
150
+ if completion_tokens > 0:
151
+ updates["completion_tokens"] = completion_tokens
152
+
153
+ # Calculate total token count
154
+ total_tokens = prompt_tokens + completion_tokens
155
+ if total_tokens > 0:
156
+ updates["total_tokens"] = total_tokens
157
+
158
+ # Use pipeline for batch operations
159
+ redis_client_instance = await get_redis_client()
160
+ client = await redis_client_instance.get_client()
161
+ pipe = client.pipeline()
162
+
163
+ for field, value in updates.items():
164
+ pipe.hincrby(redis_key, field, value)
165
+
166
+ await pipe.execute()
167
+
168
+ logger.info(
169
+ f"[Repository] User '{user_id}' detailed usage updated: prompt={prompt_tokens}, completion={completion_tokens}, total={total_tokens} in key '{redis_key}'."
170
+ )
171
+ except Exception as e:
172
+ logger.error(f"Failed to increment detailed usage for user {user_id}: {e}")
173
+ raise
174
+
175
+ async def get_usage_stats(
176
+ self, user_id: str, cycle_start_date: Optional[str] = None
177
+ ) -> Dict[str, int]:
178
+ """
179
+ Get token usage statistics for specified user
180
+
181
+ Args:
182
+ user_id: User ID
183
+ cycle_start_date: Cycle start date
184
+
185
+ Returns:
186
+ Dictionary containing token usage statistics
187
+ """
188
+ if not user_id:
189
+ return {}
190
+
191
+ redis_key = self._get_key_for_current_period(user_id, cycle_start_date)
192
+
193
+ try:
194
+ client = await get_redis_client()
195
+ stats = await client.hgetall(redis_key)
196
+
197
+ # Convert to integer type
198
+ result = {}
199
+ for key, value in stats.items():
200
+ try:
201
+ result[key] = int(value) if value else 0
202
+ except (ValueError, TypeError):
203
+ result[key] = 0
204
+
205
+ # Ensure required fields exist
206
+ result.setdefault("prompt_tokens", 0)
207
+ result.setdefault("completion_tokens", 0)
208
+ result.setdefault("total_tokens", 0)
209
+
210
+ logger.debug(f"[Repository] Retrieved usage stats for user '{user_id}': {result}")
211
+ return result
212
+
213
+ except Exception as e:
214
+ logger.error(f"Failed to get usage stats for user {user_id}: {e}")
215
+ return {
216
+ "prompt_tokens": 0,
217
+ "completion_tokens": 0,
218
+ "total_tokens": 0,
219
+ }
220
+
221
+ async def reset_usage(self, user_id: str, cycle_start_date: Optional[str] = None):
222
+ """
223
+ Reset token usage for specified user
224
+
225
+ Args:
226
+ user_id: User ID
227
+ cycle_start_date: Cycle start date
228
+ """
229
+ if not user_id:
230
+ return
231
+
232
+ redis_key = self._get_key_for_current_period(user_id, cycle_start_date)
233
+
234
+ try:
235
+ redis_client_instance = await get_redis_client()
236
+ client = await redis_client_instance.get_client()
237
+ await client.delete(redis_key)
238
+ logger.info(f"[Repository] Reset usage for user '{user_id}' in key '{redis_key}'.")
239
+ except Exception as e:
240
+ logger.error(f"Failed to reset usage for user {user_id}: {e}")
241
+ raise
242
+
243
+ async def set_usage_limit(
244
+ self, user_id: str, limit: int, cycle_start_date: Optional[str] = None
245
+ ):
246
+ """
247
+ Set token usage limit for user
248
+
249
+ Args:
250
+ user_id: User ID
251
+ limit: Token usage limit
252
+ cycle_start_date: Cycle start date
253
+ """
254
+ if not user_id or limit <= 0:
255
+ return
256
+
257
+ redis_key = self._get_key_for_current_period(user_id, cycle_start_date)
258
+
259
+ try:
260
+ client = await get_redis_client()
261
+ await client.hset(redis_key, {"usage_limit": str(limit)})
262
+ logger.info(
263
+ f"[Repository] Set usage limit {limit} for user '{user_id}' in key '{redis_key}'."
264
+ )
265
+ except Exception as e:
266
+ logger.error(f"Failed to set usage limit for user {user_id}: {e}")
267
+ raise
268
+
269
+ async def check_usage_limit(
270
+ self, user_id: str, cycle_start_date: Optional[str] = None
271
+ ) -> Dict[str, Any]:
272
+ """
273
+ Check if user has exceeded usage limit
274
+
275
+ Args:
276
+ user_id: User ID
277
+ cycle_start_date: Cycle start date
278
+
279
+ Returns:
280
+ Dictionary containing limit check results
281
+ """
282
+ if not user_id:
283
+ return {
284
+ "exceeded": False,
285
+ "current_usage": 0,
286
+ "limit": 0,
287
+ "remaining": 0,
288
+ }
289
+
290
+ try:
291
+ stats = await self.get_usage_stats(user_id, cycle_start_date)
292
+ current_usage = stats.get("total_tokens", 0)
293
+
294
+ redis_key = self._get_key_for_current_period(user_id, cycle_start_date)
295
+ client = await get_redis_client()
296
+ limit_str = await client.hget(redis_key, "usage_limit")
297
+ limit = int(limit_str) if limit_str else 0
298
+
299
+ exceeded = limit > 0 and current_usage >= limit
300
+ remaining = max(0, limit - current_usage) if limit > 0 else float("inf")
301
+
302
+ result = {
303
+ "exceeded": exceeded,
304
+ "current_usage": current_usage,
305
+ "limit": limit,
306
+ "remaining": remaining,
307
+ }
308
+
309
+ logger.debug(f"[Repository] Usage limit check for user '{user_id}': {result}")
310
+ return result
311
+
312
+ except Exception as e:
313
+ logger.error(f"Failed to check usage limit for user {user_id}: {e}")
314
+ return {
315
+ "exceeded": False,
316
+ "current_usage": 0,
317
+ "limit": 0,
318
+ "remaining": 0,
319
+ }
320
+
321
+
322
+ # Create a singleton for global application use
323
+ token_usage_repo = TokenUsageRepository()
aiecs/ws/__init__.py ADDED
File without changes
@@ -0,0 +1,52 @@
1
+ import socketio
2
+ from aiecs.config.config import get_settings
3
+
4
+ settings = get_settings()
5
+ # In production, this should be set to specific origins
6
+ # For example: ["https://your-frontend-domain.com"]
7
+ allowed_origins = (
8
+ settings.cors_allowed_origins.split(",")
9
+ if hasattr(settings, "cors_allowed_origins")
10
+ else ["http://express-gateway:3001"]
11
+ )
12
+
13
+ # Allow all origins for development (more permissive)
14
+ # In production, you should use specific origins
15
+ # Explicitly set async_mode to 'asgi' for compatibility with uvicorn
16
+ sio = socketio.AsyncServer(cors_allowed_origins="*", async_mode="asgi")
17
+ # We no longer create a FastAPI app or combined ASGI app here
18
+ # The FastAPI app will be created in main.py and the Socket.IO server will
19
+ # be mounted there
20
+
21
+ # Store connected clients by user ID
22
+ connected_clients = {}
23
+
24
+
25
+ @sio.event
26
+ async def connect(sid, environ, auth=None):
27
+ print(f"Client connected: {sid}")
28
+
29
+
30
+ @sio.event
31
+ async def disconnect(sid):
32
+ print(f"Client disconnected: {sid}")
33
+ for user, socket_id in list(connected_clients.items()):
34
+ if socket_id == sid:
35
+ del connected_clients[user]
36
+
37
+
38
+ @sio.event
39
+ async def register(sid, data):
40
+ user_id = data.get("user_id")
41
+ if user_id:
42
+ connected_clients[user_id] = sid
43
+ print(f"Registered user {user_id} on SID {sid}")
44
+
45
+
46
+ # Send progress update to user
47
+
48
+
49
+ async def push_progress(user_id: str, data: dict):
50
+ sid = connected_clients.get(user_id)
51
+ if sid:
52
+ await sio.emit("progress", data, to=sid)