google-genai 0.6.0__py3-none-any.whl → 0.7.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 +72 -78
- google/genai/_api_module.py +24 -0
- google/genai/_automatic_function_calling_util.py +43 -22
- google/genai/_common.py +0 -6
- google/genai/_extra_utils.py +22 -16
- google/genai/_replay_api_client.py +2 -2
- google/genai/_test_api_client.py +1 -1
- google/genai/_transformers.py +218 -97
- google/genai/batches.py +194 -155
- google/genai/caches.py +117 -134
- google/genai/chats.py +22 -18
- google/genai/client.py +31 -37
- google/genai/files.py +94 -125
- google/genai/live.py +11 -5
- google/genai/models.py +500 -254
- google/genai/tunings.py +85 -422
- google/genai/types.py +495 -458
- google/genai/version.py +1 -1
- {google_genai-0.6.0.dist-info → google_genai-0.7.0.dist-info}/METADATA +116 -68
- google_genai-0.7.0.dist-info/RECORD +26 -0
- google_genai-0.6.0.dist-info/RECORD +0 -25
- {google_genai-0.6.0.dist-info → google_genai-0.7.0.dist-info}/LICENSE +0 -0
- {google_genai-0.6.0.dist-info → google_genai-0.7.0.dist-info}/WHEEL +0 -0
- {google_genai-0.6.0.dist-info → google_genai-0.7.0.dist-info}/top_level.txt +0 -0
google/genai/_transformers.py
CHANGED
@@ -17,20 +17,28 @@
|
|
17
17
|
|
18
18
|
import base64
|
19
19
|
from collections.abc import Iterable, Mapping
|
20
|
+
from enum import Enum, EnumMeta
|
20
21
|
import inspect
|
21
22
|
import io
|
22
23
|
import re
|
23
24
|
import time
|
24
25
|
import typing
|
25
26
|
from typing import Any, GenericAlias, Optional, Union
|
27
|
+
import sys
|
28
|
+
|
29
|
+
if typing.TYPE_CHECKING:
|
30
|
+
import PIL.Image
|
26
31
|
|
27
|
-
import PIL.Image
|
28
|
-
import PIL.PngImagePlugin
|
29
32
|
import pydantic
|
30
33
|
|
31
34
|
from . import _api_client
|
32
35
|
from . import types
|
33
36
|
|
37
|
+
if sys.version_info >= (3, 11):
|
38
|
+
from types import UnionType
|
39
|
+
else:
|
40
|
+
UnionType = typing._UnionGenericAlias
|
41
|
+
|
34
42
|
|
35
43
|
def _resource_name(
|
36
44
|
client: _api_client.ApiClient,
|
@@ -192,9 +200,15 @@ def t_caches_model(api_client: _api_client.ApiClient, model: str):
|
|
192
200
|
return model
|
193
201
|
|
194
202
|
|
195
|
-
def pil_to_blob(img):
|
203
|
+
def pil_to_blob(img) -> types.Blob:
|
204
|
+
try:
|
205
|
+
import PIL.PngImagePlugin
|
206
|
+
PngImagePlugin = PIL.PngImagePlugin
|
207
|
+
except ImportError:
|
208
|
+
PngImagePlugin = None
|
209
|
+
|
196
210
|
bytesio = io.BytesIO()
|
197
|
-
if isinstance(img,
|
211
|
+
if PngImagePlugin is not None and isinstance(img, PngImagePlugin.PngImageFile) or img.mode == 'RGBA':
|
198
212
|
img.save(bytesio, format='PNG')
|
199
213
|
mime_type = 'image/png'
|
200
214
|
else:
|
@@ -205,20 +219,26 @@ def pil_to_blob(img):
|
|
205
219
|
return types.Blob(mime_type=mime_type, data=data)
|
206
220
|
|
207
221
|
|
208
|
-
PartType = Union[types.Part, types.PartDict, str, PIL.Image.Image]
|
222
|
+
PartType = Union[types.Part, types.PartDict, str, 'PIL.Image.Image']
|
209
223
|
|
210
224
|
|
211
225
|
def t_part(client: _api_client.ApiClient, part: PartType) -> types.Part:
|
226
|
+
try:
|
227
|
+
import PIL.Image
|
228
|
+
PIL_Image = PIL.Image.Image
|
229
|
+
except ImportError:
|
230
|
+
PIL_Image = None
|
231
|
+
|
212
232
|
if not part:
|
213
233
|
raise ValueError('content part is required.')
|
214
234
|
if isinstance(part, str):
|
215
235
|
return types.Part(text=part)
|
216
|
-
if isinstance(part,
|
236
|
+
if PIL_Image is not None and isinstance(part, PIL_Image):
|
217
237
|
return types.Part(inline_data=pil_to_blob(part))
|
218
238
|
if isinstance(part, types.File):
|
219
239
|
if not part.uri or not part.mime_type:
|
220
240
|
raise ValueError('file uri and mime_type are required.')
|
221
|
-
return types.Part.from_uri(part.uri, part.mime_type)
|
241
|
+
return types.Part.from_uri(file_uri=part.uri, mime_type=part.mime_type)
|
222
242
|
else:
|
223
243
|
return part
|
224
244
|
|
@@ -297,104 +317,192 @@ def t_contents(
|
|
297
317
|
return [t_content(client, contents)]
|
298
318
|
|
299
319
|
|
300
|
-
def
|
301
|
-
|
302
|
-
):
|
303
|
-
if isinstance(data, dict):
|
304
|
-
# Iterate over a copy of keys to allow deletion
|
305
|
-
for key in list(data.keys()):
|
306
|
-
# Only delete 'title'for the Gemini API
|
307
|
-
if client and not client.vertexai and key == 'title':
|
308
|
-
del data[key]
|
309
|
-
else:
|
310
|
-
process_schema(data[key], client)
|
311
|
-
elif isinstance(data, list):
|
312
|
-
for item in data:
|
313
|
-
process_schema(item, client)
|
320
|
+
def handle_null_fields(schema: dict[str, Any]):
|
321
|
+
"""Process null fields in the schema so it is compatible with OpenAPI.
|
314
322
|
|
315
|
-
|
323
|
+
The OpenAPI spec does not support 'type: 'null' in the schema. This function
|
324
|
+
handles this case by adding 'nullable: True' to the null field and removing
|
325
|
+
the {'type': 'null'} entry.
|
316
326
|
|
327
|
+
https://swagger.io/docs/specification/v3_0/data-models/data-types/#null
|
317
328
|
|
318
|
-
|
319
|
-
|
320
|
-
|
329
|
+
Example of schema properties before and after handling null fields:
|
330
|
+
Before:
|
331
|
+
{
|
332
|
+
"name": {
|
333
|
+
"title": "Name",
|
334
|
+
"type": "string"
|
335
|
+
},
|
336
|
+
"total_area_sq_mi": {
|
337
|
+
"anyOf": [
|
338
|
+
{
|
339
|
+
"type": "integer"
|
340
|
+
},
|
341
|
+
{
|
342
|
+
"type": "null"
|
343
|
+
}
|
344
|
+
],
|
345
|
+
"default": null,
|
346
|
+
"title": "Total Area Sq Mi"
|
347
|
+
}
|
348
|
+
}
|
321
349
|
|
322
|
-
|
323
|
-
|
350
|
+
After:
|
351
|
+
{
|
352
|
+
"name": {
|
353
|
+
"title": "Name",
|
354
|
+
"type": "string"
|
355
|
+
},
|
356
|
+
"total_area_sq_mi": {
|
357
|
+
"type": "integer",
|
358
|
+
"nullable": true,
|
359
|
+
"default": null,
|
360
|
+
"title": "Total Area Sq Mi"
|
361
|
+
}
|
362
|
+
}
|
363
|
+
"""
|
364
|
+
if (
|
365
|
+
isinstance(schema, dict)
|
366
|
+
and 'type' in schema
|
367
|
+
and schema['type'] == 'null'
|
368
|
+
):
|
369
|
+
schema['nullable'] = True
|
370
|
+
del schema['type']
|
371
|
+
elif 'anyOf' in schema:
|
372
|
+
for item in schema['anyOf']:
|
373
|
+
if 'type' in item and item['type'] == 'null':
|
374
|
+
schema['nullable'] = True
|
375
|
+
schema['anyOf'].remove({'type': 'null'})
|
376
|
+
if len(schema['anyOf']) == 1:
|
377
|
+
# If there is only one type left after removing null, remove the anyOf field.
|
378
|
+
field_type = schema['anyOf'][0]['type']
|
379
|
+
schema['type'] = field_type
|
380
|
+
del schema['anyOf']
|
324
381
|
|
325
|
-
unpack_defs(parameters, defs)
|
326
|
-
return parameters['properties']['dummy']
|
327
382
|
|
383
|
+
def process_schema(
|
384
|
+
schema: dict[str, Any],
|
385
|
+
client: Optional[_api_client.ApiClient] = None,
|
386
|
+
defs: Optional[dict[str, Any]]=None):
|
387
|
+
"""Updates the schema and each sub-schema inplace to be API-compatible.
|
328
388
|
|
329
|
-
|
330
|
-
|
389
|
+
- Removes the `title` field from the schema if the client is not vertexai.
|
390
|
+
- Inlines the $defs.
|
331
391
|
|
332
|
-
Example of a schema before and after
|
392
|
+
Example of a schema before and after (with mldev):
|
333
393
|
Before:
|
334
394
|
|
335
395
|
`schema`
|
336
396
|
|
337
|
-
{
|
338
|
-
'
|
339
|
-
'
|
340
|
-
'$ref': '#/$defs/CountryInfo'
|
341
|
-
},
|
342
|
-
'title': 'Dummy',
|
343
|
-
'type': 'array'
|
344
|
-
}
|
397
|
+
{
|
398
|
+
'items': {
|
399
|
+
'$ref': '#/$defs/CountryInfo'
|
345
400
|
},
|
346
|
-
'
|
347
|
-
'
|
348
|
-
|
401
|
+
'title': 'Placeholder',
|
402
|
+
'type': 'array'
|
403
|
+
}
|
404
|
+
|
349
405
|
|
350
406
|
`defs`
|
351
407
|
|
352
|
-
{
|
353
|
-
|
354
|
-
|
408
|
+
{
|
409
|
+
'CountryInfo': {
|
410
|
+
'properties': {
|
411
|
+
'continent': {
|
412
|
+
'title': 'Continent',
|
413
|
+
'type': 'string'
|
414
|
+
},
|
415
|
+
'gdp': {
|
416
|
+
'title': 'Gdp',
|
417
|
+
'type': 'integer'}
|
418
|
+
},
|
419
|
+
}
|
420
|
+
'required':['continent', 'gdp'],
|
421
|
+
'title': 'CountryInfo',
|
422
|
+
'type': 'object'
|
423
|
+
}
|
424
|
+
}
|
355
425
|
|
356
426
|
After:
|
357
427
|
|
358
428
|
`schema`
|
359
|
-
|
360
|
-
'
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
429
|
+
{
|
430
|
+
'items': {
|
431
|
+
'properties': {
|
432
|
+
'continent': {
|
433
|
+
'type': 'string'
|
434
|
+
},
|
435
|
+
'gdp': {
|
436
|
+
'type': 'integer'}
|
437
|
+
},
|
438
|
+
}
|
439
|
+
'required':['continent', 'gdp'],
|
440
|
+
'type': 'object'
|
441
|
+
},
|
442
|
+
'type': 'array'
|
366
443
|
}
|
367
444
|
"""
|
368
|
-
|
369
|
-
|
445
|
+
if client and not client.vertexai:
|
446
|
+
schema.pop('title', None)
|
447
|
+
|
448
|
+
if defs is None:
|
449
|
+
defs = schema.pop('$defs', {})
|
450
|
+
for _, sub_schema in defs.items():
|
451
|
+
process_schema(sub_schema, client, defs)
|
452
|
+
|
453
|
+
handle_null_fields(schema)
|
454
|
+
|
455
|
+
any_of = schema.get('anyOf', None)
|
456
|
+
if any_of is not None:
|
457
|
+
for sub_schema in any_of:
|
458
|
+
process_schema(sub_schema, client, defs)
|
370
459
|
return
|
371
460
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
unpack_defs(ref, defs)
|
387
|
-
anyof[i] = ref
|
388
|
-
continue
|
389
|
-
|
390
|
-
items = value.get('items', None)
|
391
|
-
if items is not None:
|
392
|
-
ref_key = items.get('$ref', None)
|
393
|
-
if ref_key is not None:
|
461
|
+
schema_type = schema.get('type', None)
|
462
|
+
if isinstance(schema_type, Enum):
|
463
|
+
schema_type = schema_type.value
|
464
|
+
schema_type = schema_type.upper()
|
465
|
+
|
466
|
+
if schema_type == 'OBJECT':
|
467
|
+
properties = schema.get('properties', None)
|
468
|
+
if properties is None:
|
469
|
+
return
|
470
|
+
for name, sub_schema in properties.items():
|
471
|
+
ref_key = sub_schema.get('$ref', None)
|
472
|
+
if ref_key is None:
|
473
|
+
process_schema(sub_schema, client, defs)
|
474
|
+
else:
|
394
475
|
ref = defs[ref_key.split('defs/')[-1]]
|
395
|
-
|
396
|
-
|
397
|
-
|
476
|
+
process_schema(ref, client, defs)
|
477
|
+
properties[name] = ref
|
478
|
+
elif schema_type == 'ARRAY':
|
479
|
+
sub_schema = schema.get('items', None)
|
480
|
+
if sub_schema is None:
|
481
|
+
return
|
482
|
+
ref_key = sub_schema.get('$ref', None)
|
483
|
+
if ref_key is None:
|
484
|
+
process_schema(sub_schema, client, defs)
|
485
|
+
else:
|
486
|
+
ref = defs[ref_key.split('defs/')[-1]]
|
487
|
+
process_schema(ref, client, defs)
|
488
|
+
schema['items'] = ref
|
489
|
+
|
490
|
+
def _process_enum(
|
491
|
+
enum: EnumMeta, client: Optional[_api_client.ApiClient] = None
|
492
|
+
) -> types.Schema:
|
493
|
+
for member in enum:
|
494
|
+
if not isinstance(member.value, str):
|
495
|
+
raise TypeError(
|
496
|
+
f'Enum member {member.name} value must be a string, got'
|
497
|
+
f' {type(member.value)}'
|
498
|
+
)
|
499
|
+
class Placeholder(pydantic.BaseModel):
|
500
|
+
placeholder: enum
|
501
|
+
|
502
|
+
enum_schema = Placeholder.model_json_schema()
|
503
|
+
process_schema(enum_schema, client)
|
504
|
+
enum_schema = enum_schema['properties']['placeholder']
|
505
|
+
return types.Schema.model_validate(enum_schema)
|
398
506
|
|
399
507
|
|
400
508
|
def t_schema(
|
@@ -403,28 +511,39 @@ def t_schema(
|
|
403
511
|
if not origin:
|
404
512
|
return None
|
405
513
|
if isinstance(origin, dict):
|
406
|
-
|
514
|
+
process_schema(origin, client)
|
515
|
+
return types.Schema.model_validate(origin)
|
516
|
+
if isinstance(origin, EnumMeta):
|
517
|
+
return _process_enum(origin, client)
|
407
518
|
if isinstance(origin, types.Schema):
|
408
519
|
if dict(origin) == dict(types.Schema()):
|
409
520
|
# response_schema value was coerced to an empty Schema instance because it did not adhere to the Schema field annotation
|
410
521
|
raise ValueError(f'Unsupported schema type.')
|
411
|
-
schema =
|
522
|
+
schema = origin.model_dump(exclude_unset=True)
|
523
|
+
process_schema(schema, client)
|
412
524
|
return types.Schema.model_validate(schema)
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
return types.Schema.model_validate(list_schema)
|
424
|
-
raise ValueError(f'Unsupported schema type: GenericAlias {origin}')
|
425
|
-
if issubclass(origin, pydantic.BaseModel):
|
426
|
-
schema = process_schema(origin.model_json_schema(), client)
|
525
|
+
|
526
|
+
if (
|
527
|
+
# in Python 3.9 Generic alias list[int] counts as a type,
|
528
|
+
# and breaks issubclass because it's not a class.
|
529
|
+
not isinstance(origin, GenericAlias) and
|
530
|
+
isinstance(origin, type) and
|
531
|
+
issubclass(origin, pydantic.BaseModel)
|
532
|
+
):
|
533
|
+
schema = origin.model_json_schema()
|
534
|
+
process_schema(schema, client)
|
427
535
|
return types.Schema.model_validate(schema)
|
536
|
+
elif (
|
537
|
+
isinstance(origin, GenericAlias) or isinstance(origin, type) or isinstance(origin, UnionType)
|
538
|
+
):
|
539
|
+
class Placeholder(pydantic.BaseModel):
|
540
|
+
placeholder: origin
|
541
|
+
|
542
|
+
schema = Placeholder.model_json_schema()
|
543
|
+
process_schema(schema, client)
|
544
|
+
schema = schema['properties']['placeholder']
|
545
|
+
return types.Schema.model_validate(schema)
|
546
|
+
|
428
547
|
raise ValueError(f'Unsupported schema type: {origin}')
|
429
548
|
|
430
549
|
|
@@ -464,7 +583,9 @@ def t_tool(client: _api_client.ApiClient, origin) -> types.Tool:
|
|
464
583
|
if inspect.isfunction(origin) or inspect.ismethod(origin):
|
465
584
|
return types.Tool(
|
466
585
|
function_declarations=[
|
467
|
-
types.FunctionDeclaration.from_callable(
|
586
|
+
types.FunctionDeclaration.from_callable(
|
587
|
+
client=client, callable=origin
|
588
|
+
)
|
468
589
|
]
|
469
590
|
)
|
470
591
|
else:
|