google-genai 1.60.0__py3-none-any.whl → 1.62.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.
Files changed (34) hide show
  1. google/genai/_interactions/_base_client.py +5 -2
  2. google/genai/_interactions/_compat.py +3 -3
  3. google/genai/_interactions/_utils/_json.py +50 -0
  4. google/genai/_interactions/resources/interactions.py +50 -28
  5. google/genai/_interactions/types/__init__.py +2 -1
  6. google/genai/_interactions/types/content_delta.py +1 -1
  7. google/genai/_interactions/types/function_result_content.py +2 -1
  8. google/genai/_interactions/types/function_result_content_param.py +4 -4
  9. google/genai/_interactions/types/{interaction_event.py → interaction_complete_event.py} +3 -3
  10. google/genai/_interactions/types/interaction_create_params.py +4 -4
  11. google/genai/_interactions/types/interaction_get_params.py +3 -0
  12. google/genai/_interactions/types/interaction_sse_event.py +11 -2
  13. google/genai/_interactions/types/interaction_start_event.py +36 -0
  14. google/genai/batches.py +3 -0
  15. google/genai/errors.py +19 -6
  16. google/genai/files.py +15 -15
  17. google/genai/live.py +22 -2
  18. google/genai/live_music.py +14 -1
  19. google/genai/models.py +486 -197
  20. google/genai/tests/batches/test_create_with_inlined_requests.py +31 -15
  21. google/genai/tests/batches/test_get.py +1 -1
  22. google/genai/tests/client/test_client_close.py +0 -1
  23. google/genai/tests/errors/test_api_error.py +38 -0
  24. google/genai/tests/files/test_register_table.py +1 -1
  25. google/genai/tests/transformers/test_schema.py +10 -1
  26. google/genai/tests/tunings/test_tune.py +87 -0
  27. google/genai/tunings.py +211 -20
  28. google/genai/types.py +178 -14
  29. google/genai/version.py +1 -1
  30. {google_genai-1.60.0.dist-info → google_genai-1.62.0.dist-info}/METADATA +1 -1
  31. {google_genai-1.60.0.dist-info → google_genai-1.62.0.dist-info}/RECORD +34 -32
  32. {google_genai-1.60.0.dist-info → google_genai-1.62.0.dist-info}/WHEEL +1 -1
  33. {google_genai-1.60.0.dist-info → google_genai-1.62.0.dist-info}/licenses/LICENSE +0 -0
  34. {google_genai-1.60.0.dist-info → google_genai-1.62.0.dist-info}/top_level.txt +0 -0
google/genai/errors.py CHANGED
@@ -18,6 +18,7 @@
18
18
  from typing import Any, Callable, Optional, TYPE_CHECKING, Union
19
19
  import httpx
20
20
  import json
21
+ import websockets
21
22
  from . import _common
22
23
 
23
24
 
@@ -69,14 +70,26 @@ class APIError(Exception):
69
70
  return obj
70
71
 
71
72
  def _get_status(self, response_json: Any) -> Any:
72
- return response_json.get(
73
- 'status', response_json.get('error', {}).get('status', None)
74
- )
73
+ try:
74
+ status = response_json.get(
75
+ 'status', response_json.get('error', {}).get('status', None)
76
+ )
77
+ return status
78
+ except AttributeError:
79
+ # If response_json is not a dict, return close code to handle the case
80
+ # when encountering a websocket error.
81
+ return None
75
82
 
76
83
  def _get_message(self, response_json: Any) -> Any:
77
- return response_json.get(
78
- 'message', response_json.get('error', {}).get('message', None)
79
- )
84
+ try:
85
+ message = response_json.get(
86
+ 'message', response_json.get('error', {}).get('message', None)
87
+ )
88
+ return message
89
+ except AttributeError:
90
+ # If response_json is not a dict, return it as None.
91
+ # This is to handle the case when encountering a websocket error.
92
+ return None
80
93
 
81
94
  def _get_code(self, response_json: Any) -> Any:
