aiecs 1.0.1__py3-none-any.whl → 1.7.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of aiecs might be problematic. Click here for more details.
- aiecs/__init__.py +13 -16
- aiecs/__main__.py +7 -7
- aiecs/aiecs_client.py +269 -75
- aiecs/application/executors/operation_executor.py +79 -54
- aiecs/application/knowledge_graph/__init__.py +7 -0
- aiecs/application/knowledge_graph/builder/__init__.py +37 -0
- aiecs/application/knowledge_graph/builder/data_quality.py +302 -0
- aiecs/application/knowledge_graph/builder/data_reshaping.py +293 -0
- aiecs/application/knowledge_graph/builder/document_builder.py +369 -0
- aiecs/application/knowledge_graph/builder/graph_builder.py +490 -0
- aiecs/application/knowledge_graph/builder/import_optimizer.py +396 -0
- aiecs/application/knowledge_graph/builder/schema_inference.py +462 -0
- aiecs/application/knowledge_graph/builder/schema_mapping.py +563 -0
- aiecs/application/knowledge_graph/builder/structured_pipeline.py +1384 -0
- aiecs/application/knowledge_graph/builder/text_chunker.py +317 -0
- aiecs/application/knowledge_graph/extractors/__init__.py +27 -0
- aiecs/application/knowledge_graph/extractors/base.py +98 -0
- aiecs/application/knowledge_graph/extractors/llm_entity_extractor.py +422 -0
- aiecs/application/knowledge_graph/extractors/llm_relation_extractor.py +347 -0
- aiecs/application/knowledge_graph/extractors/ner_entity_extractor.py +241 -0
- aiecs/application/knowledge_graph/fusion/__init__.py +78 -0
- aiecs/application/knowledge_graph/fusion/ab_testing.py +395 -0
- aiecs/application/knowledge_graph/fusion/abbreviation_expander.py +327 -0
- aiecs/application/knowledge_graph/fusion/alias_index.py +597 -0
- aiecs/application/knowledge_graph/fusion/alias_matcher.py +384 -0
- aiecs/application/knowledge_graph/fusion/cache_coordinator.py +343 -0
- aiecs/application/knowledge_graph/fusion/entity_deduplicator.py +433 -0
- aiecs/application/knowledge_graph/fusion/entity_linker.py +511 -0
- aiecs/application/knowledge_graph/fusion/evaluation_dataset.py +240 -0
- aiecs/application/knowledge_graph/fusion/knowledge_fusion.py +632 -0
- aiecs/application/knowledge_graph/fusion/matching_config.py +489 -0
- aiecs/application/knowledge_graph/fusion/name_normalizer.py +352 -0
- aiecs/application/knowledge_graph/fusion/relation_deduplicator.py +183 -0
- aiecs/application/knowledge_graph/fusion/semantic_name_matcher.py +464 -0
- aiecs/application/knowledge_graph/fusion/similarity_pipeline.py +534 -0
- aiecs/application/knowledge_graph/pattern_matching/__init__.py +21 -0
- aiecs/application/knowledge_graph/pattern_matching/pattern_matcher.py +342 -0
- aiecs/application/knowledge_graph/pattern_matching/query_executor.py +366 -0
- aiecs/application/knowledge_graph/profiling/__init__.py +12 -0
- aiecs/application/knowledge_graph/profiling/query_plan_visualizer.py +195 -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 +341 -0
- aiecs/application/knowledge_graph/reasoning/inference_engine.py +500 -0
- aiecs/application/knowledge_graph/reasoning/logic_form_parser.py +163 -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 +913 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/ast_validator.py +866 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/error_handler.py +475 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/parser.py +396 -0
- aiecs/application/knowledge_graph/reasoning/logic_parser/query_context.py +208 -0
- aiecs/application/knowledge_graph/reasoning/logic_query_integration.py +170 -0
- aiecs/application/knowledge_graph/reasoning/query_planner.py +855 -0
- aiecs/application/knowledge_graph/reasoning/reasoning_engine.py +518 -0
- aiecs/application/knowledge_graph/retrieval/__init__.py +27 -0
- aiecs/application/knowledge_graph/retrieval/query_intent_classifier.py +211 -0
- aiecs/application/knowledge_graph/retrieval/retrieval_strategies.py +592 -0
- aiecs/application/knowledge_graph/retrieval/strategy_types.py +23 -0
- aiecs/application/knowledge_graph/search/__init__.py +59 -0
- aiecs/application/knowledge_graph/search/hybrid_search.py +457 -0
- aiecs/application/knowledge_graph/search/reranker.py +293 -0
- aiecs/application/knowledge_graph/search/reranker_strategies.py +535 -0
- aiecs/application/knowledge_graph/search/text_similarity.py +392 -0
- aiecs/application/knowledge_graph/traversal/__init__.py +15 -0
- aiecs/application/knowledge_graph/traversal/enhanced_traversal.py +305 -0
- aiecs/application/knowledge_graph/traversal/path_scorer.py +271 -0
- aiecs/application/knowledge_graph/validators/__init__.py +13 -0
- aiecs/application/knowledge_graph/validators/relation_validator.py +239 -0
- aiecs/application/knowledge_graph/visualization/__init__.py +11 -0
- aiecs/application/knowledge_graph/visualization/graph_visualizer.py +313 -0
- aiecs/common/__init__.py +9 -0
- aiecs/common/knowledge_graph/__init__.py +17 -0
- aiecs/common/knowledge_graph/runnable.py +471 -0
- aiecs/config/__init__.py +20 -5
- aiecs/config/config.py +762 -31
- aiecs/config/graph_config.py +131 -0
- aiecs/config/tool_config.py +435 -0
- aiecs/core/__init__.py +29 -13
- aiecs/core/interface/__init__.py +2 -2
- aiecs/core/interface/execution_interface.py +22 -22
- aiecs/core/interface/storage_interface.py +37 -88
- aiecs/core/registry/__init__.py +31 -0
- aiecs/core/registry/service_registry.py +92 -0
- aiecs/domain/__init__.py +270 -1
- aiecs/domain/agent/__init__.py +191 -0
- aiecs/domain/agent/base_agent.py +3949 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1731 -0
- aiecs/domain/agent/integration/__init__.py +29 -0
- aiecs/domain/agent/integration/context_compressor.py +216 -0
- aiecs/domain/agent/integration/context_engine_adapter.py +587 -0
- aiecs/domain/agent/integration/protocols.py +281 -0
- aiecs/domain/agent/integration/retry_policy.py +218 -0
- aiecs/domain/agent/integration/role_config.py +213 -0
- aiecs/domain/agent/knowledge_aware_agent.py +1892 -0
- aiecs/domain/agent/lifecycle.py +291 -0
- aiecs/domain/agent/llm_agent.py +692 -0
- aiecs/domain/agent/memory/__init__.py +12 -0
- aiecs/domain/agent/memory/conversation.py +1124 -0
- aiecs/domain/agent/migration/__init__.py +14 -0
- aiecs/domain/agent/migration/conversion.py +163 -0
- aiecs/domain/agent/migration/legacy_wrapper.py +86 -0
- aiecs/domain/agent/models.py +894 -0
- aiecs/domain/agent/observability.py +479 -0
- aiecs/domain/agent/persistence.py +449 -0
- aiecs/domain/agent/prompts/__init__.py +29 -0
- aiecs/domain/agent/prompts/builder.py +159 -0
- aiecs/domain/agent/prompts/formatters.py +187 -0
- aiecs/domain/agent/prompts/template.py +255 -0
- aiecs/domain/agent/registry.py +253 -0
- aiecs/domain/agent/tool_agent.py +444 -0
- aiecs/domain/agent/tools/__init__.py +15 -0
- aiecs/domain/agent/tools/schema_generator.py +377 -0
- aiecs/domain/community/__init__.py +155 -0
- aiecs/domain/community/agent_adapter.py +469 -0
- aiecs/domain/community/analytics.py +432 -0
- aiecs/domain/community/collaborative_workflow.py +648 -0
- aiecs/domain/community/communication_hub.py +634 -0
- aiecs/domain/community/community_builder.py +320 -0
- aiecs/domain/community/community_integration.py +796 -0
- aiecs/domain/community/community_manager.py +803 -0
- aiecs/domain/community/decision_engine.py +849 -0
- aiecs/domain/community/exceptions.py +231 -0
- aiecs/domain/community/models/__init__.py +33 -0
- aiecs/domain/community/models/community_models.py +234 -0
- aiecs/domain/community/resource_manager.py +461 -0
- aiecs/domain/community/shared_context_manager.py +589 -0
- aiecs/domain/context/__init__.py +40 -10
- aiecs/domain/context/context_engine.py +1910 -0
- aiecs/domain/context/conversation_models.py +87 -53
- aiecs/domain/context/graph_memory.py +582 -0
- aiecs/domain/execution/model.py +12 -4
- aiecs/domain/knowledge_graph/__init__.py +19 -0
- aiecs/domain/knowledge_graph/models/__init__.py +52 -0
- aiecs/domain/knowledge_graph/models/entity.py +148 -0
- aiecs/domain/knowledge_graph/models/evidence.py +178 -0
- aiecs/domain/knowledge_graph/models/inference_rule.py +184 -0
- aiecs/domain/knowledge_graph/models/path.py +171 -0
- aiecs/domain/knowledge_graph/models/path_pattern.py +171 -0
- aiecs/domain/knowledge_graph/models/query.py +261 -0
- aiecs/domain/knowledge_graph/models/query_plan.py +181 -0
- aiecs/domain/knowledge_graph/models/relation.py +202 -0
- aiecs/domain/knowledge_graph/schema/__init__.py +23 -0
- aiecs/domain/knowledge_graph/schema/entity_type.py +131 -0
- aiecs/domain/knowledge_graph/schema/graph_schema.py +253 -0
- aiecs/domain/knowledge_graph/schema/property_schema.py +143 -0
- aiecs/domain/knowledge_graph/schema/relation_type.py +163 -0
- aiecs/domain/knowledge_graph/schema/schema_manager.py +691 -0
- aiecs/domain/knowledge_graph/schema/type_enums.py +209 -0
- aiecs/domain/task/dsl_processor.py +172 -56
- aiecs/domain/task/model.py +20 -8
- aiecs/domain/task/task_context.py +27 -24
- aiecs/infrastructure/__init__.py +0 -2
- aiecs/infrastructure/graph_storage/__init__.py +11 -0
- aiecs/infrastructure/graph_storage/base.py +837 -0
- aiecs/infrastructure/graph_storage/batch_operations.py +458 -0
- aiecs/infrastructure/graph_storage/cache.py +424 -0
- aiecs/infrastructure/graph_storage/distributed.py +223 -0
- aiecs/infrastructure/graph_storage/error_handling.py +380 -0
- aiecs/infrastructure/graph_storage/graceful_degradation.py +294 -0
- aiecs/infrastructure/graph_storage/health_checks.py +378 -0
- aiecs/infrastructure/graph_storage/in_memory.py +1197 -0
- aiecs/infrastructure/graph_storage/index_optimization.py +446 -0
- aiecs/infrastructure/graph_storage/lazy_loading.py +431 -0
- aiecs/infrastructure/graph_storage/metrics.py +344 -0
- aiecs/infrastructure/graph_storage/migration.py +400 -0
- aiecs/infrastructure/graph_storage/pagination.py +483 -0
- aiecs/infrastructure/graph_storage/performance_monitoring.py +456 -0
- aiecs/infrastructure/graph_storage/postgres.py +1563 -0
- aiecs/infrastructure/graph_storage/property_storage.py +353 -0
- aiecs/infrastructure/graph_storage/protocols.py +76 -0
- aiecs/infrastructure/graph_storage/query_optimizer.py +642 -0
- aiecs/infrastructure/graph_storage/schema_cache.py +290 -0
- aiecs/infrastructure/graph_storage/sqlite.py +1373 -0
- aiecs/infrastructure/graph_storage/streaming.py +487 -0
- aiecs/infrastructure/graph_storage/tenant.py +412 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +92 -54
- aiecs/infrastructure/messaging/websocket_manager.py +51 -35
- aiecs/infrastructure/monitoring/__init__.py +22 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +45 -11
- aiecs/infrastructure/monitoring/global_metrics_manager.py +212 -0
- aiecs/infrastructure/monitoring/structured_logger.py +3 -7
- aiecs/infrastructure/monitoring/tracing_manager.py +63 -35
- aiecs/infrastructure/persistence/__init__.py +14 -1
- aiecs/infrastructure/persistence/context_engine_client.py +184 -0
- aiecs/infrastructure/persistence/database_manager.py +67 -43
- aiecs/infrastructure/persistence/file_storage.py +180 -103
- aiecs/infrastructure/persistence/redis_client.py +74 -21
- aiecs/llm/__init__.py +73 -25
- aiecs/llm/callbacks/__init__.py +11 -0
- aiecs/llm/{custom_callbacks.py → callbacks/custom_callbacks.py} +26 -19
- aiecs/llm/client_factory.py +230 -37
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +328 -0
- aiecs/llm/clients/google_function_calling_mixin.py +415 -0
- aiecs/llm/clients/googleai_client.py +314 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +1186 -0
- aiecs/llm/clients/xai_client.py +201 -0
- aiecs/llm/config/__init__.py +51 -0
- aiecs/llm/config/config_loader.py +272 -0
- aiecs/llm/config/config_validator.py +206 -0
- aiecs/llm/config/model_config.py +143 -0
- aiecs/llm/protocols.py +149 -0
- aiecs/llm/utils/__init__.py +10 -0
- aiecs/llm/utils/validate_config.py +89 -0
- aiecs/main.py +140 -121
- aiecs/scripts/aid/VERSION_MANAGEMENT.md +138 -0
- aiecs/scripts/aid/__init__.py +19 -0
- aiecs/scripts/aid/module_checker.py +499 -0
- aiecs/scripts/aid/version_manager.py +235 -0
- aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +1 -0
- aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +1 -0
- aiecs/scripts/dependance_check/__init__.py +15 -0
- aiecs/scripts/dependance_check/dependency_checker.py +1835 -0
- aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +192 -90
- aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +203 -71
- aiecs/scripts/dependance_patch/__init__.py +7 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +11 -0
- aiecs/scripts/{fix_weasel_validator.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +21 -14
- aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +1 -1
- aiecs/scripts/knowledge_graph/__init__.py +3 -0
- aiecs/scripts/knowledge_graph/run_threshold_experiments.py +212 -0
- aiecs/scripts/migrations/multi_tenancy/README.md +142 -0
- aiecs/scripts/tools_develop/README.md +671 -0
- aiecs/scripts/tools_develop/README_CONFIG_CHECKER.md +273 -0
- aiecs/scripts/tools_develop/TOOLS_CONFIG_GUIDE.md +1287 -0
- aiecs/scripts/tools_develop/TOOL_AUTO_DISCOVERY.md +234 -0
- aiecs/scripts/tools_develop/__init__.py +21 -0
- aiecs/scripts/tools_develop/check_all_tools_config.py +548 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +257 -0
- aiecs/scripts/tools_develop/pre-commit-schema-coverage.sh +66 -0
- aiecs/scripts/tools_develop/schema_coverage.py +511 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +475 -0
- aiecs/scripts/tools_develop/verify_executor_config_fix.py +98 -0
- aiecs/scripts/tools_develop/verify_tools.py +352 -0
- aiecs/tasks/__init__.py +0 -1
- aiecs/tasks/worker.py +115 -47
- aiecs/tools/__init__.py +194 -72
- aiecs/tools/apisource/__init__.py +99 -0
- aiecs/tools/apisource/intelligence/__init__.py +19 -0
- aiecs/tools/apisource/intelligence/data_fusion.py +632 -0
- aiecs/tools/apisource/intelligence/query_analyzer.py +417 -0
- aiecs/tools/apisource/intelligence/search_enhancer.py +385 -0
- aiecs/tools/apisource/monitoring/__init__.py +9 -0
- aiecs/tools/apisource/monitoring/metrics.py +330 -0
- aiecs/tools/apisource/providers/__init__.py +112 -0
- aiecs/tools/apisource/providers/base.py +671 -0
- aiecs/tools/apisource/providers/census.py +397 -0
- aiecs/tools/apisource/providers/fred.py +535 -0
- aiecs/tools/apisource/providers/newsapi.py +409 -0
- aiecs/tools/apisource/providers/worldbank.py +352 -0
- aiecs/tools/apisource/reliability/__init__.py +12 -0
- aiecs/tools/apisource/reliability/error_handler.py +363 -0
- aiecs/tools/apisource/reliability/fallback_strategy.py +376 -0
- aiecs/tools/apisource/tool.py +832 -0
- aiecs/tools/apisource/utils/__init__.py +9 -0
- aiecs/tools/apisource/utils/validators.py +334 -0
- aiecs/tools/base_tool.py +415 -21
- aiecs/tools/docs/__init__.py +121 -0
- aiecs/tools/docs/ai_document_orchestrator.py +607 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2350 -0
- aiecs/tools/docs/content_insertion_tool.py +1320 -0
- aiecs/tools/docs/document_creator_tool.py +1464 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1016 -0
- aiecs/tools/docs/document_writer_tool.py +2008 -0
- aiecs/tools/knowledge_graph/__init__.py +17 -0
- aiecs/tools/knowledge_graph/graph_reasoning_tool.py +807 -0
- aiecs/tools/knowledge_graph/graph_search_tool.py +944 -0
- aiecs/tools/knowledge_graph/kg_builder_tool.py +524 -0
- aiecs/tools/langchain_adapter.py +300 -138
- aiecs/tools/schema_generator.py +455 -0
- aiecs/tools/search_tool/__init__.py +100 -0
- aiecs/tools/search_tool/analyzers.py +581 -0
- aiecs/tools/search_tool/cache.py +264 -0
- aiecs/tools/search_tool/constants.py +128 -0
- aiecs/tools/search_tool/context.py +224 -0
- aiecs/tools/search_tool/core.py +778 -0
- aiecs/tools/search_tool/deduplicator.py +119 -0
- aiecs/tools/search_tool/error_handler.py +242 -0
- aiecs/tools/search_tool/metrics.py +343 -0
- aiecs/tools/search_tool/rate_limiter.py +172 -0
- aiecs/tools/search_tool/schemas.py +275 -0
- aiecs/tools/statistics/__init__.py +80 -0
- aiecs/tools/statistics/ai_data_analysis_orchestrator.py +646 -0
- aiecs/tools/statistics/ai_insight_generator_tool.py +508 -0
- aiecs/tools/statistics/ai_report_orchestrator_tool.py +684 -0
- aiecs/tools/statistics/data_loader_tool.py +555 -0
- aiecs/tools/statistics/data_profiler_tool.py +638 -0
- aiecs/tools/statistics/data_transformer_tool.py +580 -0
- aiecs/tools/statistics/data_visualizer_tool.py +498 -0
- aiecs/tools/statistics/model_trainer_tool.py +507 -0
- aiecs/tools/statistics/statistical_analyzer_tool.py +472 -0
- aiecs/tools/task_tools/__init__.py +49 -36
- aiecs/tools/task_tools/chart_tool.py +200 -184
- aiecs/tools/task_tools/classfire_tool.py +268 -267
- aiecs/tools/task_tools/image_tool.py +220 -141
- aiecs/tools/task_tools/office_tool.py +226 -146
- aiecs/tools/task_tools/pandas_tool.py +477 -121
- aiecs/tools/task_tools/report_tool.py +390 -142
- aiecs/tools/task_tools/research_tool.py +149 -79
- aiecs/tools/task_tools/scraper_tool.py +339 -145
- aiecs/tools/task_tools/stats_tool.py +448 -209
- aiecs/tools/temp_file_manager.py +26 -24
- aiecs/tools/tool_executor/__init__.py +18 -16
- aiecs/tools/tool_executor/tool_executor.py +364 -52
- aiecs/utils/LLM_output_structor.py +74 -48
- aiecs/utils/__init__.py +14 -3
- aiecs/utils/base_callback.py +0 -3
- aiecs/utils/cache_provider.py +696 -0
- aiecs/utils/execution_utils.py +50 -31
- aiecs/utils/prompt_loader.py +1 -0
- aiecs/utils/token_usage_repository.py +37 -11
- aiecs/ws/socket_server.py +14 -4
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/METADATA +52 -15
- aiecs-1.7.17.dist-info/RECORD +337 -0
- aiecs-1.7.17.dist-info/entry_points.txt +13 -0
- aiecs/config/registry.py +0 -19
- aiecs/domain/context/content_engine.py +0 -982
- aiecs/llm/base_client.py +0 -99
- aiecs/llm/openai_client.py +0 -125
- aiecs/llm/vertex_client.py +0 -186
- aiecs/llm/xai_client.py +0 -184
- aiecs/scripts/dependency_checker.py +0 -857
- aiecs/scripts/quick_dependency_check.py +0 -269
- aiecs/tools/task_tools/search_api.py +0 -7
- aiecs-1.0.1.dist-info/RECORD +0 -90
- aiecs-1.0.1.dist-info/entry_points.txt +0 -7
- /aiecs/scripts/{setup_nlp_data.sh → dependance_check/setup_nlp_data.sh} +0 -0
- /aiecs/scripts/{README_WEASEL_PATCH.md → dependance_patch/fix_weasel/README_WEASEL_PATCH.md} +0 -0
- /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
- /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.17.dist-info}/top_level.txt +0 -0
|
@@ -1,48 +1,54 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import asyncio
|
|
3
3
|
import functools
|
|
4
|
-
import hashlib
|
|
5
4
|
import inspect
|
|
6
|
-
import json
|
|
7
5
|
import logging
|
|
8
6
|
import threading
|
|
9
7
|
import time
|
|
10
8
|
from concurrent.futures import ThreadPoolExecutor
|
|
11
|
-
from typing import Any, Callable, Dict, List, Optional, Type, Union
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional, Type, Union
|
|
12
10
|
from contextlib import contextmanager
|
|
13
11
|
|
|
14
|
-
from cachetools import LRUCache
|
|
15
12
|
from aiecs.utils.execution_utils import ExecutionUtils
|
|
13
|
+
from aiecs.utils.cache_provider import ICacheProvider, LRUCacheProvider
|
|
16
14
|
import re
|
|
17
|
-
from pydantic import BaseModel, ValidationError
|
|
15
|
+
from pydantic import BaseModel, ValidationError
|
|
16
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
18
17
|
|
|
19
18
|
logger = logging.getLogger(__name__)
|
|
20
19
|
|
|
21
20
|
# Base exception hierarchy
|
|
21
|
+
|
|
22
|
+
|
|
22
23
|
class ToolExecutionError(Exception):
|
|
23
24
|
"""Base exception for all tool execution errors."""
|
|
24
|
-
|
|
25
|
+
|
|
25
26
|
|
|
26
27
|
class InputValidationError(ToolExecutionError):
|
|
27
28
|
"""Error in validating input parameters."""
|
|
28
|
-
|
|
29
|
+
|
|
29
30
|
|
|
30
31
|
class SecurityError(ToolExecutionError):
|
|
31
32
|
"""Security-related error."""
|
|
32
|
-
|
|
33
|
+
|
|
33
34
|
|
|
34
35
|
class OperationError(ToolExecutionError):
|
|
35
36
|
"""Error during operation execution."""
|
|
36
|
-
|
|
37
|
+
|
|
37
38
|
|
|
38
39
|
class TimeoutError(ToolExecutionError):
|
|
39
40
|
"""Operation timed out."""
|
|
40
|
-
|
|
41
|
+
|
|
41
42
|
|
|
42
43
|
# Configuration for the executor
|
|
43
|
-
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ExecutorConfig(BaseSettings):
|
|
44
47
|
"""
|
|
45
48
|
Configuration for the ToolExecutor.
|
|
49
|
+
|
|
50
|
+
Automatically reads from environment variables with TOOL_EXECUTOR_ prefix.
|
|
51
|
+
Example: TOOL_EXECUTOR_MAX_WORKERS -> max_workers
|
|
46
52
|
|
|
47
53
|
Attributes:
|
|
48
54
|
enable_cache (bool): Enable caching of operation results.
|
|
@@ -58,7 +64,14 @@ class ExecutorConfig(BaseModel):
|
|
|
58
64
|
retry_attempts (int): Number of retry attempts for transient errors.
|
|
59
65
|
retry_backoff (float): Backoff factor for retries.
|
|
60
66
|
timeout (int): Timeout for operations in seconds.
|
|
67
|
+
enable_dual_cache (bool): Enable dual-layer caching (L1: LRU + L2: Redis).
|
|
68
|
+
enable_redis_cache (bool): Enable Redis as L2 cache (requires enable_dual_cache=True).
|
|
69
|
+
redis_cache_ttl (int): Redis cache TTL in seconds (for L2 cache).
|
|
70
|
+
l1_cache_ttl (int): L1 cache TTL in seconds (for dual-layer cache).
|
|
61
71
|
"""
|
|
72
|
+
|
|
73
|
+
model_config = SettingsConfigDict(env_prefix="TOOL_EXECUTOR_")
|
|
74
|
+
|
|
62
75
|
enable_cache: bool = True
|
|
63
76
|
cache_size: int = 100
|
|
64
77
|
cache_ttl: int = 3600
|
|
@@ -73,14 +86,22 @@ class ExecutorConfig(BaseModel):
|
|
|
73
86
|
retry_backoff: float = 1.0
|
|
74
87
|
timeout: int = 30
|
|
75
88
|
|
|
76
|
-
|
|
89
|
+
# Dual-layer cache configuration
|
|
90
|
+
enable_dual_cache: bool = False
|
|
91
|
+
enable_redis_cache: bool = False
|
|
92
|
+
redis_cache_ttl: int = 86400 # 1 day
|
|
93
|
+
l1_cache_ttl: int = 300 # 5 minutes
|
|
94
|
+
|
|
77
95
|
|
|
78
96
|
# Metrics counter
|
|
79
|
-
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class ToolExecutorStats:
|
|
80
100
|
"""
|
|
81
|
-
Tracks executor performance
|
|
101
|
+
Tracks tool executor performance statistics.
|
|
82
102
|
"""
|
|
83
|
-
|
|
103
|
+
|
|
104
|
+
def __init__(self) -> None:
|
|
84
105
|
self.requests: int = 0
|
|
85
106
|
self.failures: int = 0
|
|
86
107
|
self.cache_hits: int = 0
|
|
@@ -98,13 +119,16 @@ class ExecutorMetrics:
|
|
|
98
119
|
|
|
99
120
|
def to_dict(self) -> Dict[str, Any]:
|
|
100
121
|
return {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
122
|
+
"requests": self.requests,
|
|
123
|
+
"failures": self.failures,
|
|
124
|
+
"cache_hits": self.cache_hits,
|
|
125
|
+
"avg_processing_time": (sum(self.processing_times) / len(self.processing_times) if self.processing_times else 0.0),
|
|
105
126
|
}
|
|
106
127
|
|
|
128
|
+
|
|
107
129
|
# Decorators for tool methods
|
|
130
|
+
|
|
131
|
+
|
|
108
132
|
def validate_input(schema_class: Type[BaseModel]) -> Callable:
|
|
109
133
|
"""
|
|
110
134
|
Decorator to validate input using a Pydantic schema.
|
|
@@ -118,6 +142,7 @@ def validate_input(schema_class: Type[BaseModel]) -> Callable:
|
|
|
118
142
|
Raises:
|
|
119
143
|
InputValidationError: If input validation fails.
|
|
120
144
|
"""
|
|
145
|
+
|
|
121
146
|
def decorator(func: Callable) -> Callable:
|
|
122
147
|
@functools.wraps(func)
|
|
123
148
|
def wrapper(self, *args, **kwargs):
|
|
@@ -127,9 +152,12 @@ def validate_input(schema_class: Type[BaseModel]) -> Callable:
|
|
|
127
152
|
return func(self, **validated_kwargs)
|
|
128
153
|
except ValidationError as e:
|
|
129
154
|
raise InputValidationError(f"Invalid input parameters: {e}")
|
|
155
|
+
|
|
130
156
|
return wrapper
|
|
157
|
+
|
|
131
158
|
return decorator
|
|
132
159
|
|
|
160
|
+
|
|
133
161
|
def cache_result(ttl: Optional[int] = None) -> Callable:
|
|
134
162
|
"""
|
|
135
163
|
Decorator to cache function results with optional TTL.
|
|
@@ -140,10 +168,11 @@ def cache_result(ttl: Optional[int] = None) -> Callable:
|
|
|
140
168
|
Returns:
|
|
141
169
|
Callable: Decorated function with caching.
|
|
142
170
|
"""
|
|
171
|
+
|
|
143
172
|
def decorator(func: Callable) -> Callable:
|
|
144
173
|
@functools.wraps(func)
|
|
145
174
|
def wrapper(self, *args, **kwargs):
|
|
146
|
-
if not hasattr(self,
|
|
175
|
+
if not hasattr(self, "_executor") or not self._executor.config.enable_cache:
|
|
147
176
|
return func(self, *args, **kwargs)
|
|
148
177
|
cache_key = self._executor._get_cache_key(func.__name__, args, kwargs)
|
|
149
178
|
result = self._executor._get_from_cache(cache_key)
|
|
@@ -154,9 +183,100 @@ def cache_result(ttl: Optional[int] = None) -> Callable:
|
|
|
154
183
|
result = func(self, *args, **kwargs)
|
|
155
184
|
self._executor._add_to_cache(cache_key, result, ttl)
|
|
156
185
|
return result
|
|
186
|
+
|
|
187
|
+
return wrapper
|
|
188
|
+
|
|
189
|
+
return decorator
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def cache_result_with_strategy(
|
|
193
|
+
ttl_strategy: Optional[Union[int, Callable]] = None,
|
|
194
|
+
) -> Callable:
|
|
195
|
+
"""
|
|
196
|
+
Decorator to cache function results with flexible TTL strategy.
|
|
197
|
+
|
|
198
|
+
Supports multiple TTL strategy types:
|
|
199
|
+
1. Fixed TTL (int): Static TTL in seconds
|
|
200
|
+
2. Callable strategy: Function that calculates TTL based on result and context
|
|
201
|
+
3. None: Use default TTL from executor config
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
ttl_strategy: TTL strategy, can be:
|
|
205
|
+
- int: Fixed TTL in seconds
|
|
206
|
+
- Callable[[Any, tuple, dict], int]: Function(result, args, kwargs) -> ttl_seconds
|
|
207
|
+
- None: Use default TTL
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Callable: Decorated function with intelligent caching.
|
|
211
|
+
|
|
212
|
+
Example:
|
|
213
|
+
# Fixed TTL
|
|
214
|
+
@cache_result_with_strategy(ttl_strategy=3600)
|
|
215
|
+
def simple_operation(self, data):
|
|
216
|
+
return process(data)
|
|
217
|
+
|
|
218
|
+
# Dynamic TTL based on result
|
|
219
|
+
def calculate_ttl(result, args, kwargs):
|
|
220
|
+
if result.get('type') == 'static':
|
|
221
|
+
return 86400 # 1 day
|
|
222
|
+
return 3600 # 1 hour
|
|
223
|
+
|
|
224
|
+
@cache_result_with_strategy(ttl_strategy=calculate_ttl)
|
|
225
|
+
def smart_operation(self, query):
|
|
226
|
+
return search(query)
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
def decorator(func: Callable) -> Callable:
|
|
230
|
+
@functools.wraps(func)
|
|
231
|
+
def wrapper(self, *args, **kwargs):
|
|
232
|
+
if not hasattr(self, "_executor") or not self._executor.config.enable_cache:
|
|
233
|
+
return func(self, *args, **kwargs)
|
|
234
|
+
|
|
235
|
+
# Generate cache key
|
|
236
|
+
cache_key = self._executor._get_cache_key(func.__name__, args, kwargs)
|
|
237
|
+
|
|
238
|
+
# Check cache
|
|
239
|
+
cached = self._executor._get_from_cache(cache_key)
|
|
240
|
+
if cached is not None:
|
|
241
|
+
logger.debug(f"Cache hit for {func.__name__}")
|
|
242
|
+
self._executor._metrics.record_cache_hit()
|
|
243
|
+
return cached
|
|
244
|
+
|
|
245
|
+
# Execute function
|
|
246
|
+
result = func(self, *args, **kwargs)
|
|
247
|
+
|
|
248
|
+
# Calculate TTL based on strategy
|
|
249
|
+
# Support both regular callables and lambdas that need self
|
|
250
|
+
if callable(ttl_strategy):
|
|
251
|
+
try:
|
|
252
|
+
# Try calling with self first (for lambda self, result,
|
|
253
|
+
# args, kwargs)
|
|
254
|
+
import inspect
|
|
255
|
+
|
|
256
|
+
sig = inspect.signature(ttl_strategy)
|
|
257
|
+
if len(sig.parameters) == 4: # self, result, args, kwargs
|
|
258
|
+
ttl = ttl_strategy(self, result, args, kwargs)
|
|
259
|
+
else: # result, args, kwargs
|
|
260
|
+
ttl = ttl_strategy(result, args, kwargs)
|
|
261
|
+
|
|
262
|
+
if not isinstance(ttl, int) or ttl < 0:
|
|
263
|
+
logger.warning(f"TTL strategy returned invalid value: {ttl}. " f"Expected positive integer. Using default TTL.")
|
|
264
|
+
ttl = None
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.error(f"Error calculating TTL from strategy: {e}. Using default TTL.")
|
|
267
|
+
ttl = None
|
|
268
|
+
else:
|
|
269
|
+
ttl = self._executor._calculate_ttl_from_strategy(ttl_strategy, result, args, kwargs)
|
|
270
|
+
|
|
271
|
+
# Cache with calculated TTL
|
|
272
|
+
self._executor._add_to_cache(cache_key, result, ttl)
|
|
273
|
+
return result
|
|
274
|
+
|
|
157
275
|
return wrapper
|
|
276
|
+
|
|
158
277
|
return decorator
|
|
159
278
|
|
|
279
|
+
|
|
160
280
|
def run_in_executor(func: Callable) -> Callable:
|
|
161
281
|
"""
|
|
162
282
|
Decorator to run a synchronous function in the thread pool executor.
|
|
@@ -167,17 +287,20 @@ def run_in_executor(func: Callable) -> Callable:
|
|
|
167
287
|
Returns:
|
|
168
288
|
Callable: Async wrapper for the function.
|
|
169
289
|
"""
|
|
290
|
+
|
|
170
291
|
@functools.wraps(func)
|
|
171
292
|
async def wrapper(self, *args, **kwargs):
|
|
172
|
-
if not hasattr(self,
|
|
293
|
+
if not hasattr(self, "_executor"):
|
|
173
294
|
return await func(self, *args, **kwargs)
|
|
174
295
|
loop = asyncio.get_event_loop()
|
|
175
296
|
return await loop.run_in_executor(
|
|
176
297
|
self._executor._thread_pool,
|
|
177
|
-
functools.partial(func, self, *args, **kwargs)
|
|
298
|
+
functools.partial(func, self, *args, **kwargs),
|
|
178
299
|
)
|
|
300
|
+
|
|
179
301
|
return wrapper
|
|
180
302
|
|
|
303
|
+
|
|
181
304
|
def measure_execution_time(func: Callable) -> Callable:
|
|
182
305
|
"""
|
|
183
306
|
Decorator to measure and log execution time.
|
|
@@ -188,9 +311,10 @@ def measure_execution_time(func: Callable) -> Callable:
|
|
|
188
311
|
Returns:
|
|
189
312
|
Callable: Decorated function with timing.
|
|
190
313
|
"""
|
|
314
|
+
|
|
191
315
|
@functools.wraps(func)
|
|
192
316
|
def wrapper(self, *args, **kwargs):
|
|
193
|
-
if not hasattr(self,
|
|
317
|
+
if not hasattr(self, "_executor") or not self._executor.config.log_execution_time:
|
|
194
318
|
return func(self, *args, **kwargs)
|
|
195
319
|
start_time = time.time()
|
|
196
320
|
try:
|
|
@@ -202,8 +326,10 @@ def measure_execution_time(func: Callable) -> Callable:
|
|
|
202
326
|
execution_time = time.time() - start_time
|
|
203
327
|
logger.error(f"{func.__name__} failed after {execution_time:.4f} seconds: {e}")
|
|
204
328
|
raise
|
|
329
|
+
|
|
205
330
|
return wrapper
|
|
206
331
|
|
|
332
|
+
|
|
207
333
|
def sanitize_input(func: Callable) -> Callable:
|
|
208
334
|
"""
|
|
209
335
|
Decorator to sanitize input parameters for security.
|
|
@@ -214,18 +340,21 @@ def sanitize_input(func: Callable) -> Callable:
|
|
|
214
340
|
Returns:
|
|
215
341
|
Callable: Decorated function with sanitized inputs.
|
|
216
342
|
"""
|
|
343
|
+
|
|
217
344
|
@functools.wraps(func)
|
|
218
345
|
def wrapper(self, *args, **kwargs):
|
|
219
|
-
if not hasattr(self,
|
|
346
|
+
if not hasattr(self, "_executor") or not self._executor.config.enable_security_checks:
|
|
220
347
|
return func(self, *args, **kwargs)
|
|
221
348
|
sanitized_kwargs = {}
|
|
222
349
|
for k, v in kwargs.items():
|
|
223
|
-
if isinstance(v, str) and re.search(r
|
|
350
|
+
if isinstance(v, str) and re.search(r"(\bSELECT\b|\bINSERT\b|--|;|/\*)", v, re.IGNORECASE):
|
|
224
351
|
raise SecurityError(f"Input parameter '{k}' contains potentially malicious content")
|
|
225
352
|
sanitized_kwargs[k] = v
|
|
226
353
|
return func(self, *args, **sanitized_kwargs)
|
|
354
|
+
|
|
227
355
|
return wrapper
|
|
228
356
|
|
|
357
|
+
|
|
229
358
|
class ToolExecutor:
|
|
230
359
|
"""
|
|
231
360
|
Centralized executor for tool operations, handling:
|
|
@@ -240,12 +369,18 @@ class ToolExecutor:
|
|
|
240
369
|
executor = ToolExecutor(config={'max_workers': 8})
|
|
241
370
|
result = executor.execute(tool_instance, 'operation_name', param1='value')
|
|
242
371
|
"""
|
|
243
|
-
|
|
372
|
+
|
|
373
|
+
def __init__(
|
|
374
|
+
self,
|
|
375
|
+
config: Optional[Dict[str, Any]] = None,
|
|
376
|
+
cache_provider: Optional[ICacheProvider] = None,
|
|
377
|
+
):
|
|
244
378
|
"""
|
|
245
379
|
Initialize the executor with optional configuration.
|
|
246
380
|
|
|
247
381
|
Args:
|
|
248
382
|
config (Dict[str, Any], optional): Configuration overrides for ExecutorConfig.
|
|
383
|
+
cache_provider (ICacheProvider, optional): Custom cache provider. If None, uses default based on config.
|
|
249
384
|
|
|
250
385
|
Raises:
|
|
251
386
|
ValueError: If config is invalid.
|
|
@@ -253,18 +388,81 @@ class ToolExecutor:
|
|
|
253
388
|
self.config = ExecutorConfig(**(config or {}))
|
|
254
389
|
logging.basicConfig(
|
|
255
390
|
level=getattr(logging, self.config.log_level),
|
|
256
|
-
format=
|
|
391
|
+
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
257
392
|
)
|
|
258
393
|
self._thread_pool = ThreadPoolExecutor(max_workers=max(os.cpu_count() or 4, self.config.max_workers))
|
|
259
394
|
self._locks: Dict[str, threading.Lock] = {}
|
|
260
|
-
self._metrics =
|
|
395
|
+
self._metrics = ToolExecutorStats()
|
|
261
396
|
self.execution_utils = ExecutionUtils(
|
|
262
397
|
cache_size=self.config.cache_size,
|
|
263
398
|
cache_ttl=self.config.cache_ttl,
|
|
264
399
|
retry_attempts=self.config.retry_attempts,
|
|
265
|
-
retry_backoff=self.config.retry_backoff
|
|
400
|
+
retry_backoff=self.config.retry_backoff,
|
|
266
401
|
)
|
|
267
402
|
|
|
403
|
+
# Support pluggable cache provider
|
|
404
|
+
if cache_provider is not None:
|
|
405
|
+
# User provided custom cache provider
|
|
406
|
+
self.cache_provider = cache_provider
|
|
407
|
+
logger.info(f"Using custom cache provider: {cache_provider.__class__.__name__}")
|
|
408
|
+
elif self.config.enable_dual_cache and self.config.enable_redis_cache:
|
|
409
|
+
# Enable dual-layer cache (L1: LRU + L2: Redis)
|
|
410
|
+
self.cache_provider = self._initialize_dual_cache()
|
|
411
|
+
else:
|
|
412
|
+
# Default: use LRUCacheProvider wrapping ExecutionUtils
|
|
413
|
+
self.cache_provider = LRUCacheProvider(self.execution_utils)
|
|
414
|
+
logger.debug("Using default LRUCacheProvider")
|
|
415
|
+
|
|
416
|
+
def _initialize_dual_cache(self) -> ICacheProvider:
|
|
417
|
+
"""
|
|
418
|
+
Initialize dual-layer cache (L1: LRU + L2: Redis).
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
DualLayerCacheProvider instance or fallback to LRUCacheProvider
|
|
422
|
+
"""
|
|
423
|
+
try:
|
|
424
|
+
from aiecs.utils.cache_provider import (
|
|
425
|
+
DualLayerCacheProvider,
|
|
426
|
+
RedisCacheProvider,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
# Create L1 cache (LRU)
|
|
430
|
+
l1_cache = LRUCacheProvider(self.execution_utils)
|
|
431
|
+
|
|
432
|
+
# Create L2 cache (Redis) - this requires async initialization
|
|
433
|
+
# We'll use a lazy initialization approach
|
|
434
|
+
try:
|
|
435
|
+
# Try to get global Redis client synchronously
|
|
436
|
+
# Note: This assumes Redis client is already initialized
|
|
437
|
+
from aiecs.infrastructure.persistence import redis_client
|
|
438
|
+
|
|
439
|
+
if redis_client is not None:
|
|
440
|
+
l2_cache = RedisCacheProvider(
|
|
441
|
+
redis_client,
|
|
442
|
+
prefix="tool_executor:",
|
|
443
|
+
default_ttl=self.config.redis_cache_ttl,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
dual_cache = DualLayerCacheProvider(
|
|
447
|
+
l1_provider=l1_cache,
|
|
448
|
+
l2_provider=l2_cache,
|
|
449
|
+
l1_ttl=self.config.l1_cache_ttl,
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
logger.info("Dual-layer cache enabled (L1: LRU + L2: Redis)")
|
|
453
|
+
return dual_cache
|
|
454
|
+
else:
|
|
455
|
+
logger.warning("Redis client not initialized, falling back to LRU cache")
|
|
456
|
+
return l1_cache
|
|
457
|
+
|
|
458
|
+
except ImportError:
|
|
459
|
+
logger.warning("Redis client not available, falling back to LRU cache")
|
|
460
|
+
return l1_cache
|
|
461
|
+
|
|
462
|
+
except Exception as e:
|
|
463
|
+
logger.warning(f"Failed to initialize dual-layer cache: {e}, falling back to LRU")
|
|
464
|
+
return LRUCacheProvider(self.execution_utils)
|
|
465
|
+
|
|
268
466
|
def _get_cache_key(self, func_name: str, args: tuple, kwargs: Dict[str, Any]) -> str:
|
|
269
467
|
"""
|
|
270
468
|
Generate a context-aware cache key from function name, user ID, task ID, and arguments.
|
|
@@ -281,9 +479,64 @@ class ToolExecutor:
|
|
|
281
479
|
task_id = kwargs.get("task_id", "none")
|
|
282
480
|
return self.execution_utils.generate_cache_key(func_name, user_id, task_id, args, kwargs)
|
|
283
481
|
|
|
482
|
+
def _calculate_ttl_from_strategy(
|
|
483
|
+
self,
|
|
484
|
+
ttl_strategy: Optional[Union[int, Callable]],
|
|
485
|
+
result: Any,
|
|
486
|
+
args: tuple,
|
|
487
|
+
kwargs: Dict[str, Any],
|
|
488
|
+
) -> Optional[int]:
|
|
489
|
+
"""
|
|
490
|
+
Calculate TTL based on the provided strategy.
|
|
491
|
+
|
|
492
|
+
Supports multiple strategy types:
|
|
493
|
+
1. None: Use default TTL from config
|
|
494
|
+
2. int: Fixed TTL in seconds
|
|
495
|
+
3. Callable: Dynamic TTL calculation function
|
|
496
|
+
|
|
497
|
+
Args:
|
|
498
|
+
ttl_strategy: TTL strategy (None, int, or Callable)
|
|
499
|
+
result: Function execution result
|
|
500
|
+
args: Function positional arguments
|
|
501
|
+
kwargs: Function keyword arguments
|
|
502
|
+
|
|
503
|
+
Returns:
|
|
504
|
+
Optional[int]: Calculated TTL in seconds, or None for default
|
|
505
|
+
|
|
506
|
+
Example:
|
|
507
|
+
# Strategy function signature
|
|
508
|
+
def my_ttl_strategy(result: Any, args: tuple, kwargs: dict) -> int:
|
|
509
|
+
if result.get('type') == 'permanent':
|
|
510
|
+
return 86400 * 30 # 30 days
|
|
511
|
+
return 3600 # 1 hour
|
|
512
|
+
"""
|
|
513
|
+
# Case 1: No strategy - use default
|
|
514
|
+
if ttl_strategy is None:
|
|
515
|
+
return None
|
|
516
|
+
|
|
517
|
+
# Case 2: Fixed TTL (integer)
|
|
518
|
+
if isinstance(ttl_strategy, int):
|
|
519
|
+
return ttl_strategy
|
|
520
|
+
|
|
521
|
+
# Case 3: Callable strategy - dynamic calculation
|
|
522
|
+
if callable(ttl_strategy):
|
|
523
|
+
try:
|
|
524
|
+
calculated_ttl = ttl_strategy(result, args, kwargs)
|
|
525
|
+
if not isinstance(calculated_ttl, int) or calculated_ttl < 0:
|
|
526
|
+
logger.warning(f"TTL strategy returned invalid value: {calculated_ttl}. " f"Expected positive integer. Using default TTL.")
|
|
527
|
+
return None
|
|
528
|
+
return calculated_ttl
|
|
529
|
+
except Exception as e:
|
|
530
|
+
logger.error(f"Error calculating TTL from strategy: {e}. Using default TTL.")
|
|
531
|
+
return None
|
|
532
|
+
|
|
533
|
+
# Invalid strategy type
|
|
534
|
+
logger.warning(f"Invalid TTL strategy type: {type(ttl_strategy)}. " f"Expected None, int, or Callable. Using default TTL.")
|
|
535
|
+
return None
|
|
536
|
+
|
|
284
537
|
def _get_from_cache(self, cache_key: str) -> Optional[Any]:
|
|
285
538
|
"""
|
|
286
|
-
Get a result from cache if it exists and is not expired.
|
|
539
|
+
Get a result from cache if it exists and is not expired (synchronous).
|
|
287
540
|
|
|
288
541
|
Args:
|
|
289
542
|
cache_key (str): Cache key.
|
|
@@ -293,11 +546,11 @@ class ToolExecutor:
|
|
|
293
546
|
"""
|
|
294
547
|
if not self.config.enable_cache:
|
|
295
548
|
return None
|
|
296
|
-
return self.
|
|
549
|
+
return self.cache_provider.get(cache_key)
|
|
297
550
|
|
|
298
551
|
def _add_to_cache(self, cache_key: str, result: Any, ttl: Optional[int] = None) -> None:
|
|
299
552
|
"""
|
|
300
|
-
Add a result to the cache with optional TTL.
|
|
553
|
+
Add a result to the cache with optional TTL (synchronous).
|
|
301
554
|
|
|
302
555
|
Args:
|
|
303
556
|
cache_key (str): Cache key.
|
|
@@ -306,7 +559,46 @@ class ToolExecutor:
|
|
|
306
559
|
"""
|
|
307
560
|
if not self.config.enable_cache:
|
|
308
561
|
return
|
|
309
|
-
self.
|
|
562
|
+
self.cache_provider.set(cache_key, result, ttl)
|
|
563
|
+
|
|
564
|
+
async def _get_from_cache_async(self, cache_key: str) -> Optional[Any]:
|
|
565
|
+
"""
|
|
566
|
+
Get a result from cache if it exists and is not expired (asynchronous).
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
cache_key (str): Cache key.
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
Optional[Any]: Cached result or None.
|
|
573
|
+
"""
|
|
574
|
+
if not self.config.enable_cache:
|
|
575
|
+
return None
|
|
576
|
+
|
|
577
|
+
# Use async interface if available
|
|
578
|
+
if hasattr(self.cache_provider, "get_async"):
|
|
579
|
+
return await self.cache_provider.get_async(cache_key)
|
|
580
|
+
else:
|
|
581
|
+
# Fallback to sync interface
|
|
582
|
+
return self.cache_provider.get(cache_key)
|
|
583
|
+
|
|
584
|
+
async def _add_to_cache_async(self, cache_key: str, result: Any, ttl: Optional[int] = None) -> None:
|
|
585
|
+
"""
|
|
586
|
+
Add a result to the cache with optional TTL (asynchronous).
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
cache_key (str): Cache key.
|
|
590
|
+
result (Any): Result to cache.
|
|
591
|
+
ttl (Optional[int]): Time-to-live in seconds.
|
|
592
|
+
"""
|
|
593
|
+
if not self.config.enable_cache:
|
|
594
|
+
return
|
|
595
|
+
|
|
596
|
+
# Use async interface if available
|
|
597
|
+
if hasattr(self.cache_provider, "set_async"):
|
|
598
|
+
await self.cache_provider.set_async(cache_key, result, ttl)
|
|
599
|
+
else:
|
|
600
|
+
# Fallback to sync interface
|
|
601
|
+
self.cache_provider.set(cache_key, result, ttl)
|
|
310
602
|
|
|
311
603
|
def get_lock(self, resource_id: str) -> threading.Lock:
|
|
312
604
|
"""
|
|
@@ -379,8 +671,8 @@ class ToolExecutor:
|
|
|
379
671
|
SecurityError: If inputs contain malicious content.
|
|
380
672
|
"""
|
|
381
673
|
method = getattr(tool_instance, operation, None)
|
|
382
|
-
if not method or not callable(method) or operation.startswith(
|
|
383
|
-
available_ops = [m for m in dir(tool_instance) if not m.startswith(
|
|
674
|
+
if not method or not callable(method) or operation.startswith("_"):
|
|
675
|
+
available_ops = [m for m in dir(tool_instance) if not m.startswith("_") and callable(getattr(tool_instance, m))]
|
|
384
676
|
raise ToolExecutionError(f"Unsupported operation: {operation}. Available operations: {', '.join(available_ops)}")
|
|
385
677
|
logger.info(f"Executing {tool_instance.__class__.__name__}.{operation} with params: {kwargs}")
|
|
386
678
|
start_time = time.time()
|
|
@@ -388,7 +680,7 @@ class ToolExecutor:
|
|
|
388
680
|
# Sanitize inputs
|
|
389
681
|
if self.config.enable_security_checks:
|
|
390
682
|
for k, v in kwargs.items():
|
|
391
|
-
if isinstance(v, str) and re.search(r
|
|
683
|
+
if isinstance(v, str) and re.search(r"(\bSELECT\b|\bINSERT\b|--|;|/\*)", v, re.IGNORECASE):
|
|
392
684
|
raise SecurityError(f"Input parameter '{k}' contains potentially malicious content")
|
|
393
685
|
# Use cache if enabled
|
|
394
686
|
if self.config.enable_cache:
|
|
@@ -410,7 +702,10 @@ class ToolExecutor:
|
|
|
410
702
|
return result
|
|
411
703
|
except Exception as e:
|
|
412
704
|
self._metrics.record_failure()
|
|
413
|
-
logger.error(
|
|
705
|
+
logger.error(
|
|
706
|
+
f"Error executing {tool_instance.__class__.__name__}.{operation}: {str(e)}",
|
|
707
|
+
exc_info=True,
|
|
708
|
+
)
|
|
414
709
|
raise OperationError(f"Error executing {operation}: {str(e)}") from e
|
|
415
710
|
|
|
416
711
|
async def execute_async(self, tool_instance: Any, operation: str, **kwargs) -> Any:
|
|
@@ -431,8 +726,8 @@ class ToolExecutor:
|
|
|
431
726
|
SecurityError: If inputs contain malicious content.
|
|
432
727
|
"""
|
|
433
728
|
method = getattr(tool_instance, operation, None)
|
|
434
|
-
if not method or not callable(method) or operation.startswith(
|
|
435
|
-
available_ops = [m for m in dir(tool_instance) if not m.startswith(
|
|
729
|
+
if not method or not callable(method) or operation.startswith("_"):
|
|
730
|
+
available_ops = [m for m in dir(tool_instance) if not m.startswith("_") and callable(getattr(tool_instance, m))]
|
|
436
731
|
raise ToolExecutionError(f"Unsupported operation: {operation}. Available operations: {', '.join(available_ops)}")
|
|
437
732
|
is_async = inspect.iscoroutinefunction(method)
|
|
438
733
|
logger.info(f"Executing async {tool_instance.__class__.__name__}.{operation} with params: {kwargs}")
|
|
@@ -441,15 +736,15 @@ class ToolExecutor:
|
|
|
441
736
|
# Sanitize inputs
|
|
442
737
|
if self.config.enable_security_checks:
|
|
443
738
|
for k, v in kwargs.items():
|
|
444
|
-
if isinstance(v, str) and re.search(r
|
|
739
|
+
if isinstance(v, str) and re.search(r"(\bSELECT\b|\bINSERT\b|--|;|/\*)", v, re.IGNORECASE):
|
|
445
740
|
raise SecurityError(f"Input parameter '{k}' contains potentially malicious content")
|
|
446
|
-
# Use cache if enabled
|
|
741
|
+
# Use cache if enabled (async)
|
|
447
742
|
if self.config.enable_cache:
|
|
448
743
|
cache_key = self._get_cache_key(operation, (), kwargs)
|
|
449
|
-
cached_result = self.
|
|
744
|
+
cached_result = await self._get_from_cache_async(cache_key)
|
|
450
745
|
if cached_result is not None:
|
|
451
746
|
self._metrics.record_cache_hit()
|
|
452
|
-
logger.debug(f"Cache hit for {operation}")
|
|
747
|
+
logger.debug(f"Cache hit for {operation} (async)")
|
|
453
748
|
return cached_result
|
|
454
749
|
|
|
455
750
|
async def _execute():
|
|
@@ -457,18 +752,22 @@ class ToolExecutor:
|
|
|
457
752
|
return await method(**kwargs)
|
|
458
753
|
loop = asyncio.get_event_loop()
|
|
459
754
|
return await loop.run_in_executor(self._thread_pool, functools.partial(method, **kwargs))
|
|
755
|
+
|
|
460
756
|
result = await self._retry_operation(_execute)
|
|
461
757
|
self._metrics.record_request(time.time() - start_time)
|
|
462
758
|
if self.config.log_execution_time:
|
|
463
759
|
logger.info(f"{tool_instance.__class__.__name__}.{operation} executed in {time.time() - start_time:.4f} seconds")
|
|
464
760
|
|
|
465
|
-
# Cache result if enabled
|
|
761
|
+
# Cache result if enabled (async)
|
|
466
762
|
if self.config.enable_cache:
|
|
467
|
-
self.
|
|
763
|
+
await self._add_to_cache_async(cache_key, result)
|
|
468
764
|
return result
|
|
469
765
|
except Exception as e:
|
|
470
766
|
self._metrics.record_failure()
|
|
471
|
-
logger.error(
|
|
767
|
+
logger.error(
|
|
768
|
+
f"Error executing {tool_instance.__class__.__name__}.{operation}: {str(e)}",
|
|
769
|
+
exc_info=True,
|
|
770
|
+
)
|
|
472
771
|
raise OperationError(f"Error executing {operation}: {str(e)}") from e
|
|
473
772
|
|
|
474
773
|
async def execute_batch(self, tool_instance: Any, operations: List[Dict[str, Any]]) -> List[Any]:
|
|
@@ -488,8 +787,8 @@ class ToolExecutor:
|
|
|
488
787
|
"""
|
|
489
788
|
tasks = []
|
|
490
789
|
for op_data in operations:
|
|
491
|
-
op = op_data.get(
|
|
492
|
-
kwargs = op_data.get(
|
|
790
|
+
op = op_data.get("op")
|
|
791
|
+
kwargs = op_data.get("kwargs", {})
|
|
493
792
|
if not op:
|
|
494
793
|
raise InputValidationError("Operation name missing in batch request")
|
|
495
794
|
tasks.append(self.execute_async(tool_instance, op, **kwargs))
|
|
@@ -499,20 +798,33 @@ class ToolExecutor:
|
|
|
499
798
|
logger.error(f"Batch operation {operations[i]['op']} failed: {result}")
|
|
500
799
|
return results
|
|
501
800
|
|
|
502
|
-
|
|
801
|
+
|
|
802
|
+
# Singleton executor instance (for backward compatibility)
|
|
503
803
|
_default_executor = None
|
|
504
804
|
|
|
805
|
+
|
|
505
806
|
def get_executor(config: Optional[Dict[str, Any]] = None) -> ToolExecutor:
|
|
506
807
|
"""
|
|
507
|
-
Get or create
|
|
808
|
+
Get or create executor instance.
|
|
809
|
+
|
|
810
|
+
If config is provided, creates a new executor with that config.
|
|
811
|
+
If config is None, returns the default singleton executor.
|
|
508
812
|
|
|
509
813
|
Args:
|
|
510
814
|
config (Dict[str, Any], optional): Configuration overrides.
|
|
815
|
+
If provided, creates a new executor instance.
|
|
816
|
+
If None, returns the default singleton.
|
|
511
817
|
|
|
512
818
|
Returns:
|
|
513
|
-
ToolExecutor:
|
|
819
|
+
ToolExecutor: Executor instance.
|
|
514
820
|
"""
|
|
515
821
|
global _default_executor
|
|
822
|
+
|
|
823
|
+
# If config is provided, create a new executor with that config
|
|
824
|
+
if config is not None:
|
|
825
|
+
return ToolExecutor(config)
|
|
826
|
+
|
|
827
|
+
# Otherwise, return the default singleton
|
|
516
828
|
if _default_executor is None:
|
|
517
|
-
_default_executor = ToolExecutor(
|
|
829
|
+
_default_executor = ToolExecutor()
|
|
518
830
|
return _default_executor
|