google-genai 1.28.0__tar.gz → 1.30.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.28.0/google_genai.egg-info → google_genai-1.30.0}/PKG-INFO +2 -2
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_api_client.py +144 -55
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_automatic_function_calling_util.py +35 -7
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_live_converters.py +64 -6
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_tokens_converters.py +25 -3
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_transformers.py +51 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/batches.py +44 -5
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/caches.py +50 -6
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/chats.py +1 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/errors.py +5 -2
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/live.py +92 -88
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/models.py +443 -36
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/tokens.py +1 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/tunings.py +314 -43
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/types.py +1398 -293
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/version.py +1 -1
- {google_genai-1.28.0 → google_genai-1.30.0/google_genai.egg-info}/PKG-INFO +2 -2
- {google_genai-1.28.0 → google_genai-1.30.0}/google_genai.egg-info/requires.txt +1 -1
- {google_genai-1.28.0 → google_genai-1.30.0}/pyproject.toml +2 -2
- {google_genai-1.28.0 → google_genai-1.30.0}/LICENSE +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/MANIFEST.in +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/README.md +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/__init__.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_adapters.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_api_module.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_base_url.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_common.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_extra_utils.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_mcp_utils.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_replay_api_client.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_test_api_client.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/client.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/files.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/live_music.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/operations.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/pagers.py +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google/genai/py.typed +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google_genai.egg-info/SOURCES.txt +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google_genai.egg-info/dependency_links.txt +0 -0
- {google_genai-1.28.0 → google_genai-1.30.0}/google_genai.egg-info/top_level.txt +0 -0
- {google_genai-1.28.0 → google_genai-1.30.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.30.0
|
4
4
|
Summary: GenAI Python SDK
|
5
5
|
Author-email: Google LLC <googleapis-packages@google.com>
|
6
6
|
License: Apache-2.0
|
@@ -25,7 +25,7 @@ Requires-Dist: google-auth<3.0.0,>=2.14.1
|
|
25
25
|
Requires-Dist: httpx<1.0.0,>=0.28.1
|
26
26
|
Requires-Dist: pydantic<3.0.0,>=2.0.0
|
27
27
|
Requires-Dist: requests<3.0.0,>=2.28.1
|
28
|
-
Requires-Dist: tenacity<9.
|
28
|
+
Requires-Dist: tenacity<9.2.0,>=8.2.3
|
29
29
|
Requires-Dist: websockets<15.1.0,>=13.0.0
|
30
30
|
Requires-Dist: typing-extensions<5.0.0,>=4.11.0
|
31
31
|
Provides-Extra: aiohttp
|
@@ -20,23 +20,21 @@ The BaseApiClient is intended to be a private module and is subject to change.
|
|
20
20
|
"""
|
21
21
|
|
22
22
|
import asyncio
|
23
|
-
from collections.abc import
|
23
|
+
from collections.abc import Generator
|
24
24
|
import copy
|
25
25
|
from dataclasses import dataclass
|
26
|
-
import datetime
|
27
|
-
import http
|
28
26
|
import inspect
|
29
27
|
import io
|
30
28
|
import json
|
31
29
|
import logging
|
32
30
|
import math
|
33
31
|
import os
|
34
|
-
import ssl
|
35
32
|
import random
|
33
|
+
import ssl
|
36
34
|
import sys
|
37
35
|
import threading
|
38
36
|
import time
|
39
|
-
from typing import Any, AsyncIterator,
|
37
|
+
from typing import Any, AsyncIterator, Iterator, Optional, Tuple, TYPE_CHECKING, Union
|
40
38
|
from urllib.parse import urlparse
|
41
39
|
from urllib.parse import urlunparse
|
42
40
|
|
@@ -48,7 +46,6 @@ from google.auth.credentials import Credentials
|
|
48
46
|
from google.auth.transport.requests import Request
|
49
47
|
import httpx
|
50
48
|
from pydantic import BaseModel
|
51
|
-
from pydantic import Field
|
52
49
|
from pydantic import ValidationError
|
53
50
|
import tenacity
|
54
51
|
|
@@ -56,11 +53,11 @@ from . import _common
|
|
56
53
|
from . import errors
|
57
54
|
from . import version
|
58
55
|
from .types import HttpOptions
|
59
|
-
from .types import HttpOptionsDict
|
60
56
|
from .types import HttpOptionsOrDict
|
61
57
|
from .types import HttpResponse as SdkHttpResponse
|
62
58
|
from .types import HttpRetryOptions
|
63
59
|
|
60
|
+
|
64
61
|
try:
|
65
62
|
from websockets.asyncio.client import connect as ws_connect
|
66
63
|
except ModuleNotFoundError:
|
@@ -238,12 +235,12 @@ class HttpResponse:
|
|
238
235
|
self.headers = headers
|
239
236
|
elif isinstance(headers, httpx.Headers):
|
240
237
|
self.headers = {
|
241
|
-
|
242
|
-
|
238
|
+
key: ', '.join(headers.get_list(key)) for key in headers.keys()
|
239
|
+
}
|
243
240
|
elif type(headers).__name__ == 'CIMultiDictProxy':
|
244
241
|
self.headers = {
|
245
|
-
key: ', '.join(headers.getall(key))
|
246
|
-
|
242
|
+
key: ', '.join(headers.getall(key)) for key in headers.keys()
|
243
|
+
}
|
247
244
|
|
248
245
|
self.status_code: int = 200
|
249
246
|
self.response_stream = response_stream
|
@@ -265,68 +262,32 @@ class HttpResponse:
|
|
265
262
|
def json(self) -> Any:
|
266
263
|
if not self.response_stream[0]: # Empty response
|
267
264
|
return ''
|
268
|
-
return
|
265
|
+
return self._load_json_from_response(self.response_stream[0])
|
269
266
|
|
270
267
|
def segments(self) -> Generator[Any, None, None]:
|
271
268
|
if isinstance(self.response_stream, list):
|
272
269
|
# list of objects retrieved from replay or from non-streaming API.
|
273
270
|
for chunk in self.response_stream:
|
274
|
-
yield
|
271
|
+
yield self._load_json_from_response(chunk) if chunk else {}
|
275
272
|
elif self.response_stream is None:
|
276
273
|
yield from []
|
277
274
|
else:
|
278
275
|
# Iterator of objects retrieved from the API.
|
279
|
-
for chunk in self.
|
280
|
-
|
281
|
-
# In streaming mode, the chunk of JSON is prefixed with "data:" which
|
282
|
-
# we must strip before parsing.
|
283
|
-
if not isinstance(chunk, str):
|
284
|
-
chunk = chunk.decode('utf-8')
|
285
|
-
if chunk.startswith('data: '):
|
286
|
-
chunk = chunk[len('data: ') :]
|
287
|
-
yield json.loads(chunk)
|
276
|
+
for chunk in self._iter_response_stream():
|
277
|
+
yield self._load_json_from_response(chunk)
|
288
278
|
|
289
279
|
async def async_segments(self) -> AsyncIterator[Any]:
|
290
280
|
if isinstance(self.response_stream, list):
|
291
281
|
# list of objects retrieved from replay or from non-streaming API.
|
292
282
|
for chunk in self.response_stream:
|
293
|
-
yield
|
283
|
+
yield self._load_json_from_response(chunk) if chunk else {}
|
294
284
|
elif self.response_stream is None:
|
295
285
|
async for c in []: # type: ignore[attr-defined]
|
296
286
|
yield c
|
297
287
|
else:
|
298
288
|
# Iterator of objects retrieved from the API.
|
299
|
-
|
300
|
-
|
301
|
-
# This is httpx.Response.
|
302
|
-
if chunk:
|
303
|
-
# In async streaming mode, the chunk of JSON is prefixed with
|
304
|
-
# "data:" which we must strip before parsing.
|
305
|
-
if not isinstance(chunk, str):
|
306
|
-
chunk = chunk.decode('utf-8')
|
307
|
-
if chunk.startswith('data: '):
|
308
|
-
chunk = chunk[len('data: ') :]
|
309
|
-
yield json.loads(chunk)
|
310
|
-
elif hasattr(self.response_stream, 'content'):
|
311
|
-
# This is aiohttp.ClientResponse.
|
312
|
-
try:
|
313
|
-
while True:
|
314
|
-
chunk = await self.response_stream.content.readline()
|
315
|
-
if not chunk:
|
316
|
-
break
|
317
|
-
# In async streaming mode, the chunk of JSON is prefixed with
|
318
|
-
# "data:" which we must strip before parsing.
|
319
|
-
chunk = chunk.decode('utf-8')
|
320
|
-
if chunk.startswith('data: '):
|
321
|
-
chunk = chunk[len('data: ') :]
|
322
|
-
chunk = chunk.strip()
|
323
|
-
if chunk:
|
324
|
-
yield json.loads(chunk)
|
325
|
-
finally:
|
326
|
-
if hasattr(self, '_session') and self._session:
|
327
|
-
await self._session.close()
|
328
|
-
else:
|
329
|
-
raise ValueError('Error parsing streaming response.')
|
289
|
+
async for chunk in self._aiter_response_stream():
|
290
|
+
yield self._load_json_from_response(chunk)
|
330
291
|
|
331
292
|
def byte_segments(self) -> Generator[Union[bytes, Any], None, None]:
|
332
293
|
if isinstance(self.byte_stream, list):
|
@@ -345,6 +306,134 @@ class HttpResponse:
|
|
345
306
|
for attribute in dir(self):
|
346
307
|
response_payload[attribute] = copy.deepcopy(getattr(self, attribute))
|
347
308
|
|
309
|
+
def _iter_response_stream(self) -> Iterator[str]:
|
310
|
+
"""Iterates over chunks retrieved from the API."""
|
311
|
+
if not isinstance(self.response_stream, httpx.Response):
|
312
|
+
raise TypeError(
|
313
|
+
'Expected self.response_stream to be an httpx.Response object, '
|
314
|
+
f'but got {type(self.response_stream).__name__}.'
|
315
|
+
)
|
316
|
+
|
317
|
+
chunk = ''
|
318
|
+
balance = 0
|
319
|
+
for line in self.response_stream.iter_lines():
|
320
|
+
if not line:
|
321
|
+
continue
|
322
|
+
|
323
|
+
# In streaming mode, the response of JSON is prefixed with "data: " which
|
324
|
+
# we must strip before parsing.
|
325
|
+
if line.startswith('data: '):
|
326
|
+
yield line[len('data: '):]
|
327
|
+
continue
|
328
|
+
|
329
|
+
# When API returns an error message, it comes line by line. So we buffer
|
330
|
+
# the lines until a complete JSON string is read. A complete JSON string
|
331
|
+
# is found when the balance is 0.
|
332
|
+
for c in line:
|
333
|
+
if c == '{':
|
334
|
+
balance += 1
|
335
|
+
elif c == '}':
|
336
|
+
balance -= 1
|
337
|
+
|
338
|
+
chunk += line
|
339
|
+
if balance == 0:
|
340
|
+
yield chunk
|
341
|
+
chunk = ''
|
342
|
+
|
343
|
+
# If there is any remaining chunk, yield it.
|
344
|
+
if chunk:
|
345
|
+
yield chunk
|
346
|
+
|
347
|
+
async def _aiter_response_stream(self) -> AsyncIterator[str]:
|
348
|
+
"""Asynchronously iterates over chunks retrieved from the API."""
|
349
|
+
is_valid_response = isinstance(self.response_stream, httpx.Response) or (
|
350
|
+
has_aiohttp and isinstance(self.response_stream, aiohttp.ClientResponse)
|
351
|
+
)
|
352
|
+
if not is_valid_response:
|
353
|
+
raise TypeError(
|
354
|
+
'Expected self.response_stream to be an httpx.Response or'
|
355
|
+
' aiohttp.ClientResponse object, but got'
|
356
|
+
f' {type(self.response_stream).__name__}.'
|
357
|
+
)
|
358
|
+
|
359
|
+
chunk = ''
|
360
|
+
balance = 0
|
361
|
+
# httpx.Response has a dedicated async line iterator.
|
362
|
+
if isinstance(self.response_stream, httpx.Response):
|
363
|
+
async for line in self.response_stream.aiter_lines():
|
364
|
+
if not line:
|
365
|
+
continue
|
366
|
+
# In streaming mode, the response of JSON is prefixed with "data: "
|
367
|
+
# which we must strip before parsing.
|
368
|
+
if line.startswith('data: '):
|
369
|
+
yield line[len('data: '):]
|
370
|
+
continue
|
371
|
+
|
372
|
+
# When API returns an error message, it comes line by line. So we buffer
|
373
|
+
# the lines until a complete JSON string is read. A complete JSON string
|
374
|
+
# is found when the balance is 0.
|
375
|
+
for c in line:
|
376
|
+
if c == '{':
|
377
|
+
balance += 1
|
378
|
+
elif c == '}':
|
379
|
+
balance -= 1
|
380
|
+
|
381
|
+
chunk += line
|
382
|
+
if balance == 0:
|
383
|
+
yield chunk
|
384
|
+
chunk = ''
|
385
|
+
|
386
|
+
# aiohttp.ClientResponse uses a content stream that we read line by line.
|
387
|
+
elif has_aiohttp and isinstance(
|
388
|
+
self.response_stream, aiohttp.ClientResponse
|
389
|
+
):
|
390
|
+
while True:
|
391
|
+
# Read a line from the stream. This returns bytes.
|
392
|
+
line_bytes = await self.response_stream.content.readline()
|
393
|
+
if not line_bytes:
|
394
|
+
break
|
395
|
+
# Decode the bytes and remove trailing whitespace and newlines.
|
396
|
+
line = line_bytes.decode('utf-8').rstrip()
|
397
|
+
if not line:
|
398
|
+
continue
|
399
|
+
|
400
|
+
# In streaming mode, the response of JSON is prefixed with "data: "
|
401
|
+
# which we must strip before parsing.
|
402
|
+
if line.startswith('data: '):
|
403
|
+
yield line[len('data: '):]
|
404
|
+
continue
|
405
|
+
|
406
|
+
# When API returns an error message, it comes line by line. So we buffer
|
407
|
+
# the lines until a complete JSON string is read. A complete JSON string
|
408
|
+
# is found when the balance is 0.
|
409
|
+
for c in line:
|
410
|
+
if c == '{':
|
411
|
+
balance += 1
|
412
|
+
elif c == '}':
|
413
|
+
balance -= 1
|
414
|
+
|
415
|
+
chunk += line
|
416
|
+
if balance == 0:
|
417
|
+
yield chunk
|
418
|
+
chunk = ''
|
419
|
+
|
420
|
+
# If there is any remaining chunk, yield it.
|
421
|
+
if chunk:
|
422
|
+
yield chunk
|
423
|
+
|
424
|
+
if hasattr(self, '_session') and self._session:
|
425
|
+
await self._session.close()
|
426
|
+
|
427
|
+
@classmethod
|
428
|
+
def _load_json_from_response(cls, response: Any) -> Any:
|
429
|
+
"""Loads JSON from the response, or raises an error if the parsing fails."""
|
430
|
+
try:
|
431
|
+
return json.loads(response)
|
432
|
+
except json.JSONDecodeError as e:
|
433
|
+
raise errors.UnknownApiResponseError(
|
434
|
+
f'Failed to parse response as JSON. Raw response: {response}'
|
435
|
+
) from e
|
436
|
+
|
348
437
|
|
349
438
|
# Default retry options.
|
350
439
|
# The config is based on https://cloud.google.com/storage/docs/retry-strategy.
|
@@ -548,7 +637,7 @@ class BaseApiClient:
|
|
548
637
|
|
549
638
|
has_sufficient_auth = (self.project and self.location) or self.api_key
|
550
639
|
|
551
|
-
if
|
640
|
+
if not has_sufficient_auth and not validated_http_options.base_url:
|
552
641
|
# Skip sufficient auth check if base url is provided in http options.
|
553
642
|
raise ValueError(
|
554
643
|
'Project and location or API key must be set when using the Vertex '
|
{google_genai-1.28.0 → google_genai-1.30.0}/google/genai/_automatic_function_calling_util.py
RENAMED
@@ -41,6 +41,39 @@ _py_builtin_type_to_schema_type = {
|
|
41
41
|
}
|
42
42
|
|
43
43
|
|
44
|
+
def _raise_for_unsupported_param(
|
45
|
+
param: inspect.Parameter, func_name: str, exception: Union[Exception, type[Exception]]
|
46
|
+
) -> None:
|
47
|
+
raise ValueError(
|
48
|
+
f'Failed to parse the parameter {param} of function {func_name} for'
|
49
|
+
' automatic function calling.Automatic function calling works best with'
|
50
|
+
' simpler function signature schema, consider manually parsing your'
|
51
|
+
f' function declaration for function {func_name}.'
|
52
|
+
) from exception
|
53
|
+
|
54
|
+
|
55
|
+
def _handle_params_as_deferred_annotations(param: inspect.Parameter, annotation_under_future: dict[str, Any], name: str) -> inspect.Parameter:
|
56
|
+
"""Catches the case when type hints are stored as strings."""
|
57
|
+
if isinstance(param.annotation, str):
|
58
|
+
param = param.replace(annotation=annotation_under_future[name])
|
59
|
+
return param
|
60
|
+
|
61
|
+
|
62
|
+
def _add_unevaluated_items_to_fixed_len_tuple_schema(
|
63
|
+
json_schema: dict[str, Any]
|
64
|
+
) -> dict[str, Any]:
|
65
|
+
if (
|
66
|
+
json_schema.get('maxItems')
|
67
|
+
and (
|
68
|
+
json_schema.get('prefixItems')
|
69
|
+
and len(json_schema['prefixItems']) == json_schema['maxItems']
|
70
|
+
)
|
71
|
+
and json_schema.get('type') == 'array'
|
72
|
+
):
|
73
|
+
json_schema['unevaluatedItems'] = False
|
74
|
+
return json_schema
|
75
|
+
|
76
|
+
|
44
77
|
def _is_builtin_primitive_or_compound(
|
45
78
|
annotation: inspect.Parameter.annotation, # type: ignore[valid-type]
|
46
79
|
) -> bool:
|
@@ -92,7 +125,7 @@ def _is_default_value_compatible(
|
|
92
125
|
return False
|
93
126
|
|
94
127
|
|
95
|
-
def _parse_schema_from_parameter(
|
128
|
+
def _parse_schema_from_parameter( # type: ignore[return]
|
96
129
|
api_option: Literal['VERTEX_AI', 'GEMINI_API'],
|
97
130
|
param: inspect.Parameter,
|
98
131
|
func_name: str,
|
@@ -267,12 +300,7 @@ def _parse_schema_from_parameter(
|
|
267
300
|
)
|
268
301
|
schema.required = _get_required_fields(schema)
|
269
302
|
return schema
|
270
|
-
|
271
|
-
f'Failed to parse the parameter {param} of function {func_name} for'
|
272
|
-
' automatic function calling.Automatic function calling works best with'
|
273
|
-
' simpler function signature schema, consider manually parsing your'
|
274
|
-
f' function declaration for function {func_name}.'
|
275
|
-
)
|
303
|
+
_raise_for_unsupported_param(param, func_name, ValueError)
|
276
304
|
|
277
305
|
|
278
306
|
def _get_required_fields(schema: types.Schema) -> Optional[list[str]]:
|
@@ -312,6 +312,11 @@ def _GoogleSearch_to_mldev(
|
|
312
312
|
_Interval_to_mldev(getv(from_object, ['time_range_filter']), to_object),
|
313
313
|
)
|
314
314
|
|
315
|
+
if getv(from_object, ['exclude_domains']) is not None:
|
316
|
+
raise ValueError(
|
317
|
+
'exclude_domains parameter is not supported in Gemini API.'
|
318
|
+
)
|
319
|
+
|
315
320
|
return to_object
|
316
321
|
|
317
322
|
|
@@ -359,6 +364,17 @@ def _UrlContext_to_mldev(
|
|
359
364
|
return to_object
|
360
365
|
|
361
366
|
|
367
|
+
def _ToolComputerUse_to_mldev(
|
368
|
+
from_object: Union[dict[str, Any], object],
|
369
|
+
parent_object: Optional[dict[str, Any]] = None,
|
370
|
+
) -> dict[str, Any]:
|
371
|
+
to_object: dict[str, Any] = {}
|
372
|
+
if getv(from_object, ['environment']) is not None:
|
373
|
+
setv(to_object, ['environment'], getv(from_object, ['environment']))
|
374
|
+
|
375
|
+
return to_object
|
376
|
+
|
377
|
+
|
362
378
|
def _Tool_to_mldev(
|
363
379
|
from_object: Union[dict[str, Any], object],
|
364
380
|
parent_object: Optional[dict[str, Any]] = None,
|
@@ -408,12 +424,18 @@ def _Tool_to_mldev(
|
|
408
424
|
_UrlContext_to_mldev(getv(from_object, ['url_context']), to_object),
|
409
425
|
)
|
410
426
|
|
427
|
+
if getv(from_object, ['computer_use']) is not None:
|
428
|
+
setv(
|
429
|
+
to_object,
|
430
|
+
['computerUse'],
|
431
|
+
_ToolComputerUse_to_mldev(
|
432
|
+
getv(from_object, ['computer_use']), to_object
|
433
|
+
),
|
434
|
+
)
|
435
|
+
|
411
436
|
if getv(from_object, ['code_execution']) is not None:
|
412
437
|
setv(to_object, ['codeExecution'], getv(from_object, ['code_execution']))
|
413
438
|
|
414
|
-
if getv(from_object, ['computer_use']) is not None:
|
415
|
-
setv(to_object, ['computerUse'], getv(from_object, ['computer_use']))
|
416
|
-
|
417
439
|
return to_object
|
418
440
|
|
419
441
|
|
@@ -1098,6 +1120,13 @@ def _LiveMusicGenerationConfig_to_mldev(
|
|
1098
1120
|
getv(from_object, ['only_bass_and_drums']),
|
1099
1121
|
)
|
1100
1122
|
|
1123
|
+
if getv(from_object, ['music_generation_mode']) is not None:
|
1124
|
+
setv(
|
1125
|
+
to_object,
|
1126
|
+
['musicGenerationMode'],
|
1127
|
+
getv(from_object, ['music_generation_mode']),
|
1128
|
+
)
|
1129
|
+
|
1101
1130
|
return to_object
|
1102
1131
|
|
1103
1132
|
|
@@ -1437,6 +1466,9 @@ def _GoogleSearch_to_vertex(
|
|
1437
1466
|
),
|
1438
1467
|
)
|
1439
1468
|
|
1469
|
+
if getv(from_object, ['exclude_domains']) is not None:
|
1470
|
+
setv(to_object, ['excludeDomains'], getv(from_object, ['exclude_domains']))
|
1471
|
+
|
1440
1472
|
return to_object
|
1441
1473
|
|
1442
1474
|
|
@@ -1480,6 +1512,8 @@ def _EnterpriseWebSearch_to_vertex(
|
|
1480
1512
|
parent_object: Optional[dict[str, Any]] = None,
|
1481
1513
|
) -> dict[str, Any]:
|
1482
1514
|
to_object: dict[str, Any] = {}
|
1515
|
+
if getv(from_object, ['exclude_domains']) is not None:
|
1516
|
+
setv(to_object, ['excludeDomains'], getv(from_object, ['exclude_domains']))
|
1483
1517
|
|
1484
1518
|
return to_object
|
1485
1519
|
|
@@ -1559,6 +1593,17 @@ def _UrlContext_to_vertex(
|
|
1559
1593
|
return to_object
|
1560
1594
|
|
1561
1595
|
|
1596
|
+
def _ToolComputerUse_to_vertex(
|
1597
|
+
from_object: Union[dict[str, Any], object],
|
1598
|
+
parent_object: Optional[dict[str, Any]] = None,
|
1599
|
+
) -> dict[str, Any]:
|
1600
|
+
to_object: dict[str, Any] = {}
|
1601
|
+
if getv(from_object, ['environment']) is not None:
|
1602
|
+
setv(to_object, ['environment'], getv(from_object, ['environment']))
|
1603
|
+
|
1604
|
+
return to_object
|
1605
|
+
|
1606
|
+
|
1562
1607
|
def _Tool_to_vertex(
|
1563
1608
|
from_object: Union[dict[str, Any], object],
|
1564
1609
|
parent_object: Optional[dict[str, Any]] = None,
|
@@ -1618,12 +1663,18 @@ def _Tool_to_vertex(
|
|
1618
1663
|
_UrlContext_to_vertex(getv(from_object, ['url_context']), to_object),
|
1619
1664
|
)
|
1620
1665
|
|
1666
|
+
if getv(from_object, ['computer_use']) is not None:
|
1667
|
+
setv(
|
1668
|
+
to_object,
|
1669
|
+
['computerUse'],
|
1670
|
+
_ToolComputerUse_to_vertex(
|
1671
|
+
getv(from_object, ['computer_use']), to_object
|
1672
|
+
),
|
1673
|
+
)
|
1674
|
+
|
1621
1675
|
if getv(from_object, ['code_execution']) is not None:
|
1622
1676
|
setv(to_object, ['codeExecution'], getv(from_object, ['code_execution']))
|
1623
1677
|
|
1624
|
-
if getv(from_object, ['computer_use']) is not None:
|
1625
|
-
setv(to_object, ['computerUse'], getv(from_object, ['computer_use']))
|
1626
|
-
|
1627
1678
|
return to_object
|
1628
1679
|
|
1629
1680
|
|
@@ -2871,6 +2922,13 @@ def _LiveMusicGenerationConfig_from_mldev(
|
|
2871
2922
|
getv(from_object, ['onlyBassAndDrums']),
|
2872
2923
|
)
|
2873
2924
|
|
2925
|
+
if getv(from_object, ['musicGenerationMode']) is not None:
|
2926
|
+
setv(
|
2927
|
+
to_object,
|
2928
|
+
['music_generation_mode'],
|
2929
|
+
getv(from_object, ['musicGenerationMode']),
|
2930
|
+
)
|
2931
|
+
|
2874
2932
|
return to_object
|
2875
2933
|
|
2876
2934
|
|
@@ -312,6 +312,11 @@ def _GoogleSearch_to_mldev(
|
|
312
312
|
_Interval_to_mldev(getv(from_object, ['time_range_filter']), to_object),
|
313
313
|
)
|
314
314
|
|
315
|
+
if getv(from_object, ['exclude_domains']) is not None:
|
316
|
+
raise ValueError(
|
317
|
+
'exclude_domains parameter is not supported in Gemini API.'
|
318
|
+
)
|
319
|
+
|
315
320
|
return to_object
|
316
321
|
|
317
322
|
|
@@ -359,6 +364,17 @@ def _UrlContext_to_mldev(
|
|
359
364
|
return to_object
|
360
365
|
|
361
366
|
|
367
|
+
def _ToolComputerUse_to_mldev(
|
368
|
+
from_object: Union[dict[str, Any], object],
|
369
|
+
parent_object: Optional[dict[str, Any]] = None,
|
370
|
+
) -> dict[str, Any]:
|
371
|
+
to_object: dict[str, Any] = {}
|
372
|
+
if getv(from_object, ['environment']) is not None:
|
373
|
+
setv(to_object, ['environment'], getv(from_object, ['environment']))
|
374
|
+
|
375
|
+
return to_object
|
376
|
+
|
377
|
+
|
362
378
|
def _Tool_to_mldev(
|
363
379
|
from_object: Union[dict[str, Any], object],
|
364
380
|
parent_object: Optional[dict[str, Any]] = None,
|
@@ -408,12 +424,18 @@ def _Tool_to_mldev(
|
|
408
424
|
_UrlContext_to_mldev(getv(from_object, ['url_context']), to_object),
|
409
425
|
)
|
410
426
|
|
427
|
+
if getv(from_object, ['computer_use']) is not None:
|
428
|
+
setv(
|
429
|
+
to_object,
|
430
|
+
['computerUse'],
|
431
|
+
_ToolComputerUse_to_mldev(
|
432
|
+
getv(from_object, ['computer_use']), to_object
|
433
|
+
),
|
434
|
+
)
|
435
|
+
|
411
436
|
if getv(from_object, ['code_execution']) is not None:
|
412
437
|
setv(to_object, ['codeExecution'], getv(from_object, ['code_execution']))
|
413
438
|
|
414
|
-
if getv(from_object, ['computer_use']) is not None:
|
415
|
-
setv(to_object, ['computerUse'], getv(from_object, ['computer_use']))
|
416
|
-
|
417
439
|
return to_object
|
418
440
|
|
419
441
|
|
@@ -28,6 +28,7 @@ import types as builtin_types
|
|
28
28
|
import typing
|
29
29
|
from typing import Any, GenericAlias, List, Optional, Sequence, Union # type: ignore[attr-defined]
|
30
30
|
from ._mcp_utils import mcp_to_gemini_tool
|
31
|
+
from ._common import get_value_by_path as getv
|
31
32
|
|
32
33
|
if typing.TYPE_CHECKING:
|
33
34
|
import PIL.Image
|
@@ -1224,3 +1225,53 @@ def t_tool_response(
|
|
1224
1225
|
f'Could not convert input (type "{type(input)}") to '
|
1225
1226
|
'`types.LiveClientToolResponse`'
|
1226
1227
|
) from e
|
1228
|
+
|
1229
|
+
|
1230
|
+
def t_metrics(
|
1231
|
+
metrics: list[types.MetricSubclass]
|
1232
|
+
) -> list[dict[str, Any]]:
|
1233
|
+
"""Prepares the metric payload for the evaluation request.
|
1234
|
+
|
1235
|
+
Args:
|
1236
|
+
request_dict: The dictionary containing the request details.
|
1237
|
+
resolved_metrics: A list of resolved metric objects.
|
1238
|
+
|
1239
|
+
Returns:
|
1240
|
+
The updated request dictionary with the prepared metric payload.
|
1241
|
+
"""
|
1242
|
+
metrics_payload = []
|
1243
|
+
|
1244
|
+
for metric in metrics:
|
1245
|
+
metric_payload_item: dict[str, Any] = {}
|
1246
|
+
metric_payload_item['aggregation_metrics'] = [
|
1247
|
+
'AVERAGE',
|
1248
|
+
'STANDARD_DEVIATION',
|
1249
|
+
]
|
1250
|
+
|
1251
|
+
metric_name = getv(metric, ['name']).lower()
|
1252
|
+
|
1253
|
+
if metric_name == 'exact_match':
|
1254
|
+
metric_payload_item['exact_match_spec'] = {}
|
1255
|
+
elif metric_name == 'bleu':
|
1256
|
+
metric_payload_item['bleu_spec'] = {}
|
1257
|
+
elif metric_name.startswith('rouge'):
|
1258
|
+
rouge_type = metric_name.replace("_", "")
|
1259
|
+
metric_payload_item['rouge_spec'] = {'rouge_type': rouge_type}
|
1260
|
+
|
1261
|
+
elif hasattr(metric, 'prompt_template') and metric.prompt_template:
|
1262
|
+
pointwise_spec = {'metric_prompt_template': metric.prompt_template}
|
1263
|
+
system_instruction = getv(metric, ['judge_model_system_instruction'])
|
1264
|
+
if system_instruction:
|
1265
|
+
pointwise_spec['system_instruction'] = system_instruction
|
1266
|
+
return_raw_output = getv(metric, ['return_raw_output'])
|
1267
|
+
if return_raw_output:
|
1268
|
+
pointwise_spec['custom_output_format_config'] = { # type: ignore[assignment]
|
1269
|
+
'return_raw_output': return_raw_output
|
1270
|
+
}
|
1271
|
+
metric_payload_item['pointwise_metric_spec'] = pointwise_spec
|
1272
|
+
else:
|
1273
|
+
raise ValueError(
|
1274
|
+
'Unsupported metric type or invalid metric name:' f' {metric_name}'
|
1275
|
+
)
|
1276
|
+
metrics_payload.append(metric_payload_item)
|
1277
|
+
return metrics_payload
|