google-genai 1.17.0__py3-none-any.whl → 1.19.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/__init__.py CHANGED
@@ -17,9 +17,8 @@
17
17
 
18
18
  from . import version
19
19
  from .client import Client
20
- from .live import live_ephemeral_connect
21
20
 
22
21
 
23
22
  __version__ = version.__version__
24
23
 
25
- __all__ = ['Client', 'live_ephemeral_connect']
24
+ __all__ = ['Client']
@@ -63,6 +63,31 @@ 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
+
71
+ # This method checks for the API key in the environment variables. Google API
72
+ # key is precedenced over Gemini API key.
73
+ def _get_env_api_key() -> Optional[str]:
74
+ """Gets the API key from environment variables, prioritizing GOOGLE_API_KEY.
75
+
76
+ Returns:
77
+ The API key string if found, otherwise None. Empty string is considered
78
+ invalid.
79
+ """
80
+ env_google_api_key = os.environ.get('GOOGLE_API_KEY', None)
81
+ env_gemini_api_key = os.environ.get('GEMINI_API_KEY', None)
82
+ if env_google_api_key and env_gemini_api_key:
83
+ logger.warning(
84
+ 'Both GOOGLE_API_KEY and GEMINI_API_KEY are set. Using'
85
+ ' GOOGLE_API_KEY.'
86
+ )
87
+
88
+ return env_google_api_key or env_gemini_api_key or None
89
+
90
+
66
91
  def _append_library_version_headers(headers: dict[str, str]) -> None:
67
92
  """Appends the telemetry header to the headers dict."""
68
93
  library_label = f'google-genai-sdk/{version.__version__}'
@@ -366,7 +391,7 @@ class BaseApiClient:
366
391
  # Retrieve implicitly set values from the environment.
367
392
  env_project = os.environ.get('GOOGLE_CLOUD_PROJECT', None)
368
393
  env_location = os.environ.get('GOOGLE_CLOUD_LOCATION', None)
369
- env_api_key = os.environ.get('GOOGLE_API_KEY', None)
394
+ env_api_key = _get_env_api_key()
370
395
  self.project = project or env_project
371
396
  self.location = location or env_location
372
397
  self.api_key = api_key or env_api_key
@@ -625,6 +650,11 @@ class BaseApiClient:
625
650
  versioned_path,
626
651
  )
627
652
 
653
+ if self.api_key and self.api_key.startswith('auth_tokens/'):
654
+ raise EphemeralTokenAPIKeyError(
655
+ 'Ephemeral tokens can only be used with the live API.'
656
+ )
657
+
628
658
  timeout_in_seconds = _get_timeout_in_seconds(patched_http_options.timeout)
629
659
 
630
660
  if patched_http_options.headers is None:
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 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):
@@ -353,6 +353,13 @@ def _Part_to_mldev(
353
353
  ),
354
354
  )
355
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
+
356
363
  if getv(from_object, ['code_execution_result']) is not None:
357
364
  setv(
358
365
  to_object,
@@ -415,6 +422,13 @@ def _Part_to_vertex(
415
422
  ),
416
423
  )
417
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
+
418
432
  if getv(from_object, ['code_execution_result']) is not None:
419
433
  setv(
420
434
  to_object,
@@ -967,7 +981,13 @@ def _Tool_to_vertex(
967
981
  )
968
982
 
969
983
  if getv(from_object, ['url_context']) is not None:
970
- raise ValueError('url_context parameter is not supported in Vertex AI.')
984
+ setv(
985
+ to_object,
986
+ ['urlContext'],
987
+ _UrlContext_to_vertex(
988
+ api_client, getv(from_object, ['url_context']), to_object
989
+ ),
990
+ )
971
991
 
972
992
  if getv(from_object, ['code_execution']) is not None:
973
993
  setv(to_object, ['codeExecution'], getv(from_object, ['code_execution']))
@@ -2759,6 +2779,13 @@ def _Part_from_mldev(
2759
2779
  ),
2760
2780
  )
2761
2781
 
2782
+ if getv(from_object, ['thoughtSignature']) is not None:
2783
+ setv(
2784
+ to_object,
2785
+ ['thought_signature'],
2786
+ getv(from_object, ['thoughtSignature']),
2787
+ )
2788
+
2762
2789
  if getv(from_object, ['codeExecutionResult']) is not None:
2763
2790
  setv(
2764
2791
  to_object,
@@ -2821,6 +2848,13 @@ def _Part_from_vertex(
2821
2848
  ),
2822
2849
  )
2823
2850
 
2851
+ if getv(from_object, ['thoughtSignature']) is not None:
2852
+ setv(
2853
+ to_object,
2854
+ ['thought_signature'],
2855
+ getv(from_object, ['thoughtSignature']),
2856
+ )
2857
+
2824
2858
  if getv(from_object, ['codeExecutionResult']) is not None:
2825
2859
  setv(
2826
2860
  to_object,
@@ -353,6 +353,13 @@ def _Part_to_mldev(
353
353
  ),
354
354
  )
355
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
+
356
363
  if getv(from_object, ['code_execution_result']) is not None:
357
364
  setv(
358
365
  to_object,
@@ -415,6 +422,13 @@ def _Part_to_vertex(
415
422
  ),
416
423
  )
417
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
+
418
432
  if getv(from_object, ['code_execution_result']) is not None:
