{"record":{"id":"27c0c1c8247b0df8","repo":"agentscope-ai/agentscope","slug":"cannot-safely-sanitize-gemini-schema-with-both-a-m","errorCode":null,"errorMessage":"Cannot safely sanitize Gemini schema with both a multi-type nullable type array and anyOf.","messagePattern":"Cannot safely sanitize Gemini schema with both a multi-type nullable type array and anyOf\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"src/agentscope/model/_gemini/_model.py","lineNumber":87,"sourceCode":"\n    schema = dict(schema)\n\n    # Gemini's Schema model does not support the JSON Schema dialect marker.\n    schema.pop(\"$schema\", None)\n\n    # Gemini (and many third-party proxies) reject `null` as a standalone\n    # functionDeclaration property type. Some MCP servers emit\n    # {\"type\": \"null\"} directly (not wrapped in anyOf) for parameters that\n    # accept None — rewrite it to \"object\" so it round-trips through the API.\n    if _is_null_schema(schema):\n        schema[\"type\"] = \"object\"\n    elif isinstance(schema.get(\"type\"), list):\n        non_null_types = [v for v in schema[\"type\"] if v != \"null\"]\n        if len(non_null_types) == 1:\n            schema[\"type\"] = non_null_types[0]\n        elif non_null_types:\n            if \"anyOf\" in schema:\n                raise ValueError(\n                    \"Cannot safely sanitize Gemini schema with both a \"\n                    \"multi-type nullable type array and anyOf.\",\n                )\n            schema.pop(\"type\")\n            schema[\"anyOf\"] = [{\"type\": type_} for type_ in non_null_types]\n        else:\n            schema[\"type\"] = \"object\"\n\n    # Remove additionalProperties — not supported by Gemini\n    schema.pop(\"additionalProperties\", None)\n\n    # Convert `const` into an equivalent single-value `enum` — Gemini's\n    # Schema model does not support the `const` keyword.\n    if \"const\" in schema:\n        const_value = schema.pop(\"const\")\n        schema.setdefault(\"enum\", [const_value])\n\n    # Simplify anyOf that only differs by a null type, e.g. Optional[X]","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/model/_gemini/_model.py#L69-L105","documentation":"Gemini's API doesn't accept nullable type arrays, so agentscope's sanitizer converts them. When a schema has a multi-element type array containing 'null' AND an anyOf key simultaneously, there is no unambiguous conversion, so it raises ValueError rather than silently producing a wrong schema.","triggerScenarios":"A Pydantic field like Union[int, str, None] rendered as type: ['integer','string','null'] combined with anyOf in the same schema node (common with nested Optional unions or custom serializers).","commonSituations":"Using Union[int, str, None] or Optional[Union[...]] fields with the Gemini model; complex nested models generated by other libs passed as tool parameters.","solutions":["Restructure the schema: express nullability via anyOf alone (drop the type array), e.g. anyOf: [{type:'integer'},{type:'string'},{type:'null'}] — in Pydantic, avoid type arrays by using explicit Union anyOf-friendly modeling","Simplify the field to a single non-null type or Optional[single_type] (Optional[Union[a,b]] often still works since single non-null type collapses)","Pre-sanitize your JSON schema before passing it to the Gemini tool call"],"exampleFix":"# before\nclass Args(BaseModel):\n    value: Optional[Union[int, str]] = None  # type: [int, str, null]\n\n# after\nclass Args(BaseModel):\n    value: Union[int, str, None] = Field(None, json_schema_extra={'anyOf': [{'type': 'integer'}, {'type': 'string'}]})\n# or simplify: value: Optional[str] = None","handlingStrategy":"validation","validationCode":"def sanitize(node):\n    if isinstance(node, dict):\n        t = node.get('type')\n        if isinstance(t, list) and len([x for x in t if x != 'null']) > 1 and 'anyOf' in node:\n            node.pop('type')\n            node['anyOf'] = [{'type': x} for x in t if x != 'null'] + node['anyOf']\n        for v in node.values():\n            sanitize(v)\n    elif isinstance(node, list):\n        for v in node:\n            sanitize(v)","typeGuard":"def has_conflicting_nullable(node: dict) -> bool:\n    t = node.get('type')\n    return isinstance(t, list) and len([x for x in t if x != 'null']) > 1 and 'anyOf' in node","tryCatchPattern":null,"preventionTips":["Prefer Optional[single_type] over Optional[Union[...]] in tool schemas for Gemini","Test tool schemas against Gemini before shipping"],"tags":["agentscope","gemini","json-schema","nullable","anyof"],"backgroundTag":"json-schema-compatibility","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}