aiecs 1.2.0__tar.gz → 1.2.2__tar.gz

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 (152) hide show
  1. {aiecs-1.2.0/aiecs.egg-info → aiecs-1.2.2}/PKG-INFO +1 -1
  2. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/__init__.py +1 -1
  3. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/aiecs_client.py +1 -1
  4. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/config/config.py +38 -1
  5. aiecs-1.2.2/aiecs/infrastructure/monitoring/__init__.py +34 -0
  6. aiecs-1.2.2/aiecs/infrastructure/monitoring/global_metrics_manager.py +207 -0
  7. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/persistence/file_storage.py +41 -28
  8. aiecs-1.2.2/aiecs/llm/__init__.py +93 -0
  9. aiecs-1.2.2/aiecs/llm/callbacks/__init__.py +12 -0
  10. {aiecs-1.2.0/aiecs/llm → aiecs-1.2.2/aiecs/llm/callbacks}/custom_callbacks.py +1 -1
  11. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/llm/client_factory.py +23 -6
  12. aiecs-1.2.2/aiecs/llm/clients/__init__.py +35 -0
  13. {aiecs-1.2.0/aiecs/llm → aiecs-1.2.2/aiecs/llm/clients}/base_client.py +73 -1
  14. {aiecs-1.2.0/aiecs/llm → aiecs-1.2.2/aiecs/llm/clients}/googleai_client.py +19 -15
  15. {aiecs-1.2.0/aiecs/llm → aiecs-1.2.2/aiecs/llm/clients}/openai_client.py +9 -14
  16. {aiecs-1.2.0/aiecs/llm → aiecs-1.2.2/aiecs/llm/clients}/vertex_client.py +15 -15
  17. {aiecs-1.2.0/aiecs/llm → aiecs-1.2.2/aiecs/llm/clients}/xai_client.py +36 -50
  18. aiecs-1.2.2/aiecs/llm/config/__init__.py +54 -0
  19. aiecs-1.2.2/aiecs/llm/config/config_loader.py +275 -0
  20. aiecs-1.2.2/aiecs/llm/config/config_validator.py +237 -0
  21. aiecs-1.2.2/aiecs/llm/config/model_config.py +132 -0
  22. aiecs-1.2.2/aiecs/llm/utils/__init__.py +11 -0
  23. aiecs-1.2.2/aiecs/llm/utils/validate_config.py +91 -0
  24. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/main.py +32 -2
  25. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/tool_executor/__init__.py +2 -2
  26. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/tool_executor/tool_executor.py +3 -3
  27. {aiecs-1.2.0 → aiecs-1.2.2/aiecs.egg-info}/PKG-INFO +1 -1
  28. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs.egg-info/SOURCES.txt +15 -6
  29. {aiecs-1.2.0 → aiecs-1.2.2}/pyproject.toml +1 -1
  30. aiecs-1.2.0/aiecs/infrastructure/monitoring/__init__.py +0 -12
  31. aiecs-1.2.0/aiecs/llm/__init__.py +0 -56
  32. {aiecs-1.2.0 → aiecs-1.2.2}/LICENSE +0 -0
  33. {aiecs-1.2.0 → aiecs-1.2.2}/MANIFEST.in +0 -0
  34. {aiecs-1.2.0 → aiecs-1.2.2}/README.md +0 -0
  35. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/__main__.py +0 -0
  36. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/application/__init__.py +0 -0
  37. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/application/executors/__init__.py +0 -0
  38. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/application/executors/operation_executor.py +0 -0
  39. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/config/__init__.py +0 -0
  40. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/config/registry.py +0 -0
  41. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/core/__init__.py +0 -0
  42. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/core/interface/__init__.py +0 -0
  43. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/core/interface/execution_interface.py +0 -0
  44. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/core/interface/storage_interface.py +0 -0
  45. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/__init__.py +0 -0
  46. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/__init__.py +0 -0
  47. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/agent_adapter.py +0 -0
  48. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/analytics.py +0 -0
  49. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/collaborative_workflow.py +0 -0
  50. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/communication_hub.py +0 -0
  51. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/community_builder.py +0 -0
  52. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/community_integration.py +0 -0
  53. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/community_manager.py +0 -0
  54. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/decision_engine.py +0 -0
  55. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/exceptions.py +0 -0
  56. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/models/__init__.py +0 -0
  57. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/models/community_models.py +0 -0
  58. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/resource_manager.py +0 -0
  59. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/community/shared_context_manager.py +0 -0
  60. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/context/__init__.py +0 -0
  61. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/context/context_engine.py +0 -0
  62. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/context/conversation_models.py +0 -0
  63. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/execution/__init__.py +0 -0
  64. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/execution/model.py +0 -0
  65. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/task/__init__.py +0 -0
  66. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/task/dsl_processor.py +0 -0
  67. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/task/model.py +0 -0
  68. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/domain/task/task_context.py +0 -0
  69. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/__init__.py +0 -0
  70. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/messaging/__init__.py +0 -0
  71. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/messaging/celery_task_manager.py +0 -0
  72. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/messaging/websocket_manager.py +0 -0
  73. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/monitoring/executor_metrics.py +0 -0
  74. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/monitoring/structured_logger.py +0 -0
  75. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/monitoring/tracing_manager.py +0 -0
  76. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/persistence/__init__.py +0 -0
  77. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/persistence/context_engine_client.py +0 -0
  78. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/persistence/database_manager.py +0 -0
  79. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/infrastructure/persistence/redis_client.py +0 -0
  80. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/__init__.py +0 -0
  81. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/aid/VERSION_MANAGEMENT.md +0 -0
  82. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/aid/__init__.py +0 -0
  83. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/aid/version_manager.py +0 -0
  84. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md +0 -0
  85. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_check/README_DEPENDENCY_CHECKER.md +0 -0
  86. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_check/__init__.py +0 -0
  87. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_check/dependency_checker.py +0 -0
  88. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_check/dependency_fixer.py +0 -0
  89. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_check/download_nlp_data.py +0 -0
  90. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_check/quick_dependency_check.py +0 -0
  91. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_check/setup_nlp_data.sh +0 -0
  92. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_patch/__init__.py +0 -0
  93. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_patch/fix_weasel/README_WEASEL_PATCH.md +0 -0
  94. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_patch/fix_weasel/__init__.py +0 -0
  95. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.py +0 -0
  96. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.sh +0 -0
  97. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_patch/fix_weasel/patch_weasel_library.sh +0 -0
  98. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/dependance_patch/fix_weasel/run_weasel_patch.sh +0 -0
  99. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/tools_develop/README.md +0 -0
  100. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/tools_develop/__init__.py +0 -0
  101. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/tools_develop/check_type_annotations.py +0 -0
  102. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/scripts/tools_develop/validate_tool_schemas.py +0 -0
  103. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tasks/__init__.py +0 -0
  104. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tasks/worker.py +0 -0
  105. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/__init__.py +0 -0
  106. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/base_tool.py +0 -0
  107. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/docs/__init__.py +0 -0
  108. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/docs/ai_document_orchestrator.py +0 -0
  109. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/docs/ai_document_writer_orchestrator.py +0 -0
  110. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/docs/content_insertion_tool.py +0 -0
  111. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/docs/document_creator_tool.py +0 -0
  112. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/docs/document_layout_tool.py +0 -0
  113. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/docs/document_parser_tool.py +0 -0
  114. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/docs/document_writer_tool.py +0 -0
  115. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/langchain_adapter.py +0 -0
  116. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/schema_generator.py +0 -0
  117. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/statistics/__init__.py +0 -0
  118. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/statistics/ai_data_analysis_orchestrator.py +0 -0
  119. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/statistics/ai_insight_generator_tool.py +0 -0
  120. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/statistics/ai_report_orchestrator_tool.py +0 -0
  121. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/statistics/data_loader_tool.py +0 -0
  122. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/statistics/data_profiler_tool.py +0 -0
  123. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/statistics/data_transformer_tool.py +0 -0
  124. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/statistics/data_visualizer_tool.py +0 -0
  125. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/statistics/model_trainer_tool.py +0 -0
  126. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/statistics/statistical_analyzer_tool.py +0 -0
  127. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/task_tools/__init__.py +0 -0
  128. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/task_tools/chart_tool.py +0 -0
  129. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/task_tools/classfire_tool.py +0 -0
  130. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/task_tools/image_tool.py +0 -0
  131. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/task_tools/office_tool.py +0 -0
  132. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/task_tools/pandas_tool.py +0 -0
  133. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/task_tools/report_tool.py +0 -0
  134. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/task_tools/research_tool.py +0 -0
  135. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/task_tools/scraper_tool.py +0 -0
  136. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/task_tools/search_tool.py +0 -0
  137. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/task_tools/stats_tool.py +0 -0
  138. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/tools/temp_file_manager.py +0 -0
  139. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/utils/LLM_output_structor.py +0 -0
  140. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/utils/__init__.py +0 -0
  141. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/utils/base_callback.py +0 -0
  142. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/utils/execution_utils.py +0 -0
  143. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/utils/logging.py +0 -0
  144. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/utils/prompt_loader.py +0 -0
  145. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/utils/token_usage_repository.py +0 -0
  146. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/ws/__init__.py +0 -0
  147. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs/ws/socket_server.py +0 -0
  148. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs.egg-info/dependency_links.txt +0 -0
  149. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs.egg-info/entry_points.txt +0 -0
  150. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs.egg-info/requires.txt +0 -0
  151. {aiecs-1.2.0 → aiecs-1.2.2}/aiecs.egg-info/top_level.txt +0 -0
  152. {aiecs-1.2.0 → aiecs-1.2.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aiecs
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: AI Execute Services - A middleware framework for AI-powered task execution and tool orchestration
5
5
  Author-email: AIECS Team <iretbl@gmail.com>
6
6
  License-Expression: MIT
@@ -5,7 +5,7 @@ A powerful Python middleware framework for building AI-powered applications
5
5
  with tool orchestration, task execution, and multi-provider LLM support.
6
6
  """
7
7
 
8
- __version__ = "1.2.0"
8
+ __version__ = "1.2.2"
9
9
  __author__ = "AIECS Team"
10
10
  __email__ = "iretbl@gmail.com"
11
11
 
@@ -12,7 +12,7 @@ from aiecs.config.config import get_settings, validate_required_settings
12
12
  from aiecs.domain.task.task_context import TaskContext
13
13
  from aiecs.tools import discover_tools, list_tools, get_tool
14
14
  from aiecs.llm.client_factory import LLMClientFactory, LLMClientManager, AIProvider
15
- from aiecs.llm.base_client import LLMMessage
15
+ from aiecs.llm.clients.base_client import LLMMessage
16
16
 
17
17
  logger = logging.getLogger(__name__)
18
18
 
@@ -1,6 +1,7 @@
1
1
  from pydantic import Field, ConfigDict
2
2
  from pydantic_settings import BaseSettings
3
3
  from functools import lru_cache
4
+ from pathlib import Path
4
5
 
5
6
  class Settings(BaseSettings):
6
7
  # LLM Provider Configuration (optional until used)
@@ -14,6 +15,13 @@ class Settings(BaseSettings):
14
15
  xai_api_key: str = Field(default="", alias="XAI_API_KEY")
15
16
  grok_api_key: str = Field(default="", alias="GROK_API_KEY") # Backward compatibility
16
17
 
18
+ # LLM Models Configuration
19
+ llm_models_config_path: str = Field(
20
+ default="",
21
+ alias="LLM_MODELS_CONFIG",
22
+ description="Path to LLM models YAML configuration file"
23
+ )
24
+
17
25
  # Infrastructure Configuration (with sensible defaults)
18
26
  celery_broker_url: str = Field(default="redis://localhost:6379/0", alias="CELERY_BROKER_URL")
19
27
  cors_allowed_origins: str = Field(default="http://localhost:3000,http://express-gateway:3001", alias="CORS_ALLOWED_ORIGINS")
@@ -46,7 +54,7 @@ class Settings(BaseSettings):
46
54
  reload: bool = Field(default=False, alias="RELOAD")
47
55
  port: int = Field(default=8000, alias="PORT")
48
56
 
49
- model_config = ConfigDict(env_file=".env", env_file_encoding="utf-8")
57
+ model_config = ConfigDict(env_file=".env", env_file_encoding="utf-8", extra="allow")
50
58
 
51
59
  @property
52
60
  def database_config(self) -> dict:
@@ -70,6 +78,35 @@ class Settings(BaseSettings):
70
78
  "local_storage_path": "./storage"
71
79
  }
72
80
 
81
+
82
+ def validate_llm_models_config(self) -> bool:
83
+ """
84
+ Validate that LLM models configuration file exists.
85
+
86
+ Returns:
87
+ True if config file exists or can be found in default locations
88
+
89
+ Raises:
90
+ FileNotFoundError: If config file doesn't exist
91
+ """
92
+ if self.llm_models_config_path:
93
+ config_path = Path(self.llm_models_config_path)
94
+ if not config_path.exists():
95
+ raise FileNotFoundError(
96
+ f"LLM models config file not found: {config_path}"
97
+ )
98
+ return True
99
+
100
+ # Check default locations
101
+ current_dir = Path(__file__).parent
102
+ default_path = current_dir / "llm_models.yaml"
103
+
104
+ if default_path.exists():
105
+ return True
106
+
107
+ # If not found, it's still okay - the config loader will try to find it
108
+ return True
109
+
73
110
  @lru_cache()
74
111
  def get_settings():
75
112
  return Settings()
@@ -0,0 +1,34 @@
1
+ """Infrastructure monitoring module
2
+
3
+ Contains monitoring, metrics, and observability infrastructure.
4
+ """
5
+
6
+ from .executor_metrics import ExecutorMetrics
7
+ from .tracing_manager import TracingManager
8
+ from .global_metrics_manager import (
9
+ initialize_global_metrics,
10
+ get_global_metrics,
11
+ close_global_metrics,
12
+ is_metrics_initialized,
13
+ get_metrics_summary,
14
+ record_operation,
15
+ record_duration,
16
+ record_operation_success,
17
+ record_operation_failure,
18
+ record_retry
19
+ )
20
+
21
+ __all__ = [
22
+ "ExecutorMetrics",
23
+ "TracingManager",
24
+ "initialize_global_metrics",
25
+ "get_global_metrics",
26
+ "close_global_metrics",
27
+ "is_metrics_initialized",
28
+ "get_metrics_summary",
29
+ "record_operation",
30
+ "record_duration",
31
+ "record_operation_success",
32
+ "record_operation_failure",
33
+ "record_retry",
34
+ ]
@@ -0,0 +1,207 @@
1
+ """
2
+ Global Metrics Manager
3
+
4
+ This module provides a singleton ExecutorMetrics instance that can be shared
5
+ across all components in the application. It follows the same pattern as
6
+ other global managers in the infrastructure layer.
7
+
8
+ Usage:
9
+ # In main.py startup:
10
+ await initialize_global_metrics()
11
+
12
+ # In any component:
13
+ from aiecs.infrastructure.monitoring.global_metrics_manager import get_global_metrics
14
+ metrics = get_global_metrics()
15
+ """
16
+
17
+ import logging
18
+ import asyncio
19
+ import os
20
+ from typing import Optional, Dict, Any
21
+ from .executor_metrics import ExecutorMetrics
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # Global singleton instance
26
+ _global_metrics: Optional[ExecutorMetrics] = None
27
+ _initialization_lock = asyncio.Lock()
28
+ _initialized = False
29
+
30
+
31
+ async def initialize_global_metrics(
32
+ enable_metrics: bool = True,
33
+ metrics_port: Optional[int] = None,
34
+ config: Optional[Dict[str, Any]] = None
35
+ ) -> Optional[ExecutorMetrics]:
36
+ """
37
+ Initialize the global ExecutorMetrics instance.
38
+
39
+ This should be called once during application startup (in main.py lifespan).
40
+
41
+ Args:
42
+ enable_metrics: Whether to enable metrics collection (default: True)
43
+ metrics_port: Port for metrics server (default: from env or 8001)
44
+ config: Additional configuration options
45
+
46
+ Returns:
47
+ The initialized ExecutorMetrics instance or None if initialization fails
48
+
49
+ Example:
50
+ @asynccontextmanager
51
+ async def lifespan(app: FastAPI):
52
+ # Startup
53
+ await initialize_global_metrics()
54
+ yield
55
+ # Shutdown
56
+ await close_global_metrics()
57
+ """
58
+ global _global_metrics, _initialized
59
+
60
+ if _initialized and _global_metrics:
61
+ logger.info("Global metrics already initialized")
62
+ return _global_metrics
63
+
64
+ async with _initialization_lock:
65
+ # Double-check after acquiring lock
66
+ if _initialized and _global_metrics:
67
+ return _global_metrics
68
+
69
+ try:
70
+ # Determine metrics port
71
+ if metrics_port is None:
72
+ metrics_port = int(os.environ.get("METRICS_PORT", "8001"))
73
+
74
+ # Check if metrics should be enabled
75
+ if not enable_metrics:
76
+ enable_metrics = os.environ.get("ENABLE_METRICS", "true").lower() == "true"
77
+
78
+ logger.info(f"Initializing global metrics (port: {metrics_port}, enabled: {enable_metrics})...")
79
+
80
+ # Create metrics instance
81
+ _global_metrics = ExecutorMetrics(
82
+ enable_metrics=enable_metrics,
83
+ metrics_port=metrics_port
84
+ )
85
+
86
+ _initialized = True
87
+ logger.info("✅ Global metrics initialized successfully")
88
+ return _global_metrics
89
+
90
+ except Exception as e:
91
+ logger.error(f"❌ Failed to initialize global metrics: {e}")
92
+ logger.warning("Application will continue without metrics (degraded mode)")
93
+ _global_metrics = None
94
+ _initialized = False
95
+ return None
96
+
97
+
98
+ def get_global_metrics() -> Optional[ExecutorMetrics]:
99
+ """
100
+ Get the global ExecutorMetrics instance.
101
+
102
+ Returns:
103
+ The global ExecutorMetrics instance or None if not initialized
104
+
105
+ Raises:
106
+ RuntimeError: If metrics are requested but not initialized
107
+
108
+ Example:
109
+ metrics = get_global_metrics()
110
+ if metrics:
111
+ metrics.record_operation('my_operation', 1)
112
+ """
113
+ global _global_metrics
114
+
115
+ if _global_metrics is None:
116
+ logger.warning("Global metrics not initialized - call initialize_global_metrics() first")
117
+ return None
118
+
119
+ return _global_metrics
120
+
121
+
122
+ async def close_global_metrics():
123
+ """
124
+ Close the global metrics instance.
125
+
126
+ This should be called during application shutdown.
127
+ """
128
+ global _global_metrics, _initialized
129
+
130
+ if _global_metrics:
131
+ try:
132
+ # ExecutorMetrics doesn't have a close method, but we can clean up
133
+ logger.info("Closing global metrics...")
134
+ _global_metrics = None
135
+ _initialized = False
136
+ logger.info("✅ Global metrics closed successfully")
137
+ except Exception as e:
138
+ logger.error(f"❌ Error closing global metrics: {e}")
139
+
140
+
141
+ def is_metrics_initialized() -> bool:
142
+ """
143
+ Check if global metrics are initialized.
144
+
145
+ Returns:
146
+ True if metrics are initialized, False otherwise
147
+ """
148
+ return _initialized and _global_metrics is not None
149
+
150
+
151
+ def get_metrics_summary() -> Dict[str, Any]:
152
+ """
153
+ Get a summary of the global metrics status.
154
+
155
+ Returns:
156
+ Dictionary containing metrics status information
157
+ """
158
+ if not is_metrics_initialized():
159
+ return {
160
+ "initialized": False,
161
+ "message": "Global metrics not initialized"
162
+ }
163
+
164
+ try:
165
+ return _global_metrics.get_metrics_summary()
166
+ except Exception as e:
167
+ return {
168
+ "initialized": True,
169
+ "error": str(e),
170
+ "message": "Failed to get metrics summary"
171
+ }
172
+
173
+
174
+ # Convenience functions for common operations
175
+ def record_operation(operation_type: str, success: bool = True, duration: Optional[float] = None, **kwargs):
176
+ """Record an operation using global metrics."""
177
+ metrics = get_global_metrics()
178
+ if metrics:
179
+ metrics.record_operation(operation_type, success, duration, **kwargs)
180
+
181
+
182
+ def record_duration(operation: str, duration: float, labels: Optional[Dict[str, str]] = None):
183
+ """Record operation duration using global metrics."""
184
+ metrics = get_global_metrics()
185
+ if metrics:
186
+ metrics.record_duration(operation, duration, labels)
187
+
188
+
189
+ def record_operation_success(operation: str, labels: Optional[Dict[str, str]] = None):
190
+ """Record operation success using global metrics."""
191
+ metrics = get_global_metrics()
192
+ if metrics:
193
+ metrics.record_operation_success(operation, labels)
194
+
195
+
196
+ def record_operation_failure(operation: str, error_type: str, labels: Optional[Dict[str, str]] = None):
197
+ """Record operation failure using global metrics."""
198
+ metrics = get_global_metrics()
199
+ if metrics:
200
+ metrics.record_operation_failure(operation, error_type, labels)
201
+
202
+
203
+ def record_retry(operation: str, attempt_number: int):
204
+ """Record retry using global metrics."""
205
+ metrics = get_global_metrics()
206
+ if metrics:
207
+ metrics.record_retry(operation, attempt_number)
@@ -29,7 +29,7 @@ except ImportError:
29
29
  GoogleCloudError = Exception
30
30
  DefaultCredentialsError = Exception
31
31
 
32
- from ..monitoring.executor_metrics import ExecutorMetrics
32
+ from ..monitoring.global_metrics_manager import get_global_metrics
33
33
 
34
34
  logger = logging.getLogger(__name__)
35
35
 
@@ -93,8 +93,8 @@ class FileStorage:
93
93
  self._cache_timestamps = {}
94
94
  self._initialized = False
95
95
 
96
- # Metrics
97
- self.metrics = ExecutorMetrics(enable_metrics=True)
96
+ # Metrics - use global metrics manager
97
+ self.metrics = get_global_metrics()
98
98
 
99
99
  # Ensure local storage directory exists
100
100
  if self.config.enable_local_fallback:
@@ -205,26 +205,30 @@ class FileStorage:
205
205
  if self._gcs_bucket:
206
206
  success = await self._store_gcs(key, serialized_data, metadata, compressed)
207
207
  if success:
208
- self.metrics.record_operation('gcs_store_success', 1)
209
- duration = (datetime.utcnow() - start_time).total_seconds()
210
- self.metrics.record_duration('gcs_store_duration', duration)
208
+ if self.metrics:
209
+ self.metrics.record_operation('gcs_store_success', 1)
210
+ duration = (datetime.utcnow() - start_time).total_seconds()
211
+ self.metrics.record_duration('gcs_store_duration', duration)
211
212
  return True
212
213
 
213
214
  # Fallback to local storage
214
215
  if self.config.enable_local_fallback:
215
216
  success = await self._store_local(key, serialized_data, metadata, compressed)
216
217
  if success:
217
- self.metrics.record_operation('local_store_success', 1)
218
- duration = (datetime.utcnow() - start_time).total_seconds()
219
- self.metrics.record_duration('local_store_duration', duration)
218
+ if self.metrics:
219
+ self.metrics.record_operation('local_store_success', 1)
220
+ duration = (datetime.utcnow() - start_time).total_seconds()
221
+ self.metrics.record_duration('local_store_duration', duration)
220
222
  return True
221
223
 
222
- self.metrics.record_operation('store_failure', 1)
224
+ if self.metrics:
225
+ self.metrics.record_operation('store_failure', 1)
223
226
  return False
224
227
 
225
228
  except Exception as e:
226
229
  logger.error(f"Failed to store data for key {key}: {e}")
227
- self.metrics.record_operation('store_error', 1)
230
+ if self.metrics:
231
+ self.metrics.record_operation('store_error', 1)
228
232
  raise FileStorageError(f"Storage failed: {e}")
229
233
 
230
234
  async def retrieve(self, key: str) -> Optional[Union[str, bytes, Dict[str, Any]]]:
@@ -247,7 +251,8 @@ class FileStorage:
247
251
  if self.config.enable_cache and key in self._cache:
248
252
  cache_time = self._cache_timestamps.get(key)
249
253
  if cache_time and (datetime.utcnow() - cache_time).total_seconds() < self.config.cache_ttl_seconds:
250
- self.metrics.record_operation('cache_hit', 1)
254
+ if self.metrics:
255
+ self.metrics.record_operation('cache_hit', 1)
251
256
  return self._cache[key]['data']
252
257
  else:
253
258
  # Remove expired cache entry
@@ -258,9 +263,10 @@ class FileStorage:
258
263
  if self._gcs_bucket:
259
264
  data = await self._retrieve_gcs(key)
260
265
  if data is not None:
261
- self.metrics.record_operation('gcs_retrieve_success', 1)
262
- duration = (datetime.utcnow() - start_time).total_seconds()
263
- self.metrics.record_duration('gcs_retrieve_duration', duration)
266
+ if self.metrics:
267
+ self.metrics.record_operation('gcs_retrieve_success', 1)
268
+ duration = (datetime.utcnow() - start_time).total_seconds()
269
+ self.metrics.record_duration('gcs_retrieve_duration', duration)
264
270
 
265
271
  # Update cache
266
272
  if self.config.enable_cache:
@@ -273,9 +279,10 @@ class FileStorage:
273
279
  if self.config.enable_local_fallback:
274
280
  data = await self._retrieve_local(key)
275
281
  if data is not None:
276
- self.metrics.record_operation('local_retrieve_success', 1)
277
- duration = (datetime.utcnow() - start_time).total_seconds()
278
- self.metrics.record_duration('local_retrieve_duration', duration)
282
+ if self.metrics:
283
+ self.metrics.record_operation('local_retrieve_success', 1)
284
+ duration = (datetime.utcnow() - start_time).total_seconds()
285
+ self.metrics.record_duration('local_retrieve_duration', duration)
279
286
 
280
287
  # Update cache
281
288
  if self.config.enable_cache:
@@ -284,12 +291,14 @@ class FileStorage:
284
291
 
285
292
  return data
286
293
 
287
- self.metrics.record_operation('retrieve_not_found', 1)
294
+ if self.metrics:
295
+ self.metrics.record_operation('retrieve_not_found', 1)
288
296
  return None
289
297
 
290
298
  except Exception as e:
291
299
  logger.error(f"Failed to retrieve data for key {key}: {e}")
292
- self.metrics.record_operation('retrieve_error', 1)
300
+ if self.metrics:
301
+ self.metrics.record_operation('retrieve_error', 1)
293
302
  raise FileStorageError(f"Retrieval failed: {e}")
294
303
 
295
304
  async def delete(self, key: str) -> bool:
@@ -317,7 +326,8 @@ class FileStorage:
317
326
  if self._gcs_bucket:
318
327
  gcs_success = await self._delete_gcs(key)
319
328
  if gcs_success:
320
- self.metrics.record_operation('gcs_delete_success', 1)
329
+ if self.metrics:
330
+ self.metrics.record_operation('gcs_delete_success', 1)
321
331
  else:
322
332
  success = False
323
333
 
@@ -325,20 +335,23 @@ class FileStorage:
325
335
  if self.config.enable_local_fallback:
326
336
  local_success = await self._delete_local(key)
327
337
  if local_success:
328
- self.metrics.record_operation('local_delete_success', 1)
338
+ if self.metrics:
339
+ self.metrics.record_operation('local_delete_success', 1)
329
340
  else:
330
341
  success = False
331
342
 
332
- if success:
333
- self.metrics.record_operation('delete_success', 1)
334
- else:
335
- self.metrics.record_operation('delete_failure', 1)
343
+ if self.metrics:
344
+ if success:
345
+ self.metrics.record_operation('delete_success', 1)
346
+ else:
347
+ self.metrics.record_operation('delete_failure', 1)
336
348
 
337
349
  return success
338
350
 
339
351
  except Exception as e:
340
352
  logger.error(f"Failed to delete data for key {key}: {e}")
341
- self.metrics.record_operation('delete_error', 1)
353
+ if self.metrics:
354
+ self.metrics.record_operation('delete_error', 1)
342
355
  raise FileStorageError(f"Deletion failed: {e}")
343
356
 
344
357
  async def exists(self, key: str) -> bool:
@@ -646,7 +659,7 @@ class FileStorage:
646
659
  'local_fallback_enabled': self.config.enable_local_fallback,
647
660
  'cache_enabled': self.config.enable_cache,
648
661
  'cache_size': len(self._cache),
649
- 'metrics': self.metrics.get_metrics_summary() if hasattr(self.metrics, 'get_metrics_summary') else {}
662
+ 'metrics': self.metrics.get_metrics_summary() if self.metrics and hasattr(self.metrics, 'get_metrics_summary') else {}
650
663
  }
651
664
 
652
665
 
@@ -0,0 +1,93 @@
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 (
51
+ CustomAsyncCallbackHandler
52
+ )
53
+
54
+ __all__ = [
55
+ # Base classes and types
56
+ 'BaseLLMClient',
57
+ 'LLMMessage',
58
+ 'LLMResponse',
59
+ 'LLMClientError',
60
+ 'ProviderNotAvailableError',
61
+ 'RateLimitError',
62
+ 'AIProvider',
63
+
64
+ # Factory and manager
65
+ 'LLMClientFactory',
66
+ 'LLMClientManager',
67
+ 'get_llm_manager',
68
+
69
+ # Individual clients
70
+ 'OpenAIClient',
71
+ 'VertexAIClient',
72
+ 'GoogleAIClient',
73
+ 'XAIClient',
74
+
75
+ # Convenience functions
76
+ 'generate_text',
77
+ 'stream_text',
78
+
79
+ # Configuration management
80
+ 'ModelCostConfig',
81
+ 'ModelCapabilities',
82
+ 'ModelDefaultParams',
83
+ 'ModelConfig',
84
+ 'ProviderConfig',
85
+ 'LLMModelsConfig',
86
+ 'LLMConfigLoader',
87
+ 'get_llm_config_loader',
88
+ 'get_llm_config',
89
+ 'reload_llm_config',
90
+
91
+ # Callbacks
92
+ 'CustomAsyncCallbackHandler',
93
+ ]
@@ -0,0 +1,12 @@
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
+ ]
12
+
@@ -4,7 +4,7 @@ import logging
4
4
  # Import the base callback handler from utils
5
5
  from aiecs.utils.base_callback import CustomAsyncCallbackHandler
6
6
  # Import LLM types for internal use only
7
- from aiecs.llm.base_client import LLMMessage, LLMResponse
7
+ from aiecs.llm.clients.base_client import LLMMessage, LLMResponse
8
8
  # Import token usage repository
9
9
  from aiecs.utils.token_usage_repository import token_usage_repo
10
10
 
@@ -2,12 +2,12 @@ import logging
2
2
  from typing import Dict, Any, Optional, Union, List
3
3
  from enum import Enum
4
4
 
5
- from .base_client import BaseLLMClient, LLMMessage, LLMResponse
6
- from .openai_client import OpenAIClient
7
- from .vertex_client import VertexAIClient
8
- from .googleai_client import GoogleAIClient
9
- from .xai_client import XAIClient
10
- from ..utils.base_callback import CustomAsyncCallbackHandler
5
+ from .clients.base_client import BaseLLMClient, LLMMessage, LLMResponse
6
+ from .clients.openai_client import OpenAIClient
7
+ from .clients.vertex_client import VertexAIClient
8
+ from .clients.googleai_client import GoogleAIClient
9
+ from .clients.xai_client import XAIClient
10
+ from .callbacks.custom_callbacks import CustomAsyncCallbackHandler
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
@@ -72,6 +72,23 @@ class LLMClientFactory:
72
72
  del cls._clients[provider]
73
73
  except Exception as e:
74
74
  logger.error(f"Error closing client {provider}: {e}")
75
+
76
+ @classmethod
77
+ def reload_config(cls):
78
+ """
79
+ Reload LLM models configuration.
80
+
81
+ This reloads the configuration from the YAML file, allowing for
82
+ hot-reloading of model settings without restarting the application.
83
+ """
84
+ try:
85
+ from aiecs.llm.config import reload_llm_config
86
+ config = reload_llm_config()
87
+ logger.info(f"Reloaded LLM configuration: {len(config.providers)} providers")
88
+ return config
89
+ except Exception as e:
90
+ logger.error(f"Failed to reload LLM configuration: {e}")
91
+ raise
75
92
 
76
93
  class LLMClientManager:
77
94
  """High-level manager for LLM operations with context-aware provider selection"""