@@ -21,7 +21,8 @@ def test_string_parameter() -> None:
2121 d = param .model_dump_tool ()
2222 assert d ["type" ] == ParameterType .STRING
2323 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
2526
2627
2728def test_integer_parameter () -> None :
@@ -141,38 +142,166 @@ def test_from_dict() -> None:
141142
142143
143144def 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)
145146 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 ()
147148
148149 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 ()
150151
151152 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 ()
153154
154155 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 ()
156157
157158 array_param = ArrayParameter (
158159 description = "Required array" ,
159160 items = StringParameter (description = "item" ),
160161 required = True ,
161162 )
162- assert array_param . model_dump_tool ()[ "required" ] is True
163+ assert "required" not in array_param . model_dump_tool ()
163164
164165 object_param = ObjectParameter (
165166 description = "Required object" ,
166167 properties = {"prop" : StringParameter (description = "property" )},
167168 required = True ,
168169 )
169- assert object_param . model_dump_tool ()[ "required" ] is True
170+ assert "required" not in object_param . model_dump_tool ()
170171
171- # Test that required=False doesn 't include the required field
172+ # Test that optional parameters also don 't include the required field
172173 optional_param = StringParameter (description = "Optional string" , required = False )
173174 assert "required" not in optional_param .model_dump_tool ()
174175
175176
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+
176305def test_tool_class () -> None :
177306 def dummy_func (** kwargs : Any ) -> dict [str , Any ]:
178307 return kwargs
0 commit comments