11from __future__ import annotations as _annotations
22
3- import warnings
4-
5- from pydantic_ai .exceptions import UserError
6-
73from .._json_schema import JsonSchema , JsonSchemaTransformer
84from . import ModelProfile
95
@@ -23,84 +19,28 @@ def google_model_profile(model_name: str) -> ModelProfile | None:
2319class GoogleJsonSchemaTransformer (JsonSchemaTransformer ):
2420 """Transforms the JSON Schema from Pydantic to be suitable for Gemini.
2521
26- Gemini which [supports](https://ai.google.dev/gemini-api/docs/function-calling#function_declarations)
27- a subset of OpenAPI v3.0.3.
28-
29- Specifically:
30- * gemini doesn't allow the `title` keyword to be set
31- * gemini doesn't allow `$defs` — we need to inline the definitions where possible
22+ Gemini supports [a subset of OpenAPI v3.0.3](https://ai.google.dev/gemini-api/docs/function-calling#function_declarations).
3223 """
3324
34- def __init__ (self , schema : JsonSchema , * , strict : bool | None = None ):
35- super ().__init__ (schema , strict = strict , prefer_inlined_defs = True , simplify_nullable_unions = True )
36-
3725 def transform (self , schema : JsonSchema ) -> JsonSchema :
38- # Note: we need to remove `additionalProperties: False` since it is currently mishandled by Gemini
39- additional_properties = schema .pop (
40- 'additionalProperties' , None
41- ) # don't pop yet so it's included in the warning
42- if additional_properties :
43- original_schema = {** schema , 'additionalProperties' : additional_properties }
44- warnings .warn (
45- '`additionalProperties` is not supported by Gemini; it will be removed from the tool JSON schema.'
46- f' Full schema: { self .schema } \n \n '
47- f'Source of additionalProperties within the full schema: { original_schema } \n \n '
48- 'If this came from a field with a type like `dict[str, MyType]`, that field will always be empty.\n \n '
49- "If Google's APIs are updated to support this properly, please create an issue on the Pydantic AI GitHub"
50- ' and we will fix this behavior.' ,
51- UserWarning ,
52- )
53-
54- schema .pop ('title' , None )
26+ # Remove properties not supported by Gemini
5527 schema .pop ('$schema' , None )
5628 if (const := schema .pop ('const' , None )) is not None :
5729 # Gemini doesn't support const, but it does support enum with a single value
5830 schema ['enum' ] = [const ]
5931 schema .pop ('discriminator' , None )
6032 schema .pop ('examples' , None )
6133
62- # TODO: Should we use the trick from pydantic_ai.models.openai._OpenAIJsonSchema
63- # where we add notes about these properties to the field description?
64- schema .pop ('exclusiveMaximum' , None )
65- schema .pop ('exclusiveMinimum' , None )
66-
67- # Gemini only supports string enums, so we need to convert any enum values to strings.
68- # Pydantic will take care of transforming the transformed string values to the correct type.
69- if enum := schema .get ('enum' ):
70- schema ['type' ] = 'string'
71- schema ['enum' ] = [str (val ) for val in enum ]
72-
7334 type_ = schema .get ('type' )
74- if 'oneOf' in schema and 'type' not in schema : # pragma: no cover
75- # This gets hit when we have a discriminated union
76- # Gemini returns an API error in this case even though it says in its error message it shouldn't...
77- # Changing the oneOf to an anyOf prevents the API error and I think is functionally equivalent
78- schema ['anyOf' ] = schema .pop ('oneOf' )
79-
8035 if type_ == 'string' and (fmt := schema .pop ('format' , None )):
8136 description = schema .get ('description' )
8237 if description :
8338 schema ['description' ] = f'{ description } (format: { fmt } )'
8439 else :
8540 schema ['description' ] = f'Format: { fmt } '
8641
87- if '$ref' in schema :
88- raise UserError (f'Recursive `$ref`s in JSON Schema are not supported by Gemini: { schema ["$ref" ]} ' )
89-
90- if 'prefixItems' in schema :
91- # prefixItems is not currently supported in Gemini, so we convert it to items for best compatibility
92- prefix_items = schema .pop ('prefixItems' )
93- items = schema .get ('items' )
94- unique_items = [items ] if items is not None else []
95- for item in prefix_items :
96- if item not in unique_items :
97- unique_items .append (item )
98- if len (unique_items ) > 1 : # pragma: no cover
99- schema ['items' ] = {'anyOf' : unique_items }
100- elif len (unique_items ) == 1 : # pragma: no branch
101- schema ['items' ] = unique_items [0 ]
102- schema .setdefault ('minItems' , len (prefix_items ))
103- if items is None : # pragma: no branch
104- schema .setdefault ('maxItems' , len (prefix_items ))
42+ # Note: exclusiveMinimum/exclusiveMaximum are NOT yet supported
43+ schema .pop ('exclusiveMinimum' , None )
44+ schema .pop ('exclusiveMaximum' , None )
10545
10646 return schema
0 commit comments