aiecs 1.0.7__py3-none-any.whl → 1.1.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 +1 -1
- aiecs/aiecs_client.py +159 -1
- aiecs/config/config.py +4 -0
- aiecs/domain/context/__init__.py +24 -0
- aiecs/infrastructure/persistence/context_engine_client.py +9 -5
- aiecs/main.py +20 -2
- aiecs/scripts/dependance_check/__init__.py +18 -0
- aiecs/scripts/{download_nlp_data.py → dependance_check/download_nlp_data.py} +50 -8
- aiecs/scripts/dependance_patch/__init__.py +8 -0
- aiecs/scripts/dependance_patch/fix_weasel/__init__.py +12 -0
- aiecs/scripts/tools_develop/README.md +340 -0
- aiecs/scripts/tools_develop/__init__.py +16 -0
- aiecs/scripts/tools_develop/check_type_annotations.py +263 -0
- aiecs/scripts/tools_develop/validate_tool_schemas.py +346 -0
- aiecs/tools/__init__.py +33 -14
- aiecs/tools/docs/__init__.py +103 -0
- aiecs/tools/docs/ai_document_orchestrator.py +543 -0
- aiecs/tools/docs/ai_document_writer_orchestrator.py +2199 -0
- aiecs/tools/docs/content_insertion_tool.py +1214 -0
- aiecs/tools/docs/document_creator_tool.py +1161 -0
- aiecs/tools/docs/document_layout_tool.py +1090 -0
- aiecs/tools/docs/document_parser_tool.py +904 -0
- aiecs/tools/docs/document_writer_tool.py +1583 -0
- aiecs/tools/langchain_adapter.py +102 -51
- aiecs/tools/schema_generator.py +265 -0
- aiecs/tools/task_tools/image_tool.py +1 -1
- aiecs/tools/task_tools/office_tool.py +9 -0
- aiecs/tools/task_tools/scraper_tool.py +1 -1
- {aiecs-1.0.7.dist-info → aiecs-1.1.0.dist-info}/METADATA +1 -1
- {aiecs-1.0.7.dist-info → aiecs-1.1.0.dist-info}/RECORD +45 -29
- aiecs-1.1.0.dist-info/entry_points.txt +9 -0
- aiecs-1.0.7.dist-info/entry_points.txt +0 -7
- /aiecs/scripts/{DEPENDENCY_SYSTEM_SUMMARY.md → dependance_check/DEPENDENCY_SYSTEM_SUMMARY.md} +0 -0
- /aiecs/scripts/{README_DEPENDENCY_CHECKER.md → dependance_check/README_DEPENDENCY_CHECKER.md} +0 -0
- /aiecs/scripts/{dependency_checker.py → dependance_check/dependency_checker.py} +0 -0
- /aiecs/scripts/{dependency_fixer.py → dependance_check/dependency_fixer.py} +0 -0
- /aiecs/scripts/{quick_dependency_check.py → dependance_check/quick_dependency_check.py} +0 -0
- /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.py → dependance_patch/fix_weasel/fix_weasel_validator.py} +0 -0
- /aiecs/scripts/{fix_weasel_validator.sh → dependance_patch/fix_weasel/fix_weasel_validator.sh} +0 -0
- /aiecs/scripts/{patch_weasel_library.sh → dependance_patch/fix_weasel/patch_weasel_library.sh} +0 -0
- /aiecs/scripts/{run_weasel_patch.sh → dependance_patch/fix_weasel/run_weasel_patch.sh} +0 -0
- {aiecs-1.0.7.dist-info → aiecs-1.1.0.dist-info}/WHEEL +0 -0
- {aiecs-1.0.7.dist-info → aiecs-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {aiecs-1.0.7.dist-info → aiecs-1.1.0.dist-info}/top_level.txt +0 -0
aiecs/tools/langchain_adapter.py
CHANGED
|
@@ -13,6 +13,9 @@ import logging
|
|
|
13
13
|
from typing import Any, Dict, List, Optional, Type, Union, get_type_hints
|
|
14
14
|
from pydantic import BaseModel, Field
|
|
15
15
|
|
|
16
|
+
# Import schema generator
|
|
17
|
+
from aiecs.tools.schema_generator import generate_schema_from_method
|
|
18
|
+
|
|
16
19
|
try:
|
|
17
20
|
from langchain.tools import BaseTool as LangchainBaseTool
|
|
18
21
|
from langchain.callbacks.manager import CallbackManagerForToolRun, AsyncCallbackManagerForToolRun
|
|
@@ -33,24 +36,27 @@ logger = logging.getLogger(__name__)
|
|
|
33
36
|
class LangchainToolAdapter(LangchainBaseTool):
|
|
34
37
|
"""
|
|
35
38
|
Langchain tool adapter for single operation
|
|
36
|
-
|
|
39
|
+
|
|
37
40
|
Wraps one operation method of BaseTool as an independent Langchain tool
|
|
38
41
|
"""
|
|
39
|
-
|
|
42
|
+
|
|
40
43
|
# Define class attributes
|
|
41
44
|
name: str = ""
|
|
42
45
|
description: str = ""
|
|
43
|
-
|
|
46
|
+
base_tool_name: str = ""
|
|
47
|
+
operation_name: str = ""
|
|
48
|
+
operation_schema: Optional[Type[BaseModel]] = None
|
|
49
|
+
|
|
44
50
|
def __init__(
|
|
45
|
-
self,
|
|
51
|
+
self,
|
|
46
52
|
base_tool_name: str,
|
|
47
|
-
operation_name: str,
|
|
53
|
+
operation_name: str,
|
|
48
54
|
operation_schema: Optional[Type[BaseModel]] = None,
|
|
49
55
|
description: Optional[str] = None
|
|
50
56
|
):
|
|
51
57
|
"""
|
|
52
58
|
Initialize adapter
|
|
53
|
-
|
|
59
|
+
|
|
54
60
|
Args:
|
|
55
61
|
base_tool_name: Original tool name
|
|
56
62
|
operation_name: Operation name
|
|
@@ -58,56 +64,55 @@ class LangchainToolAdapter(LangchainBaseTool):
|
|
|
58
64
|
description: Tool description
|
|
59
65
|
"""
|
|
60
66
|
# Construct tool name and description
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
super().__init__()
|
|
67
|
+
tool_name = f"{base_tool_name}_{operation_name}"
|
|
68
|
+
tool_description = description or f"Execute {operation_name} operation from {base_tool_name} tool"
|
|
69
|
+
|
|
70
|
+
# Initialize parent class with all required fields
|
|
71
|
+
super().__init__(
|
|
72
|
+
name=tool_name,
|
|
73
|
+
description=tool_description,
|
|
74
|
+
base_tool_name=base_tool_name,
|
|
75
|
+
operation_name=operation_name,
|
|
76
|
+
operation_schema=operation_schema,
|
|
77
|
+
args_schema=operation_schema
|
|
78
|
+
)
|
|
74
79
|
|
|
75
80
|
def _run(
|
|
76
|
-
self,
|
|
77
|
-
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
81
|
+
self,
|
|
82
|
+
run_manager: Optional[CallbackManagerForToolRun] = None,
|
|
78
83
|
**kwargs: Any
|
|
79
84
|
) -> Any:
|
|
80
85
|
"""Execute operation synchronously"""
|
|
81
86
|
try:
|
|
82
87
|
# Get original tool instance
|
|
83
|
-
base_tool = get_tool(self.
|
|
84
|
-
|
|
88
|
+
base_tool = get_tool(self.base_tool_name)
|
|
89
|
+
|
|
85
90
|
# Execute operation
|
|
86
|
-
result = base_tool.run(self.
|
|
87
|
-
|
|
91
|
+
result = base_tool.run(self.operation_name, **kwargs)
|
|
92
|
+
|
|
88
93
|
logger.info(f"Successfully executed {self.name} with result type: {type(result)}")
|
|
89
94
|
return result
|
|
90
|
-
|
|
95
|
+
|
|
91
96
|
except Exception as e:
|
|
92
97
|
logger.error(f"Error executing {self.name}: {str(e)}")
|
|
93
98
|
raise
|
|
94
|
-
|
|
99
|
+
|
|
95
100
|
async def _arun(
|
|
96
|
-
self,
|
|
101
|
+
self,
|
|
97
102
|
run_manager: Optional[AsyncCallbackManagerForToolRun] = None,
|
|
98
103
|
**kwargs: Any
|
|
99
104
|
) -> Any:
|
|
100
105
|
"""Execute operation asynchronously"""
|
|
101
106
|
try:
|
|
102
107
|
# Get original tool instance
|
|
103
|
-
base_tool = get_tool(self.
|
|
104
|
-
|
|
108
|
+
base_tool = get_tool(self.base_tool_name)
|
|
109
|
+
|
|
105
110
|
# Execute asynchronous operation
|
|
106
|
-
result = await base_tool.run_async(self.
|
|
107
|
-
|
|
111
|
+
result = await base_tool.run_async(self.operation_name, **kwargs)
|
|
112
|
+
|
|
108
113
|
logger.info(f"Successfully executed {self.name} async with result type: {type(result)}")
|
|
109
114
|
return result
|
|
110
|
-
|
|
115
|
+
|
|
111
116
|
except Exception as e:
|
|
112
117
|
logger.error(f"Error executing {self.name} async: {str(e)}")
|
|
113
118
|
raise
|
|
@@ -121,47 +126,90 @@ class ToolRegistry:
|
|
|
121
126
|
def discover_operations(self, base_tool_class: Type[BaseTool]) -> List[Dict[str, Any]]:
|
|
122
127
|
"""
|
|
123
128
|
Discover all operation methods and Schemas of BaseTool class
|
|
124
|
-
|
|
129
|
+
|
|
125
130
|
Args:
|
|
126
131
|
base_tool_class: BaseTool subclass
|
|
127
|
-
|
|
132
|
+
|
|
128
133
|
Returns:
|
|
129
134
|
List of operation information, including method names, Schemas, descriptions, etc.
|
|
130
135
|
"""
|
|
131
136
|
operations = []
|
|
132
|
-
|
|
137
|
+
|
|
133
138
|
# Get all Schema classes
|
|
139
|
+
# Build a mapping from normalized names to Schema classes
|
|
140
|
+
# Check both class-level and module-level schemas
|
|
134
141
|
schemas = {}
|
|
142
|
+
|
|
143
|
+
# 1. Check class-level schemas (e.g., ChartTool)
|
|
135
144
|
for attr_name in dir(base_tool_class):
|
|
136
145
|
attr = getattr(base_tool_class, attr_name)
|
|
137
146
|
if isinstance(attr, type) and issubclass(attr, BaseModel) and attr.__name__.endswith('Schema'):
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
# Normalize: remove 'Schema' suffix, convert to lowercase, remove underscores
|
|
148
|
+
schema_base_name = attr.__name__.replace('Schema', '')
|
|
149
|
+
normalized_name = schema_base_name.replace('_', '').lower()
|
|
150
|
+
schemas[normalized_name] = attr
|
|
151
|
+
logger.debug(f"Found class-level schema {attr.__name__} -> normalized: {normalized_name}")
|
|
152
|
+
|
|
153
|
+
# 2. Check module-level schemas (e.g., ImageTool)
|
|
154
|
+
tool_module = inspect.getmodule(base_tool_class)
|
|
155
|
+
if tool_module:
|
|
156
|
+
for attr_name in dir(tool_module):
|
|
157
|
+
if attr_name.startswith('_'):
|
|
158
|
+
continue
|
|
159
|
+
attr = getattr(tool_module, attr_name)
|
|
160
|
+
if isinstance(attr, type) and issubclass(attr, BaseModel) and attr.__name__.endswith('Schema'):
|
|
161
|
+
# Skip if already found at class level
|
|
162
|
+
schema_base_name = attr.__name__.replace('Schema', '')
|
|
163
|
+
normalized_name = schema_base_name.replace('_', '').lower()
|
|
164
|
+
if normalized_name not in schemas:
|
|
165
|
+
schemas[normalized_name] = attr
|
|
166
|
+
logger.debug(f"Found module-level schema {attr.__name__} -> normalized: {normalized_name}")
|
|
167
|
+
|
|
141
168
|
# Get all public methods
|
|
142
169
|
for method_name in dir(base_tool_class):
|
|
143
170
|
if method_name.startswith('_'):
|
|
144
171
|
continue
|
|
145
|
-
|
|
172
|
+
|
|
146
173
|
method = getattr(base_tool_class, method_name)
|
|
147
174
|
if not callable(method):
|
|
148
175
|
continue
|
|
149
|
-
|
|
150
|
-
# Skip base class methods
|
|
176
|
+
|
|
177
|
+
# Skip base class methods and Schema classes themselves
|
|
151
178
|
if method_name in ['run', 'run_async', 'run_batch']:
|
|
152
179
|
continue
|
|
153
|
-
|
|
180
|
+
|
|
181
|
+
# Skip if it's a class (like Config or Schema classes)
|
|
182
|
+
if isinstance(method, type):
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
# Normalize method name: remove underscores and convert to lowercase
|
|
186
|
+
normalized_method_name = method_name.replace('_', '').lower()
|
|
187
|
+
|
|
188
|
+
# Try to find matching schema
|
|
189
|
+
matching_schema = schemas.get(normalized_method_name)
|
|
190
|
+
|
|
191
|
+
if matching_schema:
|
|
192
|
+
logger.debug(f"Matched method {method_name} with manual schema {matching_schema.__name__}")
|
|
193
|
+
else:
|
|
194
|
+
# Auto-generate schema if not found
|
|
195
|
+
auto_schema = generate_schema_from_method(method, method_name)
|
|
196
|
+
if auto_schema:
|
|
197
|
+
matching_schema = auto_schema
|
|
198
|
+
logger.debug(f"Auto-generated schema for method {method_name}: {auto_schema.__name__}")
|
|
199
|
+
else:
|
|
200
|
+
logger.debug(f"No schema found or generated for method {method_name}")
|
|
201
|
+
|
|
154
202
|
# Get method information
|
|
155
203
|
operation_info = {
|
|
156
204
|
'name': method_name,
|
|
157
205
|
'method': method,
|
|
158
|
-
'schema':
|
|
206
|
+
'schema': matching_schema,
|
|
159
207
|
'description': inspect.getdoc(method) or f"Execute {method_name} operation",
|
|
160
208
|
'is_async': inspect.iscoroutinefunction(method)
|
|
161
209
|
}
|
|
162
|
-
|
|
210
|
+
|
|
163
211
|
operations.append(operation_info)
|
|
164
|
-
|
|
212
|
+
|
|
165
213
|
return operations
|
|
166
214
|
|
|
167
215
|
def _extract_description(self, method, base_tool_name: str, operation_name: str, schema: Optional[Type[BaseModel]] = None) -> str:
|
|
@@ -255,20 +303,23 @@ class ToolRegistry:
|
|
|
255
303
|
def create_all_langchain_tools(self) -> List[LangchainToolAdapter]:
|
|
256
304
|
"""
|
|
257
305
|
Create Langchain adapters for all registered BaseTools
|
|
258
|
-
|
|
306
|
+
|
|
259
307
|
Returns:
|
|
260
308
|
List of all Langchain tool adapters
|
|
261
309
|
"""
|
|
262
310
|
all_tools = []
|
|
263
|
-
|
|
264
|
-
|
|
311
|
+
|
|
312
|
+
# list_tools() returns a list of dicts, extract tool names
|
|
313
|
+
tool_infos = list_tools()
|
|
314
|
+
for tool_info in tool_infos:
|
|
315
|
+
tool_name = tool_info['name']
|
|
265
316
|
try:
|
|
266
317
|
tools = self.create_langchain_tools(tool_name)
|
|
267
318
|
all_tools.extend(tools)
|
|
268
319
|
except Exception as e:
|
|
269
320
|
logger.error(f"Failed to create Langchain tools for {tool_name}: {e}")
|
|
270
|
-
|
|
271
|
-
logger.info(f"Created total {len(all_tools)} Langchain tools from {len(
|
|
321
|
+
|
|
322
|
+
logger.info(f"Created total {len(all_tools)} Langchain tools from {len(tool_infos)} base tools")
|
|
272
323
|
return all_tools
|
|
273
324
|
|
|
274
325
|
def get_tool(self, name: str) -> Optional[LangchainToolAdapter]:
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"""
|
|
2
|
+
自动 Schema 生成工具
|
|
3
|
+
|
|
4
|
+
从方法签名和类型注解自动生成 Pydantic Schema
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import inspect
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Any, Dict, List, Optional, Type, get_type_hints, Union
|
|
10
|
+
from pydantic import BaseModel, Field, create_model, ConfigDict
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _normalize_type(param_type: Type) -> Type:
|
|
16
|
+
"""
|
|
17
|
+
标准化类型,处理不支持的类型
|
|
18
|
+
|
|
19
|
+
将 pandas.DataFrame 等复杂类型映射为 Any
|
|
20
|
+
"""
|
|
21
|
+
# 获取类型名称
|
|
22
|
+
type_name = getattr(param_type, '__name__', str(param_type))
|
|
23
|
+
|
|
24
|
+
# 检查是否是 pandas 类型
|
|
25
|
+
if 'DataFrame' in type_name or 'Series' in type_name:
|
|
26
|
+
return Any
|
|
27
|
+
|
|
28
|
+
return param_type
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _extract_param_description_from_docstring(docstring: str, param_name: str) -> Optional[str]:
|
|
32
|
+
"""
|
|
33
|
+
从文档字符串中提取参数描述
|
|
34
|
+
|
|
35
|
+
支持格式:
|
|
36
|
+
- Google style: Args: param_name: description
|
|
37
|
+
- NumPy style: Parameters: param_name : type description
|
|
38
|
+
"""
|
|
39
|
+
if not docstring:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
lines = docstring.split('\n')
|
|
43
|
+
in_args_section = False
|
|
44
|
+
current_param = None
|
|
45
|
+
description_lines = []
|
|
46
|
+
|
|
47
|
+
for line in lines:
|
|
48
|
+
stripped = line.strip()
|
|
49
|
+
|
|
50
|
+
# 检测 Args/Parameters 部分
|
|
51
|
+
if stripped in ['Args:', 'Arguments:', 'Parameters:']:
|
|
52
|
+
in_args_section = True
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
# 检测结束
|
|
56
|
+
if in_args_section and stripped in ['Returns:', 'Raises:', 'Yields:', 'Examples:', 'Note:', 'Notes:']:
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
if in_args_section:
|
|
60
|
+
# Google style: param_name: description 或 param_name (type): description
|
|
61
|
+
if ':' in stripped and not stripped.startswith(' '):
|
|
62
|
+
# 保存之前的参数
|
|
63
|
+
if current_param == param_name and description_lines:
|
|
64
|
+
return ' '.join(description_lines).strip()
|
|
65
|
+
|
|
66
|
+
# 解析新参数
|
|
67
|
+
parts = stripped.split(':', 1)
|
|
68
|
+
if len(parts) == 2:
|
|
69
|
+
# 移除可能的类型注解 (type)
|
|
70
|
+
param_part = parts[0].strip()
|
|
71
|
+
if '(' in param_part:
|
|
72
|
+
param_part = param_part.split('(')[0].strip()
|
|
73
|
+
|
|
74
|
+
current_param = param_part
|
|
75
|
+
description_lines = [parts[1].strip()]
|
|
76
|
+
elif current_param and stripped:
|
|
77
|
+
# 继续描述
|
|
78
|
+
description_lines.append(stripped)
|
|
79
|
+
|
|
80
|
+
# 检查最后一个参数
|
|
81
|
+
if current_param == param_name and description_lines:
|
|
82
|
+
return ' '.join(description_lines).strip()
|
|
83
|
+
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def generate_schema_from_method(
|
|
88
|
+
method: callable,
|
|
89
|
+
method_name: str,
|
|
90
|
+
base_class: Type[BaseModel] = BaseModel
|
|
91
|
+
) -> Optional[Type[BaseModel]]:
|
|
92
|
+
"""
|
|
93
|
+
从方法签名自动生成 Pydantic Schema
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
method: 要生成 Schema 的方法
|
|
97
|
+
method_name: 方法名称
|
|
98
|
+
base_class: Schema 基类
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
生成的 Pydantic Schema 类,如果无法生成则返回 None
|
|
102
|
+
"""
|
|
103
|
+
try:
|
|
104
|
+
# 获取方法签名
|
|
105
|
+
sig = inspect.signature(method)
|
|
106
|
+
|
|
107
|
+
# 获取类型注解
|
|
108
|
+
try:
|
|
109
|
+
type_hints = get_type_hints(method)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
logger.debug(f"Failed to get type hints for {method_name}: {e}")
|
|
112
|
+
type_hints = {}
|
|
113
|
+
|
|
114
|
+
# 获取文档字符串
|
|
115
|
+
docstring = inspect.getdoc(method) or f"Execute {method_name} operation"
|
|
116
|
+
|
|
117
|
+
# 提取简短描述(第一行)
|
|
118
|
+
first_line = docstring.split('\n')[0].strip()
|
|
119
|
+
schema_description = first_line if first_line else f"Execute {method_name} operation"
|
|
120
|
+
|
|
121
|
+
# 构建字段定义
|
|
122
|
+
field_definitions = {}
|
|
123
|
+
|
|
124
|
+
for param_name, param in sig.parameters.items():
|
|
125
|
+
# 跳过 self 参数
|
|
126
|
+
if param_name == 'self':
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
# 获取参数类型并标准化
|
|
130
|
+
param_type = type_hints.get(param_name, Any)
|
|
131
|
+
param_type = _normalize_type(param_type)
|
|
132
|
+
|
|
133
|
+
# 获取默认值
|
|
134
|
+
has_default = param.default != inspect.Parameter.empty
|
|
135
|
+
default_value = param.default if has_default else ...
|
|
136
|
+
|
|
137
|
+
# 从文档字符串提取参数描述
|
|
138
|
+
field_description = _extract_param_description_from_docstring(docstring, param_name)
|
|
139
|
+
if not field_description:
|
|
140
|
+
field_description = f"Parameter {param_name}"
|
|
141
|
+
|
|
142
|
+
# 创建 Field
|
|
143
|
+
if has_default:
|
|
144
|
+
if default_value is None:
|
|
145
|
+
# Optional 参数
|
|
146
|
+
field_definitions[param_name] = (
|
|
147
|
+
param_type,
|
|
148
|
+
Field(default=None, description=field_description)
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
field_definitions[param_name] = (
|
|
152
|
+
param_type,
|
|
153
|
+
Field(default=default_value, description=field_description)
|
|
154
|
+
)
|
|
155
|
+
else:
|
|
156
|
+
# 必需参数
|
|
157
|
+
field_definitions[param_name] = (
|
|
158
|
+
param_type,
|
|
159
|
+
Field(description=field_description)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# 如果没有参数(除了 self),返回 None
|
|
163
|
+
if not field_definitions:
|
|
164
|
+
logger.debug(f"No parameters found for {method_name}, skipping schema generation")
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
# 生成 Schema 类名
|
|
168
|
+
schema_name = f"{method_name.title().replace('_', '')}Schema"
|
|
169
|
+
|
|
170
|
+
# 创建 Schema 类,允许任意类型
|
|
171
|
+
schema_class = create_model(
|
|
172
|
+
schema_name,
|
|
173
|
+
__base__=base_class,
|
|
174
|
+
__doc__=schema_description,
|
|
175
|
+
__config__=ConfigDict(arbitrary_types_allowed=True),
|
|
176
|
+
**field_definitions
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
logger.debug(f"Generated schema {schema_name} for method {method_name}")
|
|
180
|
+
return schema_class
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.warning(f"Failed to generate schema for {method_name}: {e}")
|
|
184
|
+
return None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def generate_schemas_for_tool(tool_class: Type) -> Dict[str, Type[BaseModel]]:
|
|
188
|
+
"""
|
|
189
|
+
为工具类的所有方法生成 Schema
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
tool_class: 工具类
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
方法名到 Schema 类的映射
|
|
196
|
+
"""
|
|
197
|
+
schemas = {}
|
|
198
|
+
|
|
199
|
+
for method_name in dir(tool_class):
|
|
200
|
+
# 跳过私有方法和特殊方法
|
|
201
|
+
if method_name.startswith('_'):
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
# 跳过基类方法
|
|
205
|
+
if method_name in ['run', 'run_async', 'run_batch']:
|
|
206
|
+
continue
|
|
207
|
+
|
|
208
|
+
method = getattr(tool_class, method_name)
|
|
209
|
+
|
|
210
|
+
# 跳过非方法属性
|
|
211
|
+
if not callable(method):
|
|
212
|
+
continue
|
|
213
|
+
|
|
214
|
+
# 跳过类(如 Config, Schema 等)
|
|
215
|
+
if isinstance(method, type):
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
# 生成 Schema
|
|
219
|
+
schema = generate_schema_from_method(method, method_name)
|
|
220
|
+
|
|
221
|
+
if schema:
|
|
222
|
+
# 标准化方法名(移除下划线,转小写)
|
|
223
|
+
normalized_name = method_name.replace('_', '').lower()
|
|
224
|
+
schemas[normalized_name] = schema
|
|
225
|
+
logger.info(f"Generated schema for {method_name}")
|
|
226
|
+
|
|
227
|
+
return schemas
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
# 使用示例
|
|
234
|
+
if __name__ == '__main__':
|
|
235
|
+
import sys
|
|
236
|
+
sys.path.insert(0, '/home/coder1/python-middleware-dev')
|
|
237
|
+
|
|
238
|
+
from aiecs.tools import discover_tools, TOOL_CLASSES
|
|
239
|
+
|
|
240
|
+
# 配置日志
|
|
241
|
+
logging.basicConfig(level=logging.INFO)
|
|
242
|
+
|
|
243
|
+
# 发现工具
|
|
244
|
+
discover_tools()
|
|
245
|
+
|
|
246
|
+
# 为 PandasTool 生成 Schema
|
|
247
|
+
print("为 PandasTool 生成 Schema:")
|
|
248
|
+
print("=" * 80)
|
|
249
|
+
|
|
250
|
+
pandas_tool = TOOL_CLASSES['pandas']
|
|
251
|
+
schemas = generate_schemas_for_tool(pandas_tool)
|
|
252
|
+
|
|
253
|
+
print(f"\n生成了 {len(schemas)} 个 Schema:\n")
|
|
254
|
+
|
|
255
|
+
# 显示前3个示例
|
|
256
|
+
for method_name, schema in list(schemas.items())[:3]:
|
|
257
|
+
print(f"{schema.__name__}:")
|
|
258
|
+
print(f" 描述: {schema.__doc__}")
|
|
259
|
+
print(f" 字段:")
|
|
260
|
+
for field_name, field_info in schema.model_fields.items():
|
|
261
|
+
required = "必需" if field_info.is_required() else "可选"
|
|
262
|
+
default = f" (默认: {field_info.default})" if not field_info.is_required() and field_info.default is not None else ""
|
|
263
|
+
print(f" - {field_name}: {field_info.description} [{required}]{default}")
|
|
264
|
+
print()
|
|
265
|
+
|
|
@@ -214,7 +214,7 @@ class ImageTool(BaseTool):
|
|
|
214
214
|
"""Clean up Tesseract processes on destruction."""
|
|
215
215
|
self._tesseract_manager.cleanup()
|
|
216
216
|
|
|
217
|
-
def update_settings(self, config: Dict):
|
|
217
|
+
def update_settings(self, config: Dict) -> None:
|
|
218
218
|
"""
|
|
219
219
|
Update configuration settings dynamically.
|
|
220
220
|
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import logging
|
|
3
|
+
import warnings
|
|
3
4
|
from typing import List, Dict, Optional, Any
|
|
4
5
|
|
|
5
6
|
import pandas as pd
|
|
6
7
|
import pdfplumber
|
|
7
8
|
import pytesseract
|
|
8
9
|
from PIL import Image
|
|
10
|
+
|
|
11
|
+
# Configure Tika log path to user-writable directory before importing
|
|
12
|
+
os.environ['TIKA_LOG_PATH'] = os.path.expanduser('~/.cache/tika')
|
|
13
|
+
os.makedirs(os.path.expanduser('~/.cache/tika'), exist_ok=True)
|
|
14
|
+
|
|
15
|
+
# Suppress pkg_resources deprecation warning from tika
|
|
16
|
+
warnings.filterwarnings('ignore', category=UserWarning, module='tika')
|
|
17
|
+
|
|
9
18
|
from tika import parser
|
|
10
19
|
from docx import Document as DocxDocument
|
|
11
20
|
from docx.shared import Pt
|