419
433
  setv(
420
434
  to_object,
@@ -967,7 +981,13 @@ def _Tool_to_vertex(
967
981
  )
968
982
 
969
983
  if getv(from_object, ['url_context']) is not None:
970
- raise ValueError('url_context parameter is not supported in Vertex AI.')
984
+ setv(
985
+ to_object,
986
+ ['urlContext'],
987
+ _UrlContext_to_vertex(
988
+ api_client, getv(from_object, ['url_context']), to_object
989
+ ),
990
+ )
971
991
 
972
992
  if getv(from_object, ['code_execution']) is not None:
973
993
  setv(to_object, ['codeExecution'], getv(from_object, ['code_execution']))
@@ -1585,7 +1605,7 @@ def _LiveConnectConfig_to_vertex(
1585
1605
  return to_object
1586
1606
 
1587
1607
 
1588
- def _LiveEphemeralParameters_to_mldev(
1608
+ def _LiveConnectConstraints_to_mldev(
1589
1609
  api_client: BaseApiClient,
1590
1610
  from_object: Union[dict[str, Any], object],
1591
1611
  parent_object: Optional[dict[str, Any]] = None,
@@ -1610,7 +1630,7 @@ def _LiveEphemeralParameters_to_mldev(
1610
1630
  return to_object
1611
1631
 
1612
1632
 
1613
- def _LiveEphemeralParameters_to_vertex(
1633
+ def _LiveConnectConstraints_to_vertex(
1614
1634
  api_client: BaseApiClient,
1615
1635
  from_object: Union[dict[str, Any], object],
1616
1636
  parent_object: Optional[dict[str, Any]] = None,
@@ -1645,13 +1665,13 @@ def _CreateAuthTokenConfig_to_mldev(
1645
1665
  if getv(from_object, ['uses']) is not None:
1646
1666
  setv(parent_object, ['uses'], getv(from_object, ['uses']))
1647
1667
 
1648
- if getv(from_object, ['live_ephemeral_parameters']) is not None:
1668
+ if getv(from_object, ['live_connect_constraints']) is not None:
1649
1669
  setv(
1650
1670
  parent_object,
1651
1671
  ['bidiGenerateContentSetup'],
1652
- _LiveEphemeralParameters_to_mldev(
1672
+ _LiveConnectConstraints_to_mldev(
1653
1673
  api_client,
1654
- getv(from_object, ['live_ephemeral_parameters']),
1674
+ getv(from_object, ['live_connect_constraints']),
1655
1675
  to_object,
1656
1676
  ),
1657
1677
  )
@@ -1684,9 +1704,9 @@ def _CreateAuthTokenConfig_to_vertex(
1684
1704
  if getv(from_object, ['uses']) is not None:
1685
1705
  raise ValueError('uses parameter is not supported in Vertex AI.')
1686
1706
 
1687
- if getv(from_object, ['live_ephemeral_parameters']) is not None:
1707
+ if getv(from_object, ['live_connect_constraints']) is not None:
1688
1708
  raise ValueError(
1689
- 'live_ephemeral_parameters parameter is not supported in Vertex AI.'
1709
+ 'live_connect_constraints parameter is not supported in Vertex AI.'
1690
1710
  )
1691
1711
 
1692
1712
  if getv(from_object, ['lock_additional_fields']) is not None:
@@ -761,7 +761,8 @@ def process_schema(
761
761
  schema_type = schema.get('type')
762
762
  if isinstance(schema_type, Enum):
763
763
  schema_type = schema_type.value
764
- schema_type = schema_type.upper()
764
+ if isinstance(schema_type, str):
765
+ schema_type = schema_type.upper()
765
766
 
766
767
  # model_json_schema() returns a schema with a 'const' field when a Literal with one value is provided as a pydantic field
767
768
  # For example `genre: Literal['action']` becomes: {'const': 'action', 'title': 'Genre', 'type': 'string'}
google/genai/caches.py CHANGED
@@ -120,6 +120,13 @@ def _Part_to_mldev(
120
120
  ),
121
121
  )
122
122
 
123
+ if getv(from_object, ['thought_signature']) is not None:
124
+ setv(
125
+ to_object,
126
+ ['thoughtSignature'],
127
+ getv(from_object, ['thought_signature']),
128
+ )
129
+
123
130
  if getv(from_object, ['code_execution_result']) is not None:
124
131
  setv(
125
132
  to_object,
@@ -785,6 +792,13 @@ def _Part_to_vertex(
785
792
  ),
786
793
  )
787
794
 
795
+ if getv(from_object, ['thought_signature']) is not None:
796
+ setv(
797
+ to_object,
798
+ ['thoughtSignature'],
799
+ getv(from_object, ['thought_signature']),
800
+ )
801
+
788
802
  if getv(from_object, ['code_execution_result']) is not None:
789
803
  setv(
790
804
  to_object,
@@ -1078,7 +1092,13 @@ def _Tool_to_vertex(
1078
1092
  )
1079
1093
 
1080
1094
  if getv(from_object, ['url_context']) is not None:
1081
- raise ValueError('url_context parameter is not supported in Vertex AI.')
1095
+ setv(
1096
+ to_object,
1097
+ ['urlContext'],
1098
+ _UrlContext_to_vertex(
1099
+ api_client, getv(from_object, ['url_context']), to_object
1100
+ ),
1101
+ )
1082
1102
 
1083
1103
  if getv(from_object, ['code_execution']) is not None:
1084
1104
  setv(to_object, ['codeExecution'], getv(from_object, ['code_execution']))
google/genai/chats.py CHANGED
@@ -160,7 +160,7 @@ class _BaseChat:
160
160
  # Because the AFC input contains the entire curated chat history in
161
161
  # addition to the new user input, we need to truncate the AFC history
162
162
  # to deduplicate the existing chat history.
163
- automatic_function_calling_history[len(self._curated_history):]
163
+ automatic_function_calling_history[len(self._curated_history) :]
164
164
  if automatic_function_calling_history
165
165
  else [user_input]
166
166
  )
@@ -328,7 +328,7 @@ class Chat(_BaseChat):
328
328
  yield chunk
329
329
  automatic_function_calling_history = (
330
330
  chunk.automatic_function_calling_history
331
- if chunk.automatic_function_calling_history
331
+ if chunk is not None and chunk.automatic_function_calling_history
332
332
  else []
333
333
  )
334
334
  self.record_history(
@@ -495,9 +495,12 @@ class AsyncChat(_BaseChat):
495
495
  self.record_history(
496
496
  user_input=input_content,
497
497
  model_output=output_contents,
498
- automatic_function_calling_history=chunk.automatic_function_calling_history if chunk.automatic_function_calling_history else [],
498
+ automatic_function_calling_history=chunk.automatic_function_calling_history
499
+ if chunk is not None and chunk.automatic_function_calling_history
500
+ else [],
499
501
  is_valid=is_valid,
500
502
  )
503
+
501
504
  return async_generator() # type: ignore[no-untyped-call, no-any-return]
502
505
 
503
506
 
google/genai/live.py CHANGED
@@ -34,6 +34,7 @@ from . import _live_converters as live_converters
34
34
  from . import _mcp_utils
35
35
  from . import _transformers as t
36
36
  from . import client
37
+ from . import errors
37
38
  from . import types
38
39
  from ._api_client import BaseApiClient
39
40
  from ._common import get_value_by_path as getv
@@ -78,10 +79,6 @@ _FUNCTION_RESPONSE_REQUIRES_ID = (
78
79
  ' response of a ToolCall.FunctionalCalls in Google AI.'
79
80
  )
80
81
 
81
-
82
- _DUMMY_KEY = 'dummy_key'
83
-
84
-
85
82
  class AsyncSession:
86
83
  """[Preview] AsyncSession."""
87
84
 
@@ -912,25 +909,10 @@ class AsyncLive(_api_module.BaseModule):
912
909
  Yields:
913
910
  An AsyncSession object.
914
911
  """
915
- async with self._connect(
916
- model=model,
917
- config=config,
918
- ) as session:
919
- yield session
920
-
921
- @contextlib.asynccontextmanager
922
- async def _connect(
923
- self,
924
- *,
925
- model: Optional[str] = None,
926
- config: Optional[types.LiveConnectConfigOrDict] = None,
927
- uri: Optional[str] = None,
928
- ) -> AsyncIterator[AsyncSession]:
929
-
930
912
  # TODO(b/404946570): Support per request http options.
931
913
  if isinstance(config, dict):
932
914
  config = types.LiveConnectConfig(**config)
933
- if config and config.http_options and uri is None:
915
+ if config and config.http_options:
934
916
  raise ValueError(
935
917
  'google.genai.client.aio.live.connect() does not support'
936
918
  ' http_options at request-level in LiveConnectConfig yet. Please use'
@@ -945,10 +927,22 @@ class AsyncLive(_api_module.BaseModule):
945
927
  parameter_model = await _t_live_connect_config(self._api_client, config)
946
928
 
947
929
  if self._api_client.api_key and not self._api_client.vertexai:
948
- api_key = self._api_client.api_key
949
930
  version = self._api_client._http_options.api_version
950
- if uri is None:
951
- uri = f'{base_url}/ws/google.ai.generativelanguage.{version}.GenerativeService.BidiGenerateContent?key={api_key}'
931
+ api_key = self._api_client.api_key
932
+ method = 'BidiGenerateContent'
933
+ key_name = 'key'
934
+ if api_key.startswith('auth_tokens/'):
935
+ warnings.warn(
936
+ message=(
937
+ "The SDK's ephemeral token support is experimental, and may"
938
+ ' change in future versions.'
939
+ ),
940
+ category=errors.ExperimentalWarning,
941
+ )
942
+ method = 'BidiGenerateContentConstrained'
943
+ key_name = 'access_token'
944
+
945
+ uri = f'{base_url}/ws/google.ai.generativelanguage.{version}.GenerativeService.{method}?{key_name}={api_key}'
952
946
  headers = self._api_client._http_options.headers
953
947
 
954
948
  request_dict = _common.convert_to_dict(
@@ -969,8 +963,7 @@ class AsyncLive(_api_module.BaseModule):
969
963
  # Headers already contains api key for express mode.
970
964
  api_key = self._api_client.api_key
971
965
  version = self._api_client._http_options.api_version
972
- if uri is None:
973
- uri = f'{base_url}/ws/google.cloud.aiplatform.{version}.LlmBidiService/BidiGenerateContent'
966
+ uri = f'{base_url}/ws/google.cloud.aiplatform.{version}.LlmBidiService/BidiGenerateContent'
974
967
  headers = self._api_client._http_options.headers
975
968
 
976
969
  request_dict = _common.convert_to_dict(
@@ -1060,83 +1053,6 @@ class AsyncLive(_api_module.BaseModule):
1060
1053
  yield AsyncSession(api_client=self._api_client, websocket=ws)
1061
1054
 
1062
1055
 
1063
- @_common.experimental_warning(
1064
- "The SDK's Live API connection with ephemeral token implementation is"
1065
- ' experimental, and may change in future versions.',
1066
- )
1067
- @contextlib.asynccontextmanager
1068
- async def live_ephemeral_connect(
1069
- access_token: str,
1070
- model: Optional[str] = None,
1071
- config: Optional[types.LiveConnectConfigOrDict] = None,
1072
- ) -> AsyncIterator[AsyncSession]:
1073
- """[Experimental] Connect to the live server using ephermeral token (Gemini Developer API only).
1074
-
1075
- Note: the live API is currently in experimental.
1076
-
1077
- Usage:
1078
-
1079
- .. code-block:: python
1080
- from google import genai
1081
-
1082
- config = {}
1083
- async with genai.live_ephemeral_connect(
1084
- access_token='auth_tokens/12345',
1085
- model='...',
1086
- config=config,
1087
- http_options=types.HttpOptions(api_version='v1beta'),
1088
- ) as session:
1089
- await session.send_client_content(
1090
- turns=types.Content(
1091
- role='user',
1092
- parts=[types.Part(text='hello!')]
1093
- ),
1094
- turn_complete=True
1095
- )
1096
-
1097
- async for message in session.receive():
1098
- print(message)
1099
-
1100
- Args:
1101
- access_token: The access token to use for the Live session. It can be
1102
- generated by the `client.tokens.create` method.
1103
- model: The model to use for the Live session.
1104
- config: The configuration for the Live session.
1105
-
1106
- Yields:
1107
- An AsyncSession object.
1108
- """
1109
- if isinstance(config, dict):
1110
- config = types.LiveConnectConfig(**config)
1111
-
1112
- http_options = config.http_options if config else None
1113
-
1114
- base_url = (
1115
- http_options.base_url
1116
- if http_options and http_options.base_url
1117
- else 'https://generativelanguage.googleapis.com/'
1118
- )
1119
- api_version = (
1120
- http_options.api_version
1121
- if http_options and http_options.api_version
1122
- else 'v1beta'
1123
- )
1124
- internal_client = client.Client(
1125
- api_key=_DUMMY_KEY, # Can't be None during initialization
1126
- http_options=types.HttpOptions(
1127
- base_url=base_url,
1128
- api_version=api_version,
1129
- ),
1130
- )
1131
- websocket_base_url = internal_client._api_client._websocket_base_url()
1132
- uri = f'{websocket_base_url}/ws/google.ai.generativelanguage.{api_version}.GenerativeService.BidiGenerateContentConstrained?access_token={access_token}'
1133
-
1134
- async with internal_client.aio.live._connect(
1135
- model=model, config=config, uri=uri
1136
- ) as session:
1137
- yield session
1138
-
1139
-
1140
1056
  async def _t_live_connect_config(
1141
1057
  api_client: BaseApiClient,
1142
1058
  config: Optional[types.LiveConnectConfigOrDict],
google/genai/models.py CHANGED
@@ -123,6 +123,13 @@ def _Part_to_mldev(
123
123
  ),
124
124
  )
125
125
 
126
+ if getv(from_object, ['thought_signature']) is not None:
127
+ setv(
128
+ to_object,
129
+ ['thoughtSignature'],
130
+ getv(from_object, ['thought_signature']),
131
+ )
132
+
126
133
  if getv(from_object, ['code_execution_result']) is not None:
127
134
  setv(
128
135
  to_object,
@@ -1415,7 +1422,11 @@ def _GenerateVideosConfig_to_mldev(
1415
1422
  )
1416
1423
 
1417
1424
  if getv(from_object, ['enhance_prompt']) is not None:
1418
- raise ValueError('enhance_prompt parameter is not supported in Gemini API.')
1425
+ setv(
1426
+ parent_object,
1427
+ ['parameters', 'enhancePrompt'],
1428
+ getv(from_object, ['enhance_prompt']),
1429
+ )
1419
1430
 
1420
1431
  if getv(from_object, ['generate_audio']) is not None:
1421
1432
  raise ValueError('generate_audio parameter is not supported in Gemini API.')
@@ -1548,6 +1559,13 @@ def _Part_to_vertex(
1548
1559
  ),
1549
1560
  )
1550
1561
 
1562
+ if getv(from_object, ['thought_signature']) is not None:
1563
+ setv(
1564
+ to_object,
1565
+ ['thoughtSignature'],
1566
+ getv(from_object, ['thought_signature']),
1567
+ )
1568
+
1551
1569
  if getv(from_object, ['code_execution_result']) is not None:
1552
1570
  setv(
1553
1571
  to_object,
@@ -1967,7 +1985,13 @@ def _Tool_to_vertex(
1967
1985
  )
1968
1986
 
1969
1987
  if getv(from_object, ['url_context']) is not None:
1970
- raise ValueError('url_context parameter is not supported in Vertex AI.')
1988
+ setv(
1989
+ to_object,
1990
+ ['urlContext'],
1991
+ _UrlContext_to_vertex(
1992
+ api_client, getv(from_object, ['url_context']), to_object
1993
+ ),
1994
+ )
1971
1995
 
1972
1996
  if getv(from_object, ['code_execution']) is not None:
1973
1997
  setv(to_object, ['codeExecution'], getv(from_object, ['code_execution']))
@@ -3441,6 +3465,13 @@ def _Part_from_mldev(
3441
3465
  ),
3442
3466
  )
3443
3467
 
3468
+ if getv(from_object, ['thoughtSignature']) is not None:
3469
+ setv(
3470
+ to_object,
3471
+ ['thought_signature'],
3472
+ getv(from_object, ['thoughtSignature']),
3473
+ )
3474
+
3444
3475
  if getv(from_object, ['codeExecutionResult']) is not None:
3445
3476
  setv(
3446
3477
  to_object,
@@ -4138,6 +4169,13 @@ def _Part_from_vertex(
4138
4169
  ),
4139
4170
  )
4140
4171
 
4172
+ if getv(from_object, ['thoughtSignature']) is not None:
4173
+ setv(
4174
+ to_object,
4175
+ ['thought_signature'],
4176
+ getv(from_object, ['thoughtSignature']),
4177
+ )
4178
+
4141
4179
  if getv(from_object, ['codeExecutionResult']) is not None:
4142
4180
  setv(
4143
4181
  to_object,
@@ -4266,6 +4304,15 @@ def _Candidate_from_vertex(
4266
4304
  if getv(from_object, ['finishReason']) is not None:
4267
4305
  setv(to_object, ['finish_reason'], getv(from_object, ['finishReason']))
4268
4306
 
4307
+ if getv(from_object, ['urlContextMetadata']) is not None:
4308
+ setv(
4309
+ to_object,
4310
+ ['url_context_metadata'],
4311
+ _UrlContextMetadata_from_vertex(
4312
+ api_client, getv(from_object, ['urlContextMetadata']), to_object
4313
+ ),
4314
+ )
4315
+
4269
4316
  if getv(from_object, ['avgLogprobs']) is not None:
4270
4317
  setv(to_object, ['avg_logprobs'], getv(from_object, ['avgLogprobs']))
4271
4318
 
@@ -6105,7 +6152,8 @@ class Models(_api_module.BaseModule):
6105
6152
  )
6106
6153
  yield chunk
6107
6154
  if (
6108
- not chunk.candidates
6155
+ chunk is None
6156
+ or not chunk.candidates
6109
6157
  or not chunk.candidates[0].content
6110
6158
  or not chunk.candidates[0].content.parts
6111
6159
  ):
@@ -6120,7 +6168,7 @@ class Models(_api_module.BaseModule):
6120
6168
  break
6121
6169
 
6122
6170
  # Append function response parts to contents for the next request.
6123
- if chunk.candidates is not None:
6171
+ if chunk is not None and chunk.candidates is not None:
6124
6172
  func_call_content = chunk.candidates[0].content
6125
6173
  func_response_content = types.Content(
6126
6174
  role='user',
@@ -7609,7 +7657,8 @@ class AsyncModels(_api_module.BaseModule):
7609
7657
  )
7610
7658
  yield chunk
7611
7659
  if (
7612
- not chunk.candidates
7660
+ chunk is None
7661
+ or not chunk.candidates
7613
7662
  or not chunk.candidates[0].content
7614
7663
  or not chunk.candidates[0].content.parts
7615
7664
  ):
@@ -7623,6 +7672,8 @@ class AsyncModels(_api_module.BaseModule):
7623
7672
  if not func_response_parts:
7624
7673
  break
7625
7674
 
7675
+ if chunk is None:
7676
+ continue
7626
7677
  # Append function response parts to contents for the next request.
7627
7678
  func_call_content = chunk.candidates[0].content
7628
7679
  func_response_content = types.Content(
google/genai/tokens.py CHANGED
@@ -143,6 +143,7 @@ class Tokens(_api_module.BaseModule):
143
143
  Usage:
144
144
 
145
145
  .. code-block:: python
146
+
146
147
  # Case 1: If LiveEphemeralParameters is unset, unlock LiveConnectConfig
147
148
  # when using the token in Live API sessions. Each session connection can
148
149
  # use a different configuration.
@@ -154,6 +155,7 @@ class Tokens(_api_module.BaseModule):
154
155
  auth_token = client.tokens.create(config=config)
155
156
 
156
157
  .. code-block:: python
158
+
157
159
  # Case 2: If LiveEphemeralParameters is set, lock all fields in
158
160
  # LiveConnectConfig when using the token in Live API sessions. For
159
161
  # example, changing `output_audio_transcription` in the Live API
@@ -170,7 +172,9 @@ class Tokens(_api_module.BaseModule):
170
172
  ),
171
173
  )
172
174
  )
173
- .. code-block:: python
175
+
176
+ .. code-block:: python
177
+
174
178
  # Case 3: If LiveEphemeralParameters is set and lockAdditionalFields is
175
179
  # empty, lock LiveConnectConfig with set fields (e.g.
176
180
  # system_instruction in this example) when using the token in Live API
@@ -187,7 +191,8 @@ class Tokens(_api_module.BaseModule):
187
191
  )
188
192
  )
189
193
 
190
- .. code-block:: python
194
+ .. code-block:: python
195
+
191
196
  # Case 4: If LiveEphemeralParameters is set and lockAdditionalFields is
192
197
  # set, lock LiveConnectConfig with set and additional fields (e.g.
193
198
  # system_instruction, temperature in this example) when using the token
google/genai/types.py CHANGED
@@ -59,17 +59,21 @@ _is_mcp_imported = False
59
59
  if typing.TYPE_CHECKING:
60
60
  from mcp import types as mcp_types
61
61
  from mcp import ClientSession as McpClientSession
62
+ from mcp.types import CallToolResult as McpCallToolResult
62
63
 
63
64
  _is_mcp_imported = True
64
65
  else:
65
66
  McpClientSession: typing.Type = Any
67
+ McpCallToolResult: typing.Type = Any
66
68
  try:
67
69
  from mcp import types as mcp_types
68
70
  from mcp import ClientSession as McpClientSession
71
+ from mcp.types import CallToolResult as McpCallToolResult
69
72
 
70
73
  _is_mcp_imported = True
71
74
  except ImportError:
72
75
  McpClientSession = None
76
+ McpCallToolResult = None
73
77
 
74
78
  logger = logging.getLogger('google_genai.types')
75
79
 
@@ -225,6 +229,8 @@ class FinishReason(_common.CaseInSensitiveEnum):
225
229
  """The function call generated by the model is invalid."""
226
230
  IMAGE_SAFETY = 'IMAGE_SAFETY'
227
231
  """Token generation stopped because generated images have safety violations."""
232
+ UNEXPECTED_TOOL_CALL = 'UNEXPECTED_TOOL_CALL'
233
+ """The tool call generated by the model is invalid."""
228
234
 
229
235
 
230
236
  class HarmProbability(_common.CaseInSensitiveEnum):
@@ -843,6 +849,21 @@ class FunctionResponse(_common.BaseModel):
843
849
  description="""Required. The function response in JSON object format. Use "output" key to specify function output and "error" key to specify error details (if any). If "output" and "error" keys are not specified, then whole "response" is treated as function output.""",
844
850
  )
845
851
 
852
+ @classmethod
853
+ def from_mcp_response(
854
+ cls, *, name: str, response: McpCallToolResult
855
+ ) -> 'FunctionResponse':
856
+ if not _is_mcp_imported:
857
+ raise ValueError(
858
+ 'MCP response is not supported. Please ensure that the MCP library is'
859
+ ' imported.'
860
+ )
861
+
862
+ if response.isError:
863
+ return cls(name=name, response={'error': 'MCP response is error.'})
864
+ else:
865
+ return cls(name=name, response={'result': response.content})
866
+
846
867
 
847
868
  class FunctionResponseDict(TypedDict, total=False):
848
869
  """A function response."""
@@ -887,6 +908,10 @@ class Part(_common.BaseModel):
887
908
  file_data: Optional[FileData] = Field(
888
909
  default=None, description="""Optional. URI based data."""
889
910
  )
911
+ thought_signature: Optional[bytes] = Field(
912
+ default=None,
913
+ description="""An opaque signature for the thought so it can be reused in subsequent requests.""",
914
+ )
890
915
  code_execution_result: Optional[CodeExecutionResult] = Field(
891
916
  default=None,
892
917
  description="""Optional. Result of executing the [ExecutableCode].""",
@@ -984,6 +1009,9 @@ class PartDict(TypedDict, total=False):
984
1009
  file_data: Optional[FileDataDict]
985
1010
  """Optional. URI based data."""
986
1011
 
1012
+ thought_signature: Optional[bytes]
1013
+ """An opaque signature for the thought so it can be reused in subsequent requests."""
1014
+
987
1015
  code_execution_result: Optional[CodeExecutionResultDict]
988
1016
  """Optional. Result of executing the [ExecutableCode]."""
989
1017
 
@@ -1916,12 +1944,16 @@ class FunctionDeclaration(_common.BaseModel):
1916
1944
  from . import _automatic_function_calling_util
1917
1945
 
1918
1946
  parameters_properties = {}
1947
+ annotation_under_future = typing.get_type_hints(callable)
1919
1948
  for name, param in inspect.signature(callable).parameters.items():
1920
1949
  if param.kind in (
1921
1950
  inspect.Parameter.POSITIONAL_OR_KEYWORD,
1922
1951
  inspect.Parameter.KEYWORD_ONLY,
1923
1952
  inspect.Parameter.POSITIONAL_ONLY,
1924
1953
  ):
1954
+ # This snippet catches the case when type hints are stored as strings
1955
+ if isinstance(param.annotation, str):
1956
+ param = param.replace(annotation=annotation_under_future[name])
1925
1957
  schema = _automatic_function_calling_util._parse_schema_from_parameter(
1926
1958
  api_option, param, callable.__name__
1927
1959
  )
@@ -1943,6 +1975,7 @@ class FunctionDeclaration(_common.BaseModel):
1943
1975
  declaration.parameters
1944
1976
  )
1945
1977
  )
1978
+ # TODO: b/421991354 - Remove this check once the bug is fixed.
1946
1979
  if api_option == 'GEMINI_API':
1947
1980
  return declaration
1948
1981
 
@@ -1950,14 +1983,21 @@ class FunctionDeclaration(_common.BaseModel):
1950
1983
  if return_annotation is inspect._empty:
1951
1984
  return declaration
1952
1985
 
1986
+ return_value = inspect.Parameter(
1987
+ 'return_value',
1988
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
1989
+ annotation=return_annotation,
1990
+ )
1991
+
1992
+ # This snippet catches the case when type hints are stored as strings
1993
+ if isinstance(return_value.annotation, str):
1994
+ return_value = return_value.replace(
1995
+ annotation=annotation_under_future['return']
1996
+ )
1953
1997
  declaration.response = (
1954
1998
  _automatic_function_calling_util._parse_schema_from_parameter(
1955
1999
  api_option,
1956
- inspect.Parameter(
1957
- 'return_value',
1958
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
1959
- annotation=return_annotation,
1960
- ),
2000
+ return_value,
1961
2001
  callable.__name__,
1962
2002
  )
1963
2003
  )
@@ -12492,8 +12532,8 @@ class AuthTokenDict(TypedDict, total=False):
12492
12532
  AuthTokenOrDict = Union[AuthToken, AuthTokenDict]
12493
12533
 
12494
12534
 
12495
- class LiveEphemeralParameters(_common.BaseModel):
12496
- """Config for LiveEphemeralParameters for Auth Token creation."""
12535
+ class LiveConnectConstraints(_common.BaseModel):
12536
+ """Config for LiveConnectConstraints for Auth Token creation."""
12497
12537
 
12498
12538
  model: Optional[str] = Field(
12499
12539
  default=None,
@@ -12507,8 +12547,8 @@ class LiveEphemeralParameters(_common.BaseModel):
12507
12547
  )
12508
12548
 
12509
12549
 
12510
- class LiveEphemeralParametersDict(TypedDict, total=False):
12511
- """Config for LiveEphemeralParameters for Auth Token creation."""
12550
+ class LiveConnectConstraintsDict(TypedDict, total=False):
12551
+ """Config for LiveConnectConstraints for Auth Token creation."""
12512
12552
 
12513
12553
  model: Optional[str]
12514
12554
  """ID of the model to configure in the ephemeral token for Live API.
@@ -12519,8 +12559,8 @@ class LiveEphemeralParametersDict(TypedDict, total=False):
12519
12559
  """Configuration specific to Live API connections created using this token."""
12520
12560
 
12521
12561
 
12522
- LiveEphemeralParametersOrDict = Union[
12523
- LiveEphemeralParameters, LiveEphemeralParametersDict
12562
+ LiveConnectConstraintsOrDict = Union[
12563
+ LiveConnectConstraints, LiveConnectConstraintsDict
12524
12564
  ]
12525
12565
 
12526
12566
 
@@ -12553,7 +12593,7 @@ class CreateAuthTokenConfig(_common.BaseModel):
12553
12593
  then no limit is applied. Default is 1. Resuming a Live API session does
12554
12594
  not count as a use.""",
12555
12595
  )
12556
- live_ephemeral_parameters: Optional[LiveEphemeralParameters] = Field(
12596
+ live_connect_constraints: Optional[LiveConnectConstraints] = Field(
12557
12597
  default=None,
12558
12598
  description="""Configuration specific to Live API connections created using this token.""",
12559
12599
  )
@@ -12589,7 +12629,7 @@ class CreateAuthTokenConfigDict(TypedDict, total=False):
12589
12629
  then no limit is applied. Default is 1. Resuming a Live API session does
12590
12630
  not count as a use."""
12591
12631
 
12592
- live_ephemeral_parameters: Optional[LiveEphemeralParametersDict]
12632
+ live_connect_constraints: Optional[LiveConnectConstraintsDict]
12593
12633
  """Configuration specific to Live API connections created using this token."""
12594
12634
 
12595
12635
  lock_additional_fields: Optional[list[str]]
google/genai/version.py CHANGED
@@ -13,4 +13,4 @@
13
13
  # limitations under the License.
14
14
  #
15
15
 
16
- __version__ = '1.17.0' # x-release-please-version
16
+ __version__ = '1.19.0' # x-release-please-version
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-genai
3
- Version: 1.17.0
3
+ Version: 1.19.0
4
4
  Summary: GenAI Python SDK
5
5
  Author-email: Google LLC <googleapis-packages@google.com>
6
6
  License: Apache-2.0
@@ -1,35 +1,35 @@
1
- google/genai/__init__.py,sha256=Po1TtEyh1oa7fSqVI9Yzj2gywmo7OVDKEyqJ8rs9aAk,776
1
+ google/genai/__init__.py,sha256=SYTxz3Ho06pP2TBlvDU0FkUJz8ytbR3MgEpS9HvVYq4,709
2
2
  google/genai/_adapters.py,sha256=Kok38miNYJff2n--l0zEK_hbq0y2rWOH7k75J7SMYbQ,1744
3
- google/genai/_api_client.py,sha256=N31CFoU5pj29l3RBVNDeEeFKj96TN8ZVoXRGPwJALrE,37922
3
+ google/genai/_api_client.py,sha256=AOhv4_L9aTDzT6ALvH54s5ozPUPCx4w4ShLdh8qpXvM,38879
4
4
  google/genai/_api_module.py,sha256=lj8eUWx8_LBGBz-49qz6_ywWm3GYp3d8Bg5JoOHbtbI,902
5
5
  google/genai/_automatic_function_calling_util.py,sha256=IJkPq2fT9pYxYm5Pbu5-e0nBoZKoZla7yT4_txWRKLs,10324
6
6
  google/genai/_base_url.py,sha256=E5H4dew14Y16qfnB3XRnjSCi19cJVlkaMNoM_8ip-PM,1597
7
- google/genai/_common.py,sha256=JcRu5Qo5idx1caTEJDw4Li-aJz8UT4KNAqh4N0LTmDw,10573
7
+ google/genai/_common.py,sha256=hDqunjaAL__GXZzhr37QGOCR6z6PMc_EfiRy_ukAxHo,11545
8
8
  google/genai/_extra_utils.py,sha256=_w8hB9Ag7giRR94nIOBJFTLiLEufulPXkCZpJG1XY5g,19241
9
- google/genai/_live_converters.py,sha256=lNcvNaFRcUbI5OISDGi-vjD_5hN7neHsOYichm8-czk,114696
9
+ google/genai/_live_converters.py,sha256=8J0h9JUdkwkPFugFBAW4EKTMkZATMDZWgNkc9WOh2Hg,115488
10
10
  google/genai/_mcp_utils.py,sha256=khECx-DMuHemKzOQQ3msWp7FivPeEOnl3n1lvWc_b5o,3833
11
11
  google/genai/_replay_api_client.py,sha256=BrnKkdUVejpuHvqVHK0V4m4TjvL4LaBLHRMv6vw0vGE,21361
12
12
  google/genai/_test_api_client.py,sha256=4ruFIy5_1qcbKqqIBu3HSQbpSOBrxiecBtDZaTGFR1s,4797
13
- google/genai/_tokens_converters.py,sha256=SsHrhFD_mwNKWt8IecSvkpEnjVtC6B7HfqqX3xvhcfU,51040
14
- google/genai/_transformers.py,sha256=GineAcXvT7b-GG8o9c5P4uvmI3RO7m6ixFp40MOuITc,35520
13
+ google/genai/_tokens_converters.py,sha256=IUmpIC7nKjZzp_azavxXgeML35nYSC0KRkCnGG0iZao,51477
14
+ google/genai/_transformers.py,sha256=Ii8u54Ni8388jU8OAei5sixZAmFP3o-OKbxomzBul4o,35557
15
15
  google/genai/batches.py,sha256=gyNb--psM7cqXEP5RsJ908ismvsFFlDNRxt7mCwhYgM,34943
16
- google/genai/caches.py,sha256=KeDTnt9wSe0NFoA7mwwgm81plX6sUKvNRRvCNz5Un_c,67594
17
- google/genai/chats.py,sha256=I4PlyEmyC43CdlDpHlxXpwDeYKT36AnUl7RgYUX-O5U,16978
16
+ google/genai/caches.py,sha256=Z1DiYwZUjY9zrCapfEFOz77lwfWyCsAbnrcMF2tr24g,68038
17
+ google/genai/chats.py,sha256=Hl08SbpSCMS0kYiBNGNzPI8q3rijGEW_up0g2Wun9cY,17044
18
18
  google/genai/client.py,sha256=9AWoFL2g-OQFA4Zqpecg74b7h3i5Q9ZlXF4bFePV0zA,10759
19
19
  google/genai/errors.py,sha256=nLAVglUGuPdLqLcnN_VCifoi7V17u9BQ9AfxUdRzZLg,4795
20
20
  google/genai/files.py,sha256=PB5Rh5WiLOPHcqPR1inGyhqL9PiE0BFHSL4Dz9HKtl8,39516
21
- google/genai/live.py,sha256=3_ZMdHG2Y7nlzlLcN5jF1JWj7DhtIo6hrJmC6MEpDio,41747
21
+ google/genai/live.py,sha256=ZpXHO7AFayOz2viISxs34rlG85dFH9qk-5-qOC9pcZE,39375
22
22
  google/genai/live_music.py,sha256=lO-nDz2gEJ8bYesJC1Aru4IrsV7raEePf4sH3QQT6yI,7119
23
- google/genai/models.py,sha256=87KRye1LOOPg-vmfezGkq4gdVU5YhR4qmazoC0Zmg1U,238239
23
+ google/genai/models.py,sha256=B9T4g7VLUiaZbUADKJG1UrVlvwQziZbUSgOXo3yj4oA,239463
24
24
  google/genai/operations.py,sha256=wWGngU4N3U2y_JteBkwqTmoNncehk2NcZgPyHaVulfI,20304
25
25
  google/genai/pagers.py,sha256=nyVYxp92rS-UaewO_oBgP593knofeLU6yOn6RolNoGQ,6797
26
26
  google/genai/py.typed,sha256=RsMFoLwBkAvY05t6izop4UHZtqOPLiKp3GkIEizzmQY,40
27
- google/genai/tokens.py,sha256=g2gxefzN5-36B5qUkLL6MBSe31IgXgW7k_WEstRZypg,12187
27
+ google/genai/tokens.py,sha256=31HRdGkuN1VkHn21qKBwa6PeiLFGMN-aFldPIGo9WxE,12188
28
28
  google/genai/tunings.py,sha256=aEKTVfpn6rYWEKu0CRaS93mo2iLQtX5K3nSqX6UB_jM,49500
29
- google/genai/types.py,sha256=3NgIfqsrFwc8nzHQ0q18MjT4i4PEgbUZOcNu_MLAuqk,435807
30
- google/genai/version.py,sha256=hpRoVW-Vf0hIu6bbkzred3UTNNy-QrOv-olH5U1Vaag,627
31
- google_genai-1.17.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
32
- google_genai-1.17.0.dist-info/METADATA,sha256=2GrUrmL5GLTyS8mgXQzk20qN0Vn0YDX0WyoFyXoj_bU,35579
33
- google_genai-1.17.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- google_genai-1.17.0.dist-info/top_level.txt,sha256=_1QvSJIhFAGfxb79D6DhB7SUw2X6T4rwnz_LLrbcD3c,7
35
- google_genai-1.17.0.dist-info/RECORD,,
29
+ google/genai/types.py,sha256=SjZ9GaMEHn_YoOHnCLQm0Sl9AsnYPqBVbExlF6l9fo4,437412
30
+ google/genai/version.py,sha256=4Bj2qKIYUQDZnSbV8me-05HqXSvijhv-sXzln7K77wA,627
31
+ google_genai-1.19.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
32
+ google_genai-1.19.0.dist-info/METADATA,sha256=AdmMMUAAVkTxCNDAANY8SJsTlxBoXVKg2zdxj4-RgOs,35579
33
+ google_genai-1.19.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ google_genai-1.19.0.dist-info/top_level.txt,sha256=_1QvSJIhFAGfxb79D6DhB7SUw2X6T4rwnz_LLrbcD3c,7
35
+ google_genai-1.19.0.dist-info/RECORD,,