google-genai 1.27.0__tar.gz → 1.28.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.27.0/google_genai.egg-info → google_genai-1.28.0}/PKG-INFO +1 -1
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/__init__.py +1 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_api_client.py +143 -39
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_common.py +9 -6
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_extra_utils.py +8 -8
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_mcp_utils.py +4 -1
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_replay_api_client.py +6 -2
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_transformers.py +13 -12
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/errors.py +1 -1
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/live.py +2 -3
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/models.py +81 -12
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/pagers.py +5 -5
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/tokens.py +3 -3
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/tunings.py +33 -6
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/types.py +59 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/version.py +1 -1
- {google_genai-1.27.0 → google_genai-1.28.0/google_genai.egg-info}/PKG-INFO +1 -1
- {google_genai-1.27.0 → google_genai-1.28.0}/pyproject.toml +1 -1
- {google_genai-1.27.0 → google_genai-1.28.0}/LICENSE +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/MANIFEST.in +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/README.md +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_adapters.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_api_module.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_automatic_function_calling_util.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_base_url.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_live_converters.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_test_api_client.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/_tokens_converters.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/batches.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/caches.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/chats.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/client.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/files.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/live_music.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/operations.py +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google/genai/py.typed +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google_genai.egg-info/SOURCES.txt +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google_genai.egg-info/dependency_links.txt +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google_genai.egg-info/requires.txt +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/google_genai.egg-info/top_level.txt +0 -0
- {google_genai-1.27.0 → google_genai-1.28.0}/setup.cfg +0 -0
@@ -32,6 +32,7 @@ import logging
|
|
32
32
|
import math
|
33
33
|
import os
|
34
34
|
import ssl
|
35
|
+
import random
|
35
36
|
import sys
|
36
37
|
import threading
|
37
38
|
import time
|
@@ -81,6 +82,7 @@ if TYPE_CHECKING:
|
|
81
82
|
|
82
83
|
logger = logging.getLogger('google_genai._api_client')
|
83
84
|
CHUNK_SIZE = 8 * 1024 * 1024 # 8 MB chunk size
|
85
|
+
READ_BUFFER_SIZE = 2**20
|
84
86
|
MAX_RETRY_COUNT = 3
|
85
87
|
INITIAL_RETRY_DELAY = 1 # second
|
86
88
|
DELAY_MULTIPLIER = 2
|
@@ -363,7 +365,7 @@ _RETRY_HTTP_STATUS_CODES = (
|
|
363
365
|
)
|
364
366
|
|
365
367
|
|
366
|
-
def _retry_args(options: Optional[HttpRetryOptions]) ->
|
368
|
+
def _retry_args(options: Optional[HttpRetryOptions]) -> _common.StringDict:
|
367
369
|
"""Returns the retry args for the given http retry options.
|
368
370
|
|
369
371
|
Args:
|
@@ -533,17 +535,30 @@ class BaseApiClient:
|
|
533
535
|
+ ' precedence over the API key from the environment variables.'
|
534
536
|
)
|
535
537
|
self.api_key = None
|
536
|
-
|
538
|
+
|
539
|
+
# Skip fetching project from ADC if base url is provided in http options.
|
540
|
+
if (
|
541
|
+
not self.project
|
542
|
+
and not self.api_key
|
543
|
+
and not validated_http_options.base_url
|
544
|
+
):
|
537
545
|
credentials, self.project = _load_auth(project=None)
|
538
546
|
if not self._credentials:
|
539
547
|
self._credentials = credentials
|
540
|
-
|
548
|
+
|
549
|
+
has_sufficient_auth = (self.project and self.location) or self.api_key
|
550
|
+
|
551
|
+
if (not has_sufficient_auth and not validated_http_options.base_url):
|
552
|
+
# Skip sufficient auth check if base url is provided in http options.
|
541
553
|
raise ValueError(
|
542
554
|
'Project and location or API key must be set when using the Vertex '
|
543
555
|
'AI API.'
|
544
556
|
)
|
545
557
|
if self.api_key or self.location == 'global':
|
546
558
|
self._http_options.base_url = f'https://aiplatform.googleapis.com/'
|
559
|
+
elif validated_http_options.base_url and not has_sufficient_auth:
|
560
|
+
# Avoid setting default base url and api version if base_url provided.
|
561
|
+
self._http_options.base_url = validated_http_options.base_url
|
547
562
|
else:
|
548
563
|
self._http_options.base_url = (
|
549
564
|
f'https://{self.location}-aiplatform.googleapis.com/'
|
@@ -592,7 +607,7 @@ class BaseApiClient:
|
|
592
607
|
@staticmethod
|
593
608
|
def _ensure_httpx_ssl_ctx(
|
594
609
|
options: HttpOptions,
|
595
|
-
) -> Tuple[
|
610
|
+
) -> Tuple[_common.StringDict, _common.StringDict]:
|
596
611
|
"""Ensures the SSL context is present in the HTTPX client args.
|
597
612
|
|
598
613
|
Creates a default SSL context if one is not provided.
|
@@ -626,9 +641,9 @@ class BaseApiClient:
|
|
626
641
|
)
|
627
642
|
|
628
643
|
def _maybe_set(
|
629
|
-
args: Optional[
|
644
|
+
args: Optional[_common.StringDict],
|
630
645
|
ctx: ssl.SSLContext,
|
631
|
-
) ->
|
646
|
+
) -> _common.StringDict:
|
632
647
|
"""Sets the SSL context in the client args if not set.
|
633
648
|
|
634
649
|
Does not override the SSL context if it is already set.
|
@@ -656,7 +671,7 @@ class BaseApiClient:
|
|
656
671
|
)
|
657
672
|
|
658
673
|
@staticmethod
|
659
|
-
def _ensure_aiohttp_ssl_ctx(options: HttpOptions) ->
|
674
|
+
def _ensure_aiohttp_ssl_ctx(options: HttpOptions) -> _common.StringDict:
|
660
675
|
"""Ensures the SSL context is present in the async client args.
|
661
676
|
|
662
677
|
Creates a default SSL context if one is not provided.
|
@@ -684,9 +699,9 @@ class BaseApiClient:
|
|
684
699
|
)
|
685
700
|
|
686
701
|
def _maybe_set(
|
687
|
-
args: Optional[
|
702
|
+
args: Optional[_common.StringDict],
|
688
703
|
ctx: ssl.SSLContext,
|
689
|
-
) ->
|
704
|
+
) -> _common.StringDict:
|
690
705
|
"""Sets the SSL context in the client args if not set.
|
691
706
|
|
692
707
|
Does not override the SSL context if it is already set.
|
@@ -714,7 +729,7 @@ class BaseApiClient:
|
|
714
729
|
return _maybe_set(async_args, ctx)
|
715
730
|
|
716
731
|
@staticmethod
|
717
|
-
def _ensure_websocket_ssl_ctx(options: HttpOptions) ->
|
732
|
+
def _ensure_websocket_ssl_ctx(options: HttpOptions) -> _common.StringDict:
|
718
733
|
"""Ensures the SSL context is present in the async client args.
|
719
734
|
|
720
735
|
Creates a default SSL context if one is not provided.
|
@@ -742,9 +757,9 @@ class BaseApiClient:
|
|
742
757
|
)
|
743
758
|
|
744
759
|
def _maybe_set(
|
745
|
-
args: Optional[
|
760
|
+
args: Optional[_common.StringDict],
|
746
761
|
ctx: ssl.SSLContext,
|
747
|
-
) ->
|
762
|
+
) -> _common.StringDict:
|
748
763
|
"""Sets the SSL context in the client args if not set.
|
749
764
|
|
750
765
|
Does not override the SSL context if it is already set.
|
@@ -864,7 +879,7 @@ class BaseApiClient:
|
|
864
879
|
self.vertexai
|
865
880
|
and not path.startswith('projects/')
|
866
881
|
and not query_vertex_base_models
|
867
|
-
and
|
882
|
+
and (self.project or self.location)
|
868
883
|
):
|
869
884
|
path = f'projects/{self.project}/locations/{self.location}/' + path
|
870
885
|
|
@@ -920,7 +935,8 @@ class BaseApiClient:
|
|
920
935
|
stream: bool = False,
|
921
936
|
) -> HttpResponse:
|
922
937
|
data: Optional[Union[str, bytes]] = None
|
923
|
-
|
938
|
+
# If using proj/location, fetch ADC
|
939
|
+
if self.vertexai and (self.project or self.location):
|
924
940
|
http_request.headers['Authorization'] = f'Bearer {self._access_token()}'
|
925
941
|
if self._credentials and self._credentials.quota_project_id:
|
926
942
|
http_request.headers['x-goog-user-project'] = (
|
@@ -963,8 +979,21 @@ class BaseApiClient:
|
|
963
979
|
def _request(
|
964
980
|
self,
|
965
981
|
http_request: HttpRequest,
|
982
|
+
http_options: Optional[HttpOptionsOrDict] = None,
|
966
983
|
stream: bool = False,
|
967
984
|
) -> HttpResponse:
|
985
|
+
if http_options:
|
986
|
+
parameter_model = (
|
987
|
+
HttpOptions(**http_options)
|
988
|
+
if isinstance(http_options, dict)
|
989
|
+
else http_options
|
990
|
+
)
|
991
|
+
# Support per request retry options.
|
992
|
+
if parameter_model.retry_options:
|
993
|
+
retry_kwargs = _retry_args(parameter_model.retry_options)
|
994
|
+
retry = tenacity.Retrying(**retry_kwargs)
|
995
|
+
return retry(self._request_once, http_request, stream) # type: ignore[no-any-return]
|
996
|
+
|
968
997
|
return self._retry(self._request_once, http_request, stream) # type: ignore[no-any-return]
|
969
998
|
|
970
999
|
async def _async_request_once(
|
@@ -972,7 +1001,8 @@ class BaseApiClient:
|
|
972
1001
|
) -> HttpResponse:
|
973
1002
|
data: Optional[Union[str, bytes]] = None
|
974
1003
|
|
975
|
-
|
1004
|
+
# If using proj/location, fetch ADC
|
1005
|
+
if self.vertexai and (self.project or self.location):
|
976
1006
|
http_request.headers['Authorization'] = (
|
977
1007
|
f'Bearer {await self._async_access_token()}'
|
978
1008
|
)
|
@@ -993,15 +1023,43 @@ class BaseApiClient:
|
|
993
1023
|
session = aiohttp.ClientSession(
|
994
1024
|
headers=http_request.headers,
|
995
1025
|
trust_env=True,
|
1026
|
+
read_bufsize=READ_BUFFER_SIZE,
|
996
1027
|
)
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
1028
|
+
try:
|
1029
|
+
response = await session.request(
|
1030
|
+
method=http_request.method,
|
1031
|
+
url=http_request.url,
|
1032
|
+
headers=http_request.headers,
|
1033
|
+
data=data,
|
1034
|
+
timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
|
1035
|
+
**self._async_client_session_request_args,
|
1036
|
+
)
|
1037
|
+
except (
|
1038
|
+
aiohttp.ClientConnectorError,
|
1039
|
+
aiohttp.ClientConnectorDNSError,
|
1040
|
+
aiohttp.ClientOSError,
|
1041
|
+
aiohttp.ServerDisconnectedError,
|
1042
|
+
) as e:
|
1043
|
+
await asyncio.sleep(1 + random.randint(0, 9))
|
1044
|
+
logger.info('Retrying due to aiohttp error: %s' % e)
|
1045
|
+
# Retrieve the SSL context from the session.
|
1046
|
+
self._async_client_session_request_args = (
|
1047
|
+
self._ensure_aiohttp_ssl_ctx(self._http_options)
|
1048
|
+
)
|
1049
|
+
# Instantiate a new session with the updated SSL context.
|
1050
|
+
session = aiohttp.ClientSession(
|
1051
|
+
headers=http_request.headers,
|
1052
|
+
trust_env=True,
|
1053
|
+
read_bufsize=READ_BUFFER_SIZE,
|
1054
|
+
)
|
1055
|
+
response = await session.request(
|
1056
|
+
method=http_request.method,
|
1057
|
+
url=http_request.url,
|
1058
|
+
headers=http_request.headers,
|
1059
|
+
data=data,
|
1060
|
+
timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
|
1061
|
+
**self._async_client_session_request_args,
|
1062
|
+
)
|
1005
1063
|
|
1006
1064
|
await errors.APIError.raise_for_async_response(response)
|
1007
1065
|
return HttpResponse(response.headers, response, session=session)
|
@@ -1022,20 +1080,50 @@ class BaseApiClient:
|
|
1022
1080
|
return HttpResponse(client_response.headers, client_response)
|
1023
1081
|
else:
|
1024
1082
|
if self._use_aiohttp():
|
1025
|
-
|
1026
|
-
|
1027
|
-
trust_env=True,
|
1028
|
-
) as session:
|
1029
|
-
response = await session.request(
|
1030
|
-
method=http_request.method,
|
1031
|
-
url=http_request.url,
|
1083
|
+
try:
|
1084
|
+
async with aiohttp.ClientSession(
|
1032
1085
|
headers=http_request.headers,
|
1033
|
-
|
1034
|
-
|
1035
|
-
|
1086
|
+
trust_env=True,
|
1087
|
+
read_bufsize=READ_BUFFER_SIZE,
|
1088
|
+
) as session:
|
1089
|
+
response = await session.request(
|
1090
|
+
method=http_request.method,
|
1091
|
+
url=http_request.url,
|
1092
|
+
headers=http_request.headers,
|
1093
|
+
data=data,
|
1094
|
+
timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
|
1095
|
+
**self._async_client_session_request_args,
|
1096
|
+
)
|
1097
|
+
await errors.APIError.raise_for_async_response(response)
|
1098
|
+
return HttpResponse(response.headers, [await response.text()])
|
1099
|
+
except (
|
1100
|
+
aiohttp.ClientConnectorError,
|
1101
|
+
aiohttp.ClientConnectorDNSError,
|
1102
|
+
aiohttp.ClientOSError,
|
1103
|
+
aiohttp.ServerDisconnectedError,
|
1104
|
+
) as e:
|
1105
|
+
await asyncio.sleep(1 + random.randint(0, 9))
|
1106
|
+
logger.info('Retrying due to aiohttp error: %s' % e)
|
1107
|
+
# Retrieve the SSL context from the session.
|
1108
|
+
self._async_client_session_request_args = (
|
1109
|
+
self._ensure_aiohttp_ssl_ctx(self._http_options)
|
1036
1110
|
)
|
1037
|
-
|
1038
|
-
|
1111
|
+
# Instantiate a new session with the updated SSL context.
|
1112
|
+
async with aiohttp.ClientSession(
|
1113
|
+
headers=http_request.headers,
|
1114
|
+
trust_env=True,
|
1115
|
+
read_bufsize=READ_BUFFER_SIZE,
|
1116
|
+
) as session:
|
1117
|
+
response = await session.request(
|
1118
|
+
method=http_request.method,
|
1119
|
+
url=http_request.url,
|
1120
|
+
headers=http_request.headers,
|
1121
|
+
data=data,
|
1122
|
+
timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
|
1123
|
+
**self._async_client_session_request_args,
|
1124
|
+
)
|
1125
|
+
await errors.APIError.raise_for_async_response(response)
|
1126
|
+
return HttpResponse(response.headers, [await response.text()])
|
1039
1127
|
else:
|
1040
1128
|
# aiohttp is not available. Fall back to httpx.
|
1041
1129
|
client_response = await self._async_httpx_client.request(
|
@@ -1051,13 +1139,25 @@ class BaseApiClient:
|
|
1051
1139
|
async def _async_request(
|
1052
1140
|
self,
|
1053
1141
|
http_request: HttpRequest,
|
1142
|
+
http_options: Optional[HttpOptionsOrDict] = None,
|
1054
1143
|
stream: bool = False,
|
1055
1144
|
) -> HttpResponse:
|
1145
|
+
if http_options:
|
1146
|
+
parameter_model = (
|
1147
|
+
HttpOptions(**http_options)
|
1148
|
+
if isinstance(http_options, dict)
|
1149
|
+
else http_options
|
1150
|
+
)
|
1151
|
+
# Support per request retry options.
|
1152
|
+
if parameter_model.retry_options:
|
1153
|
+
retry_kwargs = _retry_args(parameter_model.retry_options)
|
1154
|
+
retry = tenacity.AsyncRetrying(**retry_kwargs)
|
1155
|
+
return await retry(self._async_request_once, http_request, stream) # type: ignore[no-any-return]
|
1056
1156
|
return await self._async_retry( # type: ignore[no-any-return]
|
1057
1157
|
self._async_request_once, http_request, stream
|
1058
1158
|
)
|
1059
1159
|
|
1060
|
-
def get_read_only_http_options(self) ->
|
1160
|
+
def get_read_only_http_options(self) -> _common.StringDict:
|
1061
1161
|
if isinstance(self._http_options, BaseModel):
|
1062
1162
|
copied = self._http_options.model_dump()
|
1063
1163
|
else:
|
@@ -1074,7 +1174,7 @@ class BaseApiClient:
|
|
1074
1174
|
http_request = self._build_request(
|
1075
1175
|
http_method, path, request_dict, http_options
|
1076
1176
|
)
|
1077
|
-
response = self._request(http_request, stream=False)
|
1177
|
+
response = self._request(http_request, http_options, stream=False)
|
1078
1178
|
response_body = (
|
1079
1179
|
response.response_stream[0] if response.response_stream else ''
|
1080
1180
|
)
|
@@ -1091,7 +1191,7 @@ class BaseApiClient:
|
|
1091
1191
|
http_method, path, request_dict, http_options
|
1092
1192
|
)
|
1093
1193
|
|
1094
|
-
session_response = self._request(http_request, stream=True)
|
1194
|
+
session_response = self._request(http_request, http_options, stream=True)
|
1095
1195
|
for chunk in session_response.segments():
|
1096
1196
|
yield SdkHttpResponse(
|
1097
1197
|
headers=session_response.headers, body=json.dumps(chunk)
|
@@ -1108,7 +1208,9 @@ class BaseApiClient:
|
|
1108
1208
|
http_method, path, request_dict, http_options
|
1109
1209
|
)
|
1110
1210
|
|
1111
|
-
result = await self._async_request(
|
1211
|
+
result = await self._async_request(
|
1212
|
+
http_request=http_request, http_options=http_options, stream=False
|
1213
|
+
)
|
1112
1214
|
response_body = result.response_stream[0] if result.response_stream else ''
|
1113
1215
|
return SdkHttpResponse(headers=result.headers, body=response_body)
|
1114
1216
|
|
@@ -1340,6 +1442,7 @@ class BaseApiClient:
|
|
1340
1442
|
async with aiohttp.ClientSession(
|
1341
1443
|
headers=self._http_options.headers,
|
1342
1444
|
trust_env=True,
|
1445
|
+
read_bufsize=READ_BUFFER_SIZE,
|
1343
1446
|
) as session:
|
1344
1447
|
while True:
|
1345
1448
|
if isinstance(file, io.IOBase):
|
@@ -1523,6 +1626,7 @@ class BaseApiClient:
|
|
1523
1626
|
async with aiohttp.ClientSession(
|
1524
1627
|
headers=http_request.headers,
|
1525
1628
|
trust_env=True,
|
1629
|
+
read_bufsize=READ_BUFFER_SIZE,
|
1526
1630
|
) as session:
|
1527
1631
|
response = await session.request(
|
1528
1632
|
method=http_request.method,
|
@@ -22,15 +22,17 @@ import enum
|
|
22
22
|
import functools
|
23
23
|
import logging
|
24
24
|
import typing
|
25
|
-
from typing import Any, Callable,
|
25
|
+
from typing import Any, Callable, FrozenSet, Optional, Union, get_args, get_origin
|
26
26
|
import uuid
|
27
27
|
import warnings
|
28
|
-
|
29
28
|
import pydantic
|
30
29
|
from pydantic import alias_generators
|
30
|
+
from typing_extensions import TypeAlias
|
31
31
|
|
32
32
|
logger = logging.getLogger('google_genai._common')
|
33
33
|
|
34
|
+
StringDict: TypeAlias = dict[str, Any]
|
35
|
+
|
34
36
|
|
35
37
|
class ExperimentalWarning(Warning):
|
36
38
|
"""Warning for experimental features."""
|
@@ -355,7 +357,6 @@ def _pretty_repr(
|
|
355
357
|
return raw_repr.replace('\n', f'\n{next_indent_str}')
|
356
358
|
|
357
359
|
|
358
|
-
|
359
360
|
def _format_collection(
|
360
361
|
obj: Any,
|
361
362
|
*,
|
@@ -555,7 +556,9 @@ def _normalize_key_for_matching(key_str: str) -> str:
|
|
555
556
|
return key_str.replace("_", "").lower()
|
556
557
|
|
557
558
|
|
558
|
-
def align_key_case(
|
559
|
+
def align_key_case(
|
560
|
+
target_dict: StringDict, update_dict: StringDict
|
561
|
+
) -> StringDict:
|
559
562
|
"""Aligns the keys of update_dict to the case of target_dict keys.
|
560
563
|
|
561
564
|
Args:
|
@@ -565,7 +568,7 @@ def align_key_case(target_dict: dict[str, Any], update_dict: dict[str, Any]) ->
|
|
565
568
|
Returns:
|
566
569
|
A new dictionary with keys aligned to target_dict's key casing.
|
567
570
|
"""
|
568
|
-
aligned_update_dict:
|
571
|
+
aligned_update_dict: StringDict = {}
|
569
572
|
target_keys_map = {_normalize_key_for_matching(key): key for key in target_dict.keys()}
|
570
573
|
|
571
574
|
for key, value in update_dict.items():
|
@@ -587,7 +590,7 @@ def align_key_case(target_dict: dict[str, Any], update_dict: dict[str, Any]) ->
|
|
587
590
|
|
588
591
|
|
589
592
|
def recursive_dict_update(
|
590
|
-
target_dict:
|
593
|
+
target_dict: StringDict, update_dict: StringDict
|
591
594
|
) -> None:
|
592
595
|
"""Recursively updates a target dictionary with values from an update dictionary.
|
593
596
|
|
@@ -155,8 +155,8 @@ def get_function_map(
|
|
155
155
|
|
156
156
|
|
157
157
|
def convert_number_values_for_dict_function_call_args(
|
158
|
-
args:
|
159
|
-
) ->
|
158
|
+
args: _common.StringDict,
|
159
|
+
) -> _common.StringDict:
|
160
160
|
"""Converts float values in dict with no decimal to integers."""
|
161
161
|
return {
|
162
162
|
key: convert_number_values_for_function_call_args(value)
|
@@ -257,8 +257,8 @@ def convert_if_exist_pydantic_model(
|
|
257
257
|
|
258
258
|
|
259
259
|
def convert_argument_from_function(
|
260
|
-
args:
|
261
|
-
) ->
|
260
|
+
args: _common.StringDict, function: Callable[..., Any]
|
261
|
+
) -> _common.StringDict:
|
262
262
|
signature = inspect.signature(function)
|
263
263
|
func_name = function.__name__
|
264
264
|
converted_args = {}
|
@@ -274,7 +274,7 @@ def convert_argument_from_function(
|
|
274
274
|
|
275
275
|
|
276
276
|
def invoke_function_from_dict_args(
|
277
|
-
args:
|
277
|
+
args: _common.StringDict, function_to_invoke: Callable[..., Any]
|
278
278
|
) -> Any:
|
279
279
|
converted_args = convert_argument_from_function(args, function_to_invoke)
|
280
280
|
try:
|
@@ -288,7 +288,7 @@ def invoke_function_from_dict_args(
|
|
288
288
|
|
289
289
|
|
290
290
|
async def invoke_function_from_dict_args_async(
|
291
|
-
args:
|
291
|
+
args: _common.StringDict, function_to_invoke: Callable[..., Any]
|
292
292
|
) -> Any:
|
293
293
|
converted_args = convert_argument_from_function(args, function_to_invoke)
|
294
294
|
try:
|
@@ -321,7 +321,7 @@ def get_function_response_parts(
|
|
321
321
|
args = convert_number_values_for_dict_function_call_args(
|
322
322
|
part.function_call.args
|
323
323
|
)
|
324
|
-
func_response:
|
324
|
+
func_response: _common.StringDict
|
325
325
|
try:
|
326
326
|
if not isinstance(func, McpToGenAiToolAdapter):
|
327
327
|
func_response = {
|
@@ -356,7 +356,7 @@ async def get_function_response_parts_async(
|
|
356
356
|
args = convert_number_values_for_dict_function_call_args(
|
357
357
|
part.function_call.args
|
358
358
|
)
|
359
|
-
func_response:
|
359
|
+
func_response: _common.StringDict
|
360
360
|
try:
|
361
361
|
if isinstance(func, McpToGenAiToolAdapter):
|
362
362
|
mcp_tool_response = await func.call_tool(
|
@@ -19,6 +19,7 @@ from importlib.metadata import PackageNotFoundError, version
|
|
19
19
|
import typing
|
20
20
|
from typing import Any
|
21
21
|
|
22
|
+
from . import _common
|
22
23
|
from . import types
|
23
24
|
|
24
25
|
if typing.TYPE_CHECKING:
|
@@ -89,7 +90,9 @@ def set_mcp_usage_header(headers: dict[str, str]) -> None:
|
|
89
90
|
).lstrip()
|
90
91
|
|
91
92
|
|
92
|
-
def _filter_to_supported_schema(
|
93
|
+
def _filter_to_supported_schema(
|
94
|
+
schema: _common.StringDict,
|
95
|
+
) -> _common.StringDict:
|
93
96
|
"""Filters the schema to only include fields that are supported by JSONSchema."""
|
94
97
|
supported_fields: set[str] = set(types.JSONSchema.model_fields.keys())
|
95
98
|
schema_field_names: tuple[str] = ("items",) # 'additional_properties' to come
|
@@ -479,13 +479,14 @@ class ReplayApiClient(BaseApiClient):
|
|
479
479
|
def _request(
|
480
480
|
self,
|
481
481
|
http_request: HttpRequest,
|
482
|
+
http_options: Optional[HttpOptionsOrDict] = None,
|
482
483
|
stream: bool = False,
|
483
484
|
) -> HttpResponse:
|
484
485
|
self._initialize_replay_session_if_not_loaded()
|
485
486
|
if self._should_call_api():
|
486
487
|
_debug_print('api mode request: %s' % http_request)
|
487
488
|
try:
|
488
|
-
result = super()._request(http_request, stream)
|
489
|
+
result = super()._request(http_request, http_options, stream)
|
489
490
|
except errors.APIError as e:
|
490
491
|
self._record_interaction(http_request, e)
|
491
492
|
raise e
|
@@ -507,13 +508,16 @@ class ReplayApiClient(BaseApiClient):
|
|
507
508
|
async def _async_request(
|
508
509
|
self,
|
509
510
|
http_request: HttpRequest,
|
511
|
+
http_options: Optional[HttpOptionsOrDict] = None,
|
510
512
|
stream: bool = False,
|
511
513
|
) -> HttpResponse:
|
512
514
|
self._initialize_replay_session_if_not_loaded()
|
513
515
|
if self._should_call_api():
|
514
516
|
_debug_print('api mode request: %s' % http_request)
|
515
517
|
try:
|
516
|
-
result = await super()._async_request(
|
518
|
+
result = await super()._async_request(
|
519
|
+
http_request, http_options, stream
|
520
|
+
)
|
517
521
|
except errors.APIError as e:
|
518
522
|
self._record_interaction(http_request, e)
|
519
523
|
raise e
|
@@ -35,6 +35,7 @@ if typing.TYPE_CHECKING:
|
|
35
35
|
import pydantic
|
36
36
|
|
37
37
|
from . import _api_client
|
38
|
+
from . import _common
|
38
39
|
from . import types
|
39
40
|
|
40
41
|
logger = logging.getLogger('google_genai._transformers')
|
@@ -195,20 +196,20 @@ def t_models_url(
|
|
195
196
|
|
196
197
|
|
197
198
|
def t_extract_models(
|
198
|
-
response:
|
199
|
-
) -> list[
|
199
|
+
response: _common.StringDict,
|
200
|
+
) -> list[_common.StringDict]:
|
200
201
|
if not response:
|
201
202
|
return []
|
202
203
|
|
203
|
-
models: Optional[list[
|
204
|
+
models: Optional[list[_common.StringDict]] = response.get('models')
|
204
205
|
if models is not None:
|
205
206
|
return models
|
206
207
|
|
207
|
-
tuned_models: Optional[list[
|
208
|
+
tuned_models: Optional[list[_common.StringDict]] = response.get('tunedModels')
|
208
209
|
if tuned_models is not None:
|
209
210
|
return tuned_models
|
210
211
|
|
211
|
-
publisher_models: Optional[list[
|
212
|
+
publisher_models: Optional[list[_common.StringDict]] = response.get(
|
212
213
|
'publisherModels'
|
213
214
|
)
|
214
215
|
if publisher_models is not None:
|
@@ -560,7 +561,7 @@ def t_contents(
|
|
560
561
|
return result
|
561
562
|
|
562
563
|
|
563
|
-
def handle_null_fields(schema:
|
564
|
+
def handle_null_fields(schema: _common.StringDict) -> None:
|
564
565
|
"""Process null fields in the schema so it is compatible with OpenAPI.
|
565
566
|
|
566
567
|
The OpenAPI spec does not support 'type: 'null' in the schema. This function
|
@@ -639,9 +640,9 @@ def _raise_for_unsupported_mldev_properties(
|
|
639
640
|
|
640
641
|
|
641
642
|
def process_schema(
|
642
|
-
schema:
|
643
|
+
schema: _common.StringDict,
|
643
644
|
client: Optional[_api_client.BaseApiClient],
|
644
|
-
defs: Optional[
|
645
|
+
defs: Optional[_common.StringDict] = None,
|
645
646
|
*,
|
646
647
|
order_properties: bool = True,
|
647
648
|
) -> None:
|
@@ -740,7 +741,7 @@ def process_schema(
|
|
740
741
|
if (ref := schema.pop('$ref', None)) is not None:
|
741
742
|
schema.update(defs[ref.split('defs/')[-1]])
|
742
743
|
|
743
|
-
def _recurse(sub_schema:
|
744
|
+
def _recurse(sub_schema: _common.StringDict) -> _common.StringDict:
|
744
745
|
"""Returns the processed `sub_schema`, resolving its '$ref' if any."""
|
745
746
|
if (ref := sub_schema.pop('$ref', None)) is not None:
|
746
747
|
sub_schema = defs[ref.split('defs/')[-1]]
|
@@ -820,7 +821,7 @@ def _process_enum(
|
|
820
821
|
|
821
822
|
def _is_type_dict_str_any(
|
822
823
|
origin: Union[types.SchemaUnionDict, Any],
|
823
|
-
) -> TypeGuard[
|
824
|
+
) -> TypeGuard[_common.StringDict]:
|
824
825
|
"""Verifies the schema is of type dict[str, Any] for mypy type checking."""
|
825
826
|
return isinstance(origin, dict) and all(
|
826
827
|
isinstance(key, str) for key in origin
|
@@ -1075,10 +1076,10 @@ LRO_POLLING_MULTIPLIER = 1.5
|
|
1075
1076
|
|
1076
1077
|
|
1077
1078
|
def t_resolve_operation(
|
1078
|
-
api_client: _api_client.BaseApiClient, struct:
|
1079
|
+
api_client: _api_client.BaseApiClient, struct: _common.StringDict
|
1079
1080
|
) -> Any:
|
1080
1081
|
if (name := struct.get('name')) and '/operations/' in name:
|
1081
|
-
operation:
|
1082
|
+
operation: _common.StringDict = struct
|
1082
1083
|
total_seconds = 0.0
|
1083
1084
|
delay_seconds = LRO_POLLING_INITIAL_DELAY_SECONDS
|
1084
1085
|
while operation.get('done') != True:
|
@@ -65,7 +65,7 @@ class APIError(Exception):
|
|
65
65
|
'code', response_json.get('error', {}).get('code', None)
|
66
66
|
)
|
67
67
|
|
68
|
-
def _to_replay_record(self) ->
|
68
|
+
def _to_replay_record(self) -> _common.StringDict:
|
69
69
|
"""Returns a dictionary representation of the error for replay recording.
|
70
70
|
|
71
71
|
details is not included since it may expose internal information in the
|
@@ -33,7 +33,6 @@ from . import _common
|
|
33
33
|
from . import _live_converters as live_converters
|
34
34
|
from . import _mcp_utils
|
35
35
|
from . import _transformers as t
|
36
|
-
from . import client
|
37
36
|
from . import errors
|
38
37
|
from . import types
|
39
38
|
from ._api_client import BaseApiClient
|
@@ -288,7 +287,7 @@ class AsyncSession:
|
|
288
287
|
print(f'{msg.text}')
|
289
288
|
```
|
290
289
|
"""
|
291
|
-
kwargs:
|
290
|
+
kwargs: _common.StringDict = {}
|
292
291
|
if media is not None:
|
293
292
|
kwargs['media'] = media
|
294
293
|
if audio is not None:
|
@@ -639,7 +638,7 @@ class AsyncSession:
|
|
639
638
|
elif isinstance(formatted_input, Sequence) and any(
|
640
639
|
isinstance(c, str) for c in formatted_input
|
641
640
|
):
|
642
|
-
to_object:
|
641
|
+
to_object: _common.StringDict = {}
|
643
642
|
content_input_parts: list[types.PartUnion] = []
|
644
643
|
for item in formatted_input:
|
645
644
|
if isinstance(item, get_args(types.PartUnion)):
|