google-genai 1.9.0__py3-none-any.whl → 1.11.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,7 +43,7 @@ if sys.version_info >= (3, 10):
43
43
  _UNION_TYPES = (typing.Union, builtin_types.UnionType)
44
44
  from typing import TypeGuard
45
45
  else:
46
- VersionedUnionType = typing._UnionGenericAlias
46
+ VersionedUnionType = typing._UnionGenericAlias # type: ignore[attr-defined]
47
47
  _UNION_TYPES = (typing.Union,)
48
48
  from typing_extensions import TypeGuard
49
49
 
@@ -657,51 +657,57 @@ def process_schema(
657
657
  )
658
658
 
659
659
  if schema.get('title') == 'PlaceholderLiteralEnum':
660
- schema.pop('title', None)
661
-
662
- # If a dict is provided directly to response_schema, it may use `any_of`
663
- # instead of `anyOf`. Otherwise model_json_schema() uses `anyOf`
664
- if schema.get('any_of', None) is not None:
665
- schema['anyOf'] = schema.pop('any_of')
660
+ del schema['title']
661
+
662
+ # Standardize spelling for relevant schema fields. For example, if a dict is
663
+ # provided directly to response_schema, it may use `any_of` instead of `anyOf.
664
+ # Otherwise, model_json_schema() uses `anyOf`.
665
+ for from_name, to_name in [
666
+ ('additional_properties', 'additionalProperties'),
667
+ ('any_of', 'anyOf'),
668
+ ('prefix_items', 'prefixItems'),
669
+ ('property_ordering', 'propertyOrdering'),
670
+ ]:
671
+ if (value := schema.pop(from_name, None)) is not None:
672
+ schema[to_name] = value
666
673
 
667
674
  if defs is None:
668
675
  defs = schema.pop('$defs', {})
669
676
  for _, sub_schema in defs.items():
670
- process_schema(sub_schema, client, defs)
677
+ # We can skip the '$ref' check, because JSON schema forbids a '$ref' from
678
+ # directly referencing another '$ref':
679
+ # https://json-schema.org/understanding-json-schema/structuring#recursion
680
+ process_schema(
681
+ sub_schema, client, defs, order_properties=order_properties
682
+ )
671
683
 
672
684
  handle_null_fields(schema)
673
685
 
674
686
  # After removing null fields, Optional fields with only one possible type
675
687
  # will have a $ref key that needs to be flattened
676
688
  # For example: {'default': None, 'description': 'Name of the person', 'nullable': True, '$ref': '#/$defs/TestPerson'}
677
- schema_ref = schema.get('$ref', None)
678
- if schema_ref is not None:
679
- ref = defs[schema_ref.split('defs/')[-1]]
680
- for schema_key in list(ref.keys()):
681
- schema[schema_key] = ref[schema_key]
682
- del schema['$ref']
683
-
684
- any_of = schema.get('anyOf', None)
685
- if any_of is not None:
686
- for sub_schema in any_of:
687
- # $ref is present in any_of if the schema is a union of Pydantic classes
688
- ref_key = sub_schema.get('$ref', None)
689
- if ref_key is None:
690
- process_schema(sub_schema, client, defs)
691
- else:
692
- ref = defs[ref_key.split('defs/')[-1]]
693
- any_of.append(ref)
694
- schema['anyOf'] = [item for item in any_of if '$ref' not in item]
689
+ if (ref := schema.pop('$ref', None)) is not None:
690
+ schema.update(defs[ref.split('defs/')[-1]])
691
+
692
+ def _recurse(sub_schema: dict[str, Any]) -> dict[str, Any]:
693
+ """Returns the processed `sub_schema`, resolving its '$ref' if any."""
694
+ if (ref := sub_schema.pop('$ref', None)) is not None:
695
+ sub_schema = defs[ref.split('defs/')[-1]]
696
+ process_schema(sub_schema, client, defs, order_properties=order_properties)
697
+ return sub_schema
698
+
699
+ if (any_of := schema.get('anyOf')) is not None:
700
+ schema['anyOf'] = [_recurse(sub_schema) for sub_schema in any_of]
695
701
  return
