@@ -21,7 +21,8 @@ def test_string_parameter() -> None:
21
21
d = param .model_dump_tool ()
22
22
assert d ["type" ] == ParameterType .STRING
23
23
assert d ["enum" ] == ["a" , "b" ]
24
- assert d ["required" ] is True
24
+ # Note: 'required' is handled at the object level, not individual parameter level
25
+ assert "required" not in d
25
26
26
27
27
28
def test_integer_parameter () -> None :
@@ -141,38 +142,166 @@ def test_from_dict() -> None:
141
142
142
143
143
144
def test_required_parameter () -> None :
144
- # Test that required=True is included in model_dump_tool output for different parameter types
145
+ # Test that individual parameters don't include 'required' field (it's handled at object level)
145
146
string_param = StringParameter (description = "Required string" , required = True )
146
- assert string_param . model_dump_tool ()[ "required" ] is True
147
+ assert "required" not in string_param . model_dump_tool ()
147
148
148
149
integer_param = IntegerParameter (description = "Required integer" , required = True )
149
- assert integer_param . model_dump_tool ()[ "required" ] is True
150
+ assert "required" not in integer_param . model_dump_tool ()
150
151
151
152
number_param = NumberParameter (description = "Required number" , required = True )
152
- assert number_param . model_dump_tool ()[ "required" ] is True
153
+ assert "required" not in number_param . model_dump_tool ()
153
154
154
155
boolean_param = BooleanParameter (description = "Required boolean" , required = True )
155
- assert boolean_param . model_dump_tool ()[ "required" ] is True
156
+ assert "required" not in boolean_param . model_dump_tool ()
156
157
157
158
array_param = ArrayParameter (
158
159
description = "Required array" ,
159
160
items = StringParameter (description = "item" ),
160
161
required = True ,
161
162
)
162
- assert array_param . model_dump_tool ()[ "required" ] is True
163
+ assert "required" not in array_param . model_dump_tool ()
163
164
164
165
object_param = ObjectParameter (
165
166
description = "Required object" ,
166
167
properties = {"prop" : StringParameter (description = "property" )},
167
168
required = True ,
168
169
)
169
- assert object_param . model_dump_tool ()[ "required" ] is True
170
+ assert "required" not in object_param . model_dump_tool ()
170
171
171
- # Test that required=False doesn 't include the required field
172
+ # Test that optional parameters also don 't include the required field
172
173
optional_param = StringParameter (description = "Optional string" , required = False )
173
174
assert "required" not in optional_param .model_dump_tool ()
174
175
175
176
177
+ def test_object_parameter_additional_properties_always_present () -> None :
178
+ """Test that additionalProperties is always present in ObjectParameter schema, fixing OpenAI compatibility."""
179
+
180
+ # Test additionalProperties=True (default)
181
+ obj_param_true = ObjectParameter (
182
+ description = "Object with additional properties" ,
183
+ properties = {"prop" : StringParameter (description = "A property" )},
184
+ additional_properties = True ,
185
+ )
186
+ schema_true = obj_param_true .model_dump_tool ()
187
+ assert "additionalProperties" in schema_true
188
+ assert schema_true ["additionalProperties" ] is True
189
+
190
+ # Test additionalProperties=False
191
+ obj_param_false = ObjectParameter (
192
+ description = "Object without additional properties" ,
193
+ properties = {"prop" : StringParameter (description = "A property" )},
194
+ additional_properties = False ,
195
+ )
196
+ schema_false = obj_param_false .model_dump_tool ()
197
+ assert "additionalProperties" in schema_false
198
+ assert schema_false ["additionalProperties" ] is False
199
+
200
+
201
+ def test_json_schema_compatibility () -> None :
202
+ """Test that the generated schema is compatible with JSON Schema specification."""
203
+
204
+ # Create a complex object with nested properties and required fields
205
+ nested_obj = ObjectParameter (
206
+ description = "Nested object" ,
207
+ properties = {
208
+ "nested_prop" : StringParameter (description = "Nested string" ),
209
+ },
210
+ additional_properties = True ,
211
+ )
212
+
213
+ main_obj = ObjectParameter (
214
+ description = "Main object" ,
215
+ properties = {
216
+ "required_string" : StringParameter (description = "Required string" ),
217
+ "optional_number" : NumberParameter (description = "Optional number" ),
218
+ "nested_object" : nested_obj ,
219
+ },
220
+ required_properties = ["required_string" ],
221
+ additional_properties = False ,
222
+ )
223
+
224
+ schema = main_obj .model_dump_tool ()
225
+
226
+ # Verify JSON Schema structure
227
+ assert schema ["type" ] == "object"
228
+ assert "properties" in schema
229
+ assert "required" in schema
230
+ assert "additionalProperties" in schema
231
+
232
+ # Check required is an array (not boolean on individual properties)
233
+ assert isinstance (schema ["required" ], list )
234
+ assert "required_string" in schema ["required" ]
235
+ assert len (schema ["required" ]) == 1
236
+
237
+ # Check individual properties don't have 'required' field
238
+ for prop_name , prop_schema in schema ["properties" ].items ():
239
+ assert "required" not in prop_schema
240
+
241
+ # Check additionalProperties is properly set at all levels
242
+ assert schema ["additionalProperties" ] is False
243
+ assert schema ["properties" ]["nested_object" ]["additionalProperties" ] is True
244
+
245
+
246
+ def test_text2cypher_retriever_schema_compatibility () -> None :
247
+ """Test the specific schema structure that caused the OpenAI API error."""
248
+
249
+ # Simulate the Text2CypherRetriever parameter structure
250
+ prompt_params = ObjectParameter (
251
+ description = "Parameter prompt_params" ,
252
+ properties = {},
253
+ additional_properties = True , # This was missing in the original bug
254
+ )
255
+
256
+ t2c_params = ObjectParameter (
257
+ description = "Parameters for Text2CypherRetriever" ,
258
+ properties = {
259
+ "query_text" : StringParameter (description = "Parameter query_text" ),
260
+ "prompt_params" : prompt_params ,
261
+ },
262
+ required_properties = ["query_text" ],
263
+ additional_properties = False ,
264
+ )
265
+
266
+ schema = t2c_params .model_dump_tool ()
267
+
268
+ # Verify the fix: prompt_params should have additionalProperties
269
+ prompt_params_schema = schema ["properties" ]["prompt_params" ]
270
+ assert "additionalProperties" in prompt_params_schema
271
+ assert prompt_params_schema ["additionalProperties" ] is True
272
+
273
+ # Verify query_text doesn't have individual 'required' field
274
+ query_text_schema = schema ["properties" ]["query_text" ]
275
+ assert "required" not in query_text_schema
276
+
277
+ # Verify required array at object level
278
+ assert schema ["required" ] == ["query_text" ]
279
+
280
+
281
+ def test_exclude_parameter_in_object_schema () -> None :
282
+ """Test that exclude parameter works correctly in ObjectParameter.model_dump_tool()."""
283
+
284
+ obj_param = ObjectParameter (
285
+ description = "Test object" ,
286
+ properties = {
287
+ "prop1" : StringParameter (description = "Property 1" ),
288
+ "prop2" : IntegerParameter (description = "Property 2" ),
289
+ },
290
+ required_properties = ["prop1" ],
291
+ additional_properties = True ,
292
+ )
293
+
294
+ # Test excluding required field
295
+ schema_no_required = obj_param .model_dump_tool (exclude = ["required" ])
296
+ assert "required" not in schema_no_required
297
+ assert "additionalProperties" in schema_no_required # Should still be present
298
+
299
+ # Test excluding additionalProperties field
300
+ schema_no_additional = obj_param .model_dump_tool (exclude = ["additional_properties" ])
301
+ assert "additionalProperties" not in schema_no_additional
302
+ assert "required" in schema_no_additional # Should still be present
303
+
304
+
176
305
def test_tool_class () -> None :
177
306
def dummy_func (** kwargs : Any ) -> dict [str , Any ]:
178
307
return kwargs
0 commit comments