autobyteus 1.1.5__py3-none-any.whl → 1.1.7__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 (103) hide show
  1. autobyteus/agent/context/agent_config.py +6 -1
  2. autobyteus/agent/context/agent_runtime_state.py +7 -1
  3. autobyteus/agent/handlers/llm_user_message_ready_event_handler.py +30 -7
  4. autobyteus/agent/handlers/tool_result_event_handler.py +100 -88
  5. autobyteus/agent/handlers/user_input_message_event_handler.py +22 -25
  6. autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +7 -1
  7. autobyteus/agent/message/__init__.py +7 -5
  8. autobyteus/agent/message/agent_input_user_message.py +6 -16
  9. autobyteus/agent/message/context_file.py +24 -24
  10. autobyteus/agent/message/context_file_type.py +29 -8
  11. autobyteus/agent/message/multimodal_message_builder.py +47 -0
  12. autobyteus/agent/streaming/stream_event_payloads.py +23 -4
  13. autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +6 -2
  14. autobyteus/agent/tool_invocation.py +27 -2
  15. autobyteus/agent_team/agent_team_builder.py +22 -1
  16. autobyteus/agent_team/bootstrap_steps/agent_configuration_preparation_step.py +9 -2
  17. autobyteus/agent_team/context/agent_team_config.py +1 -0
  18. autobyteus/agent_team/context/agent_team_runtime_state.py +0 -2
  19. autobyteus/llm/api/autobyteus_llm.py +33 -33
  20. autobyteus/llm/api/bedrock_llm.py +13 -5
  21. autobyteus/llm/api/claude_llm.py +13 -27
  22. autobyteus/llm/api/gemini_llm.py +108 -42
  23. autobyteus/llm/api/groq_llm.py +4 -3
  24. autobyteus/llm/api/mistral_llm.py +97 -51
  25. autobyteus/llm/api/nvidia_llm.py +6 -5
  26. autobyteus/llm/api/ollama_llm.py +37 -12
  27. autobyteus/llm/api/openai_compatible_llm.py +91 -91
  28. autobyteus/llm/autobyteus_provider.py +1 -1
  29. autobyteus/llm/base_llm.py +42 -139
  30. autobyteus/llm/extensions/base_extension.py +6 -6
  31. autobyteus/llm/extensions/token_usage_tracking_extension.py +3 -2
  32. autobyteus/llm/llm_factory.py +131 -61
  33. autobyteus/llm/ollama_provider_resolver.py +1 -0
  34. autobyteus/llm/providers.py +1 -0
  35. autobyteus/llm/token_counter/token_counter_factory.py +3 -1
  36. autobyteus/llm/user_message.py +43 -35
  37. autobyteus/llm/utils/llm_config.py +34 -18
  38. autobyteus/llm/utils/media_payload_formatter.py +99 -0
  39. autobyteus/llm/utils/messages.py +32 -25
  40. autobyteus/llm/utils/response_types.py +9 -3
  41. autobyteus/llm/utils/token_usage.py +6 -5
  42. autobyteus/multimedia/__init__.py +31 -0
  43. autobyteus/multimedia/audio/__init__.py +11 -0
  44. autobyteus/multimedia/audio/api/__init__.py +4 -0
  45. autobyteus/multimedia/audio/api/autobyteus_audio_client.py +59 -0
  46. autobyteus/multimedia/audio/api/gemini_audio_client.py +219 -0
  47. autobyteus/multimedia/audio/audio_client_factory.py +120 -0
  48. autobyteus/multimedia/audio/audio_model.py +97 -0
  49. autobyteus/multimedia/audio/autobyteus_audio_provider.py +108 -0
  50. autobyteus/multimedia/audio/base_audio_client.py +40 -0
  51. autobyteus/multimedia/image/__init__.py +11 -0
  52. autobyteus/multimedia/image/api/__init__.py +9 -0
  53. autobyteus/multimedia/image/api/autobyteus_image_client.py +97 -0
  54. autobyteus/multimedia/image/api/gemini_image_client.py +188 -0
  55. autobyteus/multimedia/image/api/openai_image_client.py +142 -0
  56. autobyteus/multimedia/image/autobyteus_image_provider.py +109 -0
  57. autobyteus/multimedia/image/base_image_client.py +67 -0
  58. autobyteus/multimedia/image/image_client_factory.py +118 -0
  59. autobyteus/multimedia/image/image_model.py +97 -0
  60. autobyteus/multimedia/providers.py +5 -0
  61. autobyteus/multimedia/runtimes.py +8 -0
  62. autobyteus/multimedia/utils/__init__.py +10 -0
  63. autobyteus/multimedia/utils/api_utils.py +19 -0
  64. autobyteus/multimedia/utils/multimedia_config.py +29 -0
  65. autobyteus/multimedia/utils/response_types.py +13 -0
  66. autobyteus/task_management/tools/publish_task_plan.py +4 -16
  67. autobyteus/task_management/tools/update_task_status.py +4 -19
  68. autobyteus/tools/__init__.py +5 -4
  69. autobyteus/tools/base_tool.py +98 -29
  70. autobyteus/tools/browser/standalone/__init__.py +0 -1
  71. autobyteus/tools/google_search.py +149 -0
  72. autobyteus/tools/mcp/schema_mapper.py +29 -71
  73. autobyteus/tools/multimedia/__init__.py +8 -0
  74. autobyteus/tools/multimedia/audio_tools.py +116 -0
  75. autobyteus/tools/multimedia/image_tools.py +186 -0
  76. autobyteus/tools/parameter_schema.py +82 -89
  77. autobyteus/tools/pydantic_schema_converter.py +81 -0
  78. autobyteus/tools/tool_category.py +1 -0
  79. autobyteus/tools/usage/formatters/default_json_example_formatter.py +89 -20
  80. autobyteus/tools/usage/formatters/default_xml_example_formatter.py +115 -41
  81. autobyteus/tools/usage/formatters/default_xml_schema_formatter.py +50 -20
  82. autobyteus/tools/usage/formatters/gemini_json_example_formatter.py +55 -22
  83. autobyteus/tools/usage/formatters/google_json_example_formatter.py +54 -21
  84. autobyteus/tools/usage/formatters/openai_json_example_formatter.py +53 -23
  85. autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +270 -94
  86. autobyteus/tools/usage/parsers/provider_aware_tool_usage_parser.py +5 -2
  87. autobyteus/tools/usage/providers/tool_manifest_provider.py +43 -16
  88. autobyteus/tools/usage/registries/tool_formatting_registry.py +9 -2
  89. autobyteus/tools/usage/registries/tool_usage_parser_registry.py +9 -2
  90. autobyteus-1.1.7.dist-info/METADATA +204 -0
  91. {autobyteus-1.1.5.dist-info → autobyteus-1.1.7.dist-info}/RECORD +98 -71
  92. examples/run_browser_agent.py +1 -1
  93. examples/run_google_slides_agent.py +2 -2
  94. examples/run_mcp_google_slides_client.py +1 -1
  95. examples/run_sqlite_agent.py +1 -1
  96. autobyteus/llm/utils/image_payload_formatter.py +0 -89
  97. autobyteus/tools/ask_user_input.py +0 -40
  98. autobyteus/tools/browser/standalone/factory/google_search_factory.py +0 -25
  99. autobyteus/tools/browser/standalone/google_search_ui.py +0 -126
  100. autobyteus-1.1.5.dist-info/METADATA +0 -161
  101. {autobyteus-1.1.5.dist-info → autobyteus-1.1.7.dist-info}/WHEEL +0 -0
  102. {autobyteus-1.1.5.dist-info → autobyteus-1.1.7.dist-info}/licenses/LICENSE +0 -0
  103. {autobyteus-1.1.5.dist-info → autobyteus-1.1.7.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,186 @@
1
+ import os
2
+ import logging
3
+ from typing import Optional, List
4
+
5
+ from autobyteus.tools.base_tool import BaseTool
6
+ from autobyteus.tools.parameter_schema import ParameterSchema, ParameterDefinition, ParameterType
7
+ from autobyteus.tools.tool_category import ToolCategory
8
+ from autobyteus.multimedia.image import image_client_factory, ImageModel, ImageClientFactory
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def _get_configured_model_identifier(env_var: str, default_model: Optional[str] = None) -> str:
14
+ """
15
+ Retrieves a model identifier from an environment variable, with a fallback to a default.
16
+ """
17
+ model_identifier = os.getenv(env_var)
18
+ if not model_identifier:
19
+ if default_model:
20
+ return default_model
21
+ raise ValueError(f"The '{env_var}' environment variable is not set. Please configure it.")
22
+ return model_identifier
23
+
24
+
25
+ def _build_dynamic_image_schema(base_params: List[ParameterDefinition], model_env_var: str, default_model: str) -> ParameterSchema:
26
+ """
27
+ Builds the tool schema dynamically based on the configured image model.
28
+ """
29
+ try:
30
+ model_identifier = _get_configured_model_identifier(model_env_var, default_model)
31
+ ImageClientFactory.ensure_initialized()
32
+ model = ImageModel[model_identifier]
33
+ except (ValueError, KeyError) as e:
34
+ logger.error(f"Cannot generate image tool schema. Check environment and model registry. Error: {e}")
35
+ raise RuntimeError(f"Failed to configure image tool. Error: {e}")
36
+
37
+ config_schema = ParameterSchema()
38
+ if model.parameter_schema:
39
+ for name, meta in model.parameter_schema.items():
40
+ param_type_str = meta.get("type", "string").upper()
41
+ param_type = getattr(ParameterType, param_type_str, ParameterType.STRING)
42
+
43
+ allowed_values = meta.get("allowed_values")
44
+ if param_type == ParameterType.STRING and allowed_values:
45
+ param_type = ParameterType.ENUM
46
+
47
+ config_schema.add_parameter(ParameterDefinition(
48
+ name=name,
49
+ param_type=param_type,
50
+ description=meta.get("description", ""),
51
+ required=False,
52
+ default_value=meta.get("default"),
53
+ enum_values=allowed_values
54
+ ))
55
+
56
+ schema = ParameterSchema()
57
+ for param in base_params:
58
+ schema.add_parameter(param)
59
+
60
+ if config_schema.parameters:
61
+ schema.add_parameter(ParameterDefinition(
62
+ name="generation_config",
63
+ param_type=ParameterType.OBJECT,
64
+ description=f"Model-specific generation parameters for the configured '{model_identifier}' model.",
65
+ required=False,
66
+ object_schema=config_schema
67
+ ))
68
+ return schema
69
+
70
+
71
+ class GenerateImageTool(BaseTool):
72
+ """
73
+ An agent tool for generating images from a text prompt using a pre-configured model.
74
+ """
75
+ CATEGORY = ToolCategory.MULTIMEDIA
76
+ MODEL_ENV_VAR = "DEFAULT_IMAGE_GENERATION_MODEL"
77
+ DEFAULT_MODEL = "gpt-image-1"
78
+
79
+ @classmethod
80
+ def get_name(cls) -> str:
81
+ return "GenerateImage"
82
+
83
+ @classmethod
84
+ def get_description(cls) -> str:
85
+ return (
86
+ "Generates one or more images based on a textual description (prompt) using the system's default image model. "
87
+ "Returns a list of URLs to the generated images upon success."
88
+ )
89
+
90
+ @classmethod
91
+ def get_argument_schema(cls) -> Optional[ParameterSchema]:
92
+ base_params = [
93
+ ParameterDefinition(
94
+ name="prompt",
95
+ param_type=ParameterType.STRING,
96
+ description="A detailed textual description of the image to generate.",
97
+ required=True
98
+ )
99
+ ]
100
+ return _build_dynamic_image_schema(base_params, cls.MODEL_ENV_VAR, cls.DEFAULT_MODEL)
101
+
102
+ async def _execute(self, context, prompt: str, generation_config: Optional[dict] = None) -> List[str]:
103
+ model_identifier = _get_configured_model_identifier(self.MODEL_ENV_VAR, self.DEFAULT_MODEL)
104
+ logger.info(f"GenerateImageTool executing with configured model '{model_identifier}'.")
105
+ client = None
106
+ try:
107
+ client = image_client_factory.create_image_client(model_identifier=model_identifier)
108
+ response = await client.generate_image(prompt=prompt, generation_config=generation_config)
109
+
110
+ if not response.image_urls:
111
+ raise ValueError("Image generation failed to return any image URLs.")
112
+
113
+ return response.image_urls
114
+ finally:
115
+ if client:
116
+ await client.cleanup()
117
+
118
+
119
+ class EditImageTool(BaseTool):
120
+ """
121
+ An agent tool for editing an existing image using a text prompt and a pre-configured model.
122
+ """
123
+ CATEGORY = ToolCategory.MULTIMEDIA
124
+ MODEL_ENV_VAR = "DEFAULT_IMAGE_GENERATION_MODEL"
125
+ DEFAULT_MODEL = "gpt-image-1"
126
+
127
+ @classmethod
128
+ def get_name(cls) -> str:
129
+ return "EditImage"
130
+
131
+ @classmethod
132
+ def get_description(cls) -> str:
133
+ return (
134
+ "Edits an existing image based on a textual description (prompt) using the system's default image model. "
135
+ "A mask can be provided to specify the exact area to edit (inpainting). "
136
+ "Returns a list of URLs to the edited images."
137
+ )
138
+
139
+ @classmethod
140
+ def get_argument_schema(cls) -> Optional[ParameterSchema]:
141
+ base_params = [
142
+ ParameterDefinition(
143
+ name="prompt",
144
+ param_type=ParameterType.STRING,
145
+ description="A detailed textual description of the edits to apply to the image.",
146
+ required=True
147
+ ),
148
+ ParameterDefinition(
149
+ name="input_image_urls",
150
+ param_type=ParameterType.STRING,
151
+ description="A comma-separated string of URLs to the source images that need to be edited. Some models may only use the first URL.",
152
+ required=True
153
+ ),
154
+ ParameterDefinition(
155
+ name="mask_image_url",
156
+ param_type=ParameterType.STRING,
157
+ description="Optional. A URL to a mask image (PNG). The transparent areas of this mask define where the input image should be edited.",
158
+ required=False
159
+ )
160
+ ]
161
+ return _build_dynamic_image_schema(base_params, cls.MODEL_ENV_VAR, cls.DEFAULT_MODEL)
162
+
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
+ model_identifier = _get_configured_model_identifier(self.MODEL_ENV_VAR, self.DEFAULT_MODEL)
165
+ logger.info(f"EditImageTool executing with configured model '{model_identifier}'.")
166
+ client = None
167
+ try:
168
+ urls_list = [url.strip() for url in input_image_urls.split(',') if url.strip()]
169
+ if not urls_list:
170
+ raise ValueError("The 'input_image_urls' parameter cannot be empty.")
171
+
172
+ client = image_client_factory.create_image_client(model_identifier=model_identifier)
173
+ response = await client.edit_image(
174
+ prompt=prompt,
175
+ input_image_urls=urls_list,
176
+ mask_url=mask_image_url,
177
+ generation_config=generation_config
178
+ )
179
+
180
+ if not response.image_urls:
181
+ raise ValueError("Image editing failed to return any image URLs.")
182
+
183
+ return response.image_urls
184
+ finally:
185
+ if client:
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
@@ -17,6 +17,7 @@ class ToolCategory(str, Enum):
17
17
  TASK_MANAGEMENT = "Task Management" # NEW CATEGORY ADDED
18
18
  GENERAL = "General"
19
19
  MCP = "MCP"
20
+ MULTIMEDIA = "Multimedia"
20
21
 
21
22
  def __str__(self) -> str:
22
23
  return self.value