diff --git a/letta/llm_api/google_ai_client.py b/letta/llm_api/google_ai_client.py index dc20f1f4..36541fb0 100644 --- a/letta/llm_api/google_ai_client.py +++ b/letta/llm_api/google_ai_client.py @@ -267,6 +267,41 @@ class GoogleAIClient(LLMClientBase): except KeyError as e: raise e + def _clean_google_ai_schema_properties(self, schema_part: dict): + """Recursively clean schema parts to remove unsupported Google AI keywords.""" + if not isinstance(schema_part, dict): + return + + # Per https://ai.google.dev/gemini-api/docs/function-calling?example=meeting#notes_and_limitations + # * Only a subset of the OpenAPI schema is supported. + # * Supported parameter types in Python are limited. + unsupported_keys = ["default", "exclusiveMaximum", "exclusiveMinimum"] + keys_to_remove_at_this_level = [key for key in unsupported_keys if key in schema_part] + for key_to_remove in keys_to_remove_at_this_level: + logger.warning(f"Removing unsupported keyword '{key_to_remove}' from schema part.") + del schema_part[key_to_remove] + + if schema_part.get("type") == "string" and "format" in schema_part: + allowed_formats = ["enum", "date-time"] + if schema_part["format"] not in allowed_formats: + logger.warning(f"Removing unsupported format '{schema_part['format']}' for string type. Allowed: {allowed_formats}") + del schema_part["format"] + + # Check properties within the current level + if "properties" in schema_part and isinstance(schema_part["properties"], dict): + for prop_name, prop_schema in schema_part["properties"].items(): + self._clean_google_ai_schema_properties(prop_schema) + + # Check items within arrays + if "items" in schema_part and isinstance(schema_part["items"], dict): + self._clean_google_ai_schema_properties(schema_part["items"]) + + # Check within anyOf, allOf, oneOf lists + for key in ["anyOf", "allOf", "oneOf"]: + if key in schema_part and isinstance(schema_part[key], list): + for item_schema in schema_part[key]: + self._clean_google_ai_schema_properties(item_schema) + def convert_tools_to_google_ai_format(self, tools: List[Tool], llm_config: LLMConfig) -> List[dict]: """ OpenAI style: @@ -326,6 +361,10 @@ class GoogleAIClient(LLMClientBase): for func in function_list: # Note: Google AI API used to have weird casing requirements, but not any more + # Google AI API only supports a subset of OpenAPI 3.0, so unsupported params must be cleaned + if "parameters" in func and isinstance(func["parameters"], dict): + self._clean_google_ai_schema_properties(func["parameters"]) + # Add inner thoughts if llm_config.put_inner_thoughts_in_kwargs: from letta.local_llm.constants import INNER_THOUGHTS_KWARG, INNER_THOUGHTS_KWARG_DESCRIPTION