696
702
 
697
- schema_type = schema.get('type', None)
703
+ schema_type = schema.get('type')
698
704
  if isinstance(schema_type, Enum):
699
705
  schema_type = schema_type.value
700
706
  schema_type = schema_type.upper()
701
707
 
702
708
  # model_json_schema() returns a schema with a 'const' field when a Literal with one value is provided as a pydantic field
703
709
  # For example `genre: Literal['action']` becomes: {'const': 'action', 'title': 'Genre', 'type': 'string'}
704
- const = schema.get('const', None)
710
+ const = schema.get('const')
705
711
  if const is not None:
706
712
  if schema_type == 'STRING':
707
713
  schema['enum'] = [const]
@@ -710,38 +716,25 @@ def process_schema(
710
716
  raise ValueError('Literal values must be strings.')
711
717
 
712
718
  if schema_type == 'OBJECT':
713
- properties = schema.get('properties', None)
714
- if properties is None:
715
- return
716
- for name, sub_schema in properties.items():
717
- ref_key = sub_schema.get('$ref', None)
718
- if ref_key is None:
719
- process_schema(sub_schema, client, defs)
720
- else:
721
- ref = defs[ref_key.split('defs/')[-1]]
722
- process_schema(ref, client, defs)
723
- properties[name] = ref
724
- if (
725
- len(properties.items()) > 1
726
- and order_properties
727
- and all(
728
- ordering_key not in schema
729
- for ordering_key in ['property_ordering', 'propertyOrdering']
730
- )
731
- ):
732
- property_names = list(properties.keys())
733
- schema['property_ordering'] = property_names
719
+ if (properties := schema.get('properties')) is not None:
720
+ for name, sub_schema in list(properties.items()):
721
+ properties[name] = _recurse(sub_schema)
722
+ if (
723
+ len(properties.items()) > 1
724
+ and order_properties
725
+ and 'propertyOrdering' not in schema
726
+ ):
727
+ schema['property_ordering'] = list(properties.keys())
728
+ if (additional := schema.get('additionalProperties')) is not None:
729
+ # It is legal to set 'additionalProperties' to a bool:
730
+ # https://json-schema.org/understanding-json-schema/reference/object#additionalproperties
731
+ if isinstance(additional, dict):
732
+ schema['additionalProperties'] = _recurse(additional)
734
733
  elif schema_type == 'ARRAY':
735
- sub_schema = schema.get('items', None)
736
- if sub_schema is None:
737
- return
738
- ref_key = sub_schema.get('$ref', None)
739
- if ref_key is None:
740
- process_schema(sub_schema, client, defs)
741
- else:
742
- ref = defs[ref_key.split('defs/')[-1]]
743
- process_schema(ref, client, defs)
744
- schema['items'] = ref
734
+ if (items := schema.get('items')) is not None:
735
+ schema['items'] = _recurse(items)
736
+ if (prefixes := schema.get('prefixItems')) is not None:
737
+ schema['prefixItems'] = [_recurse(prefix) for prefix in prefixes]
745
738
 
746
739
 
747
740
  def _process_enum(
@@ -861,6 +854,8 @@ def t_tool(client: _api_client.BaseApiClient, origin) -> Optional[types.Tool]:
861
854
  )
862
855
  ]
863
856
  )
857
+ elif isinstance(origin, dict):
858
+ return types.Tool.model_validate(origin)
864
859
  else:
865
860
  return origin
866
861
 
@@ -1033,3 +1028,121 @@ def t_bytes(api_client: _api_client.BaseApiClient, data: bytes) -> str:
1033
1028
  if not isinstance(data, bytes):
1034
1029
  return data
1035
1030
  return base64.b64encode(data).decode('ascii')
