google-genai 1.16.1__tar.gz → 1.18.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.16.1/google_genai.egg-info → google_genai-1.18.0}/PKG-INFO +2 -2
- {google_genai-1.16.1 → google_genai-1.18.0}/README.md +1 -1
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/__init__.py +1 -2
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_api_client.py +10 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_automatic_function_calling_util.py +1 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_common.py +36 -1
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_live_converters.py +134 -31
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_mcp_utils.py +3 -6
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_replay_api_client.py +51 -1
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_tokens_converters.py +75 -13
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_transformers.py +22 -3
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/caches.py +74 -6
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/chats.py +6 -3
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/live.py +39 -101
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/models.py +349 -17
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/tunings.py +6 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/types.py +333 -162
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/version.py +1 -1
- {google_genai-1.16.1 → google_genai-1.18.0/google_genai.egg-info}/PKG-INFO +2 -2
- {google_genai-1.16.1 → google_genai-1.18.0}/pyproject.toml +1 -1
- {google_genai-1.16.1 → google_genai-1.18.0}/LICENSE +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/MANIFEST.in +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_adapters.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_api_module.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_base_url.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_extra_utils.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/_test_api_client.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/batches.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/client.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/errors.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/files.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/live_music.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/operations.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/pagers.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/py.typed +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google/genai/tokens.py +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google_genai.egg-info/SOURCES.txt +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google_genai.egg-info/dependency_links.txt +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google_genai.egg-info/requires.txt +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/google_genai.egg-info/top_level.txt +0 -0
- {google_genai-1.16.1 → google_genai-1.18.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: google-genai
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.18.0
|
4
4
|
Summary: GenAI Python SDK
|
5
5
|
Author-email: Google LLC <googleapis-packages@google.com>
|
6
6
|
License: Apache-2.0
|
@@ -148,7 +148,7 @@ Pydantic model types are available in the `types` module.
|
|
148
148
|
|
149
149
|
## Models
|
150
150
|
|
151
|
-
The `client.models`
|
151
|
+
The `client.models` module exposes model inferencing and model getters.
|
152
152
|
See the 'Create a client' section above to initialize a client.
|
153
153
|
|
154
154
|
### Generate Content
|
@@ -117,7 +117,7 @@ Pydantic model types are available in the `types` module.
|
|
117
117
|
|
118
118
|
## Models
|
119
119
|
|
120
|
-
The `client.models`
|
120
|
+
The `client.models` module exposes model inferencing and model getters.
|
121
121
|
See the 'Create a client' section above to initialize a client.
|
122
122
|
|
123
123
|
### Generate Content
|
@@ -63,6 +63,11 @@ MAX_RETRY_COUNT = 3
|
|
63
63
|
INITIAL_RETRY_DELAY = 1 # second
|
64
64
|
DELAY_MULTIPLIER = 2
|
65
65
|
|
66
|
+
|
67
|
+
class EphemeralTokenAPIKeyError(ValueError):
|
68
|
+
"""Error raised when the API key is invalid."""
|
69
|
+
|
70
|
+
|
66
71
|
def _append_library_version_headers(headers: dict[str, str]) -> None:
|
67
72
|
"""Appends the telemetry header to the headers dict."""
|
68
73
|
library_label = f'google-genai-sdk/{version.__version__}'
|
@@ -625,6 +630,11 @@ class BaseApiClient:
|
|
625
630
|
versioned_path,
|
626
631
|
)
|
627
632
|
|
633
|
+
if self.api_key and self.api_key.startswith('auth_tokens/'):
|
634
|
+
raise EphemeralTokenAPIKeyError(
|
635
|
+
'Ephemeral tokens can only be used with the live API.'
|
636
|
+
)
|
637
|
+
|
628
638
|
timeout_in_seconds = _get_timeout_in_seconds(patched_http_options.timeout)
|
629
639
|
|
630
640
|
if patched_http_options.headers is None:
|
@@ -20,7 +20,7 @@ import datetime
|
|
20
20
|
import enum
|
21
21
|
import functools
|
22
22
|
import typing
|
23
|
-
from typing import Any, Callable, Optional, Union
|
23
|
+
from typing import Any, Callable, Optional, Union, get_origin, get_args
|
24
24
|
import uuid
|
25
25
|
import warnings
|
26
26
|
|
@@ -154,6 +154,38 @@ def convert_to_dict(obj: object) -> Any:
|
|
154
154
|
return obj
|
155
155
|
|
156
156
|
|
157
|
+
def _is_struct_type(annotation: type) -> bool:
|
158
|
+
"""Checks if the given annotation is list[dict[str, typing.Any]]
|
159
|
+
or typing.List[typing.Dict[str, typing.Any]].
|
160
|
+
|
161
|
+
This maps to Struct type in the API.
|
162
|
+
"""
|
163
|
+
outer_origin = get_origin(annotation)
|
164
|
+
outer_args = get_args(annotation)
|
165
|
+
|
166
|
+
if outer_origin is not list: # Python 3.9+ normalizes list
|
167
|
+
return False
|
168
|
+
|
169
|
+
if not outer_args or len(outer_args) != 1:
|
170
|
+
return False
|
171
|
+
|
172
|
+
inner_annotation = outer_args[0]
|
173
|
+
|
174
|
+
inner_origin = get_origin(inner_annotation)
|
175
|
+
inner_args = get_args(inner_annotation)
|
176
|
+
|
177
|
+
if inner_origin is not dict: # Python 3.9+ normalizes to dict
|
178
|
+
return False
|
179
|
+
|
180
|
+
if not inner_args or len(inner_args) != 2:
|
181
|
+
# dict should have exactly two type arguments
|
182
|
+
return False
|
183
|
+
|
184
|
+
# Check if the dict arguments are str and typing.Any
|
185
|
+
key_type, value_type = inner_args
|
186
|
+
return key_type is str and value_type is typing.Any
|
187
|
+
|
188
|
+
|
157
189
|
def _remove_extra_fields(
|
158
190
|
model: Any, response: dict[str, object]
|
159
191
|
) -> None:
|
@@ -188,6 +220,9 @@ def _remove_extra_fields(
|
|
188
220
|
if isinstance(value, dict) and typing.get_origin(annotation) is not dict:
|
189
221
|
_remove_extra_fields(annotation, value)
|
190
222
|
elif isinstance(value, list):
|
223
|
+
if _is_struct_type(annotation):
|
224
|
+
continue
|
225
|
+
|
191
226
|
for item in value:
|
192
227
|
# assume a list of dict is list of BaseModel
|
193
228
|
if isinstance(item, dict):
|
@@ -281,6 +281,42 @@ def _Blob_to_vertex(
|
|
281
281
|
return to_object
|
282
282
|
|
283
283
|
|
284
|
+
def _FileData_to_mldev(
|
285
|
+
api_client: BaseApiClient,
|
286
|
+
from_object: Union[dict[str, Any], object],
|
287
|
+
parent_object: Optional[dict[str, Any]] = None,
|
288
|
+
) -> dict[str, Any]:
|
289
|
+
to_object: dict[str, Any] = {}
|
290
|
+
if getv(from_object, ['display_name']) is not None:
|
291
|
+
raise ValueError('display_name parameter is not supported in Gemini API.')
|
292
|
+
|
293
|
+
if getv(from_object, ['file_uri']) is not None:
|
294
|
+
setv(to_object, ['fileUri'], getv(from_object, ['file_uri']))
|
295
|
+
|
296
|
+
if getv(from_object, ['mime_type']) is not None:
|
297
|
+
setv(to_object, ['mimeType'], getv(from_object, ['mime_type']))
|
298
|
+
|
299
|
+
return to_object
|
300
|
+
|
301
|
+
|
302
|
+
def _FileData_to_vertex(
|
303
|
+
api_client: BaseApiClient,
|
304
|
+
from_object: Union[dict[str, Any], object],
|
305
|
+
parent_object: Optional[dict[str, Any]] = None,
|
306
|
+
) -> dict[str, Any]:
|
307
|
+
to_object: dict[str, Any] = {}
|
308
|
+
if getv(from_object, ['display_name']) is not None:
|
309
|
+
setv(to_object, ['displayName'], getv(from_object, ['display_name']))
|
310
|
+
|
311
|
+
if getv(from_object, ['file_uri']) is not None:
|
312
|
+
setv(to_object, ['fileUri'], getv(from_object, ['file_uri']))
|
313
|
+
|
314
|
+
if getv(from_object, ['mime_type']) is not None:
|
315
|
+
setv(to_object, ['mimeType'], getv(from_object, ['mime_type']))
|
316
|
+
|
317
|
+
return to_object
|
318
|
+
|
319
|
+
|
284
320
|
def _Part_to_mldev(
|
285
321
|
api_client: BaseApiClient,
|
286
322
|
from_object: Union[dict[str, Any], object],
|
@@ -308,6 +344,22 @@ def _Part_to_mldev(
|
|
308
344
|
),
|
309
345
|
)
|
310
346
|
|
347
|
+
if getv(from_object, ['file_data']) is not None:
|
348
|
+
setv(
|
349
|
+
to_object,
|
350
|
+
['fileData'],
|
351
|
+
_FileData_to_mldev(
|
352
|
+
api_client, getv(from_object, ['file_data']), to_object
|
353
|
+
),
|
354
|
+
)
|
355
|
+
|
356
|
+
if getv(from_object, ['thought_signature']) is not None:
|
357
|
+
setv(
|
358
|
+
to_object,
|
359
|
+
['thoughtSignature'],
|
360
|
+
getv(from_object, ['thought_signature']),
|
361
|
+
)
|
362
|
+
|
311
363
|
if getv(from_object, ['code_execution_result']) is not None:
|
312
364
|
setv(
|
313
365
|
to_object,
|
@@ -318,9 +370,6 @@ def _Part_to_mldev(
|
|
318
370
|
if getv(from_object, ['executable_code']) is not None:
|
319
371
|
setv(to_object, ['executableCode'], getv(from_object, ['executable_code']))
|
320
372
|
|
321
|
-
if getv(from_object, ['file_data']) is not None:
|
322
|
-
setv(to_object, ['fileData'], getv(from_object, ['file_data']))
|
323
|
-
|
324
373
|
if getv(from_object, ['function_call']) is not None:
|
325
374
|
setv(to_object, ['functionCall'], getv(from_object, ['function_call']))
|
326
375
|
|
@@ -364,6 +413,22 @@ def _Part_to_vertex(
|
|
364
413
|
),
|
365
414
|
)
|
366
415
|
|
416
|
+
if getv(from_object, ['file_data']) is not None:
|
417
|
+
setv(
|
418
|
+
to_object,
|
419
|
+
['fileData'],
|
420
|
+
_FileData_to_vertex(
|
421
|
+
api_client, getv(from_object, ['file_data']), to_object
|
422
|
+
),
|
423
|
+
)
|
424
|
+
|
425
|
+
if getv(from_object, ['thought_signature']) is not None:
|
426
|
+
setv(
|
427
|
+
to_object,
|
428
|
+
['thoughtSignature'],
|
429
|
+
getv(from_object, ['thought_signature']),
|
430
|
+
)
|
431
|
+
|
367
432
|
if getv(from_object, ['code_execution_result']) is not None:
|
368
433
|
setv(
|
369
434
|
to_object,
|
@@ -374,9 +439,6 @@ def _Part_to_vertex(
|
|
374
439
|
if getv(from_object, ['executable_code']) is not None:
|
375
440
|
setv(to_object, ['executableCode'], getv(from_object, ['executable_code']))
|
376
441
|
|
377
|
-
if getv(from_object, ['file_data']) is not None:
|
378
|
-
setv(to_object, ['fileData'], getv(from_object, ['file_data']))
|
379
|
-
|
380
442
|
if getv(from_object, ['function_call']) is not None:
|
381
443
|
setv(to_object, ['functionCall'], getv(from_object, ['function_call']))
|
382
444
|
|
@@ -2345,13 +2407,6 @@ def _LiveMusicGenerationConfig_to_mldev(
|
|
2345
2407
|
getv(from_object, ['only_bass_and_drums']),
|
2346
2408
|
)
|
2347
2409
|
|
2348
|
-
if getv(from_object, ['music_generation_mode']) is not None:
|
2349
|
-
setv(
|
2350
|
-
to_object,
|
2351
|
-
['musicGenerationMode'],
|
2352
|
-
getv(from_object, ['music_generation_mode']),
|
2353
|
-
)
|
2354
|
-
|
2355
2410
|
return to_object
|
2356
2411
|
|
2357
2412
|
|
@@ -2396,11 +2451,6 @@ def _LiveMusicGenerationConfig_to_vertex(
|
|
2396
2451
|
'only_bass_and_drums parameter is not supported in Vertex AI.'
|
2397
2452
|
)
|
2398
2453
|
|
2399
|
-
if getv(from_object, ['music_generation_mode']) is not None:
|
2400
|
-
raise ValueError(
|
2401
|
-
'music_generation_mode parameter is not supported in Vertex AI.'
|
2402
|
-
)
|
2403
|
-
|
2404
2454
|
return to_object
|
2405
2455
|
|
2406
2456
|
|
@@ -2653,6 +2703,40 @@ def _Blob_from_vertex(
|
|
2653
2703
|
return to_object
|
2654
2704
|
|
2655
2705
|
|
2706
|
+
def _FileData_from_mldev(
|
2707
|
+
api_client: BaseApiClient,
|
2708
|
+
from_object: Union[dict[str, Any], object],
|
2709
|
+
parent_object: Optional[dict[str, Any]] = None,
|
2710
|
+
) -> dict[str, Any]:
|
2711
|
+
to_object: dict[str, Any] = {}
|
2712
|
+
|
2713
|
+
if getv(from_object, ['fileUri']) is not None:
|
2714
|
+
setv(to_object, ['file_uri'], getv(from_object, ['fileUri']))
|
2715
|
+
|
2716
|
+
if getv(from_object, ['mimeType']) is not None:
|
2717
|
+
setv(to_object, ['mime_type'], getv(from_object, ['mimeType']))
|
2718
|
+
|
2719
|
+
return to_object
|
2720
|
+
|
2721
|
+
|
2722
|
+
def _FileData_from_vertex(
|
2723
|
+
api_client: BaseApiClient,
|
2724
|
+
from_object: Union[dict[str, Any], object],
|
2725
|
+
parent_object: Optional[dict[str, Any]] = None,
|
2726
|
+
) -> dict[str, Any]:
|
2727
|
+
to_object: dict[str, Any] = {}
|
2728
|
+
if getv(from_object, ['displayName']) is not None:
|
2729
|
+
setv(to_object, ['display_name'], getv(from_object, ['displayName']))
|
2730
|
+
|
2731
|
+
if getv(from_object, ['fileUri']) is not None:
|
2732
|
+
setv(to_object, ['file_uri'], getv(from_object, ['fileUri']))
|
2733
|
+
|
2734
|
+
if getv(from_object, ['mimeType']) is not None:
|
2735
|
+
setv(to_object, ['mime_type'], getv(from_object, ['mimeType']))
|
2736
|
+
|
2737
|
+
return to_object
|
2738
|
+
|
2739
|
+
|
2656
2740
|
def _Part_from_mldev(
|
2657
2741
|
api_client: BaseApiClient,
|
2658
2742
|
from_object: Union[dict[str, Any], object],
|
@@ -2680,6 +2764,22 @@ def _Part_from_mldev(
|
|
2680
2764
|
),
|
2681
2765
|
)
|
2682
2766
|
|
2767
|
+
if getv(from_object, ['fileData']) is not None:
|
2768
|
+
setv(
|
2769
|
+
to_object,
|
2770
|
+
['file_data'],
|
2771
|
+
_FileData_from_mldev(
|
2772
|
+
api_client, getv(from_object, ['fileData']), to_object
|
2773
|
+
),
|
2774
|
+
)
|
2775
|
+
|
2776
|
+
if getv(from_object, ['thoughtSignature']) is not None:
|
2777
|
+
setv(
|
2778
|
+
to_object,
|
2779
|
+
['thought_signature'],
|
2780
|
+
getv(from_object, ['thoughtSignature']),
|
2781
|
+
)
|
2782
|
+
|
2683
2783
|
if getv(from_object, ['codeExecutionResult']) is not None:
|
2684
2784
|
setv(
|
2685
2785
|
to_object,
|
@@ -2690,9 +2790,6 @@ def _Part_from_mldev(
|
|
2690
2790
|
if getv(from_object, ['executableCode']) is not None:
|
2691
2791
|
setv(to_object, ['executable_code'], getv(from_object, ['executableCode']))
|
2692
2792
|
|
2693
|
-
if getv(from_object, ['fileData']) is not None:
|
2694
|
-
setv(to_object, ['file_data'], getv(from_object, ['fileData']))
|
2695
|
-
|
2696
2793
|
if getv(from_object, ['functionCall']) is not None:
|
2697
2794
|
setv(to_object, ['function_call'], getv(from_object, ['functionCall']))
|
2698
2795
|
|
@@ -2736,6 +2833,22 @@ def _Part_from_vertex(
|
|
2736
2833
|
),
|
2737
2834
|
)
|
2738
2835
|
|
2836
|
+
if getv(from_object, ['fileData']) is not None:
|
2837
|
+
setv(
|
2838
|
+
to_object,
|
2839
|
+
['file_data'],
|
2840
|
+
_FileData_from_vertex(
|
2841
|
+
api_client, getv(from_object, ['fileData']), to_object
|
2842
|
+
),
|
2843
|
+
)
|
2844
|
+
|
2845
|
+
if getv(from_object, ['thoughtSignature']) is not None:
|
2846
|
+
setv(
|
2847
|
+
to_object,
|
2848
|
+
['thought_signature'],
|
2849
|
+
getv(from_object, ['thoughtSignature']),
|
2850
|
+
)
|
2851
|
+
|
2739
2852
|
if getv(from_object, ['codeExecutionResult']) is not None:
|
2740
2853
|
setv(
|
2741
2854
|
to_object,
|
@@ -2746,9 +2859,6 @@ def _Part_from_vertex(
|
|
2746
2859
|
if getv(from_object, ['executableCode']) is not None:
|
2747
2860
|
setv(to_object, ['executable_code'], getv(from_object, ['executableCode']))
|
2748
2861
|
|
2749
|
-
if getv(from_object, ['fileData']) is not None:
|
2750
|
-
setv(to_object, ['file_data'], getv(from_object, ['fileData']))
|
2751
|
-
|
2752
2862
|
if getv(from_object, ['functionCall']) is not None:
|
2753
2863
|
setv(to_object, ['function_call'], getv(from_object, ['functionCall']))
|
2754
2864
|
|
@@ -3676,13 +3786,6 @@ def _LiveMusicGenerationConfig_from_mldev(
|
|
3676
3786
|
getv(from_object, ['onlyBassAndDrums']),
|
3677
3787
|
)
|
3678
3788
|
|
3679
|
-
if getv(from_object, ['musicGenerationMode']) is not None:
|
3680
|
-
setv(
|
3681
|
-
to_object,
|
3682
|
-
['music_generation_mode'],
|
3683
|
-
getv(from_object, ['musicGenerationMode']),
|
3684
|
-
)
|
3685
|
-
|
3686
3789
|
return to_object
|
3687
3790
|
|
3688
3791
|
|
@@ -83,13 +83,10 @@ def set_mcp_usage_header(headers: dict[str, str]) -> None:
|
|
83
83
|
version_label = version("mcp")
|
84
84
|
except PackageNotFoundError:
|
85
85
|
version_label = "0.0.0"
|
86
|
-
# TODO: b/418827318 - Investigate weather the duplicate mcp label check is
|
87
|
-
# necessary.
|
88
|
-
mcp_label = f"mcp_used/{version_label}"
|
89
86
|
existing_header = headers.get("x-goog-api-client", "")
|
90
|
-
|
91
|
-
|
92
|
-
|
87
|
+
headers["x-goog-api-client"] = (
|
88
|
+
existing_header + f" mcp_used/{version_label}"
|
89
|
+
).lstrip()
|
93
90
|
|
94
91
|
|
95
92
|
def _filter_to_supported_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
@@ -18,6 +18,7 @@
|
|
18
18
|
import base64
|
19
19
|
import copy
|
20
20
|
import datetime
|
21
|
+
import enum
|
21
22
|
import inspect
|
22
23
|
import io
|
23
24
|
import json
|
@@ -37,6 +38,55 @@ from .types import HttpOptions, HttpOptionsOrDict
|
|
37
38
|
from .types import GenerateVideosOperation
|
38
39
|
|
39
40
|
|
41
|
+
def to_snake_case(name: str) -> str:
|
42
|
+
"""Converts a string from camelCase or PascalCase to snake_case."""
|
43
|
+
|
44
|
+
if not isinstance(name, str):
|
45
|
+
name = str(name)
|
46
|
+
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
|
47
|
+
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
|
48
|
+
|
49
|
+
|
50
|
+
def _normalize_json_case(obj: Any) -> Any:
|
51
|
+
if isinstance(obj, dict):
|
52
|
+
return {
|
53
|
+
to_snake_case(k): _normalize_json_case(v)
|
54
|
+
for k, v in obj.items()
|
55
|
+
}
|
56
|
+
elif isinstance(obj, list):
|
57
|
+
return [_normalize_json_case(item) for item in obj]
|
58
|
+
elif isinstance(obj, enum.Enum):
|
59
|
+
return obj.value
|
60
|
+
else:
|
61
|
+
return obj
|
62
|
+
|
63
|
+
|
64
|
+
def _equals_ignore_key_case(obj1: Any, obj2: Any) -> bool:
|
65
|
+
"""Compares two Python objects for equality ignoring key casing.
|
66
|
+
|
67
|
+
Returns:
|
68
|
+
bool: True if the two objects are equal regardless of key casing
|
69
|
+
(camelCase vs. snake_case). For example, the following are considered equal:
|
70
|
+
|
71
|
+
{'my_key': 'my_value'}
|
72
|
+
{'myKey': 'my_value'}
|
73
|
+
|
74
|
+
This also considers enums and strings with the same value as equal.
|
75
|
+
For example, the following are considered equal:
|
76
|
+
|
77
|
+
{'type': <Type.STRING: 'STRING'>}}
|
78
|
+
{'type': 'STRING'}
|
79
|
+
"""
|
80
|
+
|
81
|
+
normalized_obj_1 = _normalize_json_case(obj1)
|
82
|
+
normalized_obj_2 = _normalize_json_case(obj2)
|
83
|
+
|
84
|
+
if normalized_obj_1 == normalized_obj_2:
|
85
|
+
return True
|
86
|
+
else:
|
87
|
+
return False
|
88
|
+
|
89
|
+
|
40
90
|
def _redact_version_numbers(version_string: str) -> str:
|
41
91
|
"""Redacts version numbers in the form x.y.z from a string."""
|
42
92
|
return re.sub(r'\d+\.\d+\.\d+', '{VERSION_NUMBER}', version_string)
|
@@ -358,7 +408,7 @@ class ReplayApiClient(BaseApiClient):
|
|
358
408
|
|
359
409
|
actual_request_body = [request_data_copy]
|
360
410
|
expected_request_body = interaction.request.body_segments
|
361
|
-
assert actual_request_body
|
411
|
+
assert _equals_ignore_key_case(actual_request_body, expected_request_body), (
|
362
412
|
'Request body mismatch:\n'
|
363
413
|
f'Actual: {actual_request_body}\n'
|
364
414
|
f'Expected: {expected_request_body}'
|
@@ -281,6 +281,42 @@ def _Blob_to_vertex(
|
|
281
281
|
return to_object
|
282
282
|
|
283
283
|
|
284
|
+
def _FileData_to_mldev(
|
285
|
+
api_client: BaseApiClient,
|
286
|
+
from_object: Union[dict[str, Any], object],
|
287
|
+
parent_object: Optional[dict[str, Any]] = None,
|
288
|
+
) -> dict[str, Any]:
|
289
|
+
to_object: dict[str, Any] = {}
|
290
|
+
if getv(from_object, ['display_name']) is not None:
|
291
|
+
raise ValueError('display_name parameter is not supported in Gemini API.')
|
292
|
+
|
293
|
+
if getv(from_object, ['file_uri']) is not None:
|
294
|
+
setv(to_object, ['fileUri'], getv(from_object, ['file_uri']))
|
295
|
+
|
296
|
+
if getv(from_object, ['mime_type']) is not None:
|
297
|
+
setv(to_object, ['mimeType'], getv(from_object, ['mime_type']))
|
298
|
+
|
299
|
+
return to_object
|
300
|
+
|
301
|
+
|
302
|
+
def _FileData_to_vertex(
|
303
|
+
api_client: BaseApiClient,
|
304
|
+
from_object: Union[dict[str, Any], object],
|
305
|
+
parent_object: Optional[dict[str, Any]] = None,
|
306
|
+
) -> dict[str, Any]:
|
307
|
+
to_object: dict[str, Any] = {}
|
308
|
+
if getv(from_object, ['display_name']) is not None:
|
309
|
+
setv(to_object, ['displayName'], getv(from_object, ['display_name']))
|
310
|
+
|
311
|
+
if getv(from_object, ['file_uri']) is not None:
|
312
|
+
setv(to_object, ['fileUri'], getv(from_object, ['file_uri']))
|
313
|
+
|
314
|
+
if getv(from_object, ['mime_type']) is not None:
|
315
|
+
setv(to_object, ['mimeType'], getv(from_object, ['mime_type']))
|
316
|
+
|
317
|
+
return to_object
|
318
|
+
|
319
|
+
|
284
320
|
def _Part_to_mldev(
|
285
321
|
api_client: BaseApiClient,
|
286
322
|
from_object: Union[dict[str, Any], object],
|
@@ -308,6 +344,22 @@ def _Part_to_mldev(
|
|
308
344
|
),
|
309
345
|
)
|
310
346
|
|
347
|
+
if getv(from_object, ['file_data']) is not None:
|
348
|
+
setv(
|
349
|
+
to_object,
|
350
|
+
['fileData'],
|
351
|
+
_FileData_to_mldev(
|
352
|
+
api_client, getv(from_object, ['file_data']), to_object
|
353
|
+
),
|
354
|
+
)
|
355
|
+
|
356
|
+
if getv(from_object, ['thought_signature']) is not None:
|
357
|
+
setv(
|
358
|
+
to_object,
|
359
|
+
['thoughtSignature'],
|
360
|
+
getv(from_object, ['thought_signature']),
|
361
|
+
)
|
362
|
+
|
311
363
|
if getv(from_object, ['code_execution_result']) is not None:
|
312
364
|
setv(
|
313
365
|
to_object,
|
@@ -318,9 +370,6 @@ def _Part_to_mldev(
|
|
318
370
|
if getv(from_object, ['executable_code']) is not None:
|
319
371
|
setv(to_object, ['executableCode'], getv(from_object, ['executable_code']))
|
320
372
|
|
321
|
-
if getv(from_object, ['file_data']) is not None:
|
322
|
-
setv(to_object, ['fileData'], getv(from_object, ['file_data']))
|
323
|
-
|
324
373
|
if getv(from_object, ['function_call']) is not None:
|
325
374
|
setv(to_object, ['functionCall'], getv(from_object, ['function_call']))
|
326
375
|
|
@@ -364,6 +413,22 @@ def _Part_to_vertex(
|
|
364
413
|
),
|
365
414
|
)
|
366
415
|
|
416
|
+
if getv(from_object, ['file_data']) is not None:
|
417
|
+
setv(
|
418
|
+
to_object,
|
419
|
+
['fileData'],
|
420
|
+
_FileData_to_vertex(
|
421
|
+
api_client, getv(from_object, ['file_data']), to_object
|
422
|
+
),
|
423
|
+
)
|
424
|
+
|
425
|
+
if getv(from_object, ['thought_signature']) is not None:
|
426
|
+
setv(
|
427
|
+
to_object,
|
428
|
+
['thoughtSignature'],
|
429
|
+
getv(from_object, ['thought_signature']),
|
430
|
+
)
|
431
|
+
|
367
432
|
if getv(from_object, ['code_execution_result']) is not None:
|
368
433
|
setv(
|
369
434
|
to_object,
|
@@ -374,9 +439,6 @@ def _Part_to_vertex(
|
|
374
439
|
if getv(from_object, ['executable_code']) is not None:
|
375
440
|
setv(to_object, ['executableCode'], getv(from_object, ['executable_code']))
|
376
441
|
|
377
|
-
if getv(from_object, ['file_data']) is not None:
|
378
|
-
setv(to_object, ['fileData'], getv(from_object, ['file_data']))
|
379
|
-
|
380
442
|
if getv(from_object, ['function_call']) is not None:
|
381
443
|
setv(to_object, ['functionCall'], getv(from_object, ['function_call']))
|
382
444
|
|
@@ -1537,7 +1599,7 @@ def _LiveConnectConfig_to_vertex(
|
|
1537
1599
|
return to_object
|
1538
1600
|
|
1539
1601
|
|
1540
|
-
def
|
1602
|
+
def _LiveConnectConstraints_to_mldev(
|
1541
1603
|
api_client: BaseApiClient,
|
1542
1604
|
from_object: Union[dict[str, Any], object],
|
1543
1605
|
parent_object: Optional[dict[str, Any]] = None,
|
@@ -1562,7 +1624,7 @@ def _LiveEphemeralParameters_to_mldev(
|
|
1562
1624
|
return to_object
|
1563
1625
|
|
1564
1626
|
|
1565
|
-
def
|
1627
|
+
def _LiveConnectConstraints_to_vertex(
|
1566
1628
|
api_client: BaseApiClient,
|
1567
1629
|
from_object: Union[dict[str, Any], object],
|
1568
1630
|
parent_object: Optional[dict[str, Any]] = None,
|
@@ -1597,13 +1659,13 @@ def _CreateAuthTokenConfig_to_mldev(
|
|
1597
1659
|
if getv(from_object, ['uses']) is not None:
|
1598
1660
|
setv(parent_object, ['uses'], getv(from_object, ['uses']))
|
1599
1661
|
|
1600
|
-
if getv(from_object, ['
|
1662
|
+
if getv(from_object, ['live_connect_constraints']) is not None:
|
1601
1663
|
setv(
|
1602
1664
|
parent_object,
|
1603
1665
|
['bidiGenerateContentSetup'],
|
1604
|
-
|
1666
|
+
_LiveConnectConstraints_to_mldev(
|
1605
1667
|
api_client,
|
1606
|
-
getv(from_object, ['
|
1668
|
+
getv(from_object, ['live_connect_constraints']),
|
1607
1669
|
to_object,
|
1608
1670
|
),
|
1609
1671
|
)
|
@@ -1636,9 +1698,9 @@ def _CreateAuthTokenConfig_to_vertex(
|
|
1636
1698
|
if getv(from_object, ['uses']) is not None:
|
1637
1699
|
raise ValueError('uses parameter is not supported in Vertex AI.')
|
1638
1700
|
|
1639
|
-
if getv(from_object, ['
|
1701
|
+
if getv(from_object, ['live_connect_constraints']) is not None:
|
1640
1702
|
raise ValueError(
|
1641
|
-
'
|
1703
|
+
'live_connect_constraints parameter is not supported in Vertex AI.'
|
1642
1704
|
)
|
1643
1705
|
|
1644
1706
|
if getv(from_object, ['lock_additional_fields']) is not None:
|
@@ -630,6 +630,21 @@ def handle_null_fields(schema: dict[str, Any]) -> None:
|
|
630
630
|
del schema['anyOf']
|
631
631
|
|
632
632
|
|
633
|
+
def _raise_for_unsupported_schema_type(origin: Any) -> None:
|
634
|
+
"""Raises an error if the schema type is unsupported."""
|
635
|
+
raise ValueError(f'Unsupported schema type: {origin}')
|
636
|
+
|
637
|
+
|
638
|
+
def _raise_for_unsupported_mldev_properties(schema: Any, client: _api_client.BaseApiClient) -> None:
|
639
|
+
if not client.vertexai and (
|
640
|
+
schema.get('additionalProperties')
|
641
|
+
or schema.get('additional_properties')
|
642
|
+
):
|
643
|
+
raise ValueError(
|
644
|
+
'additionalProperties is not supported in the Gemini API.'
|
645
|
+
)
|
646
|
+
|
647
|
+
|
633
648
|
def process_schema(
|
634
649
|
schema: dict[str, Any],
|
635
650
|
client: _api_client.BaseApiClient,
|
@@ -700,6 +715,8 @@ def process_schema(
|
|
700
715
|
if schema.get('title') == 'PlaceholderLiteralEnum':
|
701
716
|
del schema['title']
|
702
717
|
|
718
|
+
_raise_for_unsupported_mldev_properties(schema, client)
|
719
|
+
|
703
720
|
# Standardize spelling for relevant schema fields. For example, if a dict is
|
704
721
|
# provided directly to response_schema, it may use `any_of` instead of `anyOf.
|
705
722
|
# Otherwise, model_json_schema() uses `anyOf`.
|
@@ -744,7 +761,8 @@ def process_schema(
|
|
744
761
|
schema_type = schema.get('type')
|
745
762
|
if isinstance(schema_type, Enum):
|
746
763
|
schema_type = schema_type.value
|
747
|
-
schema_type
|
764
|
+
if isinstance(schema_type, str):
|
765
|
+
schema_type = schema_type.upper()
|
748
766
|
|
749
767
|
# model_json_schema() returns a schema with a 'const' field when a Literal with one value is provided as a pydantic field
|
750
768
|
# For example `genre: Literal['action']` becomes: {'const': 'action', 'title': 'Genre', 'type': 'string'}
|
@@ -818,8 +836,9 @@ def t_schema(
|
|
818
836
|
return _process_enum(origin, client)
|
819
837
|
if isinstance(origin, types.Schema):
|
820
838
|
if dict(origin) == dict(types.Schema()):
|
821
|
-
# response_schema value was coerced to an empty Schema instance because
|
822
|
-
|
839
|
+
# response_schema value was coerced to an empty Schema instance because
|
840
|
+
# it did not adhere to the Schema field annotation
|
841
|
+
_raise_for_unsupported_schema_type(origin)
|
823
842
|
schema = origin.model_dump(exclude_unset=True)
|
824
843
|
process_schema(schema, client)
|
825
844
|
return types.Schema.model_validate(schema)
|