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
|
@@ -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
70
|
|
|
71
|
+
# Schemas for operations - moved to ImageTool class as inner classes
|
|
79
72
|
|
|
80
|
-
class MetadataSchema(BaseFileSchema):
|
|
81
|
-
"""Schema for metadata extraction operation."""
|
|
82
|
-
include_exif: bool = False
|
|
83
73
|
|
|
84
|
-
|
|
85
|
-
"""Schema for resize operation."""
|
|
86
|
-
output_path: str
|
|
87
|
-
width: int
|
|
88
|
-
height: int
|
|
89
|
-
|
|
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
|
-
|
|
103
|
-
class FilterSchema(BaseFileSchema):
|
|
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,129 @@ 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
|
+
|
|
152
|
+
# Schema definitions
|
|
153
|
+
class LoadSchema(BaseFileSchema):
|
|
154
|
+
"""Schema for load operation"""
|
|
155
|
+
|
|
156
|
+
file_path: str = Field(description="Path to the image file to load")
|
|
157
|
+
|
|
158
|
+
class OcrSchema(BaseFileSchema):
|
|
159
|
+
"""Schema for ocr operation"""
|
|
160
|
+
|
|
161
|
+
file_path: str = Field(description="Path to the image file for OCR text extraction")
|
|
162
|
+
lang: Optional[str] = Field(default=None, description="Optional language code for OCR (e.g., 'eng', 'chi_sim'). Uses default 'eng' if not specified")
|
|
163
|
+
|
|
164
|
+
class MetadataSchema(BaseFileSchema):
|
|
165
|
+
"""Schema for metadata operation"""
|
|
166
|
+
|
|
167
|
+
file_path: str = Field(description="Path to the image file to extract metadata from")
|
|
168
|
+
include_exif: bool = Field(default=False, description="Whether to include EXIF data in the metadata. If False, only basic info (size, mode) is returned")
|
|
169
|
+
|
|
170
|
+
class ResizeSchema(BaseFileSchema):
|
|
171
|
+
"""Schema for resize operation"""
|
|
172
|
+
|
|
173
|
+
file_path: str = Field(description="Path to the source image file")
|
|
174
|
+
output_path: str = Field(description="Path where the resized image will be saved")
|
|
175
|
+
width: int = Field(description="Target width in pixels for the resized image")
|
|
176
|
+
height: int = Field(description="Target height in pixels for the resized image")
|
|
177
|
+
|
|
178
|
+
@field_validator("output_path")
|
|
179
|
+
@classmethod
|
|
180
|
+
def validate_output_path(cls, v: str) -> str:
|
|
181
|
+
"""Validate output path for existence and extension."""
|
|
182
|
+
abs_path = os.path.abspath(os.path.normpath(v))
|
|
183
|
+
ext = os.path.splitext(abs_path)[1].lower()
|
|
184
|
+
if ext not in _DEFAULT_ALLOWED_EXTENSIONS:
|
|
185
|
+
raise SecurityError(f"Output extension '{ext}' not allowed, expected {_DEFAULT_ALLOWED_EXTENSIONS}")
|
|
186
|
+
if os.path.exists(abs_path):
|
|
187
|
+
raise FileOperationError(f"Output file already exists: {abs_path}")
|
|
188
|
+
return abs_path
|
|
189
|
+
|
|
190
|
+
class FilterSchema(BaseFileSchema):
|
|
191
|
+
"""Schema for filter operation"""
|
|
192
|
+
|
|
193
|
+
file_path: str = Field(description="Path to the source image file")
|
|
194
|
+
output_path: str = Field(description="Path where the filtered image will be saved")
|
|
195
|
+
filter_type: str = Field(default="blur", description="Type of filter to apply: 'blur', 'sharpen', or 'edge_enhance'")
|
|
196
|
+
|
|
197
|
+
@field_validator("filter_type")
|
|
198
|
+
@classmethod
|
|
199
|
+
def validate_filter_type(cls, v: str) -> str:
|
|
200
|
+
"""Validate filter type."""
|
|
201
|
+
valid_filters = ["blur", "sharpen", "edge_enhance"]
|
|
202
|
+
if v not in valid_filters:
|
|
203
|
+
raise ValueError(f"Invalid filter_type '{v}', expected {valid_filters}")
|
|
204
|
+
return v
|
|
205
|
+
|
|
206
|
+
@field_validator("output_path")
|
|
207
|
+
@classmethod
|
|
208
|
+
def validate_output_path(cls, v: str) -> str:
|
|
209
|
+
"""Validate output path for existence and extension."""
|
|
210
|
+
abs_path = os.path.abspath(os.path.normpath(v))
|
|
211
|
+
ext = os.path.splitext(abs_path)[1].lower()
|
|
212
|
+
if ext not in _DEFAULT_ALLOWED_EXTENSIONS:
|
|
213
|
+
raise SecurityError(f"Output extension '{ext}' not allowed, expected {_DEFAULT_ALLOWED_EXTENSIONS}")
|
|
214
|
+
if os.path.exists(abs_path):
|
|
215
|
+
raise FileOperationError(f"Output file already exists: {abs_path}")
|
|
216
|
+
return abs_path
|
|
217
|
+
|
|
218
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None, **kwargs):
|
|
187
219
|
"""
|
|
188
|
-
Initialize ImageTool with
|
|
220
|
+
Initialize ImageTool with configuration and resources.
|
|
189
221
|
|
|
190
222
|
Args:
|
|
191
|
-
config (Dict, optional): Configuration overrides for
|
|
223
|
+
config (Dict, optional): Configuration overrides for ImageTool.
|
|
224
|
+
**kwargs: Additional arguments passed to BaseTool (e.g., tool_name)
|
|
192
225
|
|
|
193
226
|
Raises:
|
|
194
227
|
ValueError: If config contains invalid settings.
|
|
228
|
+
|
|
229
|
+
Configuration is automatically loaded by BaseTool from:
|
|
230
|
+
1. Explicit config dict (highest priority)
|
|
231
|
+
2. YAML config files (config/tools/image.yaml)
|
|
232
|
+
3. Environment variables (via dotenv from .env files)
|
|
233
|
+
4. Tool defaults (lowest priority)
|
|
195
234
|
"""
|
|
196
|
-
super().__init__(config)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
raise ValueError(f"Invalid configuration: {e}")
|
|
235
|
+
super().__init__(config, **kwargs)
|
|
236
|
+
|
|
237
|
+
# Configuration is automatically loaded by BaseTool into self._config_obj
|
|
238
|
+
# Access config via self._config_obj (BaseSettings instance)
|
|
239
|
+
self.config = self._config_obj if self._config_obj else self.Config()
|
|
240
|
+
|
|
203
241
|
self.logger = logging.getLogger(__name__)
|
|
204
242
|
if not self.logger.handlers:
|
|
205
243
|
handler = logging.StreamHandler()
|
|
206
|
-
handler.setFormatter(logging.Formatter(
|
|
244
|
+
handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
|
|
207
245
|
self.logger.addHandler(handler)
|
|
208
246
|
self.logger.setLevel(logging.INFO)
|
|
247
|
+
|
|
209
248
|
# Initialize Tesseract manager
|
|
210
|
-
self._tesseract_manager = TesseractManager(self.
|
|
249
|
+
self._tesseract_manager = TesseractManager(self.config.tesseract_pool_size)
|
|
211
250
|
self._tesseract_manager.initialize()
|
|
212
251
|
|
|
213
252
|
def __del__(self):
|
|
214
253
|
"""Clean up Tesseract processes on destruction."""
|
|
215
254
|
self._tesseract_manager.cleanup()
|
|
216
255
|
|
|
217
|
-
def
|
|
256
|
+
def update_config(self, config: Dict) -> None:
|
|
218
257
|
"""
|
|
219
258
|
Update configuration settings dynamically.
|
|
220
259
|
|
|
@@ -225,11 +264,11 @@ class ImageTool(BaseTool):
|
|
|
225
264
|
ValueError: If config contains invalid settings.
|
|
226
265
|
"""
|
|
227
266
|
try:
|
|
228
|
-
self.
|
|
267
|
+
self.config = self.Config(**{**self.config.model_dump(), **config})
|
|
229
268
|
# Reinitialize Tesseract if pool size changes
|
|
230
|
-
if
|
|
269
|
+
if "tesseract_pool_size" in config:
|
|
231
270
|
self._tesseract_manager.cleanup()
|
|
232
|
-
self._tesseract_manager = TesseractManager(self.
|
|
271
|
+
self._tesseract_manager = TesseractManager(self.config.tesseract_pool_size)
|
|
233
272
|
self._tesseract_manager.initialize()
|
|
234
273
|
except ValidationError as e:
|
|
235
274
|
raise ValueError(f"Invalid configuration: {e}")
|
|
@@ -248,12 +287,12 @@ class ImageTool(BaseTool):
|
|
|
248
287
|
FileOperationError: If file is invalid or inaccessible.
|
|
249
288
|
"""
|
|
250
289
|
# Validate input using schema
|
|
251
|
-
validated_input = LoadSchema(file_path=file_path)
|
|
252
|
-
|
|
290
|
+
validated_input = self.LoadSchema(file_path=file_path)
|
|
291
|
+
|
|
253
292
|
try:
|
|
254
293
|
with Image.open(validated_input.file_path) as img:
|
|
255
294
|
img.load()
|
|
256
|
-
return {
|
|
295
|
+
return {"size": img.size, "mode": img.mode}
|
|
257
296
|
except Exception as e:
|
|
258
297
|
raise FileOperationError(f"load: Failed to load image '{file_path}': {e}")
|
|
259
298
|
|
|
@@ -272,15 +311,15 @@ class ImageTool(BaseTool):
|
|
|
272
311
|
FileOperationError: If OCR fails or Tesseract is unavailable.
|
|
273
312
|
"""
|
|
274
313
|
# Validate input using schema
|
|
275
|
-
validated_input =
|
|
276
|
-
|
|
314
|
+
validated_input = self.OcrSchema(file_path=file_path, lang=lang)
|
|
315
|
+
|
|
277
316
|
proc = self._tesseract_manager.get_process()
|
|
278
317
|
if not proc:
|
|
279
318
|
raise FileOperationError(f"ocr: No Tesseract processes available (lang: {lang or 'eng'})")
|
|
280
|
-
with tempfile.NamedTemporaryFile(suffix=
|
|
319
|
+
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as temp_file:
|
|
281
320
|
temp_path = temp_file.name
|
|
282
321
|
try:
|
|
283
|
-
img = Image.open(validated_input.file_path).convert(
|
|
322
|
+
img = Image.open(validated_input.file_path).convert("L").filter(ImageFilter.SHARPEN)
|
|
284
323
|
img.save(temp_path)
|
|
285
324
|
stdout, stderr = proc.communicate(input=temp_path, timeout=30)
|
|
286
325
|
if proc.returncode != 0:
|
|
@@ -296,7 +335,6 @@ class ImageTool(BaseTool):
|
|
|
296
335
|
except Exception as e:
|
|
297
336
|
self.logger.warning(f"Failed to remove temporary file {temp_path}: {e}")
|
|
298
337
|
|
|
299
|
-
|
|
300
338
|
def metadata(self, file_path: str, include_exif: bool = False) -> Dict[str, Any]:
|
|
301
339
|
"""
|
|
302
340
|
Retrieve metadata (size, mode, EXIF) from an image.
|
|
@@ -312,19 +350,19 @@ class ImageTool(BaseTool):
|
|
|
312
350
|
FileOperationError: If metadata extraction fails.
|
|
313
351
|
"""
|
|
314
352
|
# Validate input using schema
|
|
315
|
-
validated_input = MetadataSchema(file_path=file_path, include_exif=include_exif)
|
|
316
|
-
|
|
353
|
+
validated_input = self.MetadataSchema(file_path=file_path, include_exif=include_exif)
|
|
354
|
+
|
|
317
355
|
try:
|
|
318
356
|
with Image.open(validated_input.file_path) as img:
|
|
319
357
|
img.load()
|
|
320
|
-
info = {
|
|
358
|
+
info = {"size": img.size, "mode": img.mode}
|
|
321
359
|
if include_exif:
|
|
322
360
|
exif = {}
|
|
323
361
|
raw = img._getexif() or {}
|
|
324
362
|
for tag, val in raw.items():
|
|
325
363
|
decoded = ExifTags.TAGS.get(tag, tag)
|
|
326
364
|
exif[decoded] = val
|
|
327
|
-
info[
|
|
365
|
+
info["exif"] = exif
|
|
328
366
|
return info
|
|
329
367
|
except Exception as e:
|
|
330
368
|
raise FileOperationError(f"metadata: Failed to process '{file_path}': {e}")
|
|
@@ -346,18 +384,21 @@ class ImageTool(BaseTool):
|
|
|
346
384
|
FileOperationError: If resizing fails.
|
|
347
385
|
"""
|
|
348
386
|
# Validate input using schema
|
|
349
|
-
validated_input = ResizeSchema(
|
|
350
|
-
file_path=file_path,
|
|
351
|
-
output_path=output_path,
|
|
352
|
-
width=width,
|
|
353
|
-
height=height
|
|
387
|
+
validated_input = self.ResizeSchema(
|
|
388
|
+
file_path=file_path,
|
|
389
|
+
output_path=output_path,
|
|
390
|
+
width=width,
|
|
391
|
+
height=height,
|
|
354
392
|
)
|
|
355
|
-
|
|
393
|
+
|
|
356
394
|
try:
|
|
357
395
|
with Image.open(validated_input.file_path) as img:
|
|
358
396
|
img = img.resize((width, height), Image.Resampling.LANCZOS)
|
|
359
397
|
img.save(validated_input.output_path)
|
|
360
|
-
return {
|
|
398
|
+
return {
|
|
399
|
+
"success": True,
|
|
400
|
+
"output_path": validated_input.output_path,
|
|
401
|
+
}
|
|
361
402
|
except Exception as e:
|
|
362
403
|
raise FileOperationError(f"resize: Failed to process '{file_path}' (output_path: {output_path}): {e}")
|
|
363
404
|
|
|
@@ -377,21 +418,24 @@ class ImageTool(BaseTool):
|
|
|
377
418
|
FileOperationError: If filtering fails.
|
|
378
419
|
"""
|
|
379
420
|
# Validate input using schema
|
|
380
|
-
validated_input = FilterSchema(
|
|
381
|
-
file_path=file_path,
|
|
382
|
-
output_path=output_path,
|
|
383
|
-
filter_type=filter_type
|
|
421
|
+
validated_input = self.FilterSchema(
|
|
422
|
+
file_path=file_path,
|
|
423
|
+
output_path=output_path,
|
|
424
|
+
filter_type=filter_type,
|
|
384
425
|
)
|
|
385
|
-
|
|
426
|
+
|
|
386
427
|
try:
|
|
387
428
|
filter_map = {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
429
|
+
"blur": ImageFilter.BLUR,
|
|
430
|
+
"sharpen": ImageFilter.SHARPEN,
|
|
431
|
+
"edge_enhance": ImageFilter.EDGE_ENHANCE,
|
|
391
432
|
}
|
|
392
433
|
with Image.open(validated_input.file_path) as img:
|
|
393
434
|
img = img.filter(filter_map[filter_type])
|
|
394
435
|
img.save(validated_input.output_path)
|
|
395
|
-
return {
|
|
436
|
+
return {
|
|
437
|
+
"success": True,
|
|
438
|
+
"output_path": validated_input.output_path,
|
|
439
|
+
}
|
|
396
440
|
except Exception as e:
|
|
397
441
|
raise FileOperationError(f"filter: Failed to process '{file_path}' (output_path: {output_path}, filter_type: {filter_type}): {e}")
|