mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
fix: Fix issue with MCP tools throwing an error
Fixes: https://github.com/google/adk-python/issues/3082 PiperOrigin-RevId: 849562638
This commit is contained in:
committed by
Copybara-Service
parent
1f546df35a
commit
26e77e1694
@@ -74,6 +74,35 @@ def _to_snake_case(text: str) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def _sanitize_schema_type(
|
||||
schema: dict[str, Any], preserve_null_type: bool = False
|
||||
) -> dict[str, Any]:
|
||||
if not schema:
|
||||
schema["type"] = "object"
|
||||
if isinstance(schema.get("type"), list):
|
||||
types_no_null = [t for t in schema["type"] if t != "null"]
|
||||
nullable = len(types_no_null) != len(schema["type"])
|
||||
if "array" in types_no_null:
|
||||
non_null_type = "array"
|
||||
else:
|
||||
non_null_type = types_no_null[0] if types_no_null else "object"
|
||||
if nullable:
|
||||
schema["type"] = [non_null_type, "null"]
|
||||
else:
|
||||
schema["type"] = non_null_type
|
||||
elif schema.get("type") == "null" and not preserve_null_type:
|
||||
schema["type"] = ["object", "null"]
|
||||
|
||||
schema_type = schema.get("type")
|
||||
is_array = schema_type == "array" or (
|
||||
isinstance(schema_type, list) and "array" in schema_type
|
||||
)
|
||||
if is_array:
|
||||
schema.setdefault("items", {"type": "string"})
|
||||
|
||||
return schema
|
||||
|
||||
|
||||
def _dereference_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Resolves $ref pointers in a JSON schema."""
|
||||
|
||||
@@ -113,7 +142,7 @@ def _dereference_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
||||
|
||||
|
||||
def _sanitize_schema_formats_for_gemini(
|
||||
schema: dict[str, Any],
|
||||
schema: dict[str, Any], preserve_null_type: bool = False
|
||||
) -> dict[str, Any]:
|
||||
"""Filters the schema to only include fields that are supported by JSONSchema."""
|
||||
supported_fields: set[str] = set(_ExtendedJSONSchema.model_fields.keys())
|
||||
@@ -135,8 +164,12 @@ def _sanitize_schema_formats_for_gemini(
|
||||
field_value
|
||||
)
|
||||
elif field_name in list_schema_field_names:
|
||||
should_preserve = field_name in ("any_of", "one_of")
|
||||
snake_case_schema[field_name] = [
|
||||
_sanitize_schema_formats_for_gemini(value) for value in field_value
|
||||
_sanitize_schema_formats_for_gemini(
|
||||
value, preserve_null_type=should_preserve
|
||||
)
|
||||
for value in field_value
|
||||
]
|
||||
elif field_name in dict_schema_field_names and field_value is not None:
|
||||
snake_case_schema[field_name] = {
|
||||
@@ -158,11 +191,7 @@ 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
|
||||
|
||||
# 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
|
||||
return _sanitize_schema_type(snake_case_schema, preserve_null_type)
|
||||
|
||||
|
||||
def _to_gemini_schema(openapi_schema: dict[str, Any]) -> Schema:
|
||||
|
||||
@@ -66,9 +66,15 @@ class TestToGeminiSchema:
|
||||
"nullable_string": {"type": ["string", "null"]},
|
||||
"nullable_number": {"type": ["null", "integer"]},
|
||||
"nullable_object": {"type": ["object", "null"]},
|
||||
"object_nullable": {"type": "null"},
|
||||
"multi_types_nullable": {"type": ["string", "null", "integer"]},
|
||||
"only_null": {"type": "null"},
|
||||
"empty_default_object": {},
|
||||
"empty_list_type": {"type": []},
|
||||
"multi_type_with_array_nullable": {
|
||||
"type": ["string", "array", "null"]
|
||||
},
|
||||
"multi_type_with_array_nonnullable": {"type": ["integer", "array"]},
|
||||
},
|
||||
}
|
||||
gemini_schema = _to_gemini_schema(openapi_schema)
|
||||
@@ -88,18 +94,38 @@ class TestToGeminiSchema:
|
||||
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["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"].nullable
|
||||
|
||||
assert gemini_schema.properties["only_null"].type is None
|
||||
assert gemini_schema.properties["only_null"].type == Type.OBJECT
|
||||
assert gemini_schema.properties["only_null"].nullable
|
||||
|
||||
assert gemini_schema.properties["multi_types_nullable"].type == Type.STRING
|
||||
assert gemini_schema.properties["multi_types_nullable"].nullable
|
||||
|
||||
assert gemini_schema.properties["empty_default_object"].type == Type.OBJECT
|
||||
assert gemini_schema.properties["empty_default_object"].nullable is None
|
||||
|
||||
assert gemini_schema.properties["empty_list_type"].type == Type.OBJECT
|
||||
assert not gemini_schema.properties["empty_list_type"].nullable
|
||||
|
||||
assert (
|
||||
gemini_schema.properties["multi_type_with_array_nullable"].type
|
||||
== Type.ARRAY
|
||||
)
|
||||
assert gemini_schema.properties["multi_type_with_array_nullable"].nullable
|
||||
|
||||
assert (
|
||||
gemini_schema.properties["multi_type_with_array_nonnullable"].type
|
||||
== Type.ARRAY
|
||||
)
|
||||
assert not gemini_schema.properties[
|
||||
"multi_type_with_array_nonnullable"
|
||||
].nullable
|
||||
|
||||
def test_to_gemini_schema_nested_objects(self):
|
||||
openapi_schema = {
|
||||
"type": "object",
|
||||
@@ -144,6 +170,20 @@ class TestToGeminiSchema:
|
||||
gemini_schema = _to_gemini_schema(openapi_schema)
|
||||
assert gemini_schema.items.properties["name"].type == Type.STRING
|
||||
|
||||
def test_to_gemini_schema_array_without_items_gets_default(self):
|
||||
openapi_schema = {"type": "array"}
|
||||
gemini_schema = _to_gemini_schema(openapi_schema)
|
||||
assert gemini_schema.type == Type.ARRAY
|
||||
assert not gemini_schema.nullable
|
||||
assert gemini_schema.items.type == Type.STRING
|
||||
|
||||
def test_to_gemini_schema_nullable_array_without_items_gets_default(self):
|
||||
openapi_schema = {"type": ["array", "null"]}
|
||||
gemini_schema = _to_gemini_schema(openapi_schema)
|
||||
assert gemini_schema.type == Type.ARRAY
|
||||
assert gemini_schema.nullable
|
||||
assert gemini_schema.items.type == Type.STRING
|
||||
|
||||
def test_to_gemini_schema_any_of(self):
|
||||
openapi_schema = {
|
||||
"anyOf": [{"type": "string"}, {"type": "integer"}],
|
||||
@@ -200,7 +240,7 @@ class TestToGeminiSchema:
|
||||
},
|
||||
}
|
||||
gemini_schema = _to_gemini_schema(openapi_schema)
|
||||
# Since metadata is neither properties nor item, it will call to_gemini_schema recursively.
|
||||
# Since metadata is not properties nor item, it will call to_gemini_schema recursively.
|
||||
assert isinstance(gemini_schema.properties["metadata"], Schema)
|
||||
assert (
|
||||
gemini_schema.properties["metadata"].type == Type.OBJECT
|
||||
@@ -544,7 +584,7 @@ class TestToGeminiSchema:
|
||||
"properties": {
|
||||
"case_id": {
|
||||
"description": "The ID of the case.",
|
||||
"title": "Case ID",
|
||||
"title": "Case Id",
|
||||
"type": "string",
|
||||
},
|
||||
"next_page_token": {
|
||||
@@ -567,7 +607,7 @@ class TestToGeminiSchema:
|
||||
"properties": {
|
||||
"case_id": {
|
||||
"description": "The ID of the case.",
|
||||
"title": "Case ID",
|
||||
"title": "Case Id",
|
||||
"type": "string",
|
||||
},
|
||||
"next_page_token": {
|
||||
|
||||
Reference in New Issue
Block a user