@@ -96,6 +96,35 @@ def clean_gemini_schema(schema: dict[str, Any]) -> None:
9696 _clean_recursive (schema , is_schema_node = True )
9797
9898
99+ def clone_schema_with_openai_object_fixes (schema : dict [str , Any ]) -> dict [str , Any ]:
100+ """Clone a schema and add missing properties for object nodes.
101+
102+ OpenAI function tools reject object-typed parameter schemas when the object
103+ node does not declare a ``properties`` object. Keep the schema otherwise
104+ unchanged and only backfill empty ``properties`` where needed.
105+ """
106+ cloned = copy .deepcopy (schema )
107+ _ensure_object_properties_recursive (cloned )
108+ return cloned
109+
110+
111+ def clone_openai_tool_with_fixed_parameters (tool : dict [str , Any ]) -> dict [str , Any ]:
112+ """Clone an OpenAI Chat/Responses tool and repair function parameter schemas."""
113+ cloned = copy .deepcopy (tool )
114+
115+ function = cloned .get ("function" )
116+ if isinstance (function , dict ):
117+ params = function .get ("parameters" )
118+ if isinstance (params , dict ):
119+ _ensure_object_properties_recursive (params )
120+
121+ params = cloned .get ("parameters" )
122+ if isinstance (params , dict ):
123+ _ensure_object_properties_recursive (params )
124+
125+ return cloned
126+
127+
99128# ---------------------------------------------------------------------------
100129# Phase 1: $defs 收集
101130# ---------------------------------------------------------------------------
@@ -504,7 +533,31 @@ def _append_hint(obj: dict[str, Any], hint: str) -> None:
504533 obj ["description" ] = f"{ desc } { hint } " .strip () if desc else hint
505534
506535
536+ def _schema_type_includes_object (type_value : Any ) -> bool :
537+ if isinstance (type_value , str ):
538+ return type_value .lower () == "object"
539+ if isinstance (type_value , list ):
540+ return any (isinstance (item , str ) and item .lower () == "object" for item in type_value )
541+ return False
542+
543+
544+ def _ensure_object_properties_recursive (value : Any ) -> None :
545+ if isinstance (value , dict ):
546+ if _schema_type_includes_object (value .get ("type" )) and not isinstance (
547+ value .get ("properties" ), dict
548+ ):
549+ value ["properties" ] = {}
550+ for item in value .values ():
551+ _ensure_object_properties_recursive (item )
552+ return
553+ if isinstance (value , list ):
554+ for item in value :
555+ _ensure_object_properties_recursive (item )
556+
557+
507558__all__ = [
508559 "GEMINI_FORBIDDEN_SCHEMA_FIELDS" ,
509560 "clean_gemini_schema" ,
561+ "clone_openai_tool_with_fixed_parameters" ,
562+ "clone_schema_with_openai_object_fixes" ,
510563]
0 commit comments