google-genai 0.7.0__py3-none-any.whl → 1.0.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 +26 -25
- google/genai/_automatic_function_calling_util.py +19 -20
- google/genai/_common.py +33 -2
- google/genai/_extra_utils.py +12 -6
- google/genai/_operations.py +365 -0
- google/genai/_replay_api_client.py +7 -0
- google/genai/_transformers.py +32 -14
- google/genai/errors.py +4 -0
- google/genai/files.py +79 -71
- google/genai/live.py +5 -0
- google/genai/models.py +344 -35
- google/genai/tunings.py +288 -61
- google/genai/types.py +191 -20
- google/genai/version.py +1 -1
- {google_genai-0.7.0.dist-info → google_genai-1.0.0.dist-info}/METADATA +90 -48
- google_genai-1.0.0.dist-info/RECORD +27 -0
- google_genai-0.7.0.dist-info/RECORD +0 -26
- {google_genai-0.7.0.dist-info → google_genai-1.0.0.dist-info}/LICENSE +0 -0
- {google_genai-0.7.0.dist-info → google_genai-1.0.0.dist-info}/WHEEL +0 -0
- {google_genai-0.7.0.dist-info → google_genai-1.0.0.dist-info}/top_level.txt +0 -0
google/genai/_transformers.py
CHANGED
@@ -34,10 +34,12 @@ import pydantic
|
|
34
34
|
from . import _api_client
|
35
35
|
from . import types
|
36
36
|
|
37
|
-
if sys.version_info >= (3,
|
38
|
-
|
37
|
+
if sys.version_info >= (3, 10):
|
38
|
+
VersionedUnionType = typing.types.UnionType
|
39
|
+
_UNION_TYPES = (typing.Union, typing.types.UnionType)
|
39
40
|
else:
|
40
|
-
|
41
|
+
VersionedUnionType = typing._UnionGenericAlias
|
42
|
+
_UNION_TYPES = (typing.Union,)
|
41
43
|
|
42
44
|
|
43
45
|
def _resource_name(
|
@@ -225,6 +227,7 @@ PartType = Union[types.Part, types.PartDict, str, 'PIL.Image.Image']
|
|
225
227
|
def t_part(client: _api_client.ApiClient, part: PartType) -> types.Part:
|
226
228
|
try:
|
227
229
|
import PIL.Image
|
230
|
+
|
228
231
|
PIL_Image = PIL.Image.Image
|
229
232
|
except ImportError:
|
230
233
|
PIL_Image = None
|
@@ -342,7 +345,7 @@ def handle_null_fields(schema: dict[str, Any]):
|
|
342
345
|
"type": "null"
|
343
346
|
}
|
344
347
|
],
|
345
|
-
"default":
|
348
|
+
"default": None,
|
346
349
|
"title": "Total Area Sq Mi"
|
347
350
|
}
|
348
351
|
}
|
@@ -356,16 +359,12 @@ def handle_null_fields(schema: dict[str, Any]):
|
|
356
359
|
"total_area_sq_mi": {
|
357
360
|
"type": "integer",
|
358
361
|
"nullable": true,
|
359
|
-
"default":
|
362
|
+
"default": None,
|
360
363
|
"title": "Total Area Sq Mi"
|
361
364
|
}
|
362
365
|
}
|
363
366
|
"""
|
364
|
-
if (
|
365
|
-
isinstance(schema, dict)
|
366
|
-
and 'type' in schema
|
367
|
-
and schema['type'] == 'null'
|
368
|
-
):
|
367
|
+
if schema.get('type', None) == 'null':
|
369
368
|
schema['nullable'] = True
|
370
369
|
del schema['type']
|
371
370
|
elif 'anyOf' in schema:
|
@@ -445,6 +444,11 @@ def process_schema(
|
|
445
444
|
if client and not client.vertexai:
|
446
445
|
schema.pop('title', None)
|
447
446
|
|
447
|
+
if schema.get('default') is not None:
|
448
|
+
raise ValueError(
|
449
|
+
'Default value is not supported in the response schema for the Gemmini API.'
|
450
|
+
)
|
451
|
+
|
448
452
|
if defs is None:
|
449
453
|
defs = schema.pop('$defs', {})
|
450
454
|
for _, sub_schema in defs.items():
|
@@ -454,8 +458,19 @@ def process_schema(
|
|
454
458
|
|
455
459
|
any_of = schema.get('anyOf', None)
|
456
460
|
if any_of is not None:
|
461
|
+
if not client.vertexai:
|
462
|
+
raise ValueError(
|
463
|
+
'AnyOf is not supported in the response schema for the Gemini API.'
|
464
|
+
)
|
457
465
|
for sub_schema in any_of:
|
458
|
-
|
466
|
+
# $ref is present in any_of if the schema is a union of Pydantic classes
|
467
|
+
ref_key = sub_schema.get('$ref', None)
|
468
|
+
if ref_key is None:
|
469
|
+
process_schema(sub_schema, client, defs)
|
470
|
+
else:
|
471
|
+
ref = defs[ref_key.split('defs/')[-1]]
|
472
|
+
any_of.append(ref)
|
473
|
+
schema['anyOf'] = [item for item in any_of if '$ref' not in item]
|
459
474
|
return
|
460
475
|
|
461
476
|
schema_type = schema.get('type', None)
|
@@ -526,15 +541,18 @@ def t_schema(
|
|
526
541
|
if (
|
527
542
|
# in Python 3.9 Generic alias list[int] counts as a type,
|
528
543
|
# and breaks issubclass because it's not a class.
|
529
|
-
not isinstance(origin, GenericAlias) and
|
530
|
-
isinstance(origin, type) and
|
544
|
+
not isinstance(origin, GenericAlias) and
|
545
|
+
isinstance(origin, type) and
|
531
546
|
issubclass(origin, pydantic.BaseModel)
|
532
547
|
):
|
533
548
|
schema = origin.model_json_schema()
|
534
549
|
process_schema(schema, client)
|
535
550
|
return types.Schema.model_validate(schema)
|
536
551
|
elif (
|
537
|
-
|
552
|
+
isinstance(origin, GenericAlias)
|
553
|
+
or isinstance(origin, type)
|
554
|
+
or isinstance(origin, VersionedUnionType)
|
555
|
+
or typing.get_origin(origin) in _UNION_TYPES
|
538
556
|
):
|
539
557
|
class Placeholder(pydantic.BaseModel):
|
540
558
|
placeholder: origin
|
google/genai/errors.py
CHANGED
google/genai/files.py
CHANGED
@@ -19,7 +19,7 @@ import io
|
|
19
19
|
import mimetypes
|
20
20
|
import os
|
21
21
|
import pathlib
|
22
|
-
from typing import Optional, Union
|
22
|
+
from typing import Any, Optional, Union
|
23
23
|
from urllib.parse import urlencode
|
24
24
|
from . import _api_module
|
25
25
|
from . import _common
|
@@ -351,12 +351,12 @@ def _DeleteFileParameters_to_vertex(
|
|
351
351
|
return to_object
|
352
352
|
|
353
353
|
|
354
|
-
def _FileState_to_vertex_enum_validate(enum_value:
|
354
|
+
def _FileState_to_vertex_enum_validate(enum_value: Any):
|
355
355
|
if enum_value in set(['STATE_UNSPECIFIED', 'PROCESSING', 'ACTIVE', 'FAILED']):
|
356
356
|
raise ValueError(f'{enum_value} enum value is not supported in Vertex AI.')
|
357
357
|
|
358
358
|
|
359
|
-
def _FileSource_to_vertex_enum_validate(enum_value:
|
359
|
+
def _FileSource_to_vertex_enum_validate(enum_value: Any):
|
360
360
|
if enum_value in set(['SOURCE_UNSPECIFIED', 'UPLOADED', 'GENERATED']):
|
361
361
|
raise ValueError(f'{enum_value} enum value is not supported in Vertex AI.')
|
362
362
|
|
@@ -494,6 +494,8 @@ def _CreateFileResponse_from_mldev(
|
|
494
494
|
parent_object: dict = None,
|
495
495
|
) -> dict:
|
496
496
|
to_object = {}
|
497
|
+
if getv(from_object, ['httpHeaders']) is not None:
|
498
|
+
setv(to_object, ['http_headers'], getv(from_object, ['httpHeaders']))
|
497
499
|
|
498
500
|
return to_object
|
499
501
|
|
@@ -504,6 +506,8 @@ def _CreateFileResponse_from_vertex(
|
|
504
506
|
parent_object: dict = None,
|
505
507
|
) -> dict:
|
506
508
|
to_object = {}
|
509
|
+
if getv(from_object, ['httpHeaders']) is not None:
|
510
|
+
setv(to_object, ['http_headers'], getv(from_object, ['httpHeaders']))
|
507
511
|
|
508
512
|
return to_object
|
509
513
|
|
@@ -781,16 +785,17 @@ class Files(_api_module.BaseModule):
|
|
781
785
|
def upload(
|
782
786
|
self,
|
783
787
|
*,
|
784
|
-
|
788
|
+
file: Union[str, pathlib.Path, os.PathLike, io.IOBase],
|
785
789
|
config: Optional[types.UploadFileConfigOrDict] = None,
|
786
790
|
) -> types.File:
|
787
791
|
"""Calls the API to upload a file using a supported file service.
|
788
792
|
|
789
793
|
Args:
|
790
|
-
|
791
|
-
|
792
|
-
other words, do not use non-blocking mode or text mode.
|
793
|
-
must be seekable, that is, it must be able to call
|
794
|
+
file: A path to the file or an `IOBase` object to be uploaded. If it's an
|
795
|
+
IOBase object, it must be opened in blocking (the default) mode and
|
796
|
+
binary mode. In other words, do not use non-blocking mode or text mode.
|
797
|
+
The given stream must be seekable, that is, it must be able to call
|
798
|
+
`seek()` on 'path'.
|
794
799
|
config: Optional parameters to set `diplay_name`, `mime_type`, and `name`.
|
795
800
|
"""
|
796
801
|
if self._api_client.vertexai:
|
@@ -804,42 +809,42 @@ class Files(_api_module.BaseModule):
|
|
804
809
|
config_model = types.UploadFileConfig(**config)
|
805
810
|
else:
|
806
811
|
config_model = config
|
807
|
-
|
812
|
+
file_obj = types.File(
|
808
813
|
mime_type=config_model.mime_type,
|
809
814
|
name=config_model.name,
|
810
815
|
display_name=config_model.display_name,
|
811
816
|
)
|
812
817
|
else: # if not config
|
813
|
-
|
814
|
-
if
|
815
|
-
|
818
|
+
file_obj = types.File()
|
819
|
+
if file_obj.name is not None and not file_obj.name.startswith('files/'):
|
820
|
+
file_obj.name = f'files/{file_obj.name}'
|
816
821
|
|
817
|
-
if isinstance(
|
818
|
-
if
|
822
|
+
if isinstance(file, io.IOBase):
|
823
|
+
if file_obj.mime_type is None:
|
819
824
|
raise ValueError(
|
820
825
|
'Unknown mime type: Could not determine the mimetype for your'
|
821
826
|
' file\n please set the `mime_type` argument'
|
822
827
|
)
|
823
|
-
if hasattr(
|
824
|
-
if 'b' not in
|
828
|
+
if hasattr(file, 'mode'):
|
829
|
+
if 'b' not in file.mode:
|
825
830
|
raise ValueError('The file must be opened in binary mode.')
|
826
|
-
offset =
|
827
|
-
|
828
|
-
|
829
|
-
|
831
|
+
offset = file.tell()
|
832
|
+
file.seek(0, os.SEEK_END)
|
833
|
+
file_obj.size_bytes = file.tell() - offset
|
834
|
+
file.seek(offset, os.SEEK_SET)
|
830
835
|
else:
|
831
|
-
fs_path = os.fspath(
|
836
|
+
fs_path = os.fspath(file)
|
832
837
|
if not fs_path or not os.path.isfile(fs_path):
|
833
|
-
raise FileNotFoundError(f'{
|
834
|
-
|
835
|
-
if
|
836
|
-
|
837
|
-
if
|
838
|
+
raise FileNotFoundError(f'{file} is not a valid file path.')
|
839
|
+
file_obj.size_bytes = os.path.getsize(fs_path)
|
840
|
+
if file_obj.mime_type is None:
|
841
|
+
file_obj.mime_type, _ = mimetypes.guess_type(fs_path)
|
842
|
+
if file_obj.mime_type is None:
|
838
843
|
raise ValueError(
|
839
844
|
'Unknown mime type: Could not determine the mimetype for your'
|
840
845
|
' file\n please set the `mime_type` argument'
|
841
846
|
)
|
842
|
-
|
847
|
+
|
843
848
|
if config_model and config_model.http_options:
|
844
849
|
http_options = config_model.http_options
|
845
850
|
else:
|
@@ -849,30 +854,31 @@ class Files(_api_module.BaseModule):
|
|
849
854
|
'Content-Type': 'application/json',
|
850
855
|
'X-Goog-Upload-Protocol': 'resumable',
|
851
856
|
'X-Goog-Upload-Command': 'start',
|
852
|
-
'X-Goog-Upload-Header-Content-Length': f'{
|
853
|
-
'X-Goog-Upload-Header-Content-Type': f'{
|
857
|
+
'X-Goog-Upload-Header-Content-Length': f'{file_obj.size_bytes}',
|
858
|
+
'X-Goog-Upload-Header-Content-Type': f'{file_obj.mime_type}',
|
854
859
|
},
|
855
|
-
'deprecated_response_payload': response,
|
856
860
|
}
|
857
|
-
self._create(
|
861
|
+
response = self._create(
|
862
|
+
file=file_obj, config={'http_options': http_options}
|
863
|
+
)
|
858
864
|
|
859
865
|
if (
|
860
|
-
|
861
|
-
or 'X-Goog-Upload-URL' not in response
|
866
|
+
response.http_headers is None
|
867
|
+
or 'X-Goog-Upload-URL' not in response.http_headers
|
862
868
|
):
|
863
869
|
raise KeyError(
|
864
870
|
'Failed to create file. Upload URL did not returned from the create'
|
865
871
|
' file request.'
|
866
872
|
)
|
867
|
-
upload_url = response['
|
873
|
+
upload_url = response.http_headers['X-Goog-Upload-URL']
|
868
874
|
|
869
|
-
if isinstance(
|
875
|
+
if isinstance(file, io.IOBase):
|
870
876
|
return_file = self._api_client.upload_file(
|
871
|
-
|
877
|
+
file, upload_url, file_obj.size_bytes
|
872
878
|
)
|
873
879
|
else:
|
874
880
|
return_file = self._api_client.upload_file(
|
875
|
-
fs_path, upload_url,
|
881
|
+
fs_path, upload_url, file_obj.size_bytes
|
876
882
|
)
|
877
883
|
|
878
884
|
return types.File._from_response(
|
@@ -1211,16 +1217,17 @@ class AsyncFiles(_api_module.BaseModule):
|
|
1211
1217
|
async def upload(
|
1212
1218
|
self,
|
1213
1219
|
*,
|
1214
|
-
|
1220
|
+
file: Union[str, pathlib.Path, os.PathLike, io.IOBase],
|
1215
1221
|
config: Optional[types.UploadFileConfigOrDict] = None,
|
1216
1222
|
) -> types.File:
|
1217
1223
|
"""Calls the API to upload a file asynchronously using a supported file service.
|
1218
1224
|
|
1219
1225
|
Args:
|
1220
|
-
|
1221
|
-
|
1222
|
-
other words, do not use non-blocking mode or text mode.
|
1223
|
-
must be seekable, that is, it must be able to call
|
1226
|
+
file: A path to the file or an `IOBase` object to be uploaded. If it's an
|
1227
|
+
IOBase object, it must be opened in blocking (the default) mode and
|
1228
|
+
binary mode. In other words, do not use non-blocking mode or text mode.
|
1229
|
+
The given stream must be seekable, that is, it must be able to call
|
1230
|
+
`seek()` on 'path'.
|
1224
1231
|
config: Optional parameters to set `diplay_name`, `mime_type`, and `name`.
|
1225
1232
|
"""
|
1226
1233
|
if self._api_client.vertexai:
|
@@ -1234,43 +1241,42 @@ class AsyncFiles(_api_module.BaseModule):
|
|
1234
1241
|
config_model = types.UploadFileConfig(**config)
|
1235
1242
|
else:
|
1236
1243
|
config_model = config
|
1237
|
-
|
1244
|
+
file_obj = types.File(
|
1238
1245
|
mime_type=config_model.mime_type,
|
1239
1246
|
name=config_model.name,
|
1240
1247
|
display_name=config_model.display_name,
|
1241
1248
|
)
|
1242
1249
|
else: # if not config
|
1243
|
-
|
1244
|
-
if
|
1245
|
-
|
1250
|
+
file_obj = types.File()
|
1251
|
+
if file_obj.name is not None and not file_obj.name.startswith('files/'):
|
1252
|
+
file_obj.name = f'files/{file_obj.name}'
|
1246
1253
|
|
1247
|
-
if isinstance(
|
1248
|
-
if
|
1254
|
+
if isinstance(file, io.IOBase):
|
1255
|
+
if file_obj.mime_type is None:
|
1249
1256
|
raise ValueError(
|
1250
1257
|
'Unknown mime type: Could not determine the mimetype for your'
|
1251
1258
|
' file\n please set the `mime_type` argument'
|
1252
1259
|
)
|
1253
|
-
if hasattr(
|
1254
|
-
if 'b' not in
|
1260
|
+
if hasattr(file, 'mode'):
|
1261
|
+
if 'b' not in file.mode:
|
1255
1262
|
raise ValueError('The file must be opened in binary mode.')
|
1256
|
-
offset =
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1263
|
+
offset = file.tell()
|
1264
|
+
file.seek(0, os.SEEK_END)
|
1265
|
+
file_obj.size_bytes = file.tell() - offset
|
1266
|
+
file.seek(offset, os.SEEK_SET)
|
1260
1267
|
else:
|
1261
|
-
fs_path = os.fspath(
|
1268
|
+
fs_path = os.fspath(file)
|
1262
1269
|
if not fs_path or not os.path.isfile(fs_path):
|
1263
|
-
raise FileNotFoundError(f'{
|
1264
|
-
|
1265
|
-
if
|
1266
|
-
|
1267
|
-
if
|
1270
|
+
raise FileNotFoundError(f'{file} is not a valid file path.')
|
1271
|
+
file_obj.size_bytes = os.path.getsize(fs_path)
|
1272
|
+
if file_obj.mime_type is None:
|
1273
|
+
file_obj.mime_type, _ = mimetypes.guess_type(fs_path)
|
1274
|
+
if file_obj.mime_type is None:
|
1268
1275
|
raise ValueError(
|
1269
1276
|
'Unknown mime type: Could not determine the mimetype for your'
|
1270
1277
|
' file\n please set the `mime_type` argument'
|
1271
1278
|
)
|
1272
1279
|
|
1273
|
-
response = {}
|
1274
1280
|
if config_model and config_model.http_options:
|
1275
1281
|
http_options = config_model.http_options
|
1276
1282
|
else:
|
@@ -1280,29 +1286,31 @@ class AsyncFiles(_api_module.BaseModule):
|
|
1280
1286
|
'Content-Type': 'application/json',
|
1281
1287
|
'X-Goog-Upload-Protocol': 'resumable',
|
1282
1288
|
'X-Goog-Upload-Command': 'start',
|
1283
|
-
'X-Goog-Upload-Header-Content-Length': f'{
|
1284
|
-
'X-Goog-Upload-Header-Content-Type': f'{
|
1289
|
+
'X-Goog-Upload-Header-Content-Length': f'{file_obj.size_bytes}',
|
1290
|
+
'X-Goog-Upload-Header-Content-Type': f'{file_obj.mime_type}',
|
1285
1291
|
},
|
1286
|
-
'deprecated_response_payload': response,
|
1287
1292
|
}
|
1288
|
-
await self._create(
|
1293
|
+
response = await self._create(
|
1294
|
+
file=file_obj, config={'http_options': http_options}
|
1295
|
+
)
|
1296
|
+
|
1289
1297
|
if (
|
1290
|
-
|
1291
|
-
or 'X-Goog-Upload-URL' not in response
|
1298
|
+
response.http_headers is None
|
1299
|
+
or 'X-Goog-Upload-URL' not in response.http_headers
|
1292
1300
|
):
|
1293
1301
|
raise KeyError(
|
1294
1302
|
'Failed to create file. Upload URL did not returned from the create'
|
1295
1303
|
' file request.'
|
1296
1304
|
)
|
1297
|
-
upload_url = response['
|
1305
|
+
upload_url = response.http_headers['X-Goog-Upload-URL']
|
1298
1306
|
|
1299
|
-
if isinstance(
|
1307
|
+
if isinstance(file, io.IOBase):
|
1300
1308
|
return_file = await self._api_client.async_upload_file(
|
1301
|
-
|
1309
|
+
file, upload_url, file_obj.size_bytes
|
1302
1310
|
)
|
1303
1311
|
else:
|
1304
1312
|
return_file = await self._api_client.async_upload_file(
|
1305
|
-
fs_path, upload_url,
|
1313
|
+
fs_path, upload_url, file_obj.size_bytes
|
1306
1314
|
)
|
1307
1315
|
|
1308
1316
|
return types.File._from_response(
|
google/genai/live.py
CHANGED
@@ -29,8 +29,10 @@ from . import _api_module
|
|
29
29
|
from . import _common
|
30
30
|
from . import _transformers as t
|
31
31
|
from . import client
|
32
|
+
from . import errors
|
32
33
|
from . import types
|
33
34
|
from ._api_client import ApiClient
|
35
|
+
from ._common import experimental_warning
|
34
36
|
from ._common import get_value_by_path as getv
|
35
37
|
from ._common import set_value_by_path as setv
|
36
38
|
from .models import _Content_from_mldev
|
@@ -633,6 +635,9 @@ class AsyncLive(_api_module.BaseModule):
|
|
633
635
|
return_value['setup'].update(to_object)
|
634
636
|
return return_value
|
635
637
|
|
638
|
+
@experimental_warning(
|
639
|
+
"The live API is experimental and may change in future versions.",
|
640
|
+
)
|
636
641
|
@contextlib.asynccontextmanager
|
637
642
|
async def connect(
|
638
643
|
self,
|