82
95
  return response_json.get(
google/genai/files.py CHANGED
@@ -101,6 +101,17 @@ def _GetFileParameters_to_mldev(
101
101
  return to_object
102
102
 
103
103
 
104
+ def _InternalRegisterFilesParameters_to_mldev(
105
+ from_object: Union[dict[str, Any], object],
106
+ parent_object: Optional[dict[str, Any]] = None,
107
+ ) -> dict[str, Any]:
108
+ to_object: dict[str, Any] = {}
109
+ if getv(from_object, ['uris']) is not None:
110
+ setv(to_object, ['uris'], getv(from_object, ['uris']))
111
+
112
+ return to_object
113
+
114
+
104
115
  def _ListFilesConfig_to_mldev(
105
116
  from_object: Union[dict[str, Any], object],
106
117
  parent_object: Optional[dict[str, Any]] = None,
@@ -152,17 +163,6 @@ def _ListFilesResponse_from_mldev(
152
163
  return to_object
153
164
 
154
165
 
155
- def _RegisterFilesParameters_to_mldev(
156
- from_object: Union[dict[str, Any], object],
157
- parent_object: Optional[dict[str, Any]] = None,
158
- ) -> dict[str, Any]:
159
- to_object: dict[str, Any] = {}
160
- if getv(from_object, ['uris']) is not None:
161
- setv(to_object, ['uris'], getv(from_object, ['uris']))
162
-
163
- return to_object
164
-
165
-
166
166
  def _RegisterFilesResponse_from_mldev(
167
167
  from_object: Union[dict[str, Any], object],
168
168
  parent_object: Optional[dict[str, Any]] = None,
@@ -438,7 +438,7 @@ class Files(_api_module.BaseModule):
438
438
  uris: list[str],
439
439
  config: Optional[types.RegisterFilesConfigOrDict] = None,
440
440
  ) -> types.RegisterFilesResponse:
441
- parameter_model = types._RegisterFilesParameters(
441
+ parameter_model = types._InternalRegisterFilesParameters(
442
442
  uris=uris,
443
443
  config=config,
444
444
  )
@@ -449,7 +449,7 @@ class Files(_api_module.BaseModule):
449
449
  'This method is only supported in the Gemini Developer client.'
450
450
  )
451
451
  else:
452
- request_dict = _RegisterFilesParameters_to_mldev(parameter_model)
452
+ request_dict = _InternalRegisterFilesParameters_to_mldev(parameter_model)
453
453
  request_url_dict = request_dict.get('_url')
454
454
  if request_url_dict:
455
455
  path = 'files:register'.format_map(request_url_dict)
@@ -977,7 +977,7 @@ class AsyncFiles(_api_module.BaseModule):
977
977
  uris: list[str],
978
978
  config: Optional[types.RegisterFilesConfigOrDict] = None,
979
979
  ) -> types.RegisterFilesResponse:
980
- parameter_model = types._RegisterFilesParameters(
980
+ parameter_model = types._InternalRegisterFilesParameters(
981
981
  uris=uris,
982
982
  config=config,
983
983
  )
@@ -988,7 +988,7 @@ class AsyncFiles(_api_module.BaseModule):
988
988
  'This method is only supported in the Gemini Developer client.'
989
989
  )
990
990
  else:
991
- request_dict = _RegisterFilesParameters_to_mldev(parameter_model)
991
+ request_dict = _InternalRegisterFilesParameters_to_mldev(parameter_model)
992
992
  request_url_dict = request_dict.get('_url')
993
993
  if request_url_dict:
994
994
  path = 'files:register'.format_map(request_url_dict)
google/genai/live.py CHANGED
@@ -26,7 +26,7 @@ import warnings
26
26
 
27
27
  import google.auth
28
28
  import pydantic
29
- from websockets import ConnectionClosed
29
+ import websockets
30
30
 
31
31
  from . import _api_module
32
32
  from . import _common
@@ -41,6 +41,7 @@ from ._common import set_value_by_path as setv
41
41
  from .live_music import AsyncLiveMusic
42
42
  from .models import _Content_to_mldev
43
43
 
44
+ ConnectionClosed = websockets.ConnectionClosed
44
45
 
45
46
  try:
46
47
  from websockets.asyncio.client import ClientConnection
@@ -534,6 +535,14 @@ class AsyncSession:
534
535
  raw_response = await self._ws.recv(decode=False)
535
536
  except TypeError:
536
537
  raw_response = await self._ws.recv() # type: ignore[assignment]
538
+ except ConnectionClosed as e:
539
+ if e.rcvd:
540
+ code = e.rcvd.code
541
+ reason = e.rcvd.reason
542
+ else:
543
+ code = 1006
544
+ reason = websockets.frames.CLOSE_CODE_EXPLANATIONS.get(code, 'Abnormal closure.')
545
+ errors.APIError.raise_error(code, reason, None)
537
546
  if raw_response:
538
547
  try:
539
548
  response = json.loads(raw_response)
@@ -545,8 +554,11 @@ class AsyncSession:
545
554
  if self._api_client.vertexai:
546
555
  response_dict = live_converters._LiveServerMessage_from_vertex(response)
547
556
  else:
548
- response_dict = response
557
+ response_dict = live_converters._LiveServerMessage_from_mldev(response)
549
558
 
559
+ if not response_dict and response:
560
+ # Error handling.
561
+ errors.APIError.raise_error(response.get('code'), response, None)
550
562
  return types.LiveServerMessage._from_response(
551
563
  response=response_dict, kwargs=parameter_model.model_dump()
552
564
  )
@@ -1093,6 +1105,14 @@ class AsyncLive(_api_module.BaseModule):
1093
1105
  raw_response = await ws.recv(decode=False)
1094
1106
  except TypeError:
1095
1107
  raw_response = await ws.recv() # type: ignore[assignment]
1108
+ except ConnectionClosed as e:
1109
+ if e.rcvd:
1110
+ code = e.rcvd.code
1111
+ reason = e.rcvd.reason
1112
+ else:
1113
+ code = 1006
1114
+ reason = 'Abnormal closure.'
1115
+ errors.APIError.raise_error(code, reason, None)
1096
1116
  if raw_response:
1097
1117
  try:
1098
1118
  response = json.loads(raw_response)
@@ -19,15 +19,18 @@ import contextlib
19
19
  import json
20
20
  import logging
21
21
  from typing import AsyncIterator
22
+ import websockets
22
23
 
23
24
  from . import _api_module
24
25
  from . import _common
25
26
  from . import _live_converters as live_converters
26
27
  from . import _transformers as t
28
+ from . import errors
27
29
  from . import types
28
30
  from ._api_client import BaseApiClient
29
31
  from ._common import set_value_by_path as setv
30
32
 
33
+ ConnectionClosed = websockets.ConnectionClosed
31
34
 
32
35
  try:
33
36
  from websockets.asyncio.client import ClientConnection
@@ -122,6 +125,14 @@ class AsyncMusicSession:
122
125
  raw_response = await self._ws.recv(decode=False)
123
126
  except TypeError:
124
127
  raw_response = await self._ws.recv() # type: ignore[assignment]
128
+ except ConnectionClosed as e:
129
+ if e.rcvd:
130
+ code = e.rcvd.code
131
+ reason = e.rcvd.reason
132
+ else:
133
+ code = 1006
134
+ reason = websockets.frames.CLOSE_CODE_EXPLANATIONS.get(code, 'Abnormal closure.')
135
+ errors.APIError.raise_error(code, reason, None)
125
136
  if raw_response:
126
137
  try:
127
138
  response = json.loads(raw_response)
@@ -134,7 +145,9 @@ class AsyncMusicSession:
134
145
  raise NotImplementedError('Live music generation is not supported in Vertex AI.')
135
146
  else:
136
147
  response_dict = response
137
-
148
+ if not response_dict and response:
149
+ # Error handling.
150
+ errors.APIError.raise_error(response.get('code'), response, None)
138
151
  return types.LiveMusicServerMessage._from_response(
139
152
  response=response_dict, kwargs=parameter_model.model_dump()
140
153
  )