Skip to content

Commit ab00c41

Browse files
MarlzRanacopybara-github
authored andcommitted
fix: nullable types with gen ai bump
Merge #3280 Fixes: #3281 COPYBARA_INTEGRATE_REVIEW=#3280 from MarlzRana:marlzrana/fix-nullable-types-w-gen-ai-bump ad28b82 PiperOrigin-RevId: 824659954
1 parent e194ebb commit ab00c41

File tree

3 files changed

+26
-70
lines changed

3 files changed

+26
-70
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ dependencies = [
3939
"google-cloud-spanner>=3.56.0, <4.0.0", # For Spanner database
4040
"google-cloud-speech>=2.30.0, <3.0.0", # For Audio Transcription
4141
"google-cloud-storage>=2.18.0, <3.0.0", # For GCS Artifact service
42-
"google-genai>=1.41.0, <2.0.0", # Google GenAI SDK
42+
"google-genai>=1.45.0, <2.0.0", # Google GenAI SDK
4343
"graphviz>=0.20.2, <1.0.0", # Graphviz for graph rendering
4444
"mcp>=1.8.0, <2.0.0;python_version>='3.10'", # For MCP Toolset
4545
"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.

src/google/adk/tools/_gemini_schema_util.py

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -74,33 +74,6 @@ def _to_snake_case(text: str) -> str:
7474
return text
7575

7676

77-
def _sanitize_schema_type(schema: dict[str, Any]) -> dict[str, Any]:
78-
if not schema:
79-
schema["type"] = "object"
80-
if isinstance(schema.get("type"), list):
81-
types_no_null = [t for t in schema["type"] if t != "null"]
82-
nullable = len(types_no_null) != len(schema["type"])
83-
if "array" in types_no_null:
84-
non_null_type = "array"
85-
else:
86-
non_null_type = types_no_null[0] if types_no_null else "object"
87-
if nullable:
88-
schema["type"] = [non_null_type, "null"]
89-
else:
90-
schema["type"] = non_null_type
91-
elif schema.get("type") == "null":
92-
schema["type"] = ["object", "null"]
93-
94-
schema_type = schema.get("type")
95-
is_array = schema_type == "array" or (
96-
isinstance(schema_type, list) and "array" in schema_type
97-
)
98-
if is_array and "items" not in schema:
99-
schema["items"] = {"type": "string"}
100-
101-
return schema
102-
103-
10477
def _dereference_schema(schema: dict[str, Any]) -> dict[str, Any]:
10578
"""Resolves $ref pointers in a JSON schema."""
10679

@@ -185,11 +158,15 @@ def _sanitize_schema_formats_for_gemini(
185158
elif field_name in supported_fields and field_value is not None:
186159
snake_case_schema[field_name] = field_value
187160

188-
return _sanitize_schema_type(snake_case_schema)
161+
# If the schema is empty, assume it has the type of object
162+
if not snake_case_schema:
163+
snake_case_schema["type"] = "object"
164+
165+
return snake_case_schema
189166

190167

191168
def _to_gemini_schema(openapi_schema: dict[str, Any]) -> Schema:
192-
"""Converts an OpenAPI schema dictionary to a Gemini Schema object."""
169+
"""Converts an OpenAPI v3.1. schema dictionary to a Gemini Schema object."""
193170
if openapi_schema is None:
194171
return None
195172

tests/unittests/tools/test_gemini_schema_util.py

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,10 @@ def test_to_gemini_schema_array_string_types(self):
6565
"nonnullable_string": {"type": ["string"]},
6666
"nullable_string": {"type": ["string", "null"]},
6767
"nullable_number": {"type": ["null", "integer"]},
68-
"object_nullable": {"type": "null"},
68+
"nullable_object": {"type": ["object", "null"]},
6969
"multi_types_nullable": {"type": ["string", "null", "integer"]},
70+
"only_null": {"type": "null"},
7071
"empty_default_object": {},
71-
"empty_list_type": {"type": []},
72-
"multi_type_with_array_nullable": {
73-
"type": ["string", "array", "null"]
74-
},
75-
"multi_type_with_array_nonnullable": {"type": ["integer", "array"]},
7672
},
7773
}
7874
gemini_schema = _to_gemini_schema(openapi_schema)
@@ -89,32 +85,21 @@ def test_to_gemini_schema_array_string_types(self):
8985
assert gemini_schema.properties["nullable_number"].type == Type.INTEGER
9086
assert gemini_schema.properties["nullable_number"].nullable
9187

