@@ -618,16 +618,75 @@ def convert_tools_to_anthropic_format(tools: List[OpenAITool]) -> List[dict]:
618618 """
619619 formatted_tools = []
620620 for tool in tools :
621+ # Get the input schema
622+ input_schema = tool .function .parameters or {"type" : "object" , "properties" : {}, "required" : []}
623+
624+ # Clean up the properties in the schema
625+ # The presence of union types / default fields seems Anthropic to produce invalid JSON for tool calls
626+ if isinstance (input_schema , dict ) and "properties" in input_schema :
627+ cleaned_properties = {}
628+ for prop_name , prop_schema in input_schema .get ("properties" , {}).items ():
629+ if isinstance (prop_schema , dict ):
630+ cleaned_properties [prop_name ] = _clean_property_schema (prop_schema )
631+ else :
632+ cleaned_properties [prop_name ] = prop_schema
633+
634+ # Create cleaned input schema
635+ cleaned_input_schema = {
636+ "type" : input_schema .get ("type" , "object" ),
637+ "properties" : cleaned_properties ,
638+ }
639+
640+ # Only add required field if it exists and is non-empty
641+ if "required" in input_schema and input_schema ["required" ]:
642+ cleaned_input_schema ["required" ] = input_schema ["required" ]
643+ else :
644+ cleaned_input_schema = input_schema
645+
621646 formatted_tool = {
622647 "name" : tool .function .name ,
623648 "description" : tool .function .description if tool .function .description else "" ,
624- "input_schema" : tool . function . parameters or { "type" : "object" , "properties" : {}, "required" : []} ,
649+ "input_schema" : cleaned_input_schema ,
625650 }
626651 formatted_tools .append (formatted_tool )
627652
628653 return formatted_tools
629654
630655
656+ def _clean_property_schema (prop_schema : dict ) -> dict :
657+ """Clean up a property schema by removing defaults and simplifying union types."""
658+ cleaned = {}
659+
660+ # Handle type field - simplify union types like ["null", "string"] to just "string"
661+ if "type" in prop_schema :
662+ prop_type = prop_schema ["type" ]
663+ if isinstance (prop_type , list ):
664+ # Remove "null" from union types to simplify
665+ # e.g., ["null", "string"] becomes "string"
666+ non_null_types = [t for t in prop_type if t != "null" ]
667+ if len (non_null_types ) == 1 :
668+ cleaned ["type" ] = non_null_types [0 ]
669+ elif len (non_null_types ) > 1 :
670+ # Keep as array if multiple non-null types
671+ cleaned ["type" ] = non_null_types
672+ else :
673+ # If only "null" was in the list, default to string
674+ cleaned ["type" ] = "string"
675+ else :
676+ cleaned ["type" ] = prop_type
677+
678+ # Copy over other fields except 'default'
679+ for key , value in prop_schema .items ():
680+ if key not in ["type" , "default" ]: # Skip 'default' field
681+ if key == "properties" and isinstance (value , dict ):
682+ # Recursively clean nested properties
683+ cleaned ["properties" ] = {k : clean_property_schema (v ) if isinstance (v , dict ) else v for k , v in value .items ()}
684+ else :
685+ cleaned [key ] = value
686+
687+ return cleaned
688+
689+
631690def is_heartbeat (message : dict , is_ping : bool = False ) -> bool :
632691 """Check if the message is an automated heartbeat ping"""
633692
0 commit comments