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.
Files changed (43) hide show
  1. autobyteus/agent/context/agent_runtime_state.py +7 -1
  2. autobyteus/agent/handlers/tool_result_event_handler.py +121 -89
  3. autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +7 -1
  4. autobyteus/agent/tool_invocation.py +25 -1
  5. autobyteus/agent_team/agent_team_builder.py +22 -1
  6. autobyteus/agent_team/context/agent_team_runtime_state.py +0 -2
  7. autobyteus/llm/llm_factory.py +25 -57
  8. autobyteus/llm/ollama_provider_resolver.py +1 -0
  9. autobyteus/llm/providers.py +1 -0
  10. autobyteus/llm/token_counter/token_counter_factory.py +2 -0
  11. autobyteus/multimedia/audio/audio_model.py +2 -1
  12. autobyteus/multimedia/image/image_model.py +2 -1
  13. autobyteus/task_management/tools/publish_task_plan.py +4 -16
  14. autobyteus/task_management/tools/update_task_status.py +4 -19
  15. autobyteus/tools/__init__.py +2 -4
  16. autobyteus/tools/base_tool.py +98 -29
  17. autobyteus/tools/browser/standalone/__init__.py +0 -1
  18. autobyteus/tools/google_search.py +149 -0
  19. autobyteus/tools/mcp/schema_mapper.py +29 -71
  20. autobyteus/tools/multimedia/audio_tools.py +3 -3
  21. autobyteus/tools/multimedia/image_tools.py +5 -5
  22. autobyteus/tools/parameter_schema.py +82 -89
  23. autobyteus/tools/pydantic_schema_converter.py +81 -0
  24. autobyteus/tools/usage/formatters/default_json_example_formatter.py +89 -20
  25. autobyteus/tools/usage/formatters/default_xml_example_formatter.py +115 -41
  26. autobyteus/tools/usage/formatters/default_xml_schema_formatter.py +50 -20
  27. autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +55 -22
  28. autobyteus/tools/usage/formatters/google_json_example_formatter.py +54 -21
  29. autobyteus/tools/usage/formatters/openai_json_example_formatter.py +53 -23
  30. autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +270 -94
  31. autobyteus/tools/usage/providers/tool_manifest_provider.py +39 -14
  32. autobyteus-1.1.8.dist-info/METADATA +204 -0
  33. {autobyteus-1.1.6.dist-info → autobyteus-1.1.8.dist-info}/RECORD +39 -40
  34. examples/run_google_slides_agent.py +2 -2
  35. examples/run_mcp_google_slides_client.py +1 -1
  36. examples/run_sqlite_agent.py +1 -1
  37. autobyteus/tools/ask_user_input.py +0 -40
  38. autobyteus/tools/browser/standalone/factory/google_search_factory.py +0 -25
  39. autobyteus/tools/browser/standalone/google_search_ui.py +0 -126
  40. autobyteus-1.1.6.dist-info/METADATA +0 -161
  41. {autobyteus-1.1.6.dist-info → autobyteus-1.1.8.dist-info}/WHEEL +0 -0
  42. {autobyteus-1.1.6.dist-info → autobyteus-1.1.8.dist-info}/licenses/LICENSE +0 -0
  43. {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.warning(f"MCP JSON schema root 'type' is '{schema_type}', not 'object'. "
41
- "Mapping may be incomplete or incorrect for non-object root schemas.")
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' or 'properties' is not a dict. Resulting ParameterSchema will be empty.")
40
+ logger.warning("MCP JSON schema of type 'object' has no 'properties'. Resulting ParameterSchema will be empty.")
67
41
  return autobyteus_schema
68
-
69
- required_params: List[str] = mcp_json_schema.get("required", [])
70
- if not isinstance(required_params, list) or not all(isinstance(p, str) for p in required_params):
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 this parameter.")
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 McpSchemaMapper._MCP_TYPE_TO_AUTOBYTEUS_TYPE_MAP:
98
- autobyteus_param_type = McpSchemaMapper._MCP_TYPE_TO_AUTOBYTEUS_TYPE_MAP[mcp_param_type_str]
99
- if autobyteus_param_type == ParameterType.STRING and enum_values:
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
- if autobyteus_param_type == ParameterType.ENUM:
109
- if not enum_values or not isinstance(enum_values, list) or not all(isinstance(ev, str) for ev in enum_values):
110
- logger.warning(f"Parameter '{param_name}' is ENUM type but 'enum' field is missing, not a list, or not list of strings in MCP schema. Problematic. Schema: {enum_values}")
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, # This will now be STRING for former path types
72
+ param_type=autobyteus_param_type,
116
73
  description=description,
117
- required=(param_name in required_params),
118
- default_value=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") if mcp_param_type_str == "string" else None,
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}. MCP schema for param: {param_mcp_schema}")
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 AutoByteUs ParameterSchema with {len(autobyteus_schema.parameters)} parameters.")
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.to_json_schema_dict()
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 f"Speech generation successful. Audio file(s) saved at: {response.audio_urls}"
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.to_json_schema_dict()
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 f"Image generation successful. URLs: {response.image_urls}"
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 f"Image editing successful. URLs: {response.image_urls}"
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
- array_item_schema: Optional[Dict[str, Any]] = None
45
- object_schema: Optional[Dict[str, Any]] = None
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 array_item_schema. Will be represented as a generic array.")
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
- return False
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
- if isinstance(value, bool): return False
83
- return False
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
- return False
92
- if self.min_value is not None and float(value) < self.min_value:
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
- data["array_item_schema"] = self.array_item_schema
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
- # If a detailed object schema is provided, use it directly.
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
- if self.param_type in [ParameterType.INTEGER, ParameterType.FLOAT]:
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
- if self.param_type in [ParameterType.INTEGER, ParameterType.FLOAT]:
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 is not None:
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 by schema-based processing but passed through if possible.")
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
- "type": "object",
224
- "properties": {},
225
- "required": []
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(param_type_value)
216
+ param_type_enum = ParameterType(param_data["type"])
250
217
  except ValueError:
251
- raise ValueError(f"Invalid parameter type string '{param_type_value}' in schema data for parameter '{param_data.get('name')}'.")
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=param_data.get("array_item_schema"),
264
- object_schema=param_data.get("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 len(self.parameters) > 0
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