92-
assert gemini_schema.properties["object_nullable"].type == Type.OBJECT
93-
assert gemini_schema.properties["object_nullable"].nullable
88+
assert gemini_schema.properties["nullable_object"].type == Type.OBJECT
89+
assert gemini_schema.properties["nullable_object"].nullable
9490

95-
assert gemini_schema.properties["multi_types_nullable"].type == Type.STRING
91+
assert gemini_schema.properties["multi_types_nullable"].any_of == [
92+
Schema(type=Type.STRING),
93+
Schema(type=Type.INTEGER),
94+
]
9695
assert gemini_schema.properties["multi_types_nullable"].nullable
9796

97+
assert gemini_schema.properties["only_null"].type is None
98+
assert gemini_schema.properties["only_null"].nullable
99+
98100
assert gemini_schema.properties["empty_default_object"].type == Type.OBJECT
99101
assert gemini_schema.properties["empty_default_object"].nullable is None
100102

101-
assert gemini_schema.properties["empty_list_type"].type == Type.OBJECT
102-
assert not gemini_schema.properties["empty_list_type"].nullable
103-
104-
assert (
105-
gemini_schema.properties["multi_type_with_array_nullable"].type
106-
== Type.ARRAY
107-
)
108-
assert gemini_schema.properties["multi_type_with_array_nullable"].nullable
109-
110-
assert (
111-
gemini_schema.properties["multi_type_with_array_nonnullable"].type
112-
== Type.ARRAY
113-
)
114-
assert not gemini_schema.properties[
115-
"multi_type_with_array_nonnullable"
116-
].nullable
117-
118103
def test_to_gemini_schema_nested_objects(self):
119104
openapi_schema = {
120105
"type": "object",
@@ -159,20 +144,6 @@ def test_to_gemini_schema_nested_array(self):
159144
gemini_schema = _to_gemini_schema(openapi_schema)
160145
assert gemini_schema.items.properties["name"].type == Type.STRING
161146

162-
def test_to_gemini_schema_array_without_items_gets_default(self):
163-
openapi_schema = {"type": "array"}
164-
gemini_schema = _to_gemini_schema(openapi_schema)
165-
assert gemini_schema.type == Type.ARRAY
166-
assert not gemini_schema.nullable
167-
assert gemini_schema.items.type == Type.STRING
168-
169-
def test_to_gemini_schema_nullable_array_without_items_gets_default(self):
170-
openapi_schema = {"type": ["array", "null"]}
171-
gemini_schema = _to_gemini_schema(openapi_schema)
172-
assert gemini_schema.type == Type.ARRAY
173-
assert gemini_schema.nullable
174-
assert gemini_schema.items.type == Type.STRING
175-
176147
def test_to_gemini_schema_any_of(self):
177148
openapi_schema = {
178149
"anyOf": [{"type": "string"}, {"type": "integer"}],
@@ -182,6 +153,14 @@ def test_to_gemini_schema_any_of(self):
182153
assert gemini_schema.any_of[0].type == Type.STRING
183154
assert gemini_schema.any_of[1].type == Type.INTEGER
184155

156+
def test_to_gemini_schema_any_of_nullable(self):
157+
openapi_schema = {
158+
"anyOf": [{"type": "string"}, {"type": "null"}],
159+
}
160+
gemini_schema = _to_gemini_schema(openapi_schema)
161+
assert gemini_schema.type == Type.STRING
162+
assert gemini_schema.nullable
163+
185164
def test_to_gemini_schema_general_list(self):
186165
openapi_schema = {
187166
"type": "array",

0 commit comments

Comments
 (0)