google-genai 1.6.0__py3-none-any.whl → 1.8.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 +229 -185
- google/genai/_common.py +7 -5
- google/genai/_replay_api_client.py +28 -17
- google/genai/_transformers.py +23 -14
- google/genai/batches.py +60 -294
- google/genai/caches.py +545 -525
- google/genai/chats.py +149 -51
- google/genai/client.py +5 -3
- google/genai/errors.py +46 -23
- google/genai/files.py +90 -306
- google/genai/live.py +4 -4
- google/genai/models.py +2124 -2311
- google/genai/operations.py +103 -123
- google/genai/tunings.py +255 -271
- google/genai/types.py +207 -74
- google/genai/version.py +1 -1
- {google_genai-1.6.0.dist-info → google_genai-1.8.0.dist-info}/METADATA +11 -9
- google_genai-1.8.0.dist-info/RECORD +27 -0
- {google_genai-1.6.0.dist-info → google_genai-1.8.0.dist-info}/WHEEL +1 -1
- google_genai-1.6.0.dist-info/RECORD +0 -27
- {google_genai-1.6.0.dist-info → google_genai-1.8.0.dist-info/licenses}/LICENSE +0 -0
- {google_genai-1.6.0.dist-info → google_genai-1.8.0.dist-info}/top_level.txt +0 -0
google/genai/_common.py
CHANGED
@@ -20,7 +20,7 @@ import datetime
|
|
20
20
|
import enum
|
21
21
|
import functools
|
22
22
|
import typing
|
23
|
-
from typing import Union
|
23
|
+
from typing import Any, Union
|
24
24
|
import uuid
|
25
25
|
import warnings
|
26
26
|
|
@@ -93,7 +93,7 @@ def set_value_by_path(data, keys, value):
|
|
93
93
|
data[keys[-1]] = value
|
94
94
|
|
95
95
|
|
96
|
-
def get_value_by_path(data:
|
96
|
+
def get_value_by_path(data: Any, keys: list[str]):
|
97
97
|
"""Examples:
|
98
98
|
|
99
99
|
get_value_by_path({'a': {'b': v}}, ['a', 'b'])
|
@@ -128,7 +128,7 @@ def get_value_by_path(data: object, keys: list[str]):
|
|
128
128
|
return data
|
129
129
|
|
130
130
|
|
131
|
-
def convert_to_dict(obj:
|
131
|
+
def convert_to_dict(obj: object) -> Any:
|
132
132
|
"""Recursively converts a given object to a dictionary.
|
133
133
|
|
134
134
|
If the object is a Pydantic model, it uses the model's `model_dump()` method.
|
@@ -137,7 +137,9 @@ def convert_to_dict(obj: dict[str, object]) -> dict[str, object]:
|
|
137
137
|
obj: The object to convert.
|
138
138
|
|
139
139
|
Returns:
|
140
|
-
A dictionary representation of the object
|
140
|
+
A dictionary representation of the object, a list of objects if a list is
|
141
|
+
passed, or the object itself if it is not a dictionary, list, or Pydantic
|
142
|
+
model.
|
141
143
|
"""
|
142
144
|
if isinstance(obj, pydantic.BaseModel):
|
143
145
|
return obj.model_dump(exclude_none=True)
|
@@ -150,7 +152,7 @@ def convert_to_dict(obj: dict[str, object]) -> dict[str, object]:
|
|
150
152
|
|
151
153
|
|
152
154
|
def _remove_extra_fields(
|
153
|
-
model:
|
155
|
+
model: Any, response: dict[str, object]
|
154
156
|
) -> None:
|
155
157
|
"""Removes extra fields from the response that are not in the model.
|
156
158
|
|
@@ -119,7 +119,8 @@ def _redact_request_body(body: dict[str, object]):
|
|
119
119
|
def redact_http_request(http_request: HttpRequest):
|
120
120
|
http_request.headers = _redact_request_headers(http_request.headers)
|
121
121
|
http_request.url = _redact_request_url(http_request.url)
|
122
|
-
|
122
|
+
if not isinstance(http_request.data, bytes):
|
123
|
+
_redact_request_body(http_request.data)
|
123
124
|
|
124
125
|
|
125
126
|
def _current_file_path_and_line():
|
@@ -321,6 +322,8 @@ class ReplayApiClient(BaseApiClient):
|
|
321
322
|
raise ValueError(
|
322
323
|
'Unsupported http_response type: ' + str(type(http_response))
|
323
324
|
)
|
325
|
+
if self.replay_session is None:
|
326
|
+
raise ValueError('No replay session found.')
|
324
327
|
self.replay_session.interactions.append(
|
325
328
|
ReplayInteraction(request=request, response=response)
|
326
329
|
)
|
@@ -342,7 +345,8 @@ class ReplayApiClient(BaseApiClient):
|
|
342
345
|
request_data_copy = copy.deepcopy(http_request.data)
|
343
346
|
# Both the request and recorded request must be redacted before comparing
|
344
347
|
# so that the comparison is fair.
|
345
|
-
|
348
|
+
if not isinstance(request_data_copy, bytes):
|
349
|
+
_redact_request_body(request_data_copy)
|
346
350
|
|
347
351
|
actual_request_body = [request_data_copy]
|
348
352
|
expected_request_body = interaction.request.body_segments
|
@@ -352,9 +356,11 @@ class ReplayApiClient(BaseApiClient):
|
|
352
356
|
f'Expected: {expected_request_body}'
|
353
357
|
)
|
354
358
|
|
355
|
-
def _build_response_from_replay(self, http_request: HttpRequest):
|
359
|
+
def _build_response_from_replay(self, http_request: HttpRequest) -> HttpResponse:
|
356
360
|
redact_http_request(http_request)
|
357
361
|
|
362
|
+
if self.replay_session is None:
|
363
|
+
raise ValueError('No replay session found.')
|
358
364
|
interaction = self.replay_session.interactions[self._replay_index]
|
359
365
|
# Replay is on the right side of the assert so the diff makes more sense.
|
360
366
|
self._match_request(http_request, interaction)
|
@@ -373,6 +379,8 @@ class ReplayApiClient(BaseApiClient):
|
|
373
379
|
def _verify_response(self, response_model: BaseModel):
|
374
380
|
if self._mode == 'api':
|
375
381
|
return
|
382
|
+
if not self.replay_session:
|
383
|
+
raise ValueError('No replay session found.')
|
376
384
|
# replay_index is advanced in _build_response_from_replay, so we need to -1.
|
377
385
|
interaction = self.replay_session.interactions[self._replay_index - 1]
|
378
386
|
if self._should_update_replay():
|
@@ -453,7 +461,7 @@ class ReplayApiClient(BaseApiClient):
|
|
453
461
|
else:
|
454
462
|
return self._build_response_from_replay(http_request)
|
455
463
|
|
456
|
-
def upload_file(self, file_path: Union[str, io.IOBase], upload_url: str, upload_size: int):
|
464
|
+
def upload_file(self, file_path: Union[str, io.IOBase], upload_url: str, upload_size: int) -> HttpResponse:
|
457
465
|
if isinstance(file_path, io.IOBase):
|
458
466
|
offset = file_path.tell()
|
459
467
|
content = file_path.read()
|
@@ -474,21 +482,21 @@ class ReplayApiClient(BaseApiClient):
|
|
474
482
|
result = super().upload_file(file_path, upload_url, upload_size)
|
475
483
|
except HTTPError as e:
|
476
484
|
result = HttpResponse(
|
477
|
-
e.response.headers, [json.dumps({'reason': e.response.reason})]
|
485
|
+
dict(e.response.headers), [json.dumps({'reason': e.response.reason})]
|
478
486
|
)
|
479
487
|
result.status_code = e.response.status_code
|
480
488
|
raise e
|
481
|
-
self._record_interaction(request,
|
489
|
+
self._record_interaction(request, result)
|
482
490
|
return result
|
483
491
|
else:
|
484
|
-
return self._build_response_from_replay(request)
|
492
|
+
return self._build_response_from_replay(request)
|
485
493
|
|
486
494
|
async def async_upload_file(
|
487
495
|
self,
|
488
496
|
file_path: Union[str, io.IOBase],
|
489
497
|
upload_url: str,
|
490
498
|
upload_size: int,
|
491
|
-
) ->
|
499
|
+
) -> HttpResponse:
|
492
500
|
if isinstance(file_path, io.IOBase):
|
493
501
|
offset = file_path.tell()
|
494
502
|
content = file_path.read()
|
@@ -504,37 +512,40 @@ class ReplayApiClient(BaseApiClient):
|
|
504
512
|
method='POST', url='', data={'file_path': file_path}, headers={}
|
505
513
|
)
|
506
514
|
if self._should_call_api():
|
507
|
-
result:
|
515
|
+
result: HttpResponse
|
508
516
|
try:
|
509
517
|
result = await super().async_upload_file(
|
510
518
|
file_path, upload_url, upload_size
|
511
519
|
)
|
512
520
|
except HTTPError as e:
|
513
521
|
result = HttpResponse(
|
514
|
-
e.response.headers, [json.dumps({'reason': e.response.reason})]
|
522
|
+
dict(e.response.headers), [json.dumps({'reason': e.response.reason})]
|
515
523
|
)
|
516
524
|
result.status_code = e.response.status_code
|
517
525
|
raise e
|
518
|
-
self._record_interaction(request,
|
526
|
+
self._record_interaction(request, result)
|
519
527
|
return result
|
520
528
|
else:
|
521
|
-
return self._build_response_from_replay(request)
|
529
|
+
return self._build_response_from_replay(request)
|
522
530
|
|
523
|
-
def
|
531
|
+
def download_file(self, path: str, http_options: HttpOptions):
|
524
532
|
self._initialize_replay_session_if_not_loaded()
|
533
|
+
request = self._build_request(
|
534
|
+
'get', path=path, request_dict={}, http_options=http_options
|
535
|
+
)
|
525
536
|
if self._should_call_api():
|
526
537
|
try:
|
527
|
-
result = super().
|
538
|
+
result = super().download_file(path, http_options)
|
528
539
|
except HTTPError as e:
|
529
540
|
result = HttpResponse(
|
530
|
-
e.response.headers, [json.dumps({'reason': e.response.reason})]
|
541
|
+
dict(e.response.headers), [json.dumps({'reason': e.response.reason})]
|
531
542
|
)
|
532
543
|
result.status_code = e.response.status_code
|
533
544
|
raise e
|
534
545
|
self._record_interaction(request, result)
|
535
546
|
return result
|
536
547
|
else:
|
537
|
-
return self._build_response_from_replay(request)
|
548
|
+
return self._build_response_from_replay(request).byte_stream[0]
|
538
549
|
|
539
550
|
async def async_download_file(self, path: str, http_options):
|
540
551
|
self._initialize_replay_session_if_not_loaded()
|
@@ -546,7 +557,7 @@ class ReplayApiClient(BaseApiClient):
|
|
546
557
|
result = await super().async_download_file(path, http_options)
|
547
558
|
except HTTPError as e:
|
548
559
|
result = HttpResponse(
|
549
|
-
e.response.headers, [json.dumps({'reason': e.response.reason})]
|
560
|
+
dict(e.response.headers), [json.dumps({'reason': e.response.reason})]
|
550
561
|
)
|
551
562
|
result.status_code = e.response.status_code
|
552
563
|
raise e
|
google/genai/_transformers.py
CHANGED
@@ -181,17 +181,26 @@ def t_models_url(
|
|
181
181
|
|
182
182
|
def t_extract_models(
|
183
183
|
api_client: _api_client.BaseApiClient,
|
184
|
-
response: dict[str,
|
185
|
-
) ->
|
184
|
+
response: dict[str, Any],
|
185
|
+
) -> list[dict[str, Any]]:
|
186
186
|
if not response:
|
187
187
|
return []
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
return
|
192
|
-
|
193
|
-
|
194
|
-
|
188
|
+
|
189
|
+
models: Optional[list[dict[str, Any]]] = response.get('models')
|
190
|
+
if models is not None:
|
191
|
+
return models
|
192
|
+
|
193
|
+
tuned_models: Optional[list[dict[str, Any]]] = response.get('tunedModels')
|
194
|
+
if tuned_models is not None:
|
195
|
+
return tuned_models
|
196
|
+
|
197
|
+
publisher_models: Optional[list[dict[str, Any]]] = response.get(
|
198
|
+
'publisherModels'
|
199
|
+
)
|
200
|
+
if publisher_models is not None:
|
201
|
+
return publisher_models
|
202
|
+
|
203
|
+
if (
|
195
204
|
response.get('httpHeaders') is not None
|
196
205
|
and response.get('jsonPayload') is None
|
197
206
|
):
|
@@ -526,7 +535,6 @@ def process_schema(
|
|
526
535
|
):
|
527
536
|
"""Updates the schema and each sub-schema inplace to be API-compatible.
|
528
537
|
|
529
|
-
- Removes the `title` field from the schema if the client is not vertexai.
|
530
538
|
- Inlines the $defs.
|
531
539
|
|
532
540
|
Example of a schema before and after (with mldev):
|
@@ -570,21 +578,22 @@ def process_schema(
|
|
570
578
|
'items': {
|
571
579
|
'properties': {
|
572
580
|
'continent': {
|
573
|
-
|
581
|
+
'title': 'Continent',
|
582
|
+
'type': 'string'
|
574
583
|
},
|
575
584
|
'gdp': {
|
576
|
-
|
585
|
+
'title': 'Gdp',
|
586
|
+
'type': 'integer'
|
577
587
|
},
|
578
588
|
}
|
579
589
|
'required':['continent', 'gdp'],
|
590
|
+
'title': 'CountryInfo',
|
580
591
|
'type': 'object'
|
581
592
|
},
|
582
593
|
'type': 'array'
|
583
594
|
}
|
584
595
|
"""
|
585
596
|
if not client.vertexai:
|
586
|
-
schema.pop('title', None)
|
587
|
-
|
588
597
|
if schema.get('default') is not None:
|
589
598
|
raise ValueError(
|
590
599
|
'Default value is not supported in the response schema for the Gemini'
|