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.
- google/genai/_api_client.py +192 -35
- google/genai/_automatic_function_calling_util.py +1 -1
- google/genai/_extra_utils.py +70 -10
- google/genai/_replay_api_client.py +32 -8
- google/genai/_transformers.py +172 -59
- google/genai/files.py +22 -6
- google/genai/live.py +136 -580
- google/genai/live_converters.py +1298 -0
- google/genai/models.py +97 -15
- google/genai/operations.py +17 -9
- google/genai/tunings.py +0 -3
- google/genai/types.py +1064 -78
- google/genai/version.py +1 -1
- {google_genai-1.9.0.dist-info → google_genai-1.11.0.dist-info}/METADATA +1 -1
- google_genai-1.11.0.dist-info/RECORD +28 -0
- google_genai-1.9.0.dist-info/RECORD +0 -27
- {google_genai-1.9.0.dist-info → google_genai-1.11.0.dist-info}/WHEEL +0 -0
- {google_genai-1.9.0.dist-info → google_genai-1.11.0.dist-info}/licenses/LICENSE +0 -0
- {google_genai-1.9.0.dist-info → google_genai-1.11.0.dist-info}/top_level.txt +0 -0
google/genai/_transformers.py
CHANGED
@@ -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
|
661
|
-
|
662
|
-
#
|
663
|
-
#
|
664
|
-
|
665
|
-
|
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
|
-
|
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
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
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'
|
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'
|
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
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
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
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
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
|