Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.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.
Expand Down
33 changes: 6 additions & 27 deletions src/google/adk/tools/_gemini_schema_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down Expand Up @@ -183,11 +158,15 @@ 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:
"""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

Expand Down
68 changes: 19 additions & 49 deletions tests/unittests/tools/test_gemini_schema_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": "null"},
"nullable_object": {"type": ["object", "null"]},
"multi_types_nullable": {"type": ["string", "null", "integer"]},
"only_null": {"type": "null"},
"empty_default_object": {},
},
}
Expand All @@ -84,11 +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"].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["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
Expand Down Expand Up @@ -146,6 +153,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",
Expand Down Expand Up @@ -524,51 +539,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}
Expand Down
Loading