aiecs 1.0.1__py3-none-any.whl → 1.7.6__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 +399 -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 +3870 -0
- aiecs/domain/agent/exceptions.py +99 -0
- aiecs/domain/agent/graph_aware_mixin.py +569 -0
- aiecs/domain/agent/hybrid_agent.py +1435 -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 +884 -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 +364 -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 +224 -36
- aiecs/llm/client_resolver.py +155 -0
- aiecs/llm/clients/__init__.py +38 -0
- aiecs/llm/clients/base_client.py +324 -0
- aiecs/llm/clients/google_function_calling_mixin.py +457 -0
- aiecs/llm/clients/googleai_client.py +241 -0
- aiecs/llm/clients/openai_client.py +158 -0
- aiecs/llm/clients/openai_compatible_mixin.py +367 -0
- aiecs/llm/clients/vertex_client.py +897 -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 +1323 -0
- aiecs/tools/docs/document_layout_tool.py +1160 -0
- aiecs/tools/docs/document_parser_tool.py +1011 -0
- aiecs/tools/docs/document_writer_tool.py +1829 -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 +175 -131
- 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.6.dist-info}/METADATA +52 -15
- aiecs-1.7.6.dist-info/RECORD +337 -0
- aiecs-1.7.6.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.6.dist-info}/WHEEL +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.1.dist-info → aiecs-1.7.6.dist-info}/top_level.txt +0 -0
aiecs/tools/base_tool.py
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
|
-
import asyncio
|
|
2
1
|
import inspect
|
|
3
2
|
import logging
|
|
4
|
-
from typing import Any, Dict, List, Optional, Type
|
|
3
|
+
from typing import Any, Dict, List, Optional, Type
|
|
5
4
|
|
|
6
5
|
from pydantic import BaseModel, ValidationError
|
|
7
6
|
import re
|
|
8
7
|
|
|
9
8
|
from aiecs.tools.tool_executor import (
|
|
10
|
-
ToolExecutor,
|
|
11
|
-
ToolExecutionError,
|
|
12
9
|
InputValidationError,
|
|
13
|
-
OperationError,
|
|
14
10
|
SecurityError,
|
|
15
11
|
get_executor,
|
|
16
|
-
|
|
17
|
-
cache_result,
|
|
18
|
-
run_in_executor,
|
|
19
|
-
measure_execution_time,
|
|
20
|
-
sanitize_input
|
|
12
|
+
ExecutorConfig,
|
|
21
13
|
)
|
|
14
|
+
from aiecs.config.tool_config import get_tool_config_loader
|
|
15
|
+
from aiecs.tools.schema_generator import generate_schema_from_method
|
|
22
16
|
|
|
23
17
|
logger = logging.getLogger(__name__)
|
|
24
18
|
|
|
19
|
+
|
|
25
20
|
class BaseTool:
|
|
26
21
|
"""
|
|
27
22
|
Base class for all tools, providing common functionality:
|
|
@@ -49,26 +44,156 @@ class BaseTool:
|
|
|
49
44
|
# Implementation
|
|
50
45
|
pass
|
|
51
46
|
"""
|
|
52
|
-
|
|
47
|
+
|
|
48
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None, tool_name: Optional[str] = None):
|
|
53
49
|
"""
|
|
54
50
|
Initialize the tool with optional configuration.
|
|
55
51
|
|
|
52
|
+
Configuration is automatically loaded from:
|
|
53
|
+
1. Explicit config dict (highest priority)
|
|
54
|
+
2. YAML config files (config/tools/{tool_name}.yaml or config/tools.yaml)
|
|
55
|
+
3. Environment variables (via dotenv from .env files)
|
|
56
|
+
4. Tool defaults (lowest priority)
|
|
57
|
+
|
|
56
58
|
Args:
|
|
57
|
-
config (Dict[str, Any], optional): Tool-specific configuration
|
|
59
|
+
config (Dict[str, Any], optional): Tool-specific configuration that overrides
|
|
60
|
+
all other sources. If None, configuration is loaded automatically.
|
|
61
|
+
tool_name (str, optional): Registered tool name. If None, uses class name.
|
|
58
62
|
|
|
59
63
|
Raises:
|
|
60
64
|
ValueError: If config is invalid.
|
|
65
|
+
ValidationError: If config validation fails (when Config class exists).
|
|
61
66
|
"""
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
# Detect Config class if it exists
|
|
68
|
+
config_class = self._detect_config_class()
|
|
69
|
+
|
|
70
|
+
# Determine tool name (for config file discovery)
|
|
71
|
+
if tool_name is None:
|
|
72
|
+
tool_name = self.__class__.__name__
|
|
73
|
+
|
|
74
|
+
# Load configuration using ToolConfigLoader
|
|
75
|
+
if config_class:
|
|
76
|
+
# Tool has Config class - use loader to load and validate config
|
|
77
|
+
loader = get_tool_config_loader()
|
|
78
|
+
try:
|
|
79
|
+
loaded_config = loader.load_tool_config(
|
|
80
|
+
tool_name=tool_name,
|
|
81
|
+
config_schema=config_class,
|
|
82
|
+
explicit_config=config,
|
|
83
|
+
)
|
|
84
|
+
# Instantiate Config class with loaded config
|
|
85
|
+
self._config_obj = config_class(**loaded_config)
|
|
86
|
+
self._config = loaded_config
|
|
87
|
+
except ValidationError as e:
|
|
88
|
+
logger.error(f"Configuration validation failed for {tool_name}: {e}")
|
|
89
|
+
raise
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.warning(f"Failed to load configuration for {tool_name}: {e}. Using defaults.")
|
|
92
|
+
# Fallback to explicit config or empty dict
|
|
93
|
+
self._config = config or {}
|
|
94
|
+
try:
|
|
95
|
+
self._config_obj = config_class(**self._config)
|
|
96
|
+
except Exception:
|
|
97
|
+
# If even defaults fail, create empty config object
|
|
98
|
+
self._config_obj = None
|
|
99
|
+
else:
|
|
100
|
+
# No Config class - backward compatibility mode
|
|
101
|
+
# Still try to load from YAML/env if config provided, otherwise use as-is
|
|
102
|
+
if config:
|
|
103
|
+
# Use explicit config as-is
|
|
104
|
+
self._config = config
|
|
105
|
+
else:
|
|
106
|
+
# Try to load from YAML/env even without Config class
|
|
107
|
+
loader = get_tool_config_loader()
|
|
108
|
+
try:
|
|
109
|
+
self._config = loader.load_tool_config(
|
|
110
|
+
tool_name=tool_name,
|
|
111
|
+
config_schema=None,
|
|
112
|
+
explicit_config=None,
|
|
113
|
+
)
|
|
114
|
+
except Exception as e:
|
|
115
|
+
logger.debug(f"Could not load config for {tool_name}: {e}. Using empty config.")
|
|
116
|
+
self._config = {}
|
|
117
|
+
self._config_obj = None
|
|
118
|
+
|
|
119
|
+
# Extract only executor-related config fields to avoid passing tool-specific
|
|
120
|
+
# fields (e.g., user_agent, temp_dir) to ExecutorConfig
|
|
121
|
+
executor_config = self._extract_executor_config(self._config)
|
|
122
|
+
self._executor = get_executor(executor_config)
|
|
64
123
|
self._schemas: Dict[str, Type[BaseModel]] = {}
|
|
65
124
|
self._async_methods: List[str] = []
|
|
125
|
+
# Schema coverage tracking
|
|
126
|
+
self._schema_coverage: Dict[str, Any] = {
|
|
127
|
+
"total_methods": 0,
|
|
128
|
+
"manual_schemas": 0,
|
|
129
|
+
"auto_generated_schemas": 0,
|
|
130
|
+
"missing_schemas": 0,
|
|
131
|
+
"schema_quality": {},
|
|
132
|
+
}
|
|
66
133
|
self._register_schemas()
|
|
67
134
|
self._register_async_methods()
|
|
135
|
+
# Log schema coverage after registration
|
|
136
|
+
self._log_schema_coverage()
|
|
137
|
+
|
|
138
|
+
def _extract_executor_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
|
|
139
|
+
"""
|
|
140
|
+
Extract only executor-related configuration fields from the full config.
|
|
141
|
+
|
|
142
|
+
This prevents tool-specific fields (e.g., user_agent, temp_dir) from being
|
|
143
|
+
passed to ExecutorConfig, which would cause validation issues or be silently
|
|
144
|
+
ignored.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
config (Dict[str, Any]): Full configuration dictionary.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Dict[str, Any]: Filtered configuration containing only ExecutorConfig fields.
|
|
151
|
+
"""
|
|
152
|
+
if not config:
|
|
153
|
+
return {}
|
|
154
|
+
|
|
155
|
+
# Get all valid field names from ExecutorConfig
|
|
156
|
+
executor_fields = set(ExecutorConfig.model_fields.keys())
|
|
157
|
+
|
|
158
|
+
# Filter config to only include executor-related fields
|
|
159
|
+
executor_config = {
|
|
160
|
+
key: value
|
|
161
|
+
for key, value in config.items()
|
|
162
|
+
if key in executor_fields
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return executor_config
|
|
166
|
+
|
|
167
|
+
def _detect_config_class(self) -> Optional[Type[BaseModel]]:
|
|
168
|
+
"""
|
|
169
|
+
Detect Config class in tool class hierarchy via introspection.
|
|
170
|
+
|
|
171
|
+
Looks for a class named 'Config' that inherits from BaseModel or BaseSettings.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Config class if found, None otherwise
|
|
175
|
+
"""
|
|
176
|
+
# Check current class and all base classes
|
|
177
|
+
for cls in [self.__class__] + list(self.__class__.__mro__):
|
|
178
|
+
if hasattr(cls, "Config"):
|
|
179
|
+
config_attr = getattr(cls, "Config")
|
|
180
|
+
# Check if Config is a class and inherits from BaseModel
|
|
181
|
+
if isinstance(config_attr, type):
|
|
182
|
+
# Import BaseSettings here to avoid circular imports
|
|
183
|
+
try:
|
|
184
|
+
from pydantic_settings import BaseSettings
|
|
185
|
+
if issubclass(config_attr, (BaseModel, BaseSettings)):
|
|
186
|
+
return config_attr
|
|
187
|
+
except ImportError:
|
|
188
|
+
# Fallback if pydantic_settings not available
|
|
189
|
+
if issubclass(config_attr, BaseModel):
|
|
190
|
+
return config_attr
|
|
191
|
+
return None
|
|
68
192
|
|
|
69
193
|
def _register_schemas(self) -> None:
|
|
70
194
|
"""
|
|
71
195
|
Register Pydantic schemas for operations by inspecting inner Schema classes.
|
|
196
|
+
Falls back to auto-generation when manual schemas are missing.
|
|
72
197
|
|
|
73
198
|
Example:
|
|
74
199
|
class MyTool(BaseTool):
|
|
@@ -76,13 +201,215 @@ class BaseTool:
|
|
|
76
201
|
path: str
|
|
77
202
|
def read(self, path: str):
|
|
78
203
|
pass
|
|
79
|
-
# Registers 'read' -> ReadSchema
|
|
204
|
+
# Registers 'read' -> ReadSchema (manual)
|
|
205
|
+
# Auto-generates schema for 'write' if WriteSchema doesn't exist
|
|
80
206
|
"""
|
|
207
|
+
# First pass: Register manual schemas
|
|
208
|
+
manual_schemas = {}
|
|
81
209
|
for attr_name in dir(self.__class__):
|
|
82
210
|
attr = getattr(self.__class__, attr_name)
|
|
83
|
-
if isinstance(attr, type) and issubclass(attr, BaseModel) and attr.__name__.endswith(
|
|
84
|
-
|
|
211
|
+
if isinstance(attr, type) and issubclass(attr, BaseModel) and attr.__name__.endswith("Schema"):
|
|
212
|
+
# Normalize schema name to operation name
|
|
213
|
+
# Handle Method_nameSchema -> method_name convention
|
|
214
|
+
schema_base_name = attr.__name__.replace("Schema", "")
|
|
215
|
+
# Convert CamelCase to snake_case, then to lowercase
|
|
216
|
+
# e.g., Read_csvSchema -> read_csv
|
|
217
|
+
op_name = self._normalize_schema_name_to_method(schema_base_name)
|
|
218
|
+
manual_schemas[op_name] = attr
|
|
85
219
|
self._schemas[op_name] = attr
|
|
220
|
+
self._schema_coverage["manual_schemas"] += 1
|
|
221
|
+
logger.debug(f"Registered manual schema {attr.__name__} -> {op_name}")
|
|
222
|
+
|
|
223
|
+
# Second pass: Auto-generate schemas for methods without manual schemas
|
|
224
|
+
public_methods = self._get_public_methods()
|
|
225
|
+
self._schema_coverage["total_methods"] = len(public_methods)
|
|
226
|
+
|
|
227
|
+
for method_name in public_methods:
|
|
228
|
+
# Skip if already has manual schema
|
|
229
|
+
if method_name in self._schemas:
|
|
230
|
+
continue
|
|
231
|
+
|
|
232
|
+
# Skip async wrappers (they share schemas with sync methods)
|
|
233
|
+
if method_name.endswith("_async"):
|
|
234
|
+
sync_method_name = method_name[:-6] # Remove "_async"
|
|
235
|
+
if sync_method_name in self._schemas:
|
|
236
|
+
self._schemas[method_name] = self._schemas[sync_method_name]
|
|
237
|
+
logger.debug(f"Reusing schema for async method {method_name} from {sync_method_name}")
|
|
238
|
+
continue
|
|
239
|
+
|
|
240
|
+
# Try to auto-generate schema
|
|
241
|
+
method = getattr(self.__class__, method_name)
|
|
242
|
+
if callable(method) and not isinstance(method, type):
|
|
243
|
+
try:
|
|
244
|
+
auto_schema = generate_schema_from_method(method, method_name)
|
|
245
|
+
if auto_schema:
|
|
246
|
+
self._schemas[method_name] = auto_schema
|
|
247
|
+
self._schema_coverage["auto_generated_schemas"] += 1
|
|
248
|
+
logger.info(f"Auto-generated schema for method {method_name} -> {auto_schema.__name__}")
|
|
249
|
+
else:
|
|
250
|
+
self._schema_coverage["missing_schemas"] += 1
|
|
251
|
+
logger.debug(f"No schema generated for method {method_name} (no parameters)")
|
|
252
|
+
except Exception as e:
|
|
253
|
+
self._schema_coverage["missing_schemas"] += 1
|
|
254
|
+
logger.warning(f"Failed to auto-generate schema for {method_name}: {e}")
|
|
255
|
+
|
|
256
|
+
def _normalize_schema_name_to_method(self, schema_base_name: str) -> str:
|
|
257
|
+
"""
|
|
258
|
+
Convert schema name to method name.
|
|
259
|
+
|
|
260
|
+
Handles conventions like:
|
|
261
|
+
- Read_csvSchema -> read_csv
|
|
262
|
+
- ReadCsvSchema -> readcsv (fallback, but should use Read_csvSchema)
|
|
263
|
+
- ReadSchema -> read
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
schema_base_name: Schema name without "Schema" suffix
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
Normalized method name
|
|
270
|
+
"""
|
|
271
|
+
# If name contains underscores, preserve them (e.g., Read_csv -> read_csv)
|
|
272
|
+
if "_" in schema_base_name:
|
|
273
|
+
# Convert first letter to lowercase, keep rest as-is
|
|
274
|
+
if schema_base_name:
|
|
275
|
+
return schema_base_name[0].lower() + schema_base_name[1:]
|
|
276
|
+
return schema_base_name.lower()
|
|
277
|
+
|
|
278
|
+
# Convert CamelCase to snake_case
|
|
279
|
+
# Insert underscore before uppercase letters (except first)
|
|
280
|
+
result = []
|
|
281
|
+
for i, char in enumerate(schema_base_name):
|
|
282
|
+
if char.isupper() and i > 0:
|
|
283
|
+
result.append("_")
|
|
284
|
+
result.append(char.lower())
|
|
285
|
+
return "".join(result)
|
|
286
|
+
|
|
287
|
+
def _get_public_methods(self) -> List[str]:
|
|
288
|
+
"""
|
|
289
|
+
Get list of public methods that should have schemas.
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
List of method names
|
|
293
|
+
"""
|
|
294
|
+
methods = []
|
|
295
|
+
for attr_name in dir(self.__class__):
|
|
296
|
+
# Skip private methods
|
|
297
|
+
if attr_name.startswith("_"):
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
# Skip base class methods
|
|
301
|
+
if attr_name in ["run", "run_async", "run_batch"]:
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
attr = getattr(self.__class__, attr_name)
|
|
305
|
+
|
|
306
|
+
# Skip non-method attributes
|
|
307
|
+
if not callable(attr):
|
|
308
|
+
continue
|
|
309
|
+
|
|
310
|
+
# Skip classes (like Config, Schema, etc.)
|
|
311
|
+
if isinstance(attr, type):
|
|
312
|
+
continue
|
|
313
|
+
|
|
314
|
+
methods.append(attr_name)
|
|
315
|
+
|
|
316
|
+
return methods
|
|
317
|
+
|
|
318
|
+
def _log_schema_coverage(self) -> None:
|
|
319
|
+
"""
|
|
320
|
+
Log schema coverage metrics after registration.
|
|
321
|
+
"""
|
|
322
|
+
coverage = self._schema_coverage
|
|
323
|
+
total = coverage["total_methods"]
|
|
324
|
+
if total == 0:
|
|
325
|
+
return
|
|
326
|
+
|
|
327
|
+
manual = coverage["manual_schemas"]
|
|
328
|
+
auto = coverage["auto_generated_schemas"]
|
|
329
|
+
missing = coverage["missing_schemas"]
|
|
330
|
+
|
|
331
|
+
coverage_pct = ((manual + auto) / total * 100) if total > 0 else 0
|
|
332
|
+
|
|
333
|
+
logger.info(
|
|
334
|
+
f"Schema coverage for {self.__class__.__name__}: "
|
|
335
|
+
f"{coverage_pct:.1f}% ({manual + auto}/{total}) - "
|
|
336
|
+
f"Manual: {manual}, Auto: {auto}, Missing: {missing}"
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
if missing > 0:
|
|
340
|
+
logger.debug(f"{missing} methods without schemas in {self.__class__.__name__}")
|
|
341
|
+
|
|
342
|
+
def get_schema_coverage(self) -> Dict[str, Any]:
|
|
343
|
+
"""
|
|
344
|
+
Get schema coverage metrics for this tool.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Dictionary with coverage metrics:
|
|
348
|
+
- total_methods: Total number of public methods
|
|
349
|
+
- manual_schemas: Number of manually defined schemas
|
|
350
|
+
- auto_generated_schemas: Number of auto-generated schemas
|
|
351
|
+
- missing_schemas: Number of methods without schemas
|
|
352
|
+
- coverage_percentage: Percentage of methods with schemas
|
|
353
|
+
- quality_metrics: Quality metrics for schemas
|
|
354
|
+
"""
|
|
355
|
+
total = self._schema_coverage["total_methods"]
|
|
356
|
+
manual = self._schema_coverage["manual_schemas"]
|
|
357
|
+
auto = self._schema_coverage["auto_generated_schemas"]
|
|
358
|
+
missing = self._schema_coverage["missing_schemas"]
|
|
359
|
+
|
|
360
|
+
coverage_pct = ((manual + auto) / total * 100) if total > 0 else 0
|
|
361
|
+
|
|
362
|
+
# Calculate quality metrics
|
|
363
|
+
quality_metrics = self._calculate_schema_quality()
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
"total_methods": total,
|
|
367
|
+
"manual_schemas": manual,
|
|
368
|
+
"auto_generated_schemas": auto,
|
|
369
|
+
"missing_schemas": missing,
|
|
370
|
+
"coverage_percentage": coverage_pct,
|
|
371
|
+
"quality_metrics": quality_metrics,
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
def _calculate_schema_quality(self) -> Dict[str, float]:
|
|
375
|
+
"""
|
|
376
|
+
Calculate schema quality metrics.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
Dictionary with quality scores:
|
|
380
|
+
- description_quality: Percentage of fields with meaningful descriptions
|
|
381
|
+
- type_coverage: Percentage of fields with type annotations
|
|
382
|
+
- overall_score: Overall quality score
|
|
383
|
+
"""
|
|
384
|
+
total_fields = 0
|
|
385
|
+
fields_with_descriptions = 0
|
|
386
|
+
fields_with_types = 0
|
|
387
|
+
|
|
388
|
+
for schema in self._schemas.values():
|
|
389
|
+
if not hasattr(schema, "model_fields"):
|
|
390
|
+
continue
|
|
391
|
+
|
|
392
|
+
for field_name, field_info in schema.model_fields.items():
|
|
393
|
+
total_fields += 1
|
|
394
|
+
|
|
395
|
+
# Check for meaningful description (not just "Parameter {name}")
|
|
396
|
+
desc = field_info.description
|
|
397
|
+
if desc and desc != f"Parameter {field_name}":
|
|
398
|
+
fields_with_descriptions += 1
|
|
399
|
+
|
|
400
|
+
# Check for type annotation
|
|
401
|
+
if field_info.annotation is not None and field_info.annotation != Any:
|
|
402
|
+
fields_with_types += 1
|
|
403
|
+
|
|
404
|
+
description_quality = (fields_with_descriptions / total_fields * 100) if total_fields > 0 else 0
|
|
405
|
+
type_coverage = (fields_with_types / total_fields * 100) if total_fields > 0 else 0
|
|
406
|
+
overall_score = (description_quality + type_coverage) / 2 if total_fields > 0 else 0
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
"description_quality": description_quality,
|
|
410
|
+
"type_coverage": type_coverage,
|
|
411
|
+
"overall_score": overall_score,
|
|
412
|
+
}
|
|
86
413
|
|
|
87
414
|
def _register_async_methods(self) -> None:
|
|
88
415
|
"""
|
|
@@ -90,7 +417,7 @@ class BaseTool:
|
|
|
90
417
|
"""
|
|
91
418
|
for attr_name in dir(self.__class__):
|
|
92
419
|
attr = getattr(self.__class__, attr_name)
|
|
93
|
-
if inspect.iscoroutinefunction(attr) and not attr_name.startswith(
|
|
420
|
+
if inspect.iscoroutinefunction(attr) and not attr_name.startswith("_"):
|
|
94
421
|
self._async_methods.append(attr_name)
|
|
95
422
|
|
|
96
423
|
def _sanitize_kwargs(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -108,7 +435,7 @@ class BaseTool:
|
|
|
108
435
|
"""
|
|
109
436
|
sanitized = {}
|
|
110
437
|
for k, v in kwargs.items():
|
|
111
|
-
if isinstance(v, str) and re.search(r
|
|
438
|
+
if isinstance(v, str) and re.search(r"(\bSELECT\b|\bINSERT\b|--|;|/\*)", v, re.IGNORECASE):
|
|
112
439
|
raise SecurityError(f"Input parameter '{k}' contains potentially malicious content")
|
|
113
440
|
sanitized[k] = v
|
|
114
441
|
return sanitized
|
|
@@ -184,6 +511,8 @@ class BaseTool:
|
|
|
184
511
|
def _get_method_schema(self, method_name: str) -> Optional[Type[BaseModel]]:
|
|
185
512
|
"""
|
|
186
513
|
Get the schema for a method if it exists.
|
|
514
|
+
Checks registered schemas first, then tries to find manual schema,
|
|
515
|
+
and finally falls back to auto-generation.
|
|
187
516
|
|
|
188
517
|
Args:
|
|
189
518
|
method_name (str): The name of the method.
|
|
@@ -191,12 +520,77 @@ class BaseTool:
|
|
|
191
520
|
Returns:
|
|
192
521
|
Optional[Type[BaseModel]]: The schema class or None.
|
|
193
522
|
"""
|
|
523
|
+
# First check registered schemas (includes both manual and auto-generated)
|
|
194
524
|
if method_name in self._schemas:
|
|
195
|
-
|
|
196
|
-
|
|
525
|
+
schema = self._schemas[method_name]
|
|
526
|
+
# Log whether it's manual or auto-generated
|
|
527
|
+
schema_type = "manual" if self._is_manual_schema(method_name, schema) else "auto-generated"
|
|
528
|
+
logger.debug(f"Retrieved {schema_type} schema for method {method_name}")
|
|
529
|
+
return schema
|
|
530
|
+
|
|
531
|
+
# Try to find manual schema by convention
|
|
532
|
+
# Convert method_name to schema name (e.g., read_csv -> Read_csvSchema)
|
|
533
|
+
schema_name = self._method_name_to_schema_name(method_name)
|
|
197
534
|
for attr_name in dir(self.__class__):
|
|
198
535
|
if attr_name == schema_name:
|
|
199
536
|
attr = getattr(self.__class__, attr_name)
|
|
200
537
|
if isinstance(attr, type) and issubclass(attr, BaseModel):
|
|
538
|
+
# Register it for future use
|
|
539
|
+
self._schemas[method_name] = attr
|
|
540
|
+
self._schema_coverage["manual_schemas"] += 1
|
|
541
|
+
logger.debug(f"Found and registered manual schema {schema_name} for method {method_name}")
|
|
201
542
|
return attr
|
|
543
|
+
|
|
544
|
+
# Fallback to auto-generation if method exists
|
|
545
|
+
if hasattr(self.__class__, method_name):
|
|
546
|
+
method = getattr(self.__class__, method_name)
|
|
547
|
+
if callable(method) and not isinstance(method, type):
|
|
548
|
+
try:
|
|
549
|
+
auto_schema = generate_schema_from_method(method, method_name)
|
|
550
|
+
if auto_schema:
|
|
551
|
+
self._schemas[method_name] = auto_schema
|
|
552
|
+
self._schema_coverage["auto_generated_schemas"] += 1
|
|
553
|
+
logger.info(f"Auto-generated schema on-demand for method {method_name}")
|
|
554
|
+
return auto_schema
|
|
555
|
+
except Exception as e:
|
|
556
|
+
logger.debug(f"Could not auto-generate schema for {method_name}: {e}")
|
|
557
|
+
|
|
202
558
|
return None
|
|
559
|
+
|
|
560
|
+
def _method_name_to_schema_name(self, method_name: str) -> str:
|
|
561
|
+
"""
|
|
562
|
+
Convert method name to schema name following convention.
|
|
563
|
+
|
|
564
|
+
Examples:
|
|
565
|
+
- read_csv -> Read_csvSchema
|
|
566
|
+
- read -> ReadSchema
|
|
567
|
+
|
|
568
|
+
Args:
|
|
569
|
+
method_name: Method name in snake_case
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
Schema class name
|
|
573
|
+
"""
|
|
574
|
+
# Preserve underscores: read_csv -> Read_csv
|
|
575
|
+
parts = method_name.split("_")
|
|
576
|
+
capitalized_parts = [part.capitalize() for part in parts]
|
|
577
|
+
return "".join(capitalized_parts) + "Schema"
|
|
578
|
+
|
|
579
|
+
def _is_manual_schema(self, method_name: str, schema: Type[BaseModel]) -> bool:
|
|
580
|
+
"""
|
|
581
|
+
Check if a schema was manually defined (not auto-generated).
|
|
582
|
+
|
|
583
|
+
Args:
|
|
584
|
+
method_name: Method name
|
|
585
|
+
schema: Schema class
|
|
586
|
+
|
|
587
|
+
Returns:
|
|
588
|
+
True if schema is manually defined, False if auto-generated
|
|
589
|
+
"""
|
|
590
|
+
# Check if schema exists as a class attribute
|
|
591
|
+
schema_name = schema.__name__
|
|
592
|
+
if hasattr(self.__class__, schema_name):
|
|
593
|
+
attr = getattr(self.__class__, schema_name)
|
|
594
|
+
if isinstance(attr, type) and attr == schema:
|
|
595
|
+
return True
|
|
596
|
+
return False
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# python-middleware/aiecs/tools/docs/__init__.py
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Document Tools Module
|
|
5
|
+
|
|
6
|
+
This module contains specialized tools for document processing and analysis:
|
|
7
|
+
- document_parser_tool: Modern high-performance document parsing with AI
|
|
8
|
+
- ai_document_orchestrator: AI-powered document processing orchestrator
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
# Lazy import all document tools to avoid heavy dependencies at import time
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
# Define available document tools for lazy loading
|
|
15
|
+
_AVAILABLE_DOC_TOOLS = [
|
|
16
|
+
"document_parser_tool",
|
|
17
|
+
"ai_document_orchestrator",
|
|
18
|
+
"document_writer_tool",
|
|
19
|
+
"ai_document_writer_orchestrator",
|
|
20
|
+
"document_creator_tool",
|
|
21
|
+
"document_layout_tool",
|
|
22
|
+
"content_insertion_tool",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# Track which tools have been loaded
|
|
26
|
+
_LOADED_DOC_TOOLS = set()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _lazy_load_doc_tool(tool_name: str):
|
|
30
|
+
"""Lazy load a specific document tool module"""
|
|
31
|
+
if tool_name in _LOADED_DOC_TOOLS:
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
# Mark as loading to prevent infinite recursion
|
|
35
|
+
_LOADED_DOC_TOOLS.add(tool_name)
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
if tool_name == "document_parser_tool":
|
|
39
|
+
from . import document_parser_tool
|
|
40
|
+
|
|
41
|
+
globals()["document_parser_tool"] = document_parser_tool
|
|
42
|
+
elif tool_name == "ai_document_orchestrator":
|
|
43
|
+
from . import ai_document_orchestrator
|
|
44
|
+
|
|
45
|
+
globals()["ai_document_orchestrator"] = ai_document_orchestrator
|
|
46
|
+
elif tool_name == "document_writer_tool":
|
|
47
|
+
from . import document_writer_tool
|
|
48
|
+
|
|
49
|
+
globals()["document_writer_tool"] = document_writer_tool
|
|
50
|
+
elif tool_name == "ai_document_writer_orchestrator":
|
|
51
|
+
from . import ai_document_writer_orchestrator
|
|
52
|
+
|
|
53
|
+
globals()["ai_document_writer_orchestrator"] = ai_document_writer_orchestrator
|
|
54
|
+
elif tool_name == "document_creator_tool":
|
|
55
|
+
from . import document_creator_tool
|
|
56
|
+
|
|
57
|
+
globals()["document_creator_tool"] = document_creator_tool
|
|
58
|
+
elif tool_name == "document_layout_tool":
|
|
59
|
+
from . import document_layout_tool
|
|
60
|
+
|
|
61
|
+
globals()["document_layout_tool"] = document_layout_tool
|
|
62
|
+
elif tool_name == "content_insertion_tool":
|
|
63
|
+
from . import content_insertion_tool
|
|
64
|
+
|
|
65
|
+
globals()["content_insertion_tool"] = content_insertion_tool
|
|
66
|
+
|
|
67
|
+
except ImportError as e:
|
|
68
|
+
# Remove from loaded set if import failed
|
|
69
|
+
_LOADED_DOC_TOOLS.discard(tool_name)
|
|
70
|
+
print(f"Warning: Could not import {tool_name}: {e}")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def __getattr__(name: str):
|
|
74
|
+
"""
|
|
75
|
+
Lazy loading mechanism for document tools.
|
|
76
|
+
|
|
77
|
+
This allows importing tools like:
|
|
78
|
+
from aiecs.tools.docs import document_parser_tool
|
|
79
|
+
"""
|
|
80
|
+
if name in _AVAILABLE_DOC_TOOLS:
|
|
81
|
+
_lazy_load_doc_tool(name)
|
|
82
|
+
if name in globals():
|
|
83
|
+
return globals()[name]
|
|
84
|
+
|
|
85
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def list_doc_tools():
|
|
89
|
+
"""List all available document tools"""
|
|
90
|
+
return _AVAILABLE_DOC_TOOLS.copy()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def load_all_doc_tools():
|
|
94
|
+
"""Load all available document tools"""
|
|
95
|
+
for tool_name in _AVAILABLE_DOC_TOOLS:
|
|
96
|
+
_lazy_load_doc_tool(tool_name)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Auto-discovery of tool modules in this directory
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _discover_doc_tools():
|
|
103
|
+
"""Discover document tool modules in the current directory"""
|
|
104
|
+
current_dir = os.path.dirname(__file__)
|
|
105
|
+
if not current_dir:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
for filename in os.listdir(current_dir):
|
|
109
|
+
if filename.endswith("_tool.py") and not filename.startswith("__"):
|
|
110
|
+
tool_name = filename[:-3] # Remove .py extension
|
|
111
|
+
if tool_name not in _AVAILABLE_DOC_TOOLS:
|
|
112
|
+
_AVAILABLE_DOC_TOOLS.append(tool_name)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Discover tools on import
|
|
116
|
+
_discover_doc_tools()
|
|
117
|
+
|
|
118
|
+
__all__ = [
|
|
119
|
+
"list_doc_tools",
|
|
120
|
+
"load_all_doc_tools",
|
|
121
|
+
] + _AVAILABLE_DOC_TOOLS
|