google-genai 1.10.0__tar.gz → 1.11.0__tar.gz
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-1.10.0/google_genai.egg-info → google_genai-1.11.0}/PKG-INFO +1 -1
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/_api_client.py +75 -7
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/_extra_utils.py +69 -9
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/_transformers.py +129 -0
- google_genai-1.11.0/google/genai/live.py +932 -0
- google_genai-1.11.0/google/genai/live_converters.py +1298 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/models.py +74 -6
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/types.py +509 -21
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/version.py +1 -1
- {google_genai-1.10.0 → google_genai-1.11.0/google_genai.egg-info}/PKG-INFO +1 -1
- {google_genai-1.10.0 → google_genai-1.11.0}/google_genai.egg-info/SOURCES.txt +1 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/pyproject.toml +2 -2
- google_genai-1.10.0/google/genai/live.py +0 -1875
- {google_genai-1.10.0 → google_genai-1.11.0}/LICENSE +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/MANIFEST.in +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/README.md +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/__init__.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/_api_module.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/_automatic_function_calling_util.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/_common.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/_replay_api_client.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/_test_api_client.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/batches.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/caches.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/chats.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/client.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/errors.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/files.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/operations.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/pagers.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google/genai/tunings.py +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google_genai.egg-info/dependency_links.txt +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google_genai.egg-info/requires.txt +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/google_genai.egg-info/top_level.txt +0 -0
- {google_genai-1.10.0 → google_genai-1.11.0}/setup.cfg +0 -0
@@ -29,21 +29,31 @@ import json
|
|
29
29
|
import logging
|
30
30
|
import math
|
31
31
|
import os
|
32
|
+
import ssl
|
32
33
|
import sys
|
33
34
|
import time
|
34
35
|
from typing import Any, AsyncIterator, Optional, Tuple, Union
|
35
|
-
from urllib.parse import urlparse
|
36
|
+
from urllib.parse import urlparse
|
37
|
+
from urllib.parse import urlunparse
|
38
|
+
|
36
39
|
import anyio
|
40
|
+
import certifi
|
37
41
|
import google.auth
|
38
42
|
import google.auth.credentials
|
39
43
|
from google.auth.credentials import Credentials
|
40
44
|
from google.auth.transport.requests import Request
|
41
45
|
import httpx
|
42
|
-
from pydantic import BaseModel
|
46
|
+
from pydantic import BaseModel
|
47
|
+
from pydantic import Field
|
48
|
+
from pydantic import ValidationError
|
49
|
+
|
43
50
|
from . import _common
|
44
51
|
from . import errors
|
45
52
|
from . import version
|
46
|
-
from .types import HttpOptions
|
53
|
+
from .types import HttpOptions
|
54
|
+
from .types import HttpOptionsDict
|
55
|
+
from .types import HttpOptionsOrDict
|
56
|
+
|
47
57
|
|
48
58
|
logger = logging.getLogger('google_genai._api_client')
|
49
59
|
CHUNK_SIZE = 8 * 1024 * 1024 # 8 MB chunk size
|
@@ -414,7 +424,7 @@ class BaseApiClient:
|
|
414
424
|
if not self.api_key:
|
415
425
|
raise ValueError(
|
416
426
|
'Missing key inputs argument! To use the Google AI API,'
|
417
|
-
'provide (`api_key`) arguments. To use the Google Cloud API,'
|
427
|
+
' provide (`api_key`) arguments. To use the Google Cloud API,'
|
418
428
|
' provide (`vertexai`, `project` & `location`) arguments.'
|
419
429
|
)
|
420
430
|
self._http_options.base_url = 'https://generativelanguage.googleapis.com/'
|
@@ -432,9 +442,67 @@ class BaseApiClient:
|
|
432
442
|
else:
|
433
443
|
if self._http_options.headers is not None:
|
434
444
|
_append_library_version_headers(self._http_options.headers)
|
435
|
-
|
436
|
-
|
437
|
-
self.
|
445
|
+
|
446
|
+
client_args, async_client_args = self._ensure_ssl_ctx(self._http_options)
|
447
|
+
self._httpx_client = SyncHttpxClient(**client_args)
|
448
|
+
self._async_httpx_client = AsyncHttpxClient(**async_client_args)
|
449
|
+
|
450
|
+
@staticmethod
|
451
|
+
def _ensure_ssl_ctx(options: HttpOptions) -> (
|
452
|
+
Tuple[dict[str, Any], dict[str, Any]]):
|
453
|
+
"""Ensures the SSL context is present in the client args.
|
454
|
+
|
455
|
+
Creates a default SSL context if one is not provided.
|
456
|
+
|
457
|
+
Args:
|
458
|
+
options: The http options to check for SSL context.
|
459
|
+
|
460
|
+
Returns:
|
461
|
+
A tuple of sync/async httpx client args.
|
462
|
+
"""
|
463
|
+
|
464
|
+
verify = 'verify'
|
465
|
+
args = options.client_args
|
466
|
+
async_args = options.async_client_args
|
467
|
+
ctx = (
|
468
|
+
args.get(verify) if args else None
|
469
|
+
or async_args.get(verify) if async_args else None
|
470
|
+
)
|
471
|
+
|
472
|
+
if not ctx:
|
473
|
+
# Initialize the SSL context for the httpx client.
|
474
|
+
# Unlike requests, the httpx package does not automatically pull in the
|
475
|
+
# environment variables SSL_CERT_FILE or SSL_CERT_DIR. They need to be
|
476
|
+
# enabled explicitly.
|
477
|
+
ctx = ssl.create_default_context(
|
478
|
+
cafile=os.environ.get('SSL_CERT_FILE', certifi.where()),
|
479
|
+
capath=os.environ.get('SSL_CERT_DIR'),
|
480
|
+
)
|
481
|
+
|
482
|
+
def _maybe_set(
|
483
|
+
args: Optional[dict[str, Any]],
|
484
|
+
ctx: ssl.SSLContext,
|
485
|
+
) -> dict[str, Any]:
|
486
|
+
"""Sets the SSL context in the client args if not set.
|
487
|
+
|
488
|
+
Does not override the SSL context if it is already set.
|
489
|
+
|
490
|
+
Args:
|
491
|
+
args: The client args to to check for SSL context.
|
492
|
+
ctx: The SSL context to set.
|
493
|
+
|
494
|
+
Returns:
|
495
|
+
The client args with the SSL context included.
|
496
|
+
"""
|
497
|
+
if not args or not args.get(verify):
|
498
|
+
args = (args or {}).copy()
|
499
|
+
args[verify] = ctx
|
500
|
+
return args
|
501
|
+
|
502
|
+
return (
|
503
|
+
_maybe_set(args, ctx),
|
504
|
+
_maybe_set(async_args, ctx),
|
505
|
+
)
|
438
506
|
|
439
507
|
def _websocket_base_url(self):
|
440
508
|
url_parts = urlparse(self._http_options.base_url)
|
@@ -78,6 +78,7 @@ def format_destination(
|
|
78
78
|
|
79
79
|
def get_function_map(
|
80
80
|
config: Optional[types.GenerateContentConfigOrDict] = None,
|
81
|
+
is_caller_method_async: bool = False,
|
81
82
|
) -> dict[str, Callable]:
|
82
83
|
"""Returns a function map from the config."""
|
83
84
|
function_map: dict[str, Callable] = {}
|
@@ -87,7 +88,7 @@ def get_function_map(
|
|
87
88
|
if config_model.tools:
|
88
89
|
for tool in config_model.tools:
|
89
90
|
if callable(tool):
|
90
|
-
if inspect.iscoroutinefunction(tool):
|
91
|
+
if inspect.iscoroutinefunction(tool) and not is_caller_method_async:
|
91
92
|
raise errors.UnsupportedFunctionError(
|
92
93
|
f'Function {tool.__name__} is a coroutine function, which is not'
|
93
94
|
' supported for automatic function calling. Please manually'
|
@@ -199,11 +200,11 @@ def convert_if_exist_pydantic_model(
|
|
199
200
|
return value
|
200
201
|
|
201
202
|
|
202
|
-
def
|
203
|
-
args:
|
204
|
-
) -> Any:
|
205
|
-
signature = inspect.signature(
|
206
|
-
func_name =
|
203
|
+
def convert_argument_from_function(
|
204
|
+
args: dict[str, Any], function: Callable
|
205
|
+
) -> dict[str, Any]:
|
206
|
+
signature = inspect.signature(function)
|
207
|
+
func_name = function.__name__
|
207
208
|
converted_args = {}
|
208
209
|
for param_name, param in signature.parameters.items():
|
209
210
|
if param_name in args:
|
@@ -213,13 +214,34 @@ def invoke_function_from_dict_args(
|
|
213
214
|
param_name,
|
214
215
|
func_name,
|
215
216
|
)
|
217
|
+
return converted_args
|
218
|
+
|
219
|
+
|
220
|
+
def invoke_function_from_dict_args(
|
221
|
+
args: Dict[str, Any], function_to_invoke: Callable
|
222
|
+
) -> Any:
|
223
|
+
converted_args = convert_argument_from_function(args, function_to_invoke)
|
216
224
|
try:
|
217
225
|
return function_to_invoke(**converted_args)
|
218
226
|
except Exception as e:
|
219
227
|
raise errors.FunctionInvocationError(
|
220
|
-
f'Failed to invoke function {
|
221
|
-
f' {converted_args} from model returned function
|
222
|
-
f' {args} because of error {e}'
|
228
|
+
f'Failed to invoke function {function_to_invoke.__name__} with'
|
229
|
+
f' converted arguments {converted_args} from model returned function'
|
230
|
+
f' call argument {args} because of error {e}'
|
231
|
+
)
|
232
|
+
|
233
|
+
|
234
|
+
async def invoke_function_from_dict_args_async(
|
235
|
+
args: Dict[str, Any], function_to_invoke: Callable
|
236
|
+
) -> Any:
|
237
|
+
converted_args = convert_argument_from_function(args, function_to_invoke)
|
238
|
+
try:
|
239
|
+
return await function_to_invoke(**converted_args)
|
240
|
+
except Exception as e:
|
241
|
+
raise errors.FunctionInvocationError(
|
242
|
+
f'Failed to invoke function {function_to_invoke.__name__} with'
|
243
|
+
f' converted arguments {converted_args} from model returned function'
|
244
|
+
f' call argument {args} because of error {e}'
|
223
245
|
)
|
224
246
|
|
225
247
|
|
@@ -256,6 +278,44 @@ def get_function_response_parts(
|
|
256
278
|
func_response_parts.append(func_response_part)
|
257
279
|
return func_response_parts
|
258
280
|
|
281
|
+
async def get_function_response_parts_async(
|
282
|
+
response: types.GenerateContentResponse,
|
283
|
+
function_map: dict[str, Callable],
|
284
|
+
) -> list[types.Part]:
|
285
|
+
"""Returns the function response parts from the response."""
|
286
|
+
func_response_parts = []
|
287
|
+
if (
|
288
|
+
response.candidates is not None
|
289
|
+
and isinstance(response.candidates[0].content, types.Content)
|
290
|
+
and response.candidates[0].content.parts is not None
|
291
|
+
):
|
292
|
+
for part in response.candidates[0].content.parts:
|
293
|
+
if not part.function_call:
|
294
|
+
continue
|
295
|
+
func_name = part.function_call.name
|
296
|
+
if func_name is not None and part.function_call.args is not None:
|
297
|
+
func = function_map[func_name]
|
298
|
+
args = convert_number_values_for_dict_function_call_args(
|
299
|
+
part.function_call.args
|
300
|
+
)
|
301
|
+
func_response: dict[str, Any]
|
302
|
+
try:
|
303
|
+
if inspect.iscoroutinefunction(func):
|
304
|
+
func_response = {
|
305
|
+
'result': await invoke_function_from_dict_args_async(args, func)
|
306
|
+
}
|
307
|
+
else:
|
308
|
+
func_response = {
|
309
|
+
'result': invoke_function_from_dict_args(args, func)
|
310
|
+
}
|
311
|
+
except Exception as e: # pylint: disable=broad-except
|
312
|
+
func_response = {'error': str(e)}
|
313
|
+
func_response_part = types.Part.from_function_response(
|
314
|
+
name=func_name, response=func_response
|
315
|
+
)
|
316
|
+
func_response_parts.append(func_response_part)
|
317
|
+
return func_response_parts
|
318
|
+
|
259
319
|
|
260
320
|
def should_disable_afc(
|
261
321
|
config: Optional[types.GenerateContentConfigOrDict] = None,
|
@@ -663,7 +663,9 @@ def process_schema(
|
|
663
663
|
# provided directly to response_schema, it may use `any_of` instead of `anyOf.
|
664
664
|
# Otherwise, model_json_schema() uses `anyOf`.
|
665
665
|
for from_name, to_name in [
|
666
|
+
('additional_properties', 'additionalProperties'),
|
666
667
|
('any_of', 'anyOf'),
|
668
|
+
('prefix_items', 'prefixItems'),
|
667
669
|
('property_ordering', 'propertyOrdering'),
|
668
670
|
]:
|
669
671
|
if (value := schema.pop(from_name, None)) is not None:
|
@@ -723,9 +725,16 @@ def process_schema(
|
|
723
725
|
and 'propertyOrdering' not in schema
|
724
726
|
):
|
725
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)
|
726
733
|
elif schema_type == 'ARRAY':
|
727
734
|
if (items := schema.get('items')) is not None:
|
728
735
|
schema['items'] = _recurse(items)
|
736
|
+
if (prefixes := schema.get('prefixItems')) is not None:
|
737
|
+
schema['prefixItems'] = [_recurse(prefix) for prefix in prefixes]
|
729
738
|
|
730
739
|
|
731
740
|
def _process_enum(
|
@@ -845,6 +854,8 @@ def t_tool(client: _api_client.BaseApiClient, origin) -> Optional[types.Tool]:
|
|
845
854
|
)
|
846
855
|
]
|
847
856
|
)
|
857
|
+
elif isinstance(origin, dict):
|
858
|
+
return types.Tool.model_validate(origin)
|
848
859
|
else:
|
849
860
|
return origin
|
850
861
|
|
@@ -1017,3 +1028,121 @@ def t_bytes(api_client: _api_client.BaseApiClient, data: bytes) -> str:
|
|
1017
1028
|
if not isinstance(data, bytes):
|
1018
1029
|
return data
|
1019
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
|
+
|