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.
- aiecs/__init__.py +72 -0
- aiecs/__main__.py +41 -0
- aiecs/aiecs_client.py +469 -0
- aiecs/application/__init__.py +10 -0
- aiecs/application/executors/__init__.py +10 -0
- aiecs/application/executors/operation_executor.py +363 -0
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +375 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +356 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +531 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +443 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +319 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +100 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +327 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +349 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +244 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +23 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +387 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +343 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +580 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +189 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +344 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +378 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +199 -0
- aiecs/application/knowledge_graph/profiling/query_profiler.py +223 -0
- aiecs/application/knowledge_graph/reasoning/__init__.py +27 -0
- aiecs/application/knowledge_graph/reasoning/evidence_synthesis.py +347 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +504 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +167 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/__init__.py +79 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_builder.py +513 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_nodes.py +630 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +654 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +477 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +390 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +217 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +169 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +872 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +554 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +19 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +596 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +423 -0
- aiecs/application/knowledge_graph/search/reranker.py +295 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +553 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +398 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +329 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +269 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +189 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +321 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +484 -0
- aiecs/config/__init__.py +16 -0
- aiecs/config/config.py +498 -0
- aiecs/config/graph_config.py +137 -0
- aiecs/config/registry.py +23 -0
- aiecs/core/__init__.py +46 -0
- aiecs/core/interface/__init__.py +34 -0
- aiecs/core/interface/execution_interface.py +152 -0
- aiecs/core/interface/storage_interface.py +171 -0
- aiecs/domain/__init__.py +289 -0
- aiecs/domain/agent/__init__.py +189 -0
- aiecs/domain/agent/base_agent.py +697 -0
- aiecs/domain/agent/exceptions.py +103 -0
- aiecs/domain/agent/graph_aware_mixin.py +559 -0
- aiecs/domain/agent/hybrid_agent.py +490 -0
- aiecs/domain/agent/integration/__init__.py +26 -0
- aiecs/domain/agent/integration/context_compressor.py +222 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +252 -0
- aiecs/domain/agent/integration/retry_policy.py +219 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +646 -0
- aiecs/domain/agent/lifecycle.py +296 -0
- aiecs/domain/agent/llm_agent.py +300 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +197 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +160 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +90 -0
- aiecs/domain/agent/models.py +317 -0
- aiecs/domain/agent/observability.py +407 -0
- aiecs/domain/agent/persistence.py +289 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +161 -0
- aiecs/domain/agent/prompts/formatters.py +189 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +260 -0
- aiecs/domain/agent/tool_agent.py +257 -0
- aiecs/domain/agent/tools/__init__.py +12 -0
- aiecs/domain/agent/tools/schema_generator.py +221 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +477 -0
- aiecs/domain/community/analytics.py +481 -0
- aiecs/domain/community/collaborative_workflow.py +642 -0
- aiecs/domain/community/communication_hub.py +645 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +800 -0
- aiecs/domain/community/community_manager.py +813 -0
- aiecs/domain/community/decision_engine.py +879 -0
- aiecs/domain/community/exceptions.py +225 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +268 -0
- aiecs/domain/community/resource_manager.py +457 -0
- aiecs/domain/community/shared_context_manager.py +603 -0
- aiecs/domain/context/__init__.py +58 -0
- aiecs/domain/context/context_engine.py +989 -0
- aiecs/domain/context/conversation_models.py +354 -0
- aiecs/domain/context/graph_memory.py +467 -0
- aiecs/domain/execution/__init__.py +12 -0
- aiecs/domain/execution/model.py +57 -0
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +130 -0
- aiecs/domain/knowledge_graph/models/evidence.py +194 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +186 -0
- aiecs/domain/knowledge_graph/models/path.py +179 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +173 -0
- aiecs/domain/knowledge_graph/models/query.py +272 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +187 -0
- aiecs/domain/knowledge_graph/models/relation.py +136 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +135 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +271 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +155 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +171 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +496 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +205 -0
- aiecs/domain/task/__init__.py +13 -0
- aiecs/domain/task/dsl_processor.py +613 -0
- aiecs/domain/task/model.py +62 -0
- aiecs/domain/task/task_context.py +268 -0
- aiecs/infrastructure/__init__.py +24 -0
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +601 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +449 -0
- aiecs/infrastructure/graph_storage/cache.py +429 -0
- aiecs/infrastructure/graph_storage/distributed.py +226 -0
- aiecs/infrastructure/graph_storage/error_handling.py +390 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +306 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +514 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +483 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +410 -0
- aiecs/infrastructure/graph_storage/metrics.py +357 -0
- aiecs/infrastructure/graph_storage/migration.py +413 -0
- aiecs/infrastructure/graph_storage/pagination.py +471 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +466 -0
- aiecs/infrastructure/graph_storage/postgres.py +871 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +635 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +623 -0
- aiecs/infrastructure/graph_storage/streaming.py +495 -0
- aiecs/infrastructure/messaging/__init__.py +13 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +383 -0
- aiecs/infrastructure/messaging/websocket_manager.py +298 -0
- aiecs/infrastructure/monitoring/__init__.py +34 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +174 -0
- aiecs/infrastructure/monitoring/global_metrics_manager.py +213 -0
- aiecs/infrastructure/monitoring/structured_logger.py +48 -0
- aiecs/infrastructure/monitoring/tracing_manager.py +410 -0
- aiecs/infrastructure/persistence/__init__.py +24 -0
- aiecs/infrastructure/persistence/context_engine_client.py +187 -0
- aiecs/infrastructure/persistence/database_manager.py +333 -0
- aiecs/infrastructure/persistence/file_storage.py +754 -0
- aiecs/infrastructure/persistence/redis_client.py +220 -0
- aiecs/llm/__init__.py +86 -0
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/callbacks/custom_callbacks.py +264 -0
- aiecs/llm/client_factory.py +420 -0
- aiecs/llm/clients/__init__.py +33 -0
- aiecs/llm/clients/base_client.py +193 -0
- aiecs/llm/clients/googleai_client.py +181 -0
- aiecs/llm/clients/openai_client.py +131 -0
- aiecs/llm/clients/vertex_client.py +437 -0
- aiecs/llm/clients/xai_client.py +184 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +275 -0
- aiecs/llm/config/config_validator.py +236 -0
- aiecs/llm/config/model_config.py +151 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +91 -0
- aiecs/main.py +363 -0
- aiecs/scripts/__init__.py +3 -0
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +97 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/version_manager.py +215 -0
- aiecs/scripts/dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md +242 -0
- aiecs/scripts/dependance_check/README_DEPENDENCY_CHECKER.md +310 -0
- aiecs/scripts/dependance_check/__init__.py +17 -0
- aiecs/scripts/dependance_check/dependency_checker.py +938 -0
- aiecs/scripts/dependance_check/dependency_fixer.py +391 -0
- aiecs/scripts/dependance_check/download_nlp_data.py +396 -0
- aiecs/scripts/dependance_check/quick_dependency_check.py +270 -0
- aiecs/scripts/dependance_check/setup_nlp_data.sh +217 -0
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/README_WEASEL_PATCH.md +126 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.py +128 -0
- aiecs/scripts/dependance_patch/fix_weasel/fix_weasel_validator.sh +82 -0
- aiecs/scripts/dependance_patch/fix_weasel/patch_weasel_library.sh +188 -0
- aiecs/scripts/dependance_patch/fix_weasel/run_weasel_patch.sh +41 -0
- aiecs/scripts/tools_develop/README.md +449 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +259 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +422 -0
- aiecs/scripts/tools_develop/verify_tools.py +356 -0
- aiecs/tasks/__init__.py +1 -0
- aiecs/tasks/worker.py +172 -0
- aiecs/tools/__init__.py +299 -0
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +381 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +413 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +388 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +303 -0
- aiecs/tools/apisource/providers/__init__.py +115 -0
- aiecs/tools/apisource/providers/base.py +664 -0
- aiecs/tools/apisource/providers/census.py +401 -0
- aiecs/tools/apisource/providers/fred.py +564 -0
- aiecs/tools/apisource/providers/newsapi.py +412 -0
- aiecs/tools/apisource/providers/worldbank.py +357 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +375 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +391 -0
- aiecs/tools/apisource/tool.py +850 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +338 -0
- aiecs/tools/base_tool.py +201 -0
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +599 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2403 -0
- aiecs/tools/docs/content_insertion_tool.py +1333 -0
- aiecs/tools/docs/document_creator_tool.py +1317 -0
- aiecs/tools/docs/document_layout_tool.py +1166 -0
- aiecs/tools/docs/document_parser_tool.py +994 -0
- aiecs/tools/docs/document_writer_tool.py +1818 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +734 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +923 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +476 -0
- aiecs/tools/langchain_adapter.py +542 -0
- aiecs/tools/schema_generator.py +275 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +589 -0
- aiecs/tools/search_tool/cache.py +260 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +216 -0
- aiecs/tools/search_tool/core.py +749 -0
- aiecs/tools/search_tool/deduplicator.py +123 -0
- aiecs/tools/search_tool/error_handler.py +271 -0
- aiecs/tools/search_tool/metrics.py +371 -0
- aiecs/tools/search_tool/rate_limiter.py +178 -0
- aiecs/tools/search_tool/schemas.py +277 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +643 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +505 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +694 -0
- aiecs/tools/statistics/data_loader_tool.py +564 -0
- aiecs/tools/statistics/data_profiler_tool.py +658 -0
- aiecs/tools/statistics/data_transformer_tool.py +573 -0
- aiecs/tools/statistics/data_visualizer_tool.py +495 -0
- aiecs/tools/statistics/model_trainer_tool.py +487 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +459 -0
- aiecs/tools/task_tools/__init__.py +86 -0
- aiecs/tools/task_tools/chart_tool.py +732 -0
- aiecs/tools/task_tools/classfire_tool.py +922 -0
- aiecs/tools/task_tools/image_tool.py +447 -0
- aiecs/tools/task_tools/office_tool.py +684 -0
- aiecs/tools/task_tools/pandas_tool.py +635 -0
- aiecs/tools/task_tools/report_tool.py +635 -0
- aiecs/tools/task_tools/research_tool.py +392 -0
- aiecs/tools/task_tools/scraper_tool.py +715 -0
- aiecs/tools/task_tools/stats_tool.py +688 -0
- aiecs/tools/temp_file_manager.py +130 -0
- aiecs/tools/tool_executor/__init__.py +37 -0
- aiecs/tools/tool_executor/tool_executor.py +881 -0
- aiecs/utils/LLM_output_structor.py +445 -0
- aiecs/utils/__init__.py +34 -0
- aiecs/utils/base_callback.py +47 -0
- aiecs/utils/cache_provider.py +695 -0
- aiecs/utils/execution_utils.py +184 -0
- aiecs/utils/logging.py +1 -0
- aiecs/utils/prompt_loader.py +14 -0
- aiecs/utils/token_usage_repository.py +323 -0
- aiecs/ws/__init__.py +0 -0
- aiecs/ws/socket_server.py +52 -0
- aiecs-1.5.1.dist-info/METADATA +608 -0
- aiecs-1.5.1.dist-info/RECORD +302 -0
- aiecs-1.5.1.dist-info/WHEEL +5 -0
- aiecs-1.5.1.dist-info/entry_points.txt +10 -0
- aiecs-1.5.1.dist-info/licenses/LICENSE +225 -0
- 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)
|