Skip to content

Commit 715234c

Browse files
onematchfoxxuanyang15
authored andcommitted
fix(tools): Handle JSON Schema boolean schemas in Gemini schema conversion
Merge google#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=google#4531 from onematchfox:fix-gemini-schema-bool 383ac0c PiperOrigin-RevId: 875219362
1 parent a7b605f commit 715234c

2 files changed

Lines changed: 89 additions & 0 deletions

File tree

src/google/adk/tools/_gemini_schema_util.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,13 @@ def _sanitize_schema_formats_for_gemini(
152152
)
153153
for item in schema
154154
]
155+
# JSON Schema allows boolean schemas: `true` (accept any value) and `false`
156+
# (reject all values). Gemini has no equivalent for either. `true` is
157+
# approximated as an unconstrained object schema; `false` has no meaningful
158+
# Gemini representation and is also mapped to an object schema as a safe
159+
# fallback so that schema conversion does not crash.
160+
if isinstance(schema, bool):
161+
return {"type": "object"}
155162
if not isinstance(schema, dict):
156163
return schema
157164

tests/unittests/tools/test_gemini_schema_util.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,88 @@ def test_to_gemini_schema_properties_is_none(self):
648648
assert gemini_schema.type == Type.OBJECT
649649
assert gemini_schema.properties is None
650650

651+
def test_to_gemini_schema_boolean_true_property(self):
652+
"""Tests that a JSON Schema boolean `true` property is handled.
653+
654+
JSON Schema allows `true` as a schema meaning "accept any value".
655+
Some MCP servers use this pattern for fields whose content is not
656+
further constrained.
657+
"""
658+
openapi_schema = {
659+
"type": "object",
660+
"properties": {
661+
"items": {
662+
"type": "array",
663+
"items": {
664+
"type": "object",
665+
"properties": {
666+
"refId": {"type": "string"},
667+
"model": True, # JSON Schema boolean schema
668+
},
669+
},
670+
}
671+
},
672+
}
673+
gemini_schema = _to_gemini_schema(openapi_schema)
674+
assert isinstance(gemini_schema, Schema)
675+
items_schema = gemini_schema.properties["items"]
676+
assert items_schema.type == Type.ARRAY
677+
# `model: true` should be converted to an object schema
678+
model_schema = items_schema.items.properties["model"]
679+
assert model_schema.type == Type.OBJECT
680+
681+
def test_to_gemini_schema_boolean_false_property(self):
682+
"""Tests that a JSON Schema boolean `false` property does not raise.
683+
684+
`false` means "no value is valid" in JSON Schema, which has no Gemini
685+
equivalent. Conversion falls back to an object schema to avoid crashing;
686+
the result is semantically imprecise but safe.
687+
"""
688+
openapi_schema = {
689+
"type": "object",
690+
"properties": {
691+
"anything": False, # JSON Schema boolean schema (reject all)
692+
},
693+
}
694+
# Should not raise even though `false` has no Gemini equivalent.
695+
gemini_schema = _to_gemini_schema(openapi_schema)
696+
assert isinstance(gemini_schema, Schema)
697+
assert gemini_schema.properties["anything"] is not None
698+
699+
def test_to_gemini_schema_boolean_true_in_array_items_properties(self):
700+
"""Regression test: boolean `true` schema inside array item properties.
701+
702+
Some MCP servers use `"field": true` in an array item's properties to
703+
indicate an unconstrained field, which is valid JSON Schema.
704+
"""
705+
openapi_schema = {
706+
"type": "object",
707+
"properties": {
708+
"title": {"type": "string"},
709+
"data": {
710+
"type": "array",
711+
"items": {
712+
"type": "object",
713+
"properties": {
714+
"datasourceUid": {"type": "string"},
715+
"model": True,
716+
"queryType": {"type": "string"},
717+
"refId": {"type": "string"},
718+
},
719+
},
720+
},
721+
},
722+
"required": ["title", "data"],
723+
}
724+
# Should not raise a ValidationError
725+
gemini_schema = _to_gemini_schema(openapi_schema)
726+
assert isinstance(gemini_schema, Schema)
727+
assert gemini_schema.type == Type.OBJECT
728+
data_schema = gemini_schema.properties["data"]
729+
assert data_schema.type == Type.ARRAY
730+
model_schema = data_schema.items.properties["model"]
731+
assert model_schema.type == Type.OBJECT
732+
651733

652734
class TestToSnakeCase:
653735

0 commit comments

Comments
 (0)