fix(tools): Handle JSON Schema boolean schemas in Gemini schema conversion

Merge https://github.com/google/adk-python/pull/4531

**Problem:**
JSON Schema allows `true` and `false` as valid boolean schemas, where `true` accepts any value and `false` rejects all values. Some MCP servers  use this pattern for unconstrained fields. E.g. [mcp-grafana](https://github.com/grafana/mcp-grafana) - see [grafana-mcp-list-tools.json](https://github.com/user-attachments/files/25392430/grafana-mcp-list-tools.json) which was obtained from `tools/list`

The schema sanitizer previously passed booleans through unchanged, causing a Pydantic ValidationError when `_ExtendedJSONSchema` tried to validate them as schema objects.

```
1 validation error for _ExtendedJSONSchema
properties.data.items.properties.model
  Input should be a valid dictionary or object to extract fields from [type=model_attributes_type, input_value=True, input_type=bool]
    For further information visit https://errors.pydantic.dev/2.12/v/model_attributes_type
Traceback (most recent call last):
  ...
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/runners.py", line 561, in run_async
    async for event in agen:
      yield event
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/runners.py", line 549, in _run_with_trace
    async for event in agen:
      yield event
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/runners.py", line 778, in _exec_with_plugin
    async for event in agen:
    ...<64 lines>...
        yield event
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/runners.py", line 538, in execute
    async for event in agen:
      yield event
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/agents/base_agent.py", line 294, in run_async
    async for event in agen:
      yield event
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/agents/llm_agent.py", line 468, in _run_async_impl
    async for event in agen:
    ...<5 lines>...
        should_pause = True
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/flows/llm_flows/base_llm_flow.py", line 427, in run_async
    async for event in agen:
      last_event = event
      yield event
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/flows/llm_flows/base_llm_flow.py", line 446, in _run_one_step_async
    async for event in agen:
      yield event
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/flows/llm_flows/base_llm_flow.py", line 578, in _preprocess_async
    await tool.process_llm_request(
        tool_context=tool_context, llm_request=llm_request
    )
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/tools/base_tool.py", line 129, in process_llm_request
    llm_request.append_tools([self])
    ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/models/llm_request.py", line 255, in append_tools
    declaration = tool._get_declaration()
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/tools/mcp_tool/mcp_tool.py", line 200, in _get_declaration
    parameters = _to_gemini_schema(input_schema)
  File "/.foo/.venv/lib/python3.13/site-packages/google/adk/tools/_gemini_schema_util.py", line 218, in _to_gemini_schema
    json_schema=_ExtendedJSONSchema.model_validate(sanitized_schema),
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/.foo/.venv/lib/python3.13/site-packages/pydantic/main.py", line 716, in model_validate
    return cls.__pydantic_validator__.validate_python(
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
        obj,
        ^^^^
    ...<5 lines>...
        by_name=by_name,
        ^^^^^^^^^^^^^^^^
    )
    ^
pydantic_core._pydantic_core.ValidationError: 1 validation error for _ExtendedJSONSchema
properties.data.items.properties.model
  Input should be a valid dictionary or object to extract fields from [type=model_attributes_type, input_value=True, input_type=bool]
    For further information visit https://errors.pydantic.dev/2.12/v/model_attributes_type
```

**Solution:**

Convert boolean schemas to `{"type": "object"}` as the closest approximation available in Gemini's schema model.

Co-authored-by: Xuan Yang <xygoogle@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4531 from onematchfox:fix-gemini-schema-bool 383ac0c0c3ab78d77be4503f5d6b9ad26c41b0db
PiperOrigin-RevId: 875219362
This commit is contained in:
Brian Fox
2026-02-25 10:10:09 -08:00
committed by Copybara-Service
parent e59929e11a
commit 3256a679da
2 changed files with 89 additions and 0 deletions
@@ -152,6 +152,13 @@ def _sanitize_schema_formats_for_gemini(
)
for item in schema
]
# JSON Schema allows boolean schemas: `true` (accept any value) and `false`
# (reject all values). Gemini has no equivalent for either. `true` is
# approximated as an unconstrained object schema; `false` has no meaningful
# Gemini representation and is also mapped to an object schema as a safe
# fallback so that schema conversion does not crash.
if isinstance(schema, bool):
return {"type": "object"}
if not isinstance(schema, dict):
return schema
@@ -648,6 +648,88 @@ class TestToGeminiSchema:
assert gemini_schema.type == Type.OBJECT
assert gemini_schema.properties is None
def test_to_gemini_schema_boolean_true_property(self):
"""Tests that a JSON Schema boolean `true` property is handled.
JSON Schema allows `true` as a schema meaning "accept any value".
Some MCP servers use this pattern for fields whose content is not
further constrained.
"""
openapi_schema = {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"refId": {"type": "string"},
"model": True, # JSON Schema boolean schema
},
},
}
},
}
gemini_schema = _to_gemini_schema(openapi_schema)
assert isinstance(gemini_schema, Schema)
items_schema = gemini_schema.properties["items"]
assert items_schema.type == Type.ARRAY
# `model: true` should be converted to an object schema
model_schema = items_schema.items.properties["model"]
assert model_schema.type == Type.OBJECT
def test_to_gemini_schema_boolean_false_property(self):
"""Tests that a JSON Schema boolean `false` property does not raise.
`false` means "no value is valid" in JSON Schema, which has no Gemini
equivalent. Conversion falls back to an object schema to avoid crashing;
the result is semantically imprecise but safe.
"""
openapi_schema = {
"type": "object",
"properties": {
"anything": False, # JSON Schema boolean schema (reject all)
},
}
# Should not raise even though `false` has no Gemini equivalent.
gemini_schema = _to_gemini_schema(openapi_schema)
assert isinstance(gemini_schema, Schema)
assert gemini_schema.properties["anything"] is not None
def test_to_gemini_schema_boolean_true_in_array_items_properties(self):
"""Regression test: boolean `true` schema inside array item properties.
Some MCP servers use `"field": true` in an array item's properties to
indicate an unconstrained field, which is valid JSON Schema.
"""
openapi_schema = {
"type": "object",
"properties": {
"title": {"type": "string"},
"data": {
"type": "array",
"items": {
"type": "object",
"properties": {
"datasourceUid": {"type": "string"},
"model": True,
"queryType": {"type": "string"},
"refId": {"type": "string"},
},
},
},
},
"required": ["title", "data"],
}
# Should not raise a ValidationError
gemini_schema = _to_gemini_schema(openapi_schema)
assert isinstance(gemini_schema, Schema)
assert gemini_schema.type == Type.OBJECT
data_schema = gemini_schema.properties["data"]
assert data_schema.type == Type.ARRAY
model_schema = data_schema.items.properties["model"]
assert model_schema.type == Type.OBJECT
class TestToSnakeCase: