aiecs 1.0.0__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 +75 -0
- aiecs/__main__.py +41 -0
- aiecs/aiecs_client.py +295 -0
- aiecs/application/__init__.py +10 -0
- aiecs/application/executors/__init__.py +10 -0
- aiecs/application/executors/operation_executor.py +341 -0
- aiecs/config/__init__.py +15 -0
- aiecs/config/config.py +117 -0
- aiecs/config/registry.py +19 -0
- aiecs/core/__init__.py +46 -0
- aiecs/core/interface/__init__.py +34 -0
- aiecs/core/interface/execution_interface.py +150 -0
- aiecs/core/interface/storage_interface.py +214 -0
- aiecs/domain/__init__.py +20 -0
- aiecs/domain/context/__init__.py +28 -0
- aiecs/domain/context/content_engine.py +982 -0
- aiecs/domain/context/conversation_models.py +306 -0
- aiecs/domain/execution/__init__.py +12 -0
- aiecs/domain/execution/model.py +49 -0
- aiecs/domain/task/__init__.py +13 -0
- aiecs/domain/task/dsl_processor.py +460 -0
- aiecs/domain/task/model.py +50 -0
- aiecs/domain/task/task_context.py +257 -0
- aiecs/infrastructure/__init__.py +26 -0
- aiecs/infrastructure/messaging/__init__.py +13 -0
- aiecs/infrastructure/messaging/celery_task_manager.py +341 -0
- aiecs/infrastructure/messaging/websocket_manager.py +289 -0
- aiecs/infrastructure/monitoring/__init__.py +12 -0
- aiecs/infrastructure/monitoring/executor_metrics.py +138 -0
- aiecs/infrastructure/monitoring/structured_logger.py +50 -0
- aiecs/infrastructure/monitoring/tracing_manager.py +376 -0
- aiecs/infrastructure/persistence/__init__.py +12 -0
- aiecs/infrastructure/persistence/database_manager.py +286 -0
- aiecs/infrastructure/persistence/file_storage.py +671 -0
- aiecs/infrastructure/persistence/redis_client.py +162 -0
- aiecs/llm/__init__.py +54 -0
- aiecs/llm/base_client.py +99 -0
- aiecs/llm/client_factory.py +339 -0
- aiecs/llm/custom_callbacks.py +228 -0
- aiecs/llm/openai_client.py +125 -0
- aiecs/llm/vertex_client.py +186 -0
- aiecs/llm/xai_client.py +184 -0
- aiecs/main.py +351 -0
- aiecs/scripts/DEPENDENCY_SYSTEM_SUMMARY.md +241 -0
- aiecs/scripts/README_DEPENDENCY_CHECKER.md +309 -0
- aiecs/scripts/README_WEASEL_PATCH.md +126 -0
- aiecs/scripts/__init__.py +3 -0
- aiecs/scripts/dependency_checker.py +825 -0
- aiecs/scripts/dependency_fixer.py +348 -0
- aiecs/scripts/download_nlp_data.py +348 -0
- aiecs/scripts/fix_weasel_validator.py +121 -0
- aiecs/scripts/fix_weasel_validator.sh +82 -0
- aiecs/scripts/patch_weasel_library.sh +188 -0
- aiecs/scripts/quick_dependency_check.py +269 -0
- aiecs/scripts/run_weasel_patch.sh +41 -0
- aiecs/scripts/setup_nlp_data.sh +217 -0
- aiecs/tasks/__init__.py +2 -0
- aiecs/tasks/worker.py +111 -0
- aiecs/tools/__init__.py +196 -0
- aiecs/tools/base_tool.py +202 -0
- aiecs/tools/langchain_adapter.py +361 -0
- aiecs/tools/task_tools/__init__.py +82 -0
- aiecs/tools/task_tools/chart_tool.py +704 -0
- aiecs/tools/task_tools/classfire_tool.py +901 -0
- aiecs/tools/task_tools/image_tool.py +397 -0
- aiecs/tools/task_tools/office_tool.py +600 -0
- aiecs/tools/task_tools/pandas_tool.py +565 -0
- aiecs/tools/task_tools/report_tool.py +499 -0
- aiecs/tools/task_tools/research_tool.py +363 -0
- aiecs/tools/task_tools/scraper_tool.py +548 -0
- aiecs/tools/task_tools/search_api.py +7 -0
- aiecs/tools/task_tools/stats_tool.py +513 -0
- aiecs/tools/temp_file_manager.py +126 -0
- aiecs/tools/tool_executor/__init__.py +35 -0
- aiecs/tools/tool_executor/tool_executor.py +518 -0
- aiecs/utils/LLM_output_structor.py +409 -0
- aiecs/utils/__init__.py +23 -0
- aiecs/utils/base_callback.py +50 -0
- aiecs/utils/execution_utils.py +158 -0
- aiecs/utils/logging.py +1 -0
- aiecs/utils/prompt_loader.py +13 -0
- aiecs/utils/token_usage_repository.py +279 -0
- aiecs/ws/__init__.py +0 -0
- aiecs/ws/socket_server.py +41 -0
- aiecs-1.0.0.dist-info/METADATA +610 -0
- aiecs-1.0.0.dist-info/RECORD +90 -0
- aiecs-1.0.0.dist-info/WHEEL +5 -0
- aiecs-1.0.0.dist-info/entry_points.txt +7 -0
- aiecs-1.0.0.dist-info/licenses/LICENSE +225 -0
- aiecs-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Script to run the weasel library patch using poetry
|
|
4
|
+
# This ensures we're working within the correct virtual environment
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
10
|
+
|
|
11
|
+
echo "🔧 Running Weasel Library Patch via Poetry"
|
|
12
|
+
echo "=========================================="
|
|
13
|
+
echo "Project directory: $PROJECT_DIR"
|
|
14
|
+
echo ""
|
|
15
|
+
|
|
16
|
+
# Change to project directory
|
|
17
|
+
cd "$PROJECT_DIR"
|
|
18
|
+
|
|
19
|
+
# Check if pyproject.toml exists
|
|
20
|
+
if [ ! -f "pyproject.toml" ]; then
|
|
21
|
+
echo "❌ Error: pyproject.toml not found. Please run this script from the python-middleware directory"
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Check if poetry is available
|
|
26
|
+
if ! command -v poetry &> /dev/null; then
|
|
27
|
+
echo "❌ Error: Poetry is not installed or not in PATH"
|
|
28
|
+
echo "Please install poetry first: https://python-poetry.org/docs/#installation"
|
|
29
|
+
exit 1
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Run the Python patch script using poetry
|
|
33
|
+
echo "🚀 Running patch script with poetry..."
|
|
34
|
+
poetry run python3 scripts/fix_weasel_validator.py
|
|
35
|
+
|
|
36
|
+
echo ""
|
|
37
|
+
echo "🎉 Patch execution completed!"
|
|
38
|
+
echo ""
|
|
39
|
+
echo "Next steps:"
|
|
40
|
+
echo "1. Try running your tests again with: poetry run pytest"
|
|
41
|
+
echo "2. If the issue persists, you may need to restart your development environment"
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Setup NLP Data for AIECS ClassifierTool
|
|
3
|
+
# This script downloads required NLTK and spaCy data packages
|
|
4
|
+
|
|
5
|
+
set -e # Exit on any error
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
9
|
+
|
|
10
|
+
echo "=================================================="
|
|
11
|
+
echo "AIECS NLP Data Setup"
|
|
12
|
+
echo "=================================================="
|
|
13
|
+
echo "This script will download required NLP data for:"
|
|
14
|
+
echo " - NLTK stopwords corpus"
|
|
15
|
+
echo " - spaCy English model (en_core_web_sm)"
|
|
16
|
+
echo " - spaCy Chinese model (zh_core_web_sm)"
|
|
17
|
+
echo "=================================================="
|
|
18
|
+
echo
|
|
19
|
+
|
|
20
|
+
# Function to check if a command exists
|
|
21
|
+
command_exists() {
|
|
22
|
+
command -v "$1" >/dev/null 2>&1
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
# Function to activate virtual environment if it exists
|
|
26
|
+
activate_venv() {
|
|
27
|
+
if [[ -n "$VIRTUAL_ENV" ]]; then
|
|
28
|
+
echo "✅ Virtual environment already active: $VIRTUAL_ENV"
|
|
29
|
+
return 0
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Check for common virtual environment locations
|
|
33
|
+
local venv_paths=(
|
|
34
|
+
"$PROJECT_ROOT/venv"
|
|
35
|
+
"$PROJECT_ROOT/.venv"
|
|
36
|
+
"$PROJECT_ROOT/env"
|
|
37
|
+
"$PROJECT_ROOT/.env"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
for venv_path in "${venv_paths[@]}"; do
|
|
41
|
+
if [[ -f "$venv_path/bin/activate" ]]; then
|
|
42
|
+
echo "📦 Activating virtual environment: $venv_path"
|
|
43
|
+
source "$venv_path/bin/activate"
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
echo "⚠️ No virtual environment found. Using system Python."
|
|
49
|
+
return 1
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Function to check Python and packages
|
|
53
|
+
check_dependencies() {
|
|
54
|
+
echo "🔍 Checking dependencies..."
|
|
55
|
+
|
|
56
|
+
if ! command_exists python3; then
|
|
57
|
+
echo "❌ Python 3 is not installed or not in PATH"
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
local python_version=$(python3 --version 2>&1 | cut -d' ' -f2)
|
|
62
|
+
echo "✅ Python version: $python_version"
|
|
63
|
+
|
|
64
|
+
# Check if we're in the project directory and can import aiecs
|
|
65
|
+
cd "$PROJECT_ROOT"
|
|
66
|
+
if python3 -c "import aiecs" 2>/dev/null; then
|
|
67
|
+
echo "✅ AIECS package is available"
|
|
68
|
+
else
|
|
69
|
+
echo "⚠️ AIECS package not found. You may need to install it first:"
|
|
70
|
+
echo " pip install -e ."
|
|
71
|
+
fi
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
# Function to run the Python download script
|
|
75
|
+
run_download_script() {
|
|
76
|
+
echo
|
|
77
|
+
echo "🚀 Starting NLP data download..."
|
|
78
|
+
echo
|
|
79
|
+
|
|
80
|
+
cd "$PROJECT_ROOT"
|
|
81
|
+
|
|
82
|
+
# Try multiple ways to run the script
|
|
83
|
+
if python3 -m aiecs.scripts.download_nlp_data; then
|
|
84
|
+
echo "✅ NLP data download completed successfully!"
|
|
85
|
+
return 0
|
|
86
|
+
elif python3 aiecs/scripts/download_nlp_data.py; then
|
|
87
|
+
echo "✅ NLP data download completed successfully!"
|
|
88
|
+
return 0
|
|
89
|
+
else
|
|
90
|
+
echo "❌ Failed to run NLP data download script"
|
|
91
|
+
return 1
|
|
92
|
+
fi
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# Function to verify installation
|
|
96
|
+
verify_installation() {
|
|
97
|
+
echo
|
|
98
|
+
echo "🔍 Verifying NLP data installation..."
|
|
99
|
+
|
|
100
|
+
# Test NLTK
|
|
101
|
+
if python3 -c "
|
|
102
|
+
import nltk
|
|
103
|
+
from nltk.corpus import stopwords
|
|
104
|
+
stopwords.words('english')
|
|
105
|
+
print('✅ NLTK stopwords data available')
|
|
106
|
+
" 2>/dev/null; then
|
|
107
|
+
echo "✅ NLTK verification passed"
|
|
108
|
+
else
|
|
109
|
+
echo "⚠️ NLTK verification failed"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
# Test spaCy English model
|
|
113
|
+
if python3 -c "
|
|
114
|
+
import spacy
|
|
115
|
+
nlp = spacy.load('en_core_web_sm')
|
|
116
|
+
doc = nlp('This is a test.')
|
|
117
|
+
print('✅ spaCy English model available')
|
|
118
|
+
" 2>/dev/null; then
|
|
119
|
+
echo "✅ spaCy English model verification passed"
|
|
120
|
+
else
|
|
121
|
+
echo "⚠️ spaCy English model verification failed"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
# Test spaCy Chinese model (optional)
|
|
125
|
+
if python3 -c "
|
|
126
|
+
import spacy
|
|
127
|
+
nlp = spacy.load('zh_core_web_sm')
|
|
128
|
+
doc = nlp('这是测试。')
|
|
129
|
+
print('✅ spaCy Chinese model available')
|
|
130
|
+
" 2>/dev/null; then
|
|
131
|
+
echo "✅ spaCy Chinese model verification passed"
|
|
132
|
+
else
|
|
133
|
+
echo "⚠️ spaCy Chinese model not available (optional)"
|
|
134
|
+
fi
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Function to display usage information
|
|
138
|
+
show_usage() {
|
|
139
|
+
echo "Usage: $0 [OPTIONS]"
|
|
140
|
+
echo
|
|
141
|
+
echo "Options:"
|
|
142
|
+
echo " -h, --help Show this help message"
|
|
143
|
+
echo " -v, --verify Only verify existing installations"
|
|
144
|
+
echo " --no-venv Skip virtual environment activation"
|
|
145
|
+
echo
|
|
146
|
+
echo "This script downloads required NLP data for AIECS ClassifierTool:"
|
|
147
|
+
echo " - NLTK stopwords corpus"
|
|
148
|
+
echo " - spaCy English model (en_core_web_sm)"
|
|
149
|
+
echo " - spaCy Chinese model (zh_core_web_sm, optional)"
|
|
150
|
+
echo
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
# Parse command line arguments
|
|
154
|
+
VERIFY_ONLY=false
|
|
155
|
+
SKIP_VENV=false
|
|
156
|
+
|
|
157
|
+
while [[ $# -gt 0 ]]; do
|
|
158
|
+
case $1 in
|
|
159
|
+
-h|--help)
|
|
160
|
+
show_usage
|
|
161
|
+
exit 0
|
|
162
|
+
;;
|
|
163
|
+
-v|--verify)
|
|
164
|
+
VERIFY_ONLY=true
|
|
165
|
+
shift
|
|
166
|
+
;;
|
|
167
|
+
--no-venv)
|
|
168
|
+
SKIP_VENV=true
|
|
169
|
+
shift
|
|
170
|
+
;;
|
|
171
|
+
*)
|
|
172
|
+
echo "Unknown option: $1"
|
|
173
|
+
show_usage
|
|
174
|
+
exit 1
|
|
175
|
+
;;
|
|
176
|
+
esac
|
|
177
|
+
done
|
|
178
|
+
|
|
179
|
+
# Main execution
|
|
180
|
+
main() {
|
|
181
|
+
echo "📍 Project root: $PROJECT_ROOT"
|
|
182
|
+
echo
|
|
183
|
+
|
|
184
|
+
# Activate virtual environment if available and not skipped
|
|
185
|
+
if [[ "$SKIP_VENV" != true ]]; then
|
|
186
|
+
activate_venv || true
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
# Check dependencies
|
|
190
|
+
check_dependencies
|
|
191
|
+
|
|
192
|
+
if [[ "$VERIFY_ONLY" == true ]]; then
|
|
193
|
+
echo
|
|
194
|
+
echo "🔍 Running verification only..."
|
|
195
|
+
verify_installation
|
|
196
|
+
else
|
|
197
|
+
# Download NLP data
|
|
198
|
+
if run_download_script; then
|
|
199
|
+
verify_installation
|
|
200
|
+
echo
|
|
201
|
+
echo "🎉 NLP data setup completed successfully!"
|
|
202
|
+
echo "AIECS ClassifierTool is ready to use."
|
|
203
|
+
else
|
|
204
|
+
echo
|
|
205
|
+
echo "❌ NLP data setup failed. Please check the errors above."
|
|
206
|
+
exit 1
|
|
207
|
+
fi
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
echo
|
|
211
|
+
echo "=================================================="
|
|
212
|
+
echo "Setup complete!"
|
|
213
|
+
echo "=================================================="
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
# Run main function
|
|
217
|
+
main "$@"
|
aiecs/tasks/__init__.py
ADDED
aiecs/tasks/worker.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
from celery import Celery
|
|
2
|
+
from aiecs.config.config import get_settings
|
|
3
|
+
from aiecs.domain.task.task_context import build_context
|
|
4
|
+
from aiecs.config.registry import get_ai_service
|
|
5
|
+
from aiecs.ws.socket_server import push_progress
|
|
6
|
+
import logging
|
|
7
|
+
import json
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Dict, Any, Optional
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
settings = get_settings()
|
|
14
|
+
celery_app = Celery("ai_worker", broker=settings.celery_broker_url)
|
|
15
|
+
|
|
16
|
+
# Configure Celery
|
|
17
|
+
celery_app.conf.update(
|
|
18
|
+
task_serializer='json',
|
|
19
|
+
accept_content=['json'],
|
|
20
|
+
result_serializer='json',
|
|
21
|
+
timezone='UTC',
|
|
22
|
+
enable_utc=True,
|
|
23
|
+
task_queues={
|
|
24
|
+
'fast_tasks': {'exchange': 'fast_tasks', 'routing_key': 'fast_tasks'},
|
|
25
|
+
'heavy_tasks': {'exchange': 'heavy_tasks', 'routing_key': 'heavy_tasks'}
|
|
26
|
+
},
|
|
27
|
+
task_routes={
|
|
28
|
+
'aiecs.tasks.worker.execute_task': {'queue': 'fast_tasks'},
|
|
29
|
+
'aiecs.tasks.worker.execute_heavy_task': {'queue': 'heavy_tasks'}
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from aiecs.domain.execution.model import TaskStatus
|
|
34
|
+
|
|
35
|
+
@celery_app.task(bind=True, name="aiecs.tasks.worker.execute_task")
|
|
36
|
+
def execute_task(self, task_name: str, user_id: str, task_id: str, step: int,
|
|
37
|
+
mode: str, service: str, input_data: Dict[str, Any], context: Dict[str, Any]):
|
|
38
|
+
"""
|
|
39
|
+
Execute a fast task from the service executor queue.
|
|
40
|
+
This task is used for operations that should complete quickly.
|
|
41
|
+
"""
|
|
42
|
+
logger.info(f"Executing fast task: {task_name} for user {user_id}, task {task_id}, step {step}")
|
|
43
|
+
return _execute_service_task(self, task_name, user_id, task_id, step, mode, service, input_data, context)
|
|
44
|
+
|
|
45
|
+
@celery_app.task(bind=True, name="aiecs.tasks.worker.execute_heavy_task")
|
|
46
|
+
def execute_heavy_task(self, task_name: str, user_id: str, task_id: str, step: int,
|
|
47
|
+
mode: str, service: str, input_data: Dict[str, Any], context: Dict[str, Any]):
|
|
48
|
+
"""
|
|
49
|
+
Execute a heavy task from the service executor queue.
|
|
50
|
+
This task is used for operations that may take longer to complete.
|
|
51
|
+
"""
|
|
52
|
+
logger.info(f"Executing heavy task: {task_name} for user {user_id}, task {task_id}, step {step}")
|
|
53
|
+
return _execute_service_task(self, task_name, user_id, task_id, step, mode, service, input_data, context)
|
|
54
|
+
|
|
55
|
+
def _execute_service_task(self, task_name: str, user_id: str, task_id: str, step: int,
|
|
56
|
+
mode: str, service: str, input_data: Dict[str, Any], context: Dict[str, Any]):
|
|
57
|
+
"""
|
|
58
|
+
Common implementation for executing both fast and heavy tasks.
|
|
59
|
+
This function handles the actual task execution logic.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
# 1. Push started status
|
|
63
|
+
push_progress(user_id, {
|
|
64
|
+
"status": TaskStatus.RUNNING.value,
|
|
65
|
+
"step": step,
|
|
66
|
+
"task": task_name,
|
|
67
|
+
"message": f"Executing task: {task_name}"
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
# 2. Get the service instance
|
|
71
|
+
service_cls = get_ai_service(mode, service)
|
|
72
|
+
service_instance = service_cls()
|
|
73
|
+
|
|
74
|
+
# 3. Execute the task
|
|
75
|
+
if hasattr(service_instance, task_name) and callable(getattr(service_instance, task_name)):
|
|
76
|
+
method = getattr(service_instance, task_name)
|
|
77
|
+
result = method(input_data, context)
|
|
78
|
+
else:
|
|
79
|
+
# Fallback to a generic execution method if the specific task method doesn't exist
|
|
80
|
+
result = service_instance.execute_task(task_name, input_data, context)
|
|
81
|
+
|
|
82
|
+
# 4. Push completed status
|
|
83
|
+
push_progress(user_id, {
|
|
84
|
+
"status": TaskStatus.COMPLETED.value,
|
|
85
|
+
"step": step,
|
|
86
|
+
"task": task_name,
|
|
87
|
+
"result": result,
|
|
88
|
+
"message": f"Completed task: {task_name}"
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
"status": TaskStatus.COMPLETED.value,
|
|
93
|
+
"task": task_name,
|
|
94
|
+
"result": result
|
|
95
|
+
}
|
|
96
|
+
except Exception as e:
|
|
97
|
+
logger.error(f"Error executing task {task_name}: {str(e)}", exc_info=True)
|
|
98
|
+
# Push error status
|
|
99
|
+
push_progress(user_id, {
|
|
100
|
+
"status": TaskStatus.FAILED.value,
|
|
101
|
+
"step": step,
|
|
102
|
+
"task": task_name,
|
|
103
|
+
"error": str(e),
|
|
104
|
+
"message": f"Failed to execute task: {task_name}"
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
"status": TaskStatus.FAILED.value,
|
|
109
|
+
"task": task_name,
|
|
110
|
+
"error": str(e)
|
|
111
|
+
}
|
aiecs/tools/__init__.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# python-middleware/app/tools/__init__.py
|
|
2
|
+
|
|
3
|
+
import importlib
|
|
4
|
+
import inspect
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import pkgutil
|
|
8
|
+
from typing import Any, Dict, List, Optional, Type
|
|
9
|
+
|
|
10
|
+
from aiecs.tools.base_tool import BaseTool
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# 全局工具注册表
|
|
15
|
+
TOOL_REGISTRY = {}
|
|
16
|
+
TOOL_CLASSES = {}
|
|
17
|
+
TOOL_CONFIGS = {}
|
|
18
|
+
|
|
19
|
+
def register_tool(name):
|
|
20
|
+
"""
|
|
21
|
+
装饰器,用于注册工具类
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
name: 工具名称
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
装饰后的类
|
|
28
|
+
"""
|
|
29
|
+
def wrapper(cls):
|
|
30
|
+
# 存储工具类,但不立即实例化
|
|
31
|
+
TOOL_CLASSES[name] = cls
|
|
32
|
+
# 兼容旧版本:如果类继承自BaseTool,则不立即实例化
|
|
33
|
+
if not issubclass(cls, BaseTool):
|
|
34
|
+
TOOL_REGISTRY[name] = cls()
|
|
35
|
+
return cls
|
|
36
|
+
return wrapper
|
|
37
|
+
|
|
38
|
+
def get_tool(name):
|
|
39
|
+
"""
|
|
40
|
+
获取工具实例
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
name: 工具名称
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
工具实例
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
ValueError: 如果工具未注册
|
|
50
|
+
"""
|
|
51
|
+
if name not in TOOL_REGISTRY and name in TOOL_CLASSES:
|
|
52
|
+
# 延迟实例化BaseTool子类
|
|
53
|
+
tool_class = TOOL_CLASSES[name]
|
|
54
|
+
config = TOOL_CONFIGS.get(name, {})
|
|
55
|
+
TOOL_REGISTRY[name] = tool_class(config)
|
|
56
|
+
|
|
57
|
+
if name not in TOOL_REGISTRY:
|
|
58
|
+
raise ValueError(f"Tool '{name}' is not registered")
|
|
59
|
+
|
|
60
|
+
return TOOL_REGISTRY[name]
|
|
61
|
+
|
|
62
|
+
def list_tools():
|
|
63
|
+
"""
|
|
64
|
+
列出所有已注册的工具
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
工具信息字典列表
|
|
68
|
+
"""
|
|
69
|
+
tools = []
|
|
70
|
+
all_tool_names = list(set(list(TOOL_REGISTRY.keys()) + list(TOOL_CLASSES.keys())))
|
|
71
|
+
|
|
72
|
+
for tool_name in all_tool_names:
|
|
73
|
+
try:
|
|
74
|
+
# 优先使用已有实例的信息
|
|
75
|
+
if tool_name in TOOL_REGISTRY:
|
|
76
|
+
tool_instance = TOOL_REGISTRY[tool_name]
|
|
77
|
+
tool_info = {
|
|
78
|
+
"name": tool_name,
|
|
79
|
+
"description": getattr(tool_instance, 'description', f'{tool_name} tool'),
|
|
80
|
+
"category": getattr(tool_instance, 'category', 'general'),
|
|
81
|
+
"class_name": tool_instance.__class__.__name__,
|
|
82
|
+
"module": tool_instance.__class__.__module__,
|
|
83
|
+
"status": "loaded"
|
|
84
|
+
}
|
|
85
|
+
elif tool_name in TOOL_CLASSES:
|
|
86
|
+
# 从类定义获取信息,但不实例化
|
|
87
|
+
tool_class = TOOL_CLASSES[tool_name]
|
|
88
|
+
tool_info = {
|
|
89
|
+
"name": tool_name,
|
|
90
|
+
"description": getattr(tool_class, 'description', f'{tool_name} tool'),
|
|
91
|
+
"category": getattr(tool_class, 'category', 'general'),
|
|
92
|
+
"class_name": tool_class.__name__,
|
|
93
|
+
"module": tool_class.__module__,
|
|
94
|
+
"status": "available"
|
|
95
|
+
}
|
|
96
|
+
else:
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
tools.append(tool_info)
|
|
100
|
+
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.warning(f"Failed to get info for tool {tool_name}: {e}")
|
|
103
|
+
# 提供基本信息
|
|
104
|
+
tools.append({
|
|
105
|
+
"name": tool_name,
|
|
106
|
+
"description": f"{tool_name} (info unavailable)",
|
|
107
|
+
"category": "unknown",
|
|
108
|
+
"class_name": "Unknown",
|
|
109
|
+
"module": "unknown",
|
|
110
|
+
"status": "error"
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return tools
|
|
114
|
+
|
|
115
|
+
def discover_tools(package_path: str = "aiecs.tools"):
|
|
116
|
+
"""
|
|
117
|
+
发现并注册包中的所有工具
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
package_path: 要搜索的包路径
|
|
121
|
+
"""
|
|
122
|
+
package = importlib.import_module(package_path)
|
|
123
|
+
package_dir = os.path.dirname(package.__file__)
|
|
124
|
+
|
|
125
|
+
for _, module_name, is_pkg in pkgutil.iter_modules([package_dir]):
|
|
126
|
+
if is_pkg:
|
|
127
|
+
# 递归搜索子包中的工具
|
|
128
|
+
discover_tools(f"{package_path}.{module_name}")
|
|
129
|
+
else:
|
|
130
|
+
# 导入模块
|
|
131
|
+
try:
|
|
132
|
+
importlib.import_module(f"{package_path}.{module_name}")
|
|
133
|
+
except Exception as e:
|
|
134
|
+
logger.error(f"Error importing module {module_name}: {e}")
|
|
135
|
+
|
|
136
|
+
# 导入基础工具类供继承使用
|
|
137
|
+
from aiecs.tools.base_tool import BaseTool
|
|
138
|
+
|
|
139
|
+
# Lazy loading strategy: don't import all tools at package init
|
|
140
|
+
# Tools will be loaded on-demand when requested
|
|
141
|
+
|
|
142
|
+
def _ensure_task_tools_available():
|
|
143
|
+
"""Ensure task_tools module is available for lazy loading"""
|
|
144
|
+
try:
|
|
145
|
+
from . import task_tools
|
|
146
|
+
return True
|
|
147
|
+
except ImportError as e:
|
|
148
|
+
logger.error(f"Failed to import task_tools: {e}")
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
def _register_known_tools():
|
|
152
|
+
"""Register known tools without importing heavy dependencies"""
|
|
153
|
+
# Pre-register tool classes for discovery without importing modules
|
|
154
|
+
# This allows list_tools() to work before actual tool loading
|
|
155
|
+
|
|
156
|
+
known_tools = [
|
|
157
|
+
("chart_tool", "Chart and visualization operations"),
|
|
158
|
+
("classfire_tool", "Text classification and keyword extraction"),
|
|
159
|
+
("image_tool", "Image processing and OCR operations"),
|
|
160
|
+
("office_tool", "Office document processing"),
|
|
161
|
+
("pandas_tool", "Data analysis and manipulation"),
|
|
162
|
+
("report_tool", "Report generation and formatting"),
|
|
163
|
+
("research_tool", "Research and information gathering"),
|
|
164
|
+
("scraper_tool", "Web scraping and data extraction"),
|
|
165
|
+
("search_api", "Search API integration"),
|
|
166
|
+
("stats_tool", "Statistical analysis and computation")
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
# Register as placeholder until actually loaded
|
|
170
|
+
for tool_name, description in known_tools:
|
|
171
|
+
if tool_name not in TOOL_REGISTRY and tool_name not in TOOL_CLASSES:
|
|
172
|
+
# Create a placeholder class for discovery
|
|
173
|
+
class ToolPlaceholder:
|
|
174
|
+
def __init__(self, name, desc):
|
|
175
|
+
self.name = name
|
|
176
|
+
self.description = desc
|
|
177
|
+
self.category = "task"
|
|
178
|
+
self.is_placeholder = True
|
|
179
|
+
|
|
180
|
+
TOOL_REGISTRY[tool_name] = ToolPlaceholder(tool_name, description)
|
|
181
|
+
|
|
182
|
+
# Register known tools for discovery
|
|
183
|
+
_register_known_tools()
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
from . import db_api
|
|
187
|
+
except ImportError:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
from . import vector_search
|
|
192
|
+
except ImportError:
|
|
193
|
+
pass
|
|
194
|
+
|
|
195
|
+
# Don't auto-discover tools at import time for performance
|
|
196
|
+
# Tools will be discovered when explicitly requested via discover_tools() call
|