autobyteus 1.1.6__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.
- autobyteus/agent/context/agent_runtime_state.py +7 -1
- autobyteus/agent/handlers/tool_result_event_handler.py +100 -88
- 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.7.dist-info/METADATA +204 -0
- {autobyteus-1.1.6.dist-info → autobyteus-1.1.7.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.7.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.6.dist-info → autobyteus-1.1.7.dist-info}/licenses/LICENSE +0 -0
- {autobyteus-1.1.6.dist-info → autobyteus-1.1.7.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# file: autobyteus/autobyteus/tools/usage/formatters/default_json_example_formatter.py
|
|
2
|
-
|
|
2
|
+
import json
|
|
3
|
+
from typing import Dict, Any, TYPE_CHECKING, List, Optional, Union
|
|
3
4
|
|
|
4
|
-
from autobyteus.tools.parameter_schema import ParameterType, ParameterDefinition
|
|
5
|
+
from autobyteus.tools.parameter_schema import ParameterType, ParameterDefinition, ParameterSchema
|
|
5
6
|
from .base_formatter import BaseExampleFormatter
|
|
6
7
|
|
|
7
8
|
if TYPE_CHECKING:
|
|
@@ -10,20 +11,47 @@ if TYPE_CHECKING:
|
|
|
10
11
|
class DefaultJsonExampleFormatter(BaseExampleFormatter):
|
|
11
12
|
"""
|
|
12
13
|
Formats a tool usage example into a generic JSON format.
|
|
13
|
-
It intelligently generates detailed examples for complex object schemas
|
|
14
|
+
It intelligently generates detailed examples for complex object schemas, providing
|
|
15
|
+
both a basic (required parameters only) and an advanced (all parameters) example
|
|
16
|
+
if optional parameters are available.
|
|
14
17
|
"""
|
|
15
18
|
|
|
16
|
-
def provide(self, tool_definition: 'ToolDefinition') ->
|
|
19
|
+
def provide(self, tool_definition: 'ToolDefinition') -> str:
|
|
20
|
+
"""
|
|
21
|
+
Generates a formatted string containing basic and optionally an advanced usage example for the tool.
|
|
22
|
+
"""
|
|
23
|
+
basic_example_dict = self._create_example_structure(tool_definition, mode='basic')
|
|
24
|
+
basic_example_str = "### Example 1: Basic Call (Required Arguments)\n"
|
|
25
|
+
basic_example_str += "```json\n"
|
|
26
|
+
basic_example_str += json.dumps(basic_example_dict, indent=2)
|
|
27
|
+
basic_example_str += "\n```"
|
|
28
|
+
|
|
29
|
+
if not self._schema_has_advanced_params(tool_definition.argument_schema):
|
|
30
|
+
return basic_example_str
|
|
31
|
+
|
|
32
|
+
advanced_example_dict = self._create_example_structure(tool_definition, mode='advanced')
|
|
33
|
+
advanced_example_str = "### Example 2: Advanced Call (With Optional Arguments)\n"
|
|
34
|
+
advanced_example_str += "```json\n"
|
|
35
|
+
advanced_example_str += json.dumps(advanced_example_dict, indent=2)
|
|
36
|
+
advanced_example_str += "\n```"
|
|
37
|
+
|
|
38
|
+
return f"{basic_example_str}\n\n{advanced_example_str}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _create_example_structure(self, tool_definition: 'ToolDefinition', mode: str) -> Dict:
|
|
42
|
+
"""Helper to create the full tool call example structure for a given mode."""
|
|
17
43
|
tool_name = tool_definition.name
|
|
18
44
|
arg_schema = tool_definition.argument_schema
|
|
19
45
|
arguments = {}
|
|
20
46
|
|
|
21
47
|
if arg_schema and arg_schema.parameters:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
48
|
+
params_to_render = arg_schema.parameters
|
|
49
|
+
if mode == 'basic':
|
|
50
|
+
# In basic mode, we only render required parameters.
|
|
51
|
+
params_to_render = [p for p in arg_schema.parameters if p.required]
|
|
52
|
+
|
|
53
|
+
for param_def in params_to_render:
|
|
54
|
+
arguments[param_def.name] = self._generate_placeholder_value(param_def, mode=mode)
|
|
27
55
|
|
|
28
56
|
return {
|
|
29
57
|
"tool": {
|
|
@@ -32,12 +60,34 @@ class DefaultJsonExampleFormatter(BaseExampleFormatter):
|
|
|
32
60
|
},
|
|
33
61
|
}
|
|
34
62
|
|
|
35
|
-
def
|
|
63
|
+
def _schema_has_advanced_params(self, schema: Optional[ParameterSchema]) -> bool:
|
|
64
|
+
"""Recursively checks if a schema or any of its sub-schemas have non-required parameters."""
|
|
65
|
+
if not schema:
|
|
66
|
+
return False
|
|
67
|
+
for param in schema.parameters:
|
|
68
|
+
if not param.required:
|
|
69
|
+
return True # Found an optional param at this level
|
|
70
|
+
if param.object_schema and self._schema_has_advanced_params(param.object_schema):
|
|
71
|
+
return True # Found an optional param in a nested object
|
|
72
|
+
if isinstance(param.array_item_schema, ParameterSchema) and self._schema_has_advanced_params(param.array_item_schema):
|
|
73
|
+
return True # Found an optional param in an array of objects
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
def _generate_placeholder_value(self, param_def: ParameterDefinition, mode: str = 'basic') -> Any:
|
|
77
|
+
"""
|
|
78
|
+
Generates a placeholder value for a parameter, recursing for complex types.
|
|
79
|
+
The mode determines whether to include optional fields in nested structures.
|
|
80
|
+
"""
|
|
36
81
|
# If an object parameter has a detailed schema, generate a structured example from it.
|
|
37
82
|
if param_def.param_type == ParameterType.OBJECT and param_def.object_schema:
|
|
38
|
-
|
|
39
|
-
return DefaultJsonExampleFormatter._generate_example_from_schema(param_def.object_schema, param_def.object_schema)
|
|
83
|
+
return DefaultJsonExampleFormatter._generate_example_from_schema(param_def.object_schema, param_def.object_schema, mode=mode)
|
|
40
84
|
|
|
85
|
+
# Handle arrays with a detailed item schema.
|
|
86
|
+
if param_def.param_type == ParameterType.ARRAY and param_def.array_item_schema:
|
|
87
|
+
# Generate one example item for the array to keep it concise.
|
|
88
|
+
example_item = DefaultJsonExampleFormatter._generate_example_from_schema(param_def.array_item_schema, param_def.array_item_schema, mode=mode)
|
|
89
|
+
return [example_item]
|
|
90
|
+
|
|
41
91
|
# Fallback to simple placeholder generation for primitives or objects without schemas.
|
|
42
92
|
if param_def.default_value is not None: return param_def.default_value
|
|
43
93
|
if param_def.param_type == ParameterType.STRING: return f"example_{param_def.name}"
|
|
@@ -46,15 +96,34 @@ class DefaultJsonExampleFormatter(BaseExampleFormatter):
|
|
|
46
96
|
if param_def.param_type == ParameterType.BOOLEAN: return True
|
|
47
97
|
if param_def.param_type == ParameterType.ENUM: return param_def.enum_values[0] if param_def.enum_values else "enum_val"
|
|
48
98
|
if param_def.param_type == ParameterType.OBJECT: return {"key": "value"}
|
|
49
|
-
if param_def.param_type == ParameterType.ARRAY: return ["item1", "item2"]
|
|
99
|
+
if param_def.param_type == ParameterType.ARRAY: return ["item1", "item2"] # This now only applies to generic arrays
|
|
50
100
|
return "placeholder"
|
|
51
101
|
|
|
52
102
|
@staticmethod
|
|
53
|
-
def _generate_example_from_schema(
|
|
103
|
+
def _generate_example_from_schema(
|
|
104
|
+
sub_schema: Union[Dict[str, Any], 'ParameterSchema', 'ParameterType'],
|
|
105
|
+
full_schema: Union[Dict[str, Any], 'ParameterSchema', 'ParameterType'],
|
|
106
|
+
mode: str = 'basic'
|
|
107
|
+
) -> Any:
|
|
54
108
|
"""
|
|
55
109
|
Recursively generates an example value from a JSON schema dictionary.
|
|
56
110
|
This is a static method so it can be reused by other formatters.
|
|
111
|
+
The 'mode' parameter controls whether optional fields are included in nested objects.
|
|
112
|
+
The default mode is 'basic' to maintain backward compatibility.
|
|
57
113
|
"""
|
|
114
|
+
# FIX: Handle primitive ParameterType for array items directly.
|
|
115
|
+
if isinstance(sub_schema, ParameterType):
|
|
116
|
+
if sub_schema == ParameterType.STRING: return "example_string"
|
|
117
|
+
if sub_schema == ParameterType.INTEGER: return 1
|
|
118
|
+
if sub_schema == ParameterType.FLOAT: return 1.1
|
|
119
|
+
if sub_schema == ParameterType.BOOLEAN: return True
|
|
120
|
+
return "unknown_primitive"
|
|
121
|
+
|
|
122
|
+
if isinstance(sub_schema, ParameterSchema):
|
|
123
|
+
sub_schema = sub_schema.to_json_schema_dict()
|
|
124
|
+
if isinstance(full_schema, ParameterSchema):
|
|
125
|
+
full_schema = full_schema.to_json_schema_dict()
|
|
126
|
+
|
|
58
127
|
if "$ref" in sub_schema:
|
|
59
128
|
ref_path = sub_schema["$ref"]
|
|
60
129
|
try:
|
|
@@ -63,7 +132,7 @@ class DefaultJsonExampleFormatter(BaseExampleFormatter):
|
|
|
63
132
|
resolved_schema = full_schema
|
|
64
133
|
for part in parts:
|
|
65
134
|
resolved_schema = resolved_schema[part]
|
|
66
|
-
return DefaultJsonExampleFormatter._generate_example_from_schema(resolved_schema, full_schema)
|
|
135
|
+
return DefaultJsonExampleFormatter._generate_example_from_schema(resolved_schema, full_schema, mode=mode)
|
|
67
136
|
except (KeyError, IndexError):
|
|
68
137
|
return {"error": f"Could not resolve schema reference: {ref_path}"}
|
|
69
138
|
|
|
@@ -80,16 +149,16 @@ class DefaultJsonExampleFormatter(BaseExampleFormatter):
|
|
|
80
149
|
properties = sub_schema.get("properties", {})
|
|
81
150
|
required_fields = sub_schema.get("required", [])
|
|
82
151
|
for prop_name, prop_schema in properties.items():
|
|
83
|
-
# Include
|
|
84
|
-
if prop_name in required_fields:
|
|
85
|
-
example_obj[prop_name] = DefaultJsonExampleFormatter._generate_example_from_schema(prop_schema, full_schema)
|
|
152
|
+
# Include fields if in 'advanced' mode or if they are required.
|
|
153
|
+
if mode == 'advanced' or prop_name in required_fields:
|
|
154
|
+
example_obj[prop_name] = DefaultJsonExampleFormatter._generate_example_from_schema(prop_schema, full_schema, mode=mode)
|
|
86
155
|
return example_obj
|
|
87
156
|
|
|
88
157
|
elif schema_type == "array":
|
|
89
158
|
items_schema = sub_schema.get("items")
|
|
90
159
|
if isinstance(items_schema, dict):
|
|
91
|
-
# Generate one example item for the array to keep it concise
|
|
92
|
-
return [DefaultJsonExampleFormatter._generate_example_from_schema(items_schema, full_schema)]
|
|
160
|
+
# Generate one example item for the array to keep it concise.
|
|
161
|
+
return [DefaultJsonExampleFormatter._generate_example_from_schema(items_schema, full_schema, mode=mode)]
|
|
93
162
|
else:
|
|
94
163
|
return ["example_item_1"]
|
|
95
164
|
|