1031
+
1032
+
1033
+ def t_content_strict(content: types.ContentOrDict) -> types.Content:
1034
+ if isinstance(content, dict):
1035
+ return types.Content.model_validate(content)
1036
+ elif isinstance(content, types.Content):
1037
+ return content
1038
+ else:
1039
+ raise ValueError(
1040
+ f'Could not convert input (type "{type(content)}") to '
1041
+ '`types.Content`'
1042
+ )
1043
+
1044
+
1045
+ def t_contents_strict(
1046
+ contents: Union[Sequence[types.ContentOrDict], types.ContentOrDict],
1047
+ ) -> list[types.Content]:
1048
+ if isinstance(contents, Sequence):
1049
+ return [t_content_strict(content) for content in contents]
1050
+ else:
1051
+ return [t_content_strict(contents)]
1052
+
1053
+
1054
+ def t_client_content(
1055
+ turns: Optional[
1056
+ Union[Sequence[types.ContentOrDict], types.ContentOrDict]
1057
+ ] = None,
1058
+ turn_complete: bool = True,
1059
+ ) -> types.LiveClientContent:
1060
+ if turns is None:
1061
+ return types.LiveClientContent(turn_complete=turn_complete)
1062
+
1063
+ try:
1064
+ return types.LiveClientContent(
1065
+ turns=t_contents_strict(contents=turns),
1066
+ turn_complete=turn_complete,
1067
+ )
1068
+ except Exception as e:
1069
+ raise ValueError(
1070
+ f'Could not convert input (type "{type(turns)}") to '
1071
+ '`types.LiveClientContent`'
1072
+ ) from e
1073
+
1074
+
1075
+ def t_realtime_input(
1076
+ media: BlobUnion,
1077
+ ) -> types.LiveClientRealtimeInput:
1078
+ try:
1079
+ return types.LiveClientRealtimeInput(media_chunks=[t_blob(blob=media)])
1080
+ except Exception as e:
1081
+ raise ValueError(
1082
+ f'Could not convert input (type "{type(input)}") to '
1083
+ '`types.LiveClientRealtimeInput`'
1084
+ ) from e
1085
+
1086
+
1087
+ def t_tool_response(
1088
+ input: Union[
1089
+ types.FunctionResponseOrDict,
1090
+ Sequence[types.FunctionResponseOrDict],
1091
+ ],
1092
+ ) -> types.LiveClientToolResponse:
1093
+ if not input:
1094
+ raise ValueError(f'A tool response is required, got: \n{input}')
1095
+
1096
+ try:
1097
+ return types.LiveClientToolResponse(
1098
+ function_responses=t_function_responses(function_responses=input)
1099
+ )
1100
+ except Exception as e:
1101
+ raise ValueError(
1102
+ f'Could not convert input (type "{type(input)}") to '
1103
+ '`types.LiveClientToolResponse`'
1104
+ ) from e
1105
+
1106
+
1107
+ def t_live_speech_config(
1108
+ origin: Union[types.SpeechConfigUnionDict, Any],
1109
+ ) -> Optional[types.SpeechConfig]:
1110
+ if not origin:
1111
+ return None
1112
+ if isinstance(origin, types.SpeechConfig):
1113
+ return origin
1114
+ if isinstance(origin, str):
1115
+ # There is no way to know if the string is a voice name or a language code.
1116
+ raise ValueError(
1117
+ f'Unsupported speechConfig type: {type(origin)}. There is no way to'
1118
+ ' know if the string is a voice name or a language code.'
1119
+ )
1120
+ if isinstance(origin, dict):
1121
+ speech_config = types.SpeechConfig()
1122
+ if (
1123
+ 'voice_config' in origin
1124
+ and origin['voice_config'] is not None
1125
+ and 'prebuilt_voice_config' in origin['voice_config']
1126
+ and origin['voice_config']['prebuilt_voice_config'] is not None
1127
+ and 'voice_name' in origin['voice_config']['prebuilt_voice_config']
1128
+ ):
1129
+ speech_config.voice_config = types.VoiceConfig(
1130
+ prebuilt_voice_config=types.PrebuiltVoiceConfig(
1131
+ voice_name=origin['voice_config']['prebuilt_voice_config'].get(
1132
+ 'voice_name'
1133
+ )
1134
+ )
1135
+ )
1136
+ if 'language_code' in origin and origin['language_code'] is not None:
1137
+ speech_config.language_code = origin['language_code']
1138
+ if (
1139
+ speech_config.voice_config is None
1140
+ and speech_config.language_code is None
1141
+ ):
1142
+ raise ValueError(
1143
+ 'Unsupported speechConfig type: {type(origin)}. At least one of'
1144
+ ' voice_config or language_code must be set.'
1145
+ )
1146
+ return speech_config
1147
+ raise ValueError(f'Unsupported speechConfig type: {type(origin)}')
1148
+
google/genai/files.py CHANGED
@@ -658,6 +658,14 @@ class Files(_api_module.BaseModule):
658
658
  http_options: types.HttpOptions
