diff --git a/google/genai/_transformers.py b/google/genai/_transformers.py index 4bfe45be1..51b4b4850 100644 --- a/google/genai/_transformers.py +++ b/google/genai/_transformers.py @@ -629,15 +629,23 @@ def handle_null_fields(schema: _common.StringDict) -> None: schema['nullable'] = True del schema['type'] elif 'anyOf' in schema: - for item in schema['anyOf']: - if 'type' in item and item['type'] == 'null': - schema['nullable'] = True - schema['anyOf'].remove({'type': 'null'}) - if len(schema['anyOf']) == 1: - # If there is only one type left after removing null, remove the anyOf field. - for key, val in schema['anyOf'][0].items(): - schema[key] = val - del schema['anyOf'] + # Identify null members by their 'type', not by exact dict equality: a null + # member may carry extra keys (e.g. {'type': 'null', 'title': 'N'}), in + # which case `list.remove({'type': 'null'})` raises ValueError. Rebuilding + # the list also avoids mutating it while iterating. + non_null_items = [ + item + for item in schema['anyOf'] + if not (isinstance(item, dict) and item.get('type') == 'null') + ] + if len(non_null_items) != len(schema['anyOf']): + schema['nullable'] = True + schema['anyOf'] = non_null_items + if len(schema['anyOf']) == 1: + # If there is only one type left after removing null, remove the anyOf field. + for key, val in schema['anyOf'][0].items(): + schema[key] = val + del schema['anyOf'] def _raise_for_unsupported_schema_type(origin: Any) -> None: diff --git a/google/genai/tests/transformers/test_schema.py b/google/genai/tests/transformers/test_schema.py index 202bb679b..2ac6ecfdd 100644 --- a/google/genai/tests/transformers/test_schema.py +++ b/google/genai/tests/transformers/test_schema.py @@ -185,6 +185,29 @@ def test_schema_with_no_null_fields_is_unchanged(): assert schema_before == schema +def test_handle_null_fields_anyof_null_member_with_extra_keys(): + """A null member of anyOf may carry extra keys (e.g. a 'title'). + + It must still be detected and removed instead of raising + ValueError: list.remove(x): x not in list. Such schemas are common from + JSON-Schema-based tools (e.g. MCP) rather than plain pydantic. + """ + schema = { + 'anyOf': [ + {'type': 'integer'}, + {'type': 'null', 'title': 'N'}, + ], + 'title': 'Total Area Sq Mi', + } + + _transformers.handle_null_fields(schema) + + assert schema['nullable'] is True + # Only the integer type remains, so anyOf is flattened away. + assert 'anyOf' not in schema + assert schema['type'] == 'integer' + + @pytest.mark.parametrize('use_vertex', [True, False]) def test_schema_with_default_value(client):