From 73b1c6cf5c05f5da5651f5cbbd65b40a6c8f2c8c Mon Sep 17 00:00:00 2001 From: MarlzRana <77016115+MarlzRana@users.noreply.github.com> Date: Sat, 25 Oct 2025 15:59:28 +0100 Subject: [PATCH 1/4] fix: nullable types with gen ai bump --- pyproject.toml | 2 +- src/google/adk/tools/_gemini_schema_util.py | 31 ++-------- .../tools/test_gemini_schema_util.py | 60 ++++--------------- 3 files changed, 19 insertions(+), 74 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6782f41792..8582257a45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ "google-cloud-spanner>=3.56.0, <4.0.0", # For Spanner database "google-cloud-speech>=2.30.0, <3.0.0", # For Audio Transcription "google-cloud-storage>=2.18.0, <3.0.0", # For GCS Artifact service - "google-genai>=1.41.0, <2.0.0", # Google GenAI SDK + "google-genai>=1.44.0, <2.0.0", # Google GenAI SDK "graphviz>=0.20.2, <1.0.0", # Graphviz for graph rendering "mcp>=1.8.0, <2.0.0;python_version>='3.10'", # For MCP Toolset "opentelemetry-api>=1.37.0, <=1.37.0", # OpenTelemetry - limit upper version for sdk and api to not risk breaking changes from unstable _logs package. diff --git a/src/google/adk/tools/_gemini_schema_util.py b/src/google/adk/tools/_gemini_schema_util.py index df76f3c85b..ec25edfb51 100644 --- a/src/google/adk/tools/_gemini_schema_util.py +++ b/src/google/adk/tools/_gemini_schema_util.py @@ -74,31 +74,6 @@ def _to_snake_case(text: str) -> str: return text -def _sanitize_schema_type(schema: dict[str, Any]) -> dict[str, Any]: - if ("type" not in schema or not schema["type"]) and schema.keys().isdisjoint( - schema - ): - schema["type"] = "object" - if isinstance(schema.get("type"), list): - nullable = False - non_null_type = None - for t in schema["type"]: - if t == "null": - nullable = True - elif not non_null_type: - non_null_type = t - if not non_null_type: - non_null_type = "object" - if nullable: - schema["type"] = [non_null_type, "null"] - else: - schema["type"] = non_null_type - elif schema.get("type") == "null": - schema["type"] = ["object", "null"] - - return schema - - def _dereference_schema(schema: dict[str, Any]) -> dict[str, Any]: """Resolves $ref pointers in a JSON schema.""" @@ -183,7 +158,11 @@ def _sanitize_schema_formats_for_gemini( elif field_name in supported_fields and field_value is not None: snake_case_schema[field_name] = field_value - return _sanitize_schema_type(snake_case_schema) + # If the schema is empty, assume it has the type of object + if not snake_case_schema: + snake_case_schema["type"] = "object" + + return snake_case_schema def _to_gemini_schema(openapi_schema: dict[str, Any]) -> Schema: diff --git a/tests/unittests/tools/test_gemini_schema_util.py b/tests/unittests/tools/test_gemini_schema_util.py index f4c594d827..9ddfeb84c4 100644 --- a/tests/unittests/tools/test_gemini_schema_util.py +++ b/tests/unittests/tools/test_gemini_schema_util.py @@ -65,7 +65,7 @@ def test_to_gemini_schema_array_string_types(self): "nonnullable_string": {"type": ["string"]}, "nullable_string": {"type": ["string", "null"]}, "nullable_number": {"type": ["null", "integer"]}, - "object_nullable": {"type": "null"}, + "object_nullable": {"type": ["object", "null"]}, "multi_types_nullable": {"type": ["string", "null", "integer"]}, "empty_default_object": {}, }, @@ -87,7 +87,10 @@ def test_to_gemini_schema_array_string_types(self): assert gemini_schema.properties["object_nullable"].type == Type.OBJECT assert gemini_schema.properties["object_nullable"].nullable - assert gemini_schema.properties["multi_types_nullable"].type == Type.STRING + assert gemini_schema.properties["multi_types_nullable"].any_of == [ + Schema(type=Type.STRING), + Schema(type=Type.INTEGER), + ] assert gemini_schema.properties["multi_types_nullable"].nullable assert gemini_schema.properties["empty_default_object"].type == Type.OBJECT @@ -146,6 +149,14 @@ def test_to_gemini_schema_any_of(self): assert gemini_schema.any_of[0].type == Type.STRING assert gemini_schema.any_of[1].type == Type.INTEGER + def test_to_gemini_schema_any_of_nullable(self): + openapi_schema = { + "anyOf": [{"type": "string"}, {"type": "null"}], + } + gemini_schema = _to_gemini_schema(openapi_schema) + assert gemini_schema.type == Type.STRING + assert gemini_schema.nullable == True + def test_to_gemini_schema_general_list(self): openapi_schema = { "type": "array", @@ -524,51 +535,6 @@ def test_sanitize_schema_formats_for_gemini(self): "null", ] - def test_sanitize_schema_formats_for_gemini_nullable(self): - openapi_schema = { - "properties": { - "case_id": { - "description": "The ID of the case.", - "title": "Case Id", - "type": "string", - }, - "next_page_token": { - "anyOf": [{"type": "string"}, {"type": "null"}], - "default": None, - "description": ( - "The nextPageToken to fetch the next page of results." - ), - "title": "Next Page Token", - }, - }, - "required": ["case_id"], - "title": "list_alerts_by_caseArguments", - "type": "object", - } - openapi_schema = _sanitize_schema_formats_for_gemini(openapi_schema) - assert openapi_schema == { - "properties": { - "case_id": { - "description": "The ID of the case.", - "title": "Case Id", - "type": "string", - }, - "next_page_token": { - "any_of": [ - {"type": "string"}, - {"type": ["object", "null"]}, - ], - "description": ( - "The nextPageToken to fetch the next page of results." - ), - "title": "Next Page Token", - }, - }, - "required": ["case_id"], - "title": "list_alerts_by_caseArguments", - "type": "object", - } - def test_to_gemini_schema_properties_is_none(self): """Tests schema conversion when 'properties' field is None.""" openapi_schema = {"type": "object", "properties": None} From b91359bd7105d4e553910803c6157385eb372f8d Mon Sep 17 00:00:00 2001 From: MarlzRana <77016115+MarlzRana@users.noreply.github.com> Date: Sat, 25 Oct 2025 16:21:23 +0100 Subject: [PATCH 2/4] improve comment --- src/google/adk/tools/_gemini_schema_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/google/adk/tools/_gemini_schema_util.py b/src/google/adk/tools/_gemini_schema_util.py index ec25edfb51..d2ed560ef1 100644 --- a/src/google/adk/tools/_gemini_schema_util.py +++ b/src/google/adk/tools/_gemini_schema_util.py @@ -166,7 +166,7 @@ def _sanitize_schema_formats_for_gemini( def _to_gemini_schema(openapi_schema: dict[str, Any]) -> Schema: - """Converts an OpenAPI schema dictionary to a Gemini Schema object.""" + """Converts an OpenAPI v3.1. schema dictionary to a Gemini Schema object.""" if openapi_schema is None: return None From e2a8a7d4307546cf4a9dc10afb889728e965f7b0 Mon Sep 17 00:00:00 2001 From: MarlzRana <77016115+MarlzRana@users.noreply.github.com> Date: Sat, 25 Oct 2025 16:36:17 +0100 Subject: [PATCH 3/4] improve tests --- tests/unittests/tools/test_gemini_schema_util.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/unittests/tools/test_gemini_schema_util.py b/tests/unittests/tools/test_gemini_schema_util.py index 9ddfeb84c4..163b1266be 100644 --- a/tests/unittests/tools/test_gemini_schema_util.py +++ b/tests/unittests/tools/test_gemini_schema_util.py @@ -65,8 +65,9 @@ def test_to_gemini_schema_array_string_types(self): "nonnullable_string": {"type": ["string"]}, "nullable_string": {"type": ["string", "null"]}, "nullable_number": {"type": ["null", "integer"]}, - "object_nullable": {"type": ["object", "null"]}, + "nullable_object": {"type": ["object", "null"]}, "multi_types_nullable": {"type": ["string", "null", "integer"]}, + "only_null": {"type": "null"}, "empty_default_object": {}, }, } @@ -84,14 +85,17 @@ def test_to_gemini_schema_array_string_types(self): assert gemini_schema.properties["nullable_number"].type == Type.INTEGER assert gemini_schema.properties["nullable_number"].nullable - assert gemini_schema.properties["object_nullable"].type == Type.OBJECT - assert gemini_schema.properties["object_nullable"].nullable + assert gemini_schema.properties["nullable_object"].type == Type.OBJECT + assert gemini_schema.properties["nullable_object"].nullable assert gemini_schema.properties["multi_types_nullable"].any_of == [ Schema(type=Type.STRING), Schema(type=Type.INTEGER), ] assert gemini_schema.properties["multi_types_nullable"].nullable + + assert gemini_schema.properties["only_null"].type is None + assert gemini_schema.properties["only_null"].nullable assert gemini_schema.properties["empty_default_object"].type == Type.OBJECT assert gemini_schema.properties["empty_default_object"].nullable is None From 7a255cd4ca340f434d9592e9b19de3936d1bdbe7 Mon Sep 17 00:00:00 2001 From: MarlzRana <77016115+MarlzRana@users.noreply.github.com> Date: Sat, 25 Oct 2025 18:01:47 +0100 Subject: [PATCH 4/4] correct version --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8582257a45..a7ec020b56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ dependencies = [ "google-cloud-spanner>=3.56.0, <4.0.0", # For Spanner database "google-cloud-speech>=2.30.0, <3.0.0", # For Audio Transcription "google-cloud-storage>=2.18.0, <3.0.0", # For GCS Artifact service - "google-genai>=1.44.0, <2.0.0", # Google GenAI SDK + "google-genai>=1.45.0, <2.0.0", # Google GenAI SDK "graphviz>=0.20.2, <1.0.0", # Graphviz for graph rendering "mcp>=1.8.0, <2.0.0;python_version>='3.10'", # For MCP Toolset "opentelemetry-api>=1.37.0, <=1.37.0", # OpenTelemetry - limit upper version for sdk and api to not risk breaking changes from unstable _logs package.