659
659
  if config_model and config_model.http_options:
660
660
  http_options = config_model.http_options
661
+ http_options.api_version = ''
662
+ http_options.headers = {
663
+ 'Content-Type': 'application/json',
664
+ 'X-Goog-Upload-Protocol': 'resumable',
665
+ 'X-Goog-Upload-Command': 'start',
666
+ 'X-Goog-Upload-Header-Content-Length': f'{file_obj.size_bytes}',
667
+ 'X-Goog-Upload-Header-Content-Type': f'{file_obj.mime_type}',
668
+ }
661
669
  else:
662
670
  http_options = types.HttpOptions(
663
671
  api_version='',
@@ -685,11 +693,11 @@ class Files(_api_module.BaseModule):
685
693
 
686
694
  if isinstance(file, io.IOBase):
687
695
  return_file = self._api_client.upload_file(
688
- file, upload_url, file_obj.size_bytes
696
+ file, upload_url, file_obj.size_bytes, http_options=http_options
689
697
  )
690
698
  else:
691
699
  return_file = self._api_client.upload_file(
692
- fs_path, upload_url, file_obj.size_bytes
700
+ fs_path, upload_url, file_obj.size_bytes, http_options=http_options
693
701
  )
694
702
 
695
703
  return types.File._from_response(
@@ -778,7 +786,7 @@ class Files(_api_module.BaseModule):
778
786
 
779
787
  data = self._api_client.download_file(
780
788
  path,
781
- http_options,
789
+ http_options=http_options,
782
790
  )
783
791
 
784
792
  if isinstance(file, types.Video):
@@ -1122,6 +1130,14 @@ class AsyncFiles(_api_module.BaseModule):
1122
1130
  http_options: types.HttpOptions
1123
1131
  if config_model and config_model.http_options:
1124
1132
  http_options = config_model.http_options
1133
+ http_options.api_version = ''
1134
+ http_options.headers = {
1135
+ 'Content-Type': 'application/json',
1136
+ 'X-Goog-Upload-Protocol': 'resumable',
1137
+ 'X-Goog-Upload-Command': 'start',
1138
+ 'X-Goog-Upload-Header-Content-Length': f'{file_obj.size_bytes}',
1139
+ 'X-Goog-Upload-Header-Content-Type': f'{file_obj.mime_type}',
1140
+ }
1125
1141
  else:
1126
1142
  http_options = types.HttpOptions(
1127
1143
  api_version='',
@@ -1148,11 +1164,11 @@ class AsyncFiles(_api_module.BaseModule):
1148
1164
 
1149
1165
  if isinstance(file, io.IOBase):
1150
1166
  return_file = await self._api_client.async_upload_file(
1151
- file, upload_url, file_obj.size_bytes
1167
+ file, upload_url, file_obj.size_bytes, http_options=http_options
1152
1168
  )
1153
1169
  else:
1154
1170
  return_file = await self._api_client.async_upload_file(
1155
- fs_path, upload_url, file_obj.size_bytes
1171
+ fs_path, upload_url, file_obj.size_bytes, http_options=http_options
1156
1172
  )
1157
1173
 
1158
1174
  return types.File._from_response(
@@ -1231,7 +1247,7 @@ class AsyncFiles(_api_module.BaseModule):
1231
1247
 
1232
1248
  data = await self._api_client.async_download_file(
1233
1249
  path,
1234
- http_options,
1250
+ http_options=http_options,
1235
1251
  )
1236
1252
 
1237
1253
  return data