autobyteus 1.1.6__py3-none-any.whl → 1.1.8__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.
- autobyteus/agent/context/agent_runtime_state.py +7 -1
- autobyteus/agent/handlers/tool_result_event_handler.py +121 -89
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +7 -1
- autobyteus/agent/tool_invocation.py +25 -1
- autobyteus/agent_team/agent_team_builder.py +22 -1
- autobyteus/agent_team/context/agent_team_runtime_state.py +0 -2
- autobyteus/llm/llm_factory.py +25 -57
- autobyteus/llm/ollama_provider_resolver.py +1 -0
- autobyteus/llm/providers.py +1 -0
- autobyteus/llm/token_counter/token_counter_factory.py +2 -0
- autobyteus/multimedia/audio/audio_model.py +2 -1
- autobyteus/multimedia/image/image_model.py +2 -1
- autobyteus/task_management/tools/publish_task_plan.py +4 -16
- autobyteus/task_management/tools/update_task_status.py +4 -19
- autobyteus/tools/__init__.py +2 -4
- autobyteus/tools/base_tool.py +98 -29
- autobyteus/tools/browser/standalone/__init__.py +0 -1
- autobyteus/tools/google_search.py +149 -0
- autobyteus/tools/mcp/schema_mapper.py +29 -71
- autobyteus/tools/multimedia/audio_tools.py +3 -3
- autobyteus/tools/multimedia/image_tools.py +5 -5
- autobyteus/tools/parameter_schema.py +82 -89
- autobyteus/tools/pydantic_schema_converter.py +81 -0
- autobyteus/tools/usage/formatters/default_json_example_formatter.py +89 -20
- autobyteus/tools/usage/formatters/default_xml_example_formatter.py +115 -41
- autobyteus/tools/usage/formatters/default_xml_schema_formatter.py +50 -20
- autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +55 -22
- autobyteus/tools/usage/formatters/google_json_example_formatter.py +54 -21
- autobyteus/tools/usage/formatters/openai_json_example_formatter.py +53 -23
- autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +270 -94
- autobyteus/tools/usage/providers/tool_manifest_provider.py +39 -14
- autobyteus-1.1.8.dist-info/METADATA +204 -0
- {autobyteus-1.1.6.dist-info → autobyteus-1.1.8.dist-info}/RECORD +39 -40
- examples/run_google_slides_agent.py +2 -2
- examples/run_mcp_google_slides_client.py +1 -1
- examples/run_sqlite_agent.py +1 -1
- autobyteus/tools/ask_user_input.py +0 -40
- autobyteus/tools/browser/standalone/factory/google_search_factory.py +0 -25
- autobyteus/tools/browser/standalone/google_search_ui.py +0 -126
- autobyteus-1.1.6.dist-info/METADATA +0 -161
- {autobyteus-1.1.6.dist-info → autobyteus-1.1.8.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.6.dist-info → autobyteus-1.1.8.dist-info}/licenses/LICENSE +0 -0
- {autobyteus-1.1.6.dist-info → autobyteus-1.1.8.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# file: autobyteus/autobyteus/mcp/schema_mapper.py
|
|
1
|
+
# file: autobyteus/autobyteus/tools/mcp/schema_mapper.py
|
|
2
2
|
import logging
|
|
3
3
|
from typing import Dict, Any, List, Optional
|
|
4
4
|
|
|
@@ -8,7 +8,8 @@ logger = logging.getLogger(__name__)
|
|
|
8
8
|
|
|
9
9
|
class McpSchemaMapper:
|
|
10
10
|
"""
|
|
11
|
-
Converts MCP tool JSON schemas to AutoByteUs ParameterSchema
|
|
11
|
+
Converts MCP tool JSON schemas to AutoByteUs ParameterSchema,
|
|
12
|
+
handling nested object structures recursively.
|
|
12
13
|
"""
|
|
13
14
|
|
|
14
15
|
_MCP_TYPE_TO_AUTOBYTEUS_TYPE_MAP = {
|
|
@@ -19,12 +20,6 @@ class McpSchemaMapper:
|
|
|
19
20
|
"object": ParameterType.OBJECT,
|
|
20
21
|
"array": ParameterType.ARRAY,
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
# REMOVED: _FILE_PATH_NAMES, _DIR_PATH_PARAM_NAMES, _URI_FORMATS
|
|
24
|
-
# as FILE_PATH and DIRECTORY_PATH types are removed.
|
|
25
|
-
# All string-based path parameters will now be ParameterType.STRING.
|
|
26
|
-
# The 'format' hint from MCP schema (e.g., "uri", "url") will still be available
|
|
27
|
-
# on the ParameterDefinition if it includes 'pattern', but it won't change the type from STRING.
|
|
28
23
|
|
|
29
24
|
def map_to_autobyteus_schema(self, mcp_json_schema: Dict[str, Any]) -> ParameterSchema:
|
|
30
25
|
if not isinstance(mcp_json_schema, dict):
|
|
@@ -37,95 +32,58 @@ class McpSchemaMapper:
|
|
|
37
32
|
|
|
38
33
|
schema_type = mcp_json_schema.get("type")
|
|
39
34
|
if schema_type != "object":
|
|
40
|
-
logger.
|
|
41
|
-
|
|
42
|
-
if schema_type in McpSchemaMapper._MCP_TYPE_TO_AUTOBYTEUS_TYPE_MAP:
|
|
43
|
-
param_type_enum = McpSchemaMapper._MCP_TYPE_TO_AUTOBYTEUS_TYPE_MAP[schema_type]
|
|
44
|
-
array_item_schema_for_root: Optional[Dict[str, Any]] = None
|
|
45
|
-
if param_type_enum == ParameterType.ARRAY:
|
|
46
|
-
array_item_schema_for_root = mcp_json_schema.get("items", True)
|
|
47
|
-
|
|
48
|
-
param_def = ParameterDefinition(
|
|
49
|
-
name="input_value",
|
|
50
|
-
param_type=param_type_enum,
|
|
51
|
-
description=mcp_json_schema.get("description", "Input value for the tool."),
|
|
52
|
-
required=True,
|
|
53
|
-
default_value=mcp_json_schema.get("default"),
|
|
54
|
-
enum_values=mcp_json_schema.get("enum") if schema_type == "string" else None,
|
|
55
|
-
array_item_schema=array_item_schema_for_root
|
|
56
|
-
)
|
|
57
|
-
autobyteus_schema.add_parameter(param_def)
|
|
58
|
-
return autobyteus_schema
|
|
59
|
-
else:
|
|
60
|
-
logger.error(f"Unsupported root schema type '{schema_type}' for direct mapping to ParameterSchema properties.")
|
|
61
|
-
raise ValueError(f"MCP JSON schema root 'type' must be 'object' for typical mapping, got '{schema_type}'.")
|
|
62
|
-
|
|
35
|
+
logger.error(f"Unsupported root schema type '{schema_type}' for mapping to ParameterSchema. Must be 'object'.")
|
|
36
|
+
raise ValueError(f"MCP JSON schema root 'type' must be 'object', got '{schema_type}'.")
|
|
63
37
|
|
|
64
38
|
properties = mcp_json_schema.get("properties")
|
|
65
39
|
if not isinstance(properties, dict):
|
|
66
|
-
logger.warning("MCP JSON schema of type 'object' has no 'properties'
|
|
40
|
+
logger.warning("MCP JSON schema of type 'object' has no 'properties'. Resulting ParameterSchema will be empty.")
|
|
67
41
|
return autobyteus_schema
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
logger.warning("MCP JSON schema 'required' field is not a list of strings. Treating all params as optional.")
|
|
72
|
-
required_params = []
|
|
42
|
+
|
|
43
|
+
# FIX: The 'required' list is specific to its own schema level.
|
|
44
|
+
required_params_at_this_level: List[str] = mcp_json_schema.get("required", [])
|
|
73
45
|
|
|
74
46
|
for param_name, param_mcp_schema in properties.items():
|
|
75
47
|
if not isinstance(param_mcp_schema, dict):
|
|
76
|
-
logger.warning(f"Property '{param_name}' in MCP schema is not a dictionary. Skipping
|
|
48
|
+
logger.warning(f"Property '{param_name}' in MCP schema is not a dictionary. Skipping.")
|
|
77
49
|
continue
|
|
78
50
|
|
|
79
51
|
mcp_param_type_str = param_mcp_schema.get("type")
|
|
80
52
|
description = param_mcp_schema.get("description", f"Parameter '{param_name}'.")
|
|
81
|
-
default_value = param_mcp_schema.get("default")
|
|
82
|
-
enum_values = param_mcp_schema.get("enum")
|
|
83
|
-
# format_hint is still read but won't be used to change type to FILE_PATH/DIR_PATH
|
|
84
|
-
# format_hint = param_mcp_schema.get("format", "").lower()
|
|
85
53
|
|
|
54
|
+
nested_object_schema: Optional[ParameterSchema] = None
|
|
86
55
|
item_schema_for_array: Optional[Dict[str, Any]] = None
|
|
87
|
-
if mcp_param_type_str == "array":
|
|
88
|
-
item_schema_for_array = param_mcp_schema.get("items")
|
|
89
|
-
if item_schema_for_array is None:
|
|
90
|
-
item_schema_for_array = True
|
|
91
|
-
logger.debug(f"MCP parameter '{param_name}' is 'array' type with no 'items' schema. Defaulting to generic items (true).")
|
|
92
|
-
|
|
93
|
-
autobyteus_param_type: Optional[ParameterType] = None
|
|
94
|
-
# REMOVED: Logic block that inferred FILE_PATH or DIRECTORY_PATH based on format_hint or param_name_lower.
|
|
95
|
-
# All string types from MCP will now map to STRING or ENUM.
|
|
96
56
|
|
|
97
|
-
if mcp_param_type_str in
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
autobyteus_param_type = ParameterType.ENUM
|
|
101
|
-
elif mcp_param_type_str:
|
|
102
|
-
logger.warning(f"Unsupported MCP parameter type '{mcp_param_type_str}' for parameter '{param_name}'. Defaulting to STRING.")
|
|
103
|
-
autobyteus_param_type = ParameterType.STRING
|
|
104
|
-
else:
|
|
105
|
-
logger.warning(f"MCP parameter '{param_name}' has no 'type' specified. Defaulting to STRING.")
|
|
106
|
-
autobyteus_param_type = ParameterType.STRING
|
|
57
|
+
if mcp_param_type_str == "object" and "properties" in param_mcp_schema:
|
|
58
|
+
# Recursively map the nested object schema. The recursive call will handle its own 'required' list.
|
|
59
|
+
nested_object_schema = self.map_to_autobyteus_schema(param_mcp_schema)
|
|
107
60
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
61
|
+
elif mcp_param_type_str == "array":
|
|
62
|
+
item_schema_for_array = param_mcp_schema.get("items", True)
|
|
63
|
+
|
|
64
|
+
autobyteus_param_type = self._MCP_TYPE_TO_AUTOBYTEUS_TYPE_MAP.get(mcp_param_type_str, ParameterType.STRING)
|
|
65
|
+
enum_values = param_mcp_schema.get("enum")
|
|
66
|
+
if autobyteus_param_type == ParameterType.STRING and enum_values:
|
|
67
|
+
autobyteus_param_type = ParameterType.ENUM
|
|
111
68
|
|
|
112
69
|
try:
|
|
113
70
|
param_def = ParameterDefinition(
|
|
114
71
|
name=param_name,
|
|
115
|
-
param_type=autobyteus_param_type,
|
|
72
|
+
param_type=autobyteus_param_type,
|
|
116
73
|
description=description,
|
|
117
|
-
required=(param_name in
|
|
118
|
-
default_value=
|
|
74
|
+
required=(param_name in required_params_at_this_level), # FIX: Use the list for the current level.
|
|
75
|
+
default_value=param_mcp_schema.get("default"),
|
|
119
76
|
enum_values=enum_values if autobyteus_param_type == ParameterType.ENUM else None,
|
|
120
77
|
min_value=param_mcp_schema.get("minimum"),
|
|
121
78
|
max_value=param_mcp_schema.get("maximum"),
|
|
122
|
-
pattern=param_mcp_schema.get("pattern")
|
|
123
|
-
array_item_schema=item_schema_for_array
|
|
79
|
+
pattern=param_mcp_schema.get("pattern"),
|
|
80
|
+
array_item_schema=item_schema_for_array,
|
|
81
|
+
object_schema=nested_object_schema,
|
|
124
82
|
)
|
|
125
83
|
autobyteus_schema.add_parameter(param_def)
|
|
126
84
|
except ValueError as e:
|
|
127
|
-
logger.error(f"Failed to create ParameterDefinition for '{param_name}': {e}.
|
|
85
|
+
logger.error(f"Failed to create ParameterDefinition for '{param_name}': {e}.")
|
|
128
86
|
continue
|
|
129
87
|
|
|
130
|
-
logger.debug(f"Successfully mapped MCP schema to
|
|
88
|
+
logger.debug(f"Successfully mapped MCP schema to ParameterSchema with {len(autobyteus_schema.parameters)} params.")
|
|
131
89
|
return autobyteus_schema
|
|
@@ -63,7 +63,7 @@ def _build_dynamic_audio_schema(base_params: List[ParameterDefinition], model_en
|
|
|
63
63
|
param_type=ParameterType.OBJECT,
|
|
64
64
|
description=f"Model-specific parameters for the configured '{model_identifier}' model.",
|
|
65
65
|
required=False,
|
|
66
|
-
object_schema=config_schema
|
|
66
|
+
object_schema=config_schema
|
|
67
67
|
))
|
|
68
68
|
return schema
|
|
69
69
|
|
|
@@ -99,7 +99,7 @@ class GenerateSpeechTool(BaseTool):
|
|
|
99
99
|
]
|
|
100
100
|
return _build_dynamic_audio_schema(base_params, cls.MODEL_ENV_VAR, cls.DEFAULT_MODEL)
|
|
101
101
|
|
|
102
|
-
async def _execute(self, context, prompt: str, generation_config: Optional[dict] = None) -> str:
|
|
102
|
+
async def _execute(self, context, prompt: str, generation_config: Optional[dict] = None) -> List[str]:
|
|
103
103
|
model_identifier = _get_configured_model_identifier(self.MODEL_ENV_VAR, self.DEFAULT_MODEL)
|
|
104
104
|
logger.info(f"GenerateSpeechTool executing with configured model '{model_identifier}'.")
|
|
105
105
|
client = None
|
|
@@ -110,7 +110,7 @@ class GenerateSpeechTool(BaseTool):
|
|
|
110
110
|
if not response.audio_urls:
|
|
111
111
|
raise ValueError("Speech generation failed to return any audio file paths.")
|
|
112
112
|
|
|
113
|
-
return
|
|
113
|
+
return response.audio_urls
|
|
114
114
|
finally:
|
|
115
115
|
if client:
|
|
116
116
|
await client.cleanup()
|
|
@@ -63,7 +63,7 @@ def _build_dynamic_image_schema(base_params: List[ParameterDefinition], model_en
|
|
|
63
63
|
param_type=ParameterType.OBJECT,
|
|
64
64
|
description=f"Model-specific generation parameters for the configured '{model_identifier}' model.",
|
|
65
65
|
required=False,
|
|
66
|
-
object_schema=config_schema
|
|
66
|
+
object_schema=config_schema
|
|
67
67
|
))
|
|
68
68
|
return schema
|
|
69
69
|
|
|
@@ -99,7 +99,7 @@ class GenerateImageTool(BaseTool):
|
|
|
99
99
|
]
|
|
100
100
|
return _build_dynamic_image_schema(base_params, cls.MODEL_ENV_VAR, cls.DEFAULT_MODEL)
|
|
101
101
|
|
|
102
|
-
async def _execute(self, context, prompt: str, generation_config: Optional[dict] = None) -> str:
|
|
102
|
+
async def _execute(self, context, prompt: str, generation_config: Optional[dict] = None) -> List[str]:
|
|
103
103
|
model_identifier = _get_configured_model_identifier(self.MODEL_ENV_VAR, self.DEFAULT_MODEL)
|
|
104
104
|
logger.info(f"GenerateImageTool executing with configured model '{model_identifier}'.")
|
|
105
105
|
client = None
|
|
@@ -110,7 +110,7 @@ class GenerateImageTool(BaseTool):
|
|
|
110
110
|
if not response.image_urls:
|
|
111
111
|
raise ValueError("Image generation failed to return any image URLs.")
|
|
112
112
|
|
|
113
|
-
return
|
|
113
|
+
return response.image_urls
|
|
114
114
|
finally:
|
|
115
115
|
if client:
|
|
116
116
|
await client.cleanup()
|
|
@@ -160,7 +160,7 @@ class EditImageTool(BaseTool):
|
|
|
160
160
|
]
|
|
161
161
|
return _build_dynamic_image_schema(base_params, cls.MODEL_ENV_VAR, cls.DEFAULT_MODEL)
|
|
162
162
|
|
|
163
|
-
async def _execute(self, context, prompt: str, input_image_urls: str, generation_config: Optional[dict] = None, mask_image_url: Optional[str] = None) -> str:
|
|
163
|
+
async def _execute(self, context, prompt: str, input_image_urls: str, generation_config: Optional[dict] = None, mask_image_url: Optional[str] = None) -> List[str]:
|
|
164
164
|
model_identifier = _get_configured_model_identifier(self.MODEL_ENV_VAR, self.DEFAULT_MODEL)
|
|
165
165
|
logger.info(f"EditImageTool executing with configured model '{model_identifier}'.")
|
|
166
166
|
client = None
|
|
@@ -180,7 +180,7 @@ class EditImageTool(BaseTool):
|
|
|
180
180
|
if not response.image_urls:
|
|
181
181
|
raise ValueError("Image editing failed to return any image URLs.")
|
|
182
182
|
|
|
183
|
-
return
|
|
183
|
+
return response.image_urls
|
|
184
184
|
finally:
|
|
185
185
|
if client:
|
|
186
186
|
await client.cleanup()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# file: autobyteus/autobyteus/tools/parameter_schema.py
|
|
2
|
+
from __future__ import annotations
|
|
2
3
|
import logging
|
|
3
4
|
from typing import Dict, Any, List, Optional, Union, Type
|
|
4
5
|
from dataclasses import dataclass, field
|
|
@@ -41,8 +42,9 @@ class ParameterDefinition:
|
|
|
41
42
|
min_value: Optional[Union[int, float]] = None
|
|
42
43
|
max_value: Optional[Union[int, float]] = None
|
|
43
44
|
pattern: Optional[str] = None
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
# FIX: Allow dict for raw JSON schemas, in addition to ParameterType and ParameterSchema.
|
|
46
|
+
array_item_schema: Optional[Union[ParameterType, ParameterSchema, dict]] = None
|
|
47
|
+
object_schema: Optional[ParameterSchema] = None
|
|
46
48
|
|
|
47
49
|
def __post_init__(self):
|
|
48
50
|
if not self.name or not isinstance(self.name, str):
|
|
@@ -54,8 +56,15 @@ class ParameterDefinition:
|
|
|
54
56
|
if self.param_type == ParameterType.ENUM and not self.enum_values:
|
|
55
57
|
raise ValueError(f"ParameterDefinition '{self.name}' of type ENUM must specify enum_values")
|
|
56
58
|
|
|
59
|
+
# FIX: Update validation to allow dict for array_item_schema.
|
|
60
|
+
if self.array_item_schema is not None and not isinstance(self.array_item_schema, (ParameterType, ParameterSchema, dict)):
|
|
61
|
+
raise ValueError(f"ParameterDefinition '{self.name}': array_item_schema must be a ParameterType, ParameterSchema, or dict instance.")
|
|
62
|
+
|
|
63
|
+
if self.object_schema is not None and not isinstance(self.object_schema, ParameterSchema):
|
|
64
|
+
raise ValueError(f"ParameterDefinition '{self.name}': object_schema must be a ParameterSchema instance.")
|
|
65
|
+
|
|
57
66
|
if self.param_type == ParameterType.ARRAY and self.array_item_schema is None:
|
|
58
|
-
logger.debug(f"ParameterDefinition '{self.name}' of type ARRAY has no
|
|
67
|
+
logger.debug(f"ParameterDefinition '{self.name}' of type ARRAY has no item schema. Will be a generic array of any type.")
|
|
59
68
|
|
|
60
69
|
if self.param_type != ParameterType.ARRAY and self.array_item_schema is not None:
|
|
61
70
|
raise ValueError(f"ParameterDefinition '{self.name}': array_item_schema should only be provided if param_type is ARRAY.")
|
|
@@ -71,44 +80,30 @@ class ParameterDefinition:
|
|
|
71
80
|
return not self.required
|
|
72
81
|
|
|
73
82
|
if self.param_type == ParameterType.STRING:
|
|
74
|
-
if not isinstance(value, str):
|
|
75
|
-
|
|
76
|
-
if self.pattern:
|
|
77
|
-
if not re.match(self.pattern, value):
|
|
78
|
-
return False
|
|
83
|
+
if not isinstance(value, str): return False
|
|
84
|
+
if self.pattern and not re.match(self.pattern, value): return False
|
|
79
85
|
|
|
80
86
|
elif self.param_type == ParameterType.INTEGER:
|
|
81
|
-
if not isinstance(value, int):
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if self.min_value is not None and value < self.min_value:
|
|
85
|
-
return False
|
|
86
|
-
if self.max_value is not None and value > self.max_value:
|
|
87
|
-
return False
|
|
87
|
+
if not isinstance(value, int) or isinstance(value, bool): return False
|
|
88
|
+
if self.min_value is not None and value < self.min_value: return False
|
|
89
|
+
if self.max_value is not None and value > self.max_value: return False
|
|
88
90
|
|
|
89
91
|
elif self.param_type == ParameterType.FLOAT:
|
|
90
|
-
if not isinstance(value, (float, int)):
|
|
91
|
-
|
|
92
|
-
if self.
|
|
93
|
-
return False
|
|
94
|
-
if self.max_value is not None and float(value) > self.max_value:
|
|
95
|
-
return False
|
|
92
|
+
if not isinstance(value, (float, int)): return False
|
|
93
|
+
if self.min_value is not None and float(value) < self.min_value: return False
|
|
94
|
+
if self.max_value is not None and float(value) > self.max_value: return False
|
|
96
95
|
|
|
97
96
|
elif self.param_type == ParameterType.BOOLEAN:
|
|
98
|
-
if not isinstance(value, bool):
|
|
99
|
-
return False
|
|
97
|
+
if not isinstance(value, bool): return False
|
|
100
98
|
|
|
101
99
|
elif self.param_type == ParameterType.ENUM:
|
|
102
|
-
if not isinstance(value, str) or value not in (self.enum_values or []):
|
|
103
|
-
return False
|
|
100
|
+
if not isinstance(value, str) or value not in (self.enum_values or []): return False
|
|
104
101
|
|
|
105
102
|
elif self.param_type == ParameterType.OBJECT:
|
|
106
|
-
if not isinstance(value, dict):
|
|
107
|
-
return False
|
|
103
|
+
if not isinstance(value, dict): return False
|
|
108
104
|
|
|
109
105
|
elif self.param_type == ParameterType.ARRAY:
|
|
110
|
-
if not isinstance(value, list):
|
|
111
|
-
return False
|
|
106
|
+
if not isinstance(value, list): return False
|
|
112
107
|
|
|
113
108
|
return True
|
|
114
109
|
|
|
@@ -124,17 +119,22 @@ class ParameterDefinition:
|
|
|
124
119
|
"max_value": self.max_value,
|
|
125
120
|
"pattern": self.pattern,
|
|
126
121
|
}
|
|
122
|
+
# FIX: Correctly serialize dicts for array_item_schema.
|
|
127
123
|
if self.param_type == ParameterType.ARRAY and self.array_item_schema is not None:
|
|
128
|
-
|
|
124
|
+
if isinstance(self.array_item_schema, ParameterSchema):
|
|
125
|
+
data["array_item_schema"] = self.array_item_schema.to_dict()
|
|
126
|
+
elif isinstance(self.array_item_schema, dict):
|
|
127
|
+
data["array_item_schema"] = self.array_item_schema
|
|
128
|
+
elif isinstance(self.array_item_schema, ParameterType):
|
|
129
|
+
data["array_item_schema"] = {"type": self.array_item_schema.value}
|
|
130
|
+
|
|
129
131
|
if self.param_type == ParameterType.OBJECT and self.object_schema is not None:
|
|
130
|
-
data["object_schema"] = self.object_schema
|
|
132
|
+
data["object_schema"] = self.object_schema.to_dict()
|
|
131
133
|
return data
|
|
132
134
|
|
|
133
135
|
def to_json_schema_property_dict(self) -> Dict[str, Any]:
|
|
134
136
|
if self.param_type == ParameterType.OBJECT and self.object_schema:
|
|
135
|
-
|
|
136
|
-
# We add the description at the top level for clarity.
|
|
137
|
-
schema = self.object_schema.copy()
|
|
137
|
+
schema = self.object_schema.to_json_schema_dict()
|
|
138
138
|
schema["description"] = self.description
|
|
139
139
|
return schema
|
|
140
140
|
|
|
@@ -148,40 +148,37 @@ class ParameterDefinition:
|
|
|
148
148
|
if self.param_type == ParameterType.ENUM and self.enum_values:
|
|
149
149
|
prop_dict["enum"] = self.enum_values
|
|
150
150
|
|
|
151
|
-
if self.min_value is not None:
|
|
152
|
-
|
|
153
|
-
prop_dict["minimum"] = self.min_value
|
|
151
|
+
if self.min_value is not None and self.param_type in [ParameterType.INTEGER, ParameterType.FLOAT]:
|
|
152
|
+
prop_dict["minimum"] = self.min_value
|
|
154
153
|
|
|
155
|
-
if self.max_value is not None:
|
|
156
|
-
|
|
157
|
-
prop_dict["maximum"] = self.max_value
|
|
154
|
+
if self.max_value is not None and self.param_type in [ParameterType.INTEGER, ParameterType.FLOAT]:
|
|
155
|
+
prop_dict["maximum"] = self.max_value
|
|
158
156
|
|
|
159
157
|
if self.pattern and self.param_type == ParameterType.STRING:
|
|
160
158
|
prop_dict["pattern"] = self.pattern
|
|
161
159
|
|
|
160
|
+
# FIX: Correctly handle dicts when generating the 'items' part of an array schema.
|
|
162
161
|
if self.param_type == ParameterType.ARRAY:
|
|
163
|
-
if self.array_item_schema
|
|
162
|
+
if isinstance(self.array_item_schema, ParameterSchema):
|
|
163
|
+
prop_dict["items"] = self.array_item_schema.to_json_schema_dict()
|
|
164
|
+
elif isinstance(self.array_item_schema, dict):
|
|
164
165
|
prop_dict["items"] = self.array_item_schema
|
|
166
|
+
elif isinstance(self.array_item_schema, ParameterType):
|
|
167
|
+
prop_dict["items"] = {"type": self.array_item_schema.to_json_schema_type()}
|
|
165
168
|
else:
|
|
166
169
|
prop_dict["items"] = True
|
|
167
|
-
logger.debug(f"Parameter '{self.name}' is ARRAY type with no item schema; JSON schema 'items' will be generic.")
|
|
168
170
|
|
|
169
171
|
return prop_dict
|
|
170
172
|
|
|
171
173
|
@dataclass
|
|
172
174
|
class ParameterSchema:
|
|
173
|
-
"""
|
|
174
|
-
Describes a schema for a set of parameters, either for tool arguments or instantiation configuration.
|
|
175
|
-
"""
|
|
176
175
|
parameters: List[ParameterDefinition] = field(default_factory=list)
|
|
177
176
|
|
|
178
177
|
def add_parameter(self, parameter: ParameterDefinition) -> None:
|
|
179
178
|
if not isinstance(parameter, ParameterDefinition):
|
|
180
179
|
raise TypeError("parameter must be a ParameterDefinition instance")
|
|
181
|
-
|
|
182
180
|
if any(p.name == parameter.name for p in self.parameters):
|
|
183
181
|
raise ValueError(f"Parameter '{parameter.name}' already exists in schema")
|
|
184
|
-
|
|
185
182
|
self.parameters.append(parameter)
|
|
186
183
|
|
|
187
184
|
def get_parameter(self, name: str) -> Optional[ParameterDefinition]:
|
|
@@ -189,67 +186,64 @@ class ParameterSchema:
|
|
|
189
186
|
|
|
190
187
|
def validate_config(self, config_data: Dict[str, Any]) -> tuple[bool, List[str]]:
|
|
191
188
|
errors = []
|
|
192
|
-
|
|
193
189
|
for param_def in self.parameters:
|
|
194
190
|
if param_def.required and param_def.name not in config_data:
|
|
195
191
|
errors.append(f"Required parameter '{param_def.name}' is missing.")
|
|
196
|
-
|
|
197
192
|
for key, value in config_data.items():
|
|
198
193
|
param_def = self.get_parameter(key)
|
|
199
194
|
if not param_def:
|
|
200
|
-
logger.debug(f"Unknown parameter '{key}' provided. It will be ignored
|
|
195
|
+
logger.debug(f"Unknown parameter '{key}' provided. It will be ignored.")
|
|
201
196
|
continue
|
|
202
|
-
|
|
203
197
|
if not param_def.validate_value(value):
|
|
204
198
|
errors.append(f"Invalid value for parameter '{param_def.name}': '{str(value)[:50]}...'. Expected type compatible with {param_def.param_type.value}.")
|
|
205
|
-
|
|
206
199
|
return len(errors) == 0, errors
|
|
207
200
|
|
|
208
|
-
def get_defaults(self) -> Dict[str, Any]:
|
|
209
|
-
return {
|
|
210
|
-
param.name: param.default_value
|
|
211
|
-
for param in self.parameters
|
|
212
|
-
if param.default_value is not None
|
|
213
|
-
}
|
|
214
|
-
|
|
215
201
|
def to_dict(self) -> Dict[str, Any]:
|
|
216
|
-
return {
|
|
217
|
-
"parameters": [param.to_dict() for param in self.parameters]
|
|
218
|
-
}
|
|
202
|
+
return {"parameters": [param.to_dict() for param in self.parameters]}
|
|
219
203
|
|
|
220
204
|
def to_json_schema_dict(self) -> Dict[str, Any]:
|
|
221
205
|
if not self.parameters:
|
|
222
|
-
return {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
properties = {
|
|
229
|
-
param.name: param.to_json_schema_property_dict()
|
|
230
|
-
for param in self.parameters
|
|
231
|
-
}
|
|
232
|
-
required = [
|
|
233
|
-
param.name for param in self.parameters if param.required
|
|
234
|
-
]
|
|
235
|
-
|
|
236
|
-
return {
|
|
237
|
-
"type": "object",
|
|
238
|
-
"properties": properties,
|
|
239
|
-
"required": required,
|
|
240
|
-
}
|
|
206
|
+
return {"type": "object", "properties": {}, "required": []}
|
|
207
|
+
properties = {p.name: p.to_json_schema_property_dict() for p in self.parameters}
|
|
208
|
+
required = [p.name for p in self.parameters if p.required]
|
|
209
|
+
return {"type": "object", "properties": properties, "required": required}
|
|
241
210
|
|
|
242
211
|
@classmethod
|
|
243
212
|
def from_dict(cls, schema_data: Dict[str, Any]) -> 'ParameterSchema':
|
|
244
213
|
schema = cls()
|
|
245
|
-
|
|
246
214
|
for param_data in schema_data.get("parameters", []):
|
|
247
|
-
param_type_value = param_data["type"]
|
|
248
215
|
try:
|
|
249
|
-
param_type_enum = ParameterType(
|
|
216
|
+
param_type_enum = ParameterType(param_data["type"])
|
|
250
217
|
except ValueError:
|
|
251
|
-
raise ValueError(f"Invalid parameter type string '{
|
|
218
|
+
raise ValueError(f"Invalid parameter type string '{param_data['type']}' for param '{param_data.get('name')}'.")
|
|
219
|
+
|
|
220
|
+
array_schema_obj = None
|
|
221
|
+
if "array_item_schema" in param_data and param_data["array_item_schema"] is not None:
|
|
222
|
+
item_schema_data = param_data["array_item_schema"]
|
|
223
|
+
|
|
224
|
+
# FIX: Add robust logic to deserialize array_item_schema correctly.
|
|
225
|
+
if isinstance(item_schema_data, dict):
|
|
226
|
+
if "parameters" in item_schema_data:
|
|
227
|
+
# It's our internal ParameterSchema format.
|
|
228
|
+
array_schema_obj = ParameterSchema.from_dict(item_schema_data)
|
|
229
|
+
elif "type" in item_schema_data and len(item_schema_data) == 1:
|
|
230
|
+
# Heuristic: it's a simple primitive type like {'type': 'string'}.
|
|
231
|
+
try:
|
|
232
|
+
array_schema_obj = ParameterType(item_schema_data["type"])
|
|
233
|
+
except ValueError:
|
|
234
|
+
# Not a valid ParameterType, so treat it as a raw schema dict.
|
|
235
|
+
array_schema_obj = item_schema_data
|
|
236
|
+
else:
|
|
237
|
+
# It's a complex JSON schema dict, store it as is.
|
|
238
|
+
array_schema_obj = item_schema_data
|
|
239
|
+
else:
|
|
240
|
+
# Should not be hit if serialized with to_dict, but handle for robustness.
|
|
241
|
+
array_schema_obj = item_schema_data
|
|
252
242
|
|
|
243
|
+
object_schema_obj = None
|
|
244
|
+
if "object_schema" in param_data and param_data["object_schema"] is not None:
|
|
245
|
+
object_schema_obj = ParameterSchema.from_dict(param_data["object_schema"])
|
|
246
|
+
|
|
253
247
|
param = ParameterDefinition(
|
|
254
248
|
name=param_data["name"],
|
|
255
249
|
param_type=param_type_enum,
|
|
@@ -260,15 +254,14 @@ class ParameterSchema:
|
|
|
260
254
|
min_value=param_data.get("min_value"),
|
|
261
255
|
max_value=param_data.get("max_value"),
|
|
262
256
|
pattern=param_data.get("pattern"),
|
|
263
|
-
array_item_schema=
|
|
264
|
-
object_schema=
|
|
257
|
+
array_item_schema=array_schema_obj,
|
|
258
|
+
object_schema=object_schema_obj
|
|
265
259
|
)
|
|
266
260
|
schema.add_parameter(param)
|
|
267
|
-
|
|
268
261
|
return schema
|
|
269
262
|
|
|
270
263
|
def __len__(self) -> int:
|
|
271
264
|
return len(self.parameters)
|
|
272
265
|
|
|
273
266
|
def __bool__(self) -> bool:
|
|
274
|
-
return
|
|
267
|
+
return bool(self.parameters)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# file: autobyteus/autobyteus/tools/pydantic_to_parameter_schema.py
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Type, get_origin, get_args, Union, List, Dict
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
_PYDANTIC_TYPE_MAP = {
|
|
10
|
+
str: ParameterType.STRING,
|
|
11
|
+
int: ParameterType.INTEGER,
|
|
12
|
+
float: ParameterType.FLOAT,
|
|
13
|
+
bool: ParameterType.BOOLEAN,
|
|
14
|
+
dict: ParameterType.OBJECT,
|
|
15
|
+
list: ParameterType.ARRAY,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def pydantic_to_parameter_schema(pydantic_model: Type[BaseModel]) -> ParameterSchema:
|
|
19
|
+
"""
|
|
20
|
+
Converts a Pydantic BaseModel into an AutoByteUs ParameterSchema.
|
|
21
|
+
|
|
22
|
+
This function inspects the Pydantic model's fields and recursively builds a
|
|
23
|
+
corresponding ParameterSchema, handling nested models and lists of models.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
pydantic_model: The Pydantic model class to convert.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
A fully constructed ParameterSchema object.
|
|
30
|
+
"""
|
|
31
|
+
schema = ParameterSchema()
|
|
32
|
+
required_fields = {name for name, field_info in pydantic_model.model_fields.items() if field_info.is_required()}
|
|
33
|
+
|
|
34
|
+
for field_name, field_info in pydantic_model.model_fields.items():
|
|
35
|
+
param_type = ParameterType.STRING # Default
|
|
36
|
+
object_schema = None
|
|
37
|
+
array_item_schema = None
|
|
38
|
+
|
|
39
|
+
# Determine if the type is Optional (e.g., Union[str, None])
|
|
40
|
+
is_optional = False
|
|
41
|
+
field_type = field_info.annotation
|
|
42
|
+
origin_type = get_origin(field_type)
|
|
43
|
+
|
|
44
|
+
if origin_type is Union:
|
|
45
|
+
union_args = get_args(field_type)
|
|
46
|
+
if type(None) in union_args:
|
|
47
|
+
is_optional = True
|
|
48
|
+
# Get the non-None type from the Union
|
|
49
|
+
non_none_args = [arg for arg in union_args if arg is not type(None)]
|
|
50
|
+
if len(non_none_args) == 1:
|
|
51
|
+
field_type = non_none_args[0]
|
|
52
|
+
origin_type = get_origin(field_type)
|
|
53
|
+
|
|
54
|
+
if origin_type is list or origin_type is List:
|
|
55
|
+
param_type = ParameterType.ARRAY
|
|
56
|
+
list_item_type = get_args(field_type)[0] if get_args(field_type) else any
|
|
57
|
+
if isinstance(list_item_type, type) and issubclass(list_item_type, BaseModel):
|
|
58
|
+
# FIX: Recursively call the converter for the nested Pydantic model.
|
|
59
|
+
array_item_schema = pydantic_to_parameter_schema(list_item_type)
|
|
60
|
+
else:
|
|
61
|
+
# Fallback for list of primitives
|
|
62
|
+
primitive_type_str = _PYDANTIC_TYPE_MAP.get(list_item_type, ParameterType.STRING).value
|
|
63
|
+
array_item_schema = {"type": primitive_type_str}
|
|
64
|
+
|
|
65
|
+
elif isinstance(field_type, type) and issubclass(field_type, BaseModel):
|
|
66
|
+
param_type = ParameterType.OBJECT
|
|
67
|
+
# Recursively convert the nested Pydantic model
|
|
68
|
+
object_schema = pydantic_to_parameter_schema(field_type)
|
|
69
|
+
else:
|
|
70
|
+
param_type = _PYDANTIC_TYPE_MAP.get(field_type, ParameterType.STRING)
|
|
71
|
+
|
|
72
|
+
schema.add_parameter(ParameterDefinition(
|
|
73
|
+
name=field_name,
|
|
74
|
+
param_type=param_type,
|
|
75
|
+
description=field_info.description or f"Parameter '{field_name}'.",
|
|
76
|
+
required=field_name in required_fields and not is_optional,
|
|
77
|
+
object_schema=object_schema,
|
|
78
|
+
array_item_schema=array_item_schema
|
|
79
|
+
))
|
|
80
|
+
|
|
81
|
+
return schema
|