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,136 +1,83 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import logging
|
|
3
3
|
import subprocess
|
|
4
|
-
import uuid
|
|
5
4
|
import tempfile
|
|
6
5
|
from typing import Dict, Any, List, Optional
|
|
7
6
|
from dataclasses import dataclass
|
|
8
7
|
from dataclasses import field
|
|
9
8
|
|
|
10
|
-
from pydantic import
|
|
11
|
-
|
|
9
|
+
from pydantic import (
|
|
10
|
+
BaseModel,
|
|
11
|
+
ValidationError,
|
|
12
|
+
field_validator,
|
|
13
|
+
Field,
|
|
14
|
+
)
|
|
15
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
12
16
|
from PIL import Image, ExifTags, ImageFilter
|
|
13
17
|
from queue import Queue
|
|
14
18
|
|
|
15
19
|
from aiecs.tools.base_tool import BaseTool
|
|
16
20
|
from aiecs.tools import register_tool
|
|
17
21
|
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
# Module-level default configuration for validators
|
|
23
|
+
_DEFAULT_MAX_FILE_SIZE_MB = 50
|
|
24
|
+
_DEFAULT_ALLOWED_EXTENSIONS = [
|
|
25
|
+
".jpg",
|
|
26
|
+
".jpeg",
|
|
27
|
+
".png",
|
|
28
|
+
".bmp",
|
|
29
|
+
".tiff",
|
|
30
|
+
".gif",
|
|
31
|
+
]
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
max_file_size_mb (int): Maximum file size in megabytes.
|
|
25
|
-
allowed_extensions (List[str]): Allowed image file extensions.
|
|
26
|
-
tesseract_pool_size (int): Number of Tesseract processes for OCR.
|
|
27
|
-
env_prefix (str): Environment variable prefix for settings.
|
|
28
|
-
"""
|
|
29
|
-
max_file_size_mb: int = 50
|
|
30
|
-
allowed_extensions: List[str] = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.gif']
|
|
31
|
-
tesseract_pool_size: int = 2
|
|
32
|
-
env_prefix: str = 'IMAGE_TOOL_'
|
|
33
|
+
# Exceptions
|
|
33
34
|
|
|
34
|
-
model_config = ConfigDict(env_prefix='IMAGE_TOOL_')
|
|
35
35
|
|
|
36
|
-
# Exceptions
|
|
37
36
|
class ImageToolError(Exception):
|
|
38
37
|
"""Base exception for ImageTool errors."""
|
|
39
|
-
|
|
38
|
+
|
|
40
39
|
|
|
41
40
|
class FileOperationError(ImageToolError):
|
|
42
41
|
"""Raised when file operations fail."""
|
|
43
|
-
|
|
42
|
+
|
|
44
43
|
|
|
45
44
|
class SecurityError(ImageToolError):
|
|
46
45
|
"""Raised for security-related issues."""
|
|
47
|
-
|
|
46
|
+
|
|
48
47
|
|
|
49
48
|
# Base schema for common fields
|
|
49
|
+
|
|
50
|
+
|
|
50
51
|
class BaseFileSchema(BaseModel):
|
|
51
52
|
file_path: str
|
|
52
53
|
_mtime: Optional[float] = None # Internal use for cache
|
|
53
54
|
|
|
54
|
-
@field_validator(
|
|
55
|
+
@field_validator("file_path")
|
|
55
56
|
@classmethod
|
|
56
57
|
def validate_file_path(cls, v: str) -> str:
|
|
57
58
|
"""Validate file path for existence, size, and extension."""
|
|
58
|
-
settings = ImageSettings()
|
|
59
59
|
abs_path = os.path.abspath(os.path.normpath(v))
|
|
60
60
|
ext = os.path.splitext(abs_path)[1].lower()
|
|
61
|
-
if ext not in
|
|
62
|
-
raise SecurityError(f"Extension '{ext}' not allowed, expected {
|
|
61
|
+
if ext not in _DEFAULT_ALLOWED_EXTENSIONS:
|
|
62
|
+
raise SecurityError(f"Extension '{ext}' not allowed, expected {_DEFAULT_ALLOWED_EXTENSIONS}")
|
|
63
63
|
if not os.path.isfile(abs_path):
|
|
64
64
|
raise FileOperationError(f"File not found: {abs_path}")
|
|
65
65
|
size_mb = os.path.getsize(abs_path) / (1024 * 1024)
|
|
66
|
-
if size_mb >
|
|
67
|
-
raise FileOperationError(f"File too large: {size_mb:.1f}MB, max {
|
|
66
|
+
if size_mb > _DEFAULT_MAX_FILE_SIZE_MB:
|
|
67
|
+
raise FileOperationError(f"File too large: {size_mb:.1f}MB, max {_DEFAULT_MAX_FILE_SIZE_MB}MB")
|
|
68
68
|
return abs_path
|
|
69
69
|
|
|
70
|
-
# Schemas for operations
|
|
71
|
-
class LoadSchema(BaseFileSchema):
|
|
72
|
-
"""Schema for load operation."""
|
|
73
|
-
pass
|
|
74
|
-
|
|
75
|
-
class OCRSchema(BaseFileSchema):
|
|
76
|
-
"""Schema for OCR operation."""
|
|
77
|
-
lang: Optional[str] = None
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
class MetadataSchema(BaseFileSchema):
|
|
81
|
-
"""Schema for metadata extraction operation."""
|
|
82
|
-
include_exif: bool = False
|
|
83
70
|
|
|
84
|
-
class
|
|
85
|
-
"""Schema for resize operation."""
|
|
86
|
-
output_path: str
|
|
87
|
-
width: int
|
|
88
|
-
height: int
|
|
71
|
+
# Schemas for operations - moved to ImageTool class as inner classes
|
|
89
72
|
|
|
90
|
-
@field_validator('output_path')
|
|
91
|
-
@classmethod
|
|
92
|
-
def validate_output_path(cls, v: str) -> str:
|
|
93
|
-
"""Validate output path for existence and extension."""
|
|
94
|
-
settings = ImageSettings()
|
|
95
|
-
abs_path = os.path.abspath(os.path.normpath(v))
|
|
96
|
-
ext = os.path.splitext(abs_path)[1].lower()
|
|
97
|
-
if ext not in settings.allowed_extensions:
|
|
98
|
-
raise SecurityError(f"Output extension '{ext}' not allowed, expected {settings.allowed_extensions}")
|
|
99
|
-
if os.path.exists(abs_path):
|
|
100
|
-
raise FileOperationError(f"Output file already exists: {abs_path}")
|
|
101
|
-
return abs_path
|
|
102
73
|
|
|
103
|
-
|
|
104
|
-
"""Schema for filter operation."""
|
|
105
|
-
output_path: str
|
|
106
|
-
filter_type: str = 'blur'
|
|
74
|
+
# Tesseract process manager
|
|
107
75
|
|
|
108
|
-
@field_validator('filter_type')
|
|
109
|
-
@classmethod
|
|
110
|
-
def validate_filter_type(cls, v: str) -> str:
|
|
111
|
-
"""Validate filter type."""
|
|
112
|
-
valid_filters = ['blur', 'sharpen', 'edge_enhance']
|
|
113
|
-
if v not in valid_filters:
|
|
114
|
-
raise ValueError(f"Invalid filter_type '{v}', expected {valid_filters}")
|
|
115
|
-
return v
|
|
116
|
-
|
|
117
|
-
@field_validator('output_path')
|
|
118
|
-
@classmethod
|
|
119
|
-
def validate_output_path(cls, v: str) -> str:
|
|
120
|
-
"""Validate output path for existence and extension."""
|
|
121
|
-
settings = ImageSettings()
|
|
122
|
-
abs_path = os.path.abspath(os.path.normpath(v))
|
|
123
|
-
ext = os.path.splitext(abs_path)[1].lower()
|
|
124
|
-
if ext not in settings.allowed_extensions:
|
|
125
|
-
raise SecurityError(f"Output extension '{ext}' not allowed, expected {settings.allowed_extensions}")
|
|
126
|
-
if os.path.exists(abs_path):
|
|
127
|
-
raise FileOperationError(f"Output file already exists: {abs_path}")
|
|
128
|
-
return abs_path
|
|
129
76
|
|
|
130
|
-
# Tesseract process manager
|
|
131
77
|
@dataclass
|
|
132
78
|
class TesseractManager:
|
|
133
79
|
"""Manages a pool of Tesseract processes for OCR."""
|
|
80
|
+
|
|
134
81
|
pool_size: int
|
|
135
82
|
processes: List[subprocess.Popen] = field(default_factory=list)
|
|
136
83
|
queue: Queue = field(default_factory=lambda: Queue())
|
|
@@ -140,11 +87,11 @@ class TesseractManager:
|
|
|
140
87
|
for _ in range(self.pool_size):
|
|
141
88
|
try:
|
|
142
89
|
proc = subprocess.Popen(
|
|
143
|
-
[
|
|
90
|
+
["tesseract", "--oem", "1", "-", "stdout", "-l", "eng"],
|
|
144
91
|
stdin=subprocess.PIPE,
|
|
145
92
|
stdout=subprocess.PIPE,
|
|
146
93
|
stderr=subprocess.PIPE,
|
|
147
|
-
text=True
|
|
94
|
+
text=True,
|
|
148
95
|
)
|
|
149
96
|
self.queue.put(proc)
|
|
150
97
|
self.processes.append(proc)
|
|
@@ -171,7 +118,8 @@ class TesseractManager:
|
|
|
171
118
|
except (subprocess.TimeoutExpired, OSError) as e:
|
|
172
119
|
logging.getLogger(__name__).warning(f"Error terminating Tesseract process: {e}")
|
|
173
120
|
|
|
174
|
-
|
|
121
|
+
|
|
122
|
+
@register_tool("image")
|
|
175
123
|
class ImageTool(BaseTool):
|
|
176
124
|
"""
|
|
177
125
|
Image processing tool supporting:
|
|
@@ -183,38 +131,142 @@ class ImageTool(BaseTool):
|
|
|
183
131
|
|
|
184
132
|
Inherits from BaseTool to leverage ToolExecutor for caching, concurrency, and error handling.
|
|
185
133
|
"""
|
|
186
|
-
|
|
134
|
+
|
|
135
|
+
# Configuration schema
|
|
136
|
+
class Config(BaseSettings):
|
|
137
|
+
"""Configuration for the image tool
|
|
138
|
+
|
|
139
|
+
Automatically reads from environment variables with IMAGE_TOOL_ prefix.
|
|
140
|
+
Example: IMAGE_TOOL_MAX_FILE_SIZE_MB -> max_file_size_mb
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
model_config = SettingsConfigDict(env_prefix="IMAGE_TOOL_")
|
|
144
|
+
|
|
145
|
+
max_file_size_mb: int = Field(default=50, description="Maximum file size in megabytes")
|
|
146
|
+
allowed_extensions: List[str] = Field(
|
|
147
|
+
default=[".jpg", ".jpeg", ".png", ".bmp", ".tiff", ".gif"],
|
|
148
|
+
description="Allowed image file extensions",
|
|
149
|
+
)
|
|
150
|
+
tesseract_pool_size: int = Field(default=2, description="Number of Tesseract processes for OCR")
|
|
151
|
+
default_ocr_language: str = Field(
|
|
152
|
+
default="eng",
|
|
153
|
+
description="Default OCR language code (e.g., 'eng', 'chi_sim'). Supports multi-language format like 'eng+chi_sim'"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Schema definitions
|
|
157
|
+
class LoadSchema(BaseFileSchema):
|
|
158
|
+
"""Schema for load operation"""
|
|
159
|
+
|
|
160
|
+
file_path: str = Field(description="Path to the image file to load")
|
|
161
|
+
|
|
162
|
+
class OcrSchema(BaseFileSchema):
|
|
163
|
+
"""Schema for ocr operation"""
|
|
164
|
+
|
|
165
|
+
file_path: str = Field(description="Path to the image file for OCR text extraction")
|
|
166
|
+
lang: Optional[str] = Field(
|
|
167
|
+
default=None,
|
|
168
|
+
description="Optional language code for OCR (e.g., 'eng', 'chi_sim', 'eng+chi_sim'). If not specified, uses the configured default_ocr_language"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
class MetadataSchema(BaseFileSchema):
|
|
172
|
+
"""Schema for metadata operation"""
|
|
173
|
+
|
|
174
|
+
file_path: str = Field(description="Path to the image file to extract metadata from")
|
|
175
|
+
include_exif: bool = Field(default=False, description="Whether to include EXIF data in the metadata. If False, only basic info (size, mode) is returned")
|
|
176
|
+
|
|
177
|
+
class ResizeSchema(BaseFileSchema):
|
|
178
|
+
"""Schema for resize operation"""
|
|
179
|
+
|
|
180
|
+
file_path: str = Field(description="Path to the source image file")
|
|
181
|
+
output_path: str = Field(description="Path where the resized image will be saved")
|
|
182
|
+
width: int = Field(description="Target width in pixels for the resized image")
|
|
183
|
+
height: int = Field(description="Target height in pixels for the resized image")
|
|
184
|
+
|
|
185
|
+
@field_validator("output_path")
|
|
186
|
+
@classmethod
|
|
187
|
+
def validate_output_path(cls, v: str) -> str:
|
|
188
|
+
"""Validate output path for existence and extension."""
|
|
189
|
+
abs_path = os.path.abspath(os.path.normpath(v))
|
|
190
|
+
ext = os.path.splitext(abs_path)[1].lower()
|
|
191
|
+
if ext not in _DEFAULT_ALLOWED_EXTENSIONS:
|
|
192
|
+
raise SecurityError(f"Output extension '{ext}' not allowed, expected {_DEFAULT_ALLOWED_EXTENSIONS}")
|
|
193
|
+
if os.path.exists(abs_path):
|
|
194
|
+
raise FileOperationError(f"Output file already exists: {abs_path}")
|
|
195
|
+
return abs_path
|
|
196
|
+
|
|
197
|
+
class FilterSchema(BaseFileSchema):
|
|
198
|
+
"""Schema for filter operation"""
|
|
199
|
+
|
|
200
|
+
file_path: str = Field(description="Path to the source image file")
|
|
201
|
+
output_path: str = Field(description="Path where the filtered image will be saved")
|
|
202
|
+
filter_type: str = Field(default="blur", description="Type of filter to apply: 'blur', 'sharpen', or 'edge_enhance'")
|
|
203
|
+
|
|
204
|
+
@field_validator("filter_type")
|
|
205
|
+
@classmethod
|
|
206
|
+
def validate_filter_type(cls, v: str) -> str:
|
|
207
|
+
"""Validate filter type."""
|
|
208
|
+
valid_filters = ["blur", "sharpen", "edge_enhance"]
|
|
209
|
+
if v not in valid_filters:
|
|
210
|
+
raise ValueError(f"Invalid filter_type '{v}', expected {valid_filters}")
|
|
211
|
+
return v
|
|
212
|
+
|
|
213
|
+
@field_validator("output_path")
|
|
214
|
+
@classmethod
|
|
215
|
+
def validate_output_path(cls, v: str) -> str:
|
|
216
|
+
"""Validate output path for existence and extension."""
|
|
217
|
+
abs_path = os.path.abspath(os.path.normpath(v))
|
|
218
|
+
ext = os.path.splitext(abs_path)[1].lower()
|
|
219
|
+
if ext not in _DEFAULT_ALLOWED_EXTENSIONS:
|
|
220
|
+
raise SecurityError(f"Output extension '{ext}' not allowed, expected {_DEFAULT_ALLOWED_EXTENSIONS}")
|
|
221
|
+
if os.path.exists(abs_path):
|
|
222
|
+
raise FileOperationError(f"Output file already exists: {abs_path}")
|
|
223
|
+
return abs_path
|
|
224
|
+
|
|
225
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
187
226
|
"""
|
|
188
|
-
Initialize ImageTool with
|
|
227
|
+
Initialize ImageTool with configuration and resources.
|
|
189
228
|
|
|
190
229
|
Args:
|
|
191
|
-
config (Dict, optional): Configuration overrides for
|
|
230
|
+
config (Dict, optional): Configuration overrides for ImageTool.
|
|
231
|
+
**kwargs: Additional arguments passed to BaseTool (e.g., tool_name)
|
|
192
232
|
|
|
193
233
|
Raises:
|
|
194
234
|
ValueError: If config contains invalid settings.
|
|
235
|
+
|
|
236
|
+
Configuration is automatically loaded by BaseTool from:
|
|
237
|
+
1. Explicit config dict (highest priority)
|
|
238
|
+
2. YAML config files (config/tools/image.yaml, config/tools/image_tool.yaml, or config/tools/ImageTool.yaml)
|
|
239
|
+
3. Environment variables (via dotenv from .env files with IMAGE_TOOL_ prefix)
|
|
240
|
+
4. Tool defaults (lowest priority)
|
|
241
|
+
|
|
242
|
+
YAML configuration files are automatically discovered by ToolConfigLoader using multiple naming conventions.
|
|
243
|
+
See examples/config/tools/image_tool.yaml.example for a configuration template.
|
|
195
244
|
"""
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
245
|
+
# Pass tool_name="image" to BaseTool so it can find config/tools/image.yaml
|
|
246
|
+
if "tool_name" not in kwargs:
|
|
247
|
+
kwargs["tool_name"] = "image"
|
|
248
|
+
super().__init__(config, **kwargs)
|
|
249
|
+
|
|
250
|
+
# Configuration is automatically loaded by BaseTool into self._config_obj
|
|
251
|
+
# Access config via self._config_obj (BaseSettings instance)
|
|
252
|
+
self.config = self._config_obj if self._config_obj else self.Config()
|
|
253
|
+
|
|
203
254
|
self.logger = logging.getLogger(__name__)
|
|
204
255
|
if not self.logger.handlers:
|
|
205
256
|
handler = logging.StreamHandler()
|
|
206
|
-
handler.setFormatter(logging.Formatter(
|
|
257
|
+
handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
|
|
207
258
|
self.logger.addHandler(handler)
|
|
208
259
|
self.logger.setLevel(logging.INFO)
|
|
260
|
+
|
|
209
261
|
# Initialize Tesseract manager
|
|
210
|
-
self._tesseract_manager = TesseractManager(self.
|
|
262
|
+
self._tesseract_manager = TesseractManager(self.config.tesseract_pool_size)
|
|
211
263
|
self._tesseract_manager.initialize()
|
|
212
264
|
|
|
213
265
|
def __del__(self):
|
|
214
266
|
"""Clean up Tesseract processes on destruction."""
|
|
215
267
|
self._tesseract_manager.cleanup()
|
|
216
268
|
|
|
217
|
-
def
|
|
269
|
+
def update_config(self, config: Dict) -> None:
|
|
218
270
|
"""
|
|
219
271
|
Update configuration settings dynamically.
|
|
220
272
|
|
|
@@ -225,11 +277,11 @@ class ImageTool(BaseTool):
|
|
|
225
277
|
ValueError: If config contains invalid settings.
|
|
226
278
|
"""
|
|
227
279
|
try:
|
|
228
|
-
self.
|
|
280
|
+
self.config = self.Config(**{**self.config.model_dump(), **config})
|
|
229
281
|
# Reinitialize Tesseract if pool size changes
|
|
230
|
-
if
|
|
282
|
+
if "tesseract_pool_size" in config:
|
|
231
283
|
self._tesseract_manager.cleanup()
|
|
232
|
-
self._tesseract_manager = TesseractManager(self.
|
|
284
|
+
self._tesseract_manager = TesseractManager(self.config.tesseract_pool_size)
|
|
233
285
|
self._tesseract_manager.initialize()
|
|
234
286
|
except ValidationError as e:
|
|
235
287
|
raise ValueError(f"Invalid configuration: {e}")
|
|
@@ -248,22 +300,23 @@ class ImageTool(BaseTool):
|
|
|
248
300
|
FileOperationError: If file is invalid or inaccessible.
|
|
249
301
|
"""
|
|
250
302
|
# Validate input using schema
|
|
251
|
-
validated_input = LoadSchema(file_path=file_path)
|
|
252
|
-
|
|
303
|
+
validated_input = self.LoadSchema(file_path=file_path)
|
|
304
|
+
|
|
253
305
|
try:
|
|
254
306
|
with Image.open(validated_input.file_path) as img:
|
|
255
307
|
img.load()
|
|
256
|
-
return {
|
|
308
|
+
return {"size": img.size, "mode": img.mode}
|
|
257
309
|
except Exception as e:
|
|
258
310
|
raise FileOperationError(f"load: Failed to load image '{file_path}': {e}")
|
|
259
311
|
|
|
260
312
|
def ocr(self, file_path: str, lang: Optional[str] = None) -> str:
|
|
261
313
|
"""
|
|
262
|
-
Extract text from an image using
|
|
314
|
+
Extract text from an image using Tesseract OCR.
|
|
263
315
|
|
|
264
316
|
Args:
|
|
265
317
|
file_path (str): Path to the image file.
|
|
266
|
-
lang (Optional[str]): Language code for OCR (e.g., 'eng').
|
|
318
|
+
lang (Optional[str]): Language code for OCR (e.g., 'eng', 'chi_sim', 'eng+chi_sim').
|
|
319
|
+
If not specified, uses the configured default_ocr_language.
|
|
267
320
|
|
|
268
321
|
Returns:
|
|
269
322
|
str: Extracted text.
|
|
@@ -272,31 +325,51 @@ class ImageTool(BaseTool):
|
|
|
272
325
|
FileOperationError: If OCR fails or Tesseract is unavailable.
|
|
273
326
|
"""
|
|
274
327
|
# Validate input using schema
|
|
275
|
-
validated_input =
|
|
328
|
+
validated_input = self.OcrSchema(file_path=file_path, lang=lang)
|
|
276
329
|
|
|
277
|
-
|
|
278
|
-
if not
|
|
279
|
-
|
|
280
|
-
|
|
330
|
+
# Use configured default language if lang is not specified
|
|
331
|
+
ocr_lang = lang if lang is not None else self.config.default_ocr_language
|
|
332
|
+
|
|
333
|
+
# Prepare temporary file for image processing
|
|
334
|
+
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_file:
|
|
281
335
|
temp_path = temp_file.name
|
|
282
336
|
try:
|
|
283
|
-
|
|
337
|
+
# Preprocess image for better OCR results
|
|
338
|
+
img = Image.open(validated_input.file_path).convert("L").filter(ImageFilter.SHARPEN)
|
|
284
339
|
img.save(temp_path)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
340
|
+
|
|
341
|
+
# Call Tesseract with dynamic language parameter
|
|
342
|
+
# Use subprocess.run instead of process pool to support dynamic languages
|
|
343
|
+
try:
|
|
344
|
+
result = subprocess.run(
|
|
345
|
+
["tesseract", "--oem", "1", temp_path, "stdout", "-l", ocr_lang],
|
|
346
|
+
capture_output=True,
|
|
347
|
+
text=True,
|
|
348
|
+
timeout=30,
|
|
349
|
+
check=False, # Don't raise on non-zero return code, handle manually
|
|
350
|
+
)
|
|
351
|
+
except subprocess.TimeoutExpired:
|
|
352
|
+
raise FileOperationError(f"ocr: Tesseract timeout for '{file_path}' (lang: {ocr_lang})")
|
|
353
|
+
except FileNotFoundError:
|
|
354
|
+
raise FileOperationError("ocr: Tesseract not found. Please install Tesseract OCR.")
|
|
355
|
+
|
|
356
|
+
if result.returncode != 0:
|
|
357
|
+
raise FileOperationError(
|
|
358
|
+
f"ocr: Tesseract failed for '{file_path}' (lang: {ocr_lang}): {result.stderr}"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
return result.stdout.strip()
|
|
362
|
+
except FileOperationError:
|
|
363
|
+
raise # Re-raise FileOperationError as-is
|
|
289
364
|
except Exception as e:
|
|
290
|
-
raise FileOperationError(f"ocr: Failed to process '{file_path}' (lang: {
|
|
365
|
+
raise FileOperationError(f"ocr: Failed to process '{file_path}' (lang: {ocr_lang}): {e}")
|
|
291
366
|
finally:
|
|
292
|
-
self._tesseract_manager.return_process(proc)
|
|
293
367
|
if os.path.exists(temp_path):
|
|
294
368
|
try:
|
|
295
369
|
os.unlink(temp_path)
|
|
296
370
|
except Exception as e:
|
|
297
371
|
self.logger.warning(f"Failed to remove temporary file {temp_path}: {e}")
|
|
298
372
|
|
|
299
|
-
|
|
300
373
|
def metadata(self, file_path: str, include_exif: bool = False) -> Dict[str, Any]:
|
|
301
374
|
"""
|
|
302
375
|
Retrieve metadata (size, mode, EXIF) from an image.
|
|
@@ -312,19 +385,19 @@ class ImageTool(BaseTool):
|
|
|
312
385
|
FileOperationError: If metadata extraction fails.
|
|
313
386
|
"""
|
|
314
387
|
# Validate input using schema
|
|
315
|
-
validated_input = MetadataSchema(file_path=file_path, include_exif=include_exif)
|
|
316
|
-
|
|
388
|
+
validated_input = self.MetadataSchema(file_path=file_path, include_exif=include_exif)
|
|
389
|
+
|
|
317
390
|
try:
|
|
318
391
|
with Image.open(validated_input.file_path) as img:
|
|
319
392
|
img.load()
|
|
320
|
-
info = {
|
|
393
|
+
info = {"size": img.size, "mode": img.mode}
|
|
321
394
|
if include_exif:
|
|
322
395
|
exif = {}
|
|
323
396
|
raw = img._getexif() or {}
|
|
324
397
|
for tag, val in raw.items():
|
|
325
398
|
decoded = ExifTags.TAGS.get(tag, tag)
|
|
326
399
|
exif[decoded] = val
|
|
327
|
-
info[
|
|
400
|
+
info["exif"] = exif
|
|
328
401
|
return info
|
|
329
402
|
except Exception as e:
|
|
330
403
|
raise FileOperationError(f"metadata: Failed to process '{file_path}': {e}")
|
|
@@ -346,18 +419,21 @@ class ImageTool(BaseTool):
|
|
|
346
419
|
FileOperationError: If resizing fails.
|
|
347
420
|
"""
|
|
348
421
|
# Validate input using schema
|
|
349
|
-
validated_input = ResizeSchema(
|
|
350
|
-
file_path=file_path,
|
|
351
|
-
output_path=output_path,
|
|
352
|
-
width=width,
|
|
353
|
-
height=height
|
|
422
|
+
validated_input = self.ResizeSchema(
|
|
423
|
+
file_path=file_path,
|
|
424
|
+
output_path=output_path,
|
|
425
|
+
width=width,
|
|
426
|
+
height=height,
|
|
354
427
|
)
|
|
355
|
-
|
|
428
|
+
|
|
356
429
|
try:
|
|
357
430
|
with Image.open(validated_input.file_path) as img:
|
|
358
431
|
img = img.resize((width, height), Image.Resampling.LANCZOS)
|
|
359
432
|
img.save(validated_input.output_path)
|
|
360
|
-
return {
|
|
433
|
+
return {
|
|
434
|
+
"success": True,
|
|
435
|
+
"output_path": validated_input.output_path,
|
|
436
|
+
}
|
|
361
437
|
except Exception as e:
|
|
362
438
|
raise FileOperationError(f"resize: Failed to process '{file_path}' (output_path: {output_path}): {e}")
|
|
363
439
|
|
|
@@ -377,21 +453,24 @@ class ImageTool(BaseTool):
|
|
|
377
453
|
FileOperationError: If filtering fails.
|
|
378
454
|
"""
|
|
379
455
|
# Validate input using schema
|
|
380
|
-
validated_input = FilterSchema(
|
|
381
|
-
file_path=file_path,
|
|
382
|
-
output_path=output_path,
|
|
383
|
-
filter_type=filter_type
|
|
456
|
+
validated_input = self.FilterSchema(
|
|
457
|
+
file_path=file_path,
|
|
458
|
+
output_path=output_path,
|
|
459
|
+
filter_type=filter_type,
|
|
384
460
|
)
|
|
385
|
-
|
|
461
|
+
|
|
386
462
|
try:
|
|
387
463
|
filter_map = {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
464
|
+
"blur": ImageFilter.BLUR,
|
|
465
|
+
"sharpen": ImageFilter.SHARPEN,
|
|
466
|
+
"edge_enhance": ImageFilter.EDGE_ENHANCE,
|
|
391
467
|
}
|
|
392
468
|
with Image.open(validated_input.file_path) as img:
|
|
393
469
|
img = img.filter(filter_map[filter_type])
|
|
394
470
|
img.save(validated_input.output_path)
|
|
395
|
-
return {
|
|
471
|
+
return {
|
|
472
|
+
"success": True,
|
|
473
|
+
"output_path": validated_input.output_path,
|
|
474
|
+
}
|
|
396
475
|
except Exception as e:
|
|
397
476
|
raise FileOperationError(f"filter: Failed to process '{file_path}' (output_path: {output_path}, filter_type: {filter_type}): {e}")
|