google-genai 1.22.0__py3-none-any.whl → 1.23.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.
@@ -60,6 +60,11 @@ from .types import HttpOptionsOrDict
60
60
  from .types import HttpResponse as SdkHttpResponse
61
61
  from .types import HttpRetryOptions
62
62
 
63
+ try:
64
+ from websockets.asyncio.client import connect as ws_connect
65
+ except ModuleNotFoundError:
66
+ # This try/except is for TAP, mypy complains about it which is why we have the type: ignore
67
+ from websockets.client import connect as ws_connect # type: ignore
63
68
 
64
69
  has_aiohttp = False
65
70
  try:
@@ -227,11 +232,13 @@ class HttpResponse:
227
232
  headers: Union[dict[str, str], httpx.Headers, 'CIMultiDictProxy[str]'],
228
233
  response_stream: Union[Any, str] = None,
229
234
  byte_stream: Union[Any, bytes] = None,
235
+ session: Optional['aiohttp.ClientSession'] = None,
230
236
  ):
231
237
  self.status_code: int = 200
232
238
  self.headers = headers
233
239
  self.response_stream = response_stream
234
240
  self.byte_stream = byte_stream
241
+ self._session = session
235
242
 
236
243
  # Async iterator for async streaming.
237
244
  def __aiter__(self) -> 'HttpResponse':
@@ -291,16 +298,23 @@ class HttpResponse:
291
298
  chunk = chunk[len('data: ') :]
292
299
  yield json.loads(chunk)
293
300
  elif hasattr(self.response_stream, 'content'):
294
- async for chunk in self.response_stream.content.iter_any():
295
- # This is aiohttp.ClientResponse.
296
- if chunk:
301
+ # This is aiohttp.ClientResponse.
302
+ try:
303
+ while True:
304
+ chunk = await self.response_stream.content.readline()
305
+ if not chunk:
306
+ break
297
307
  # In async streaming mode, the chunk of JSON is prefixed with
298
308
  # "data:" which we must strip before parsing.
299
- if not isinstance(chunk, str):
300
- chunk = chunk.decode('utf-8')
309
+ chunk = chunk.decode('utf-8')
301
310
  if chunk.startswith('data: '):
302
311
  chunk = chunk[len('data: ') :]
303
- yield json.loads(chunk)
312
+ chunk = chunk.strip()
313
+ if chunk:
314
+ yield json.loads(chunk)
315
+ finally:
316
+ if hasattr(self, '_session') and self._session:
317
+ await self._session.close()
304
318
  else:
305
319
  raise ValueError('Error parsing streaming response.')
306
320
 
@@ -538,6 +552,7 @@ class BaseApiClient:
538
552
  # Default options for both clients.
539
553
  self._http_options.headers = {'Content-Type': 'application/json'}
540
554
  if self.api_key:
555
+ self.api_key = self.api_key.strip()
541
556
  if self._http_options.headers is not None:
542
557
  self._http_options.headers['x-goog-api-key'] = self.api_key
543
558
  # Update the http options with the user provided http options.
@@ -558,7 +573,10 @@ class BaseApiClient:
558
573
  # Do it once at the genai.Client level. Share among all requests.
559
574
  self._async_client_session_request_args = self._ensure_aiohttp_ssl_ctx(
560
575
  self._http_options
561
- )
576
+ )
577
+ self._websocket_ssl_ctx = self._ensure_websocket_ssl_ctx(
578
+ self._http_options
579
+ )
562
580
 
563
581
  retry_kwargs = _retry_args(self._http_options.retry_options)
564
582
  self._retry = tenacity.Retrying(**retry_kwargs, reraise=True)
@@ -688,6 +706,63 @@ class BaseApiClient:
688
706
 
689
707
  return _maybe_set(async_args, ctx)
690
708
 
709
+
710
+ @staticmethod
711
+ def _ensure_websocket_ssl_ctx(options: HttpOptions) -> dict[str, Any]:
712
+ """Ensures the SSL context is present in the async client args.
713
+
714
+ Creates a default SSL context if one is not provided.
715
+
716
+ Args:
717
+ options: The http options to check for SSL context.
718
+
719
+ Returns:
720
+ An async aiohttp ClientSession._request args.
721
+ """
722
+
723
+ verify = 'ssl' # keep it consistent with httpx.
724
+ async_args = options.async_client_args
725
+ ctx = async_args.get(verify) if async_args else None
726
+
727
+ if not ctx:
728
+ # Initialize the SSL context for the httpx client.
729
+ # Unlike requests, the aiohttp package does not automatically pull in the
730
+ # environment variables SSL_CERT_FILE or SSL_CERT_DIR. They need to be
731
+ # enabled explicitly. Instead of 'verify' at client level in httpx,
732
+ # aiohttp uses 'ssl' at request level.
733
+ ctx = ssl.create_default_context(
734
+ cafile=os.environ.get('SSL_CERT_FILE', certifi.where()),
735
+ capath=os.environ.get('SSL_CERT_DIR'),
736
+ )
737
+
738
+ def _maybe_set(
739
+ args: Optional[dict[str, Any]],
740
+ ctx: ssl.SSLContext,
741
+ ) -> dict[str, Any]:
742
+ """Sets the SSL context in the client args if not set.
743
+
744
+ Does not override the SSL context if it is already set.
745
+
746
+ Args:
747
+ args: The client args to to check for SSL context.
748
+ ctx: The SSL context to set.
749
+
750
+ Returns:
751
+ The client args with the SSL context included.
752
+ """
753
+ if not args or not args.get(verify):
754
+ args = (args or {}).copy()
755
+ args[verify] = ctx
756
+ # Drop the args that isn't in the aiohttp RequestOptions.
757
+ copied_args = args.copy()
758
+ for key in copied_args.copy():
759
+ if key not in inspect.signature(ws_connect).parameters and key != 'ssl':
760
+ del copied_args[key]
761
+ return copied_args
762
+
763
+ return _maybe_set(async_args, ctx)
764
+
765
+
691
766
  def _websocket_base_url(self) -> str:
692
767
  url_parts = urlparse(self._http_options.base_url)
693
768
  return url_parts._replace(scheme='wss').geturl() # type: ignore[arg-type, return-value]
@@ -882,6 +957,7 @@ class BaseApiClient:
882
957
  self, http_request: HttpRequest, stream: bool = False
883
958
  ) -> HttpResponse:
884
959
  data: Optional[Union[str, bytes]] = None
960
+
885
961
  if self.vertexai and not self.api_key:
886
962
  http_request.headers['Authorization'] = (
887
963
  f'Bearer {await self._async_access_token()}'
@@ -912,8 +988,9 @@ class BaseApiClient:
912
988
  timeout=aiohttp.ClientTimeout(connect=http_request.timeout),
913
989
  **self._async_client_session_request_args,
914
990
  )
991
+
915
992
  await errors.APIError.raise_for_async_response(response)
916
- return HttpResponse(response.headers, response)
993
+ return HttpResponse(response.headers, response, session=session)
917
994
  else:
918
995
  # aiohttp is not available. Fall back to httpx.
919
996
  httpx_request = self._async_httpx_client.build_request(
google/genai/live.py CHANGED
@@ -1037,7 +1037,10 @@ class AsyncLive(_api_module.BaseModule):
1037
1037
  if headers is None:
1038
1038
  headers = {}
1039
1039
  _mcp_utils.set_mcp_usage_header(headers)
1040
- async with ws_connect(uri, additional_headers=headers) as ws:
1040
+
1041
+ async with ws_connect(
1042
+ uri, additional_headers=headers, **self._api_client._websocket_ssl_ctx
1043
+ ) as ws:
1041
1044
  await ws.send(request)
1042
1045
  try:
1043
1046
  # websockets 14.0+
google/genai/tunings.py CHANGED
@@ -108,6 +108,11 @@ def _TuningDataset_to_mldev(
108
108
  if getv(from_object, ['gcs_uri']) is not None:
109
109
  raise ValueError('gcs_uri parameter is not supported in Gemini API.')
110
110
 
111
+ if getv(from_object, ['vertex_dataset_resource']) is not None:
112
+ raise ValueError(
113
+ 'vertex_dataset_resource parameter is not supported in Gemini API.'
114
+ )
115
+
111
116
  if getv(from_object, ['examples']) is not None:
112
117
  setv(
113
118
  to_object,
@@ -129,6 +134,11 @@ def _TuningValidationDataset_to_mldev(
129
134
  if getv(from_object, ['gcs_uri']) is not None:
130
135
  raise ValueError('gcs_uri parameter is not supported in Gemini API.')
131
136
 
137
+ if getv(from_object, ['vertex_dataset_resource']) is not None:
138
+ raise ValueError(
139
+ 'vertex_dataset_resource parameter is not supported in Gemini API.'
140
+ )
141
+
132
142
  return to_object
133
143
 
134
144
 
@@ -302,6 +312,13 @@ def _TuningDataset_to_vertex(
302
312
  getv(from_object, ['gcs_uri']),
303
313
  )
304
314
 
315
+ if getv(from_object, ['vertex_dataset_resource']) is not None:
316
+ setv(
317
+ parent_object,
318
+ ['supervisedTuningSpec', 'trainingDatasetUri'],
319
+ getv(from_object, ['vertex_dataset_resource']),
320
+ )
321
+
305
322
  if getv(from_object, ['examples']) is not None:
306
323
  raise ValueError('examples parameter is not supported in Vertex AI.')
307
324
 
@@ -316,6 +333,13 @@ def _TuningValidationDataset_to_vertex(
316
333
  if getv(from_object, ['gcs_uri']) is not None:
317
334
  setv(to_object, ['validationDatasetUri'], getv(from_object, ['gcs_uri']))
318
335
 
336
+ if getv(from_object, ['vertex_dataset_resource']) is not None:
337
+ setv(
338
+ parent_object,
339
+ ['supervisedTuningSpec', 'trainingDatasetUri'],
340
+ getv(from_object, ['vertex_dataset_resource']),
341
+ )
342
+
319
343
  return to_object
320
344
 
321
345
 
google/genai/types.py CHANGED
@@ -8699,6 +8699,10 @@ class TuningDataset(_common.BaseModel):
8699
8699
  default=None,
8700
8700
  description="""GCS URI of the file containing training dataset in JSONL format.""",
8701
8701
  )
8702
+ vertex_dataset_resource: Optional[str] = Field(
8703
+ default=None,
8704
+ description="""The resource name of the Vertex Multimodal Dataset that is used as training dataset. Example: 'projects/my-project-id-or-number/locations/my-location/datasets/my-dataset-id'.""",
8705
+ )
8702
8706
  examples: Optional[list[TuningExample]] = Field(
8703
8707
  default=None,
8704
8708
  description="""Inline examples with simple input/output text.""",
@@ -8711,6 +8715,9 @@ class TuningDatasetDict(TypedDict, total=False):
8711
8715
  gcs_uri: Optional[str]
8712
8716
  """GCS URI of the file containing training dataset in JSONL format."""
8713
8717
 
8718
+ vertex_dataset_resource: Optional[str]
8719
+ """The resource name of the Vertex Multimodal Dataset that is used as training dataset. Example: 'projects/my-project-id-or-number/locations/my-location/datasets/my-dataset-id'."""
8720
+
8714
8721
  examples: Optional[list[TuningExampleDict]]
8715
8722
  """Inline examples with simple input/output text."""
8716
8723
 
@@ -8724,6 +8731,10 @@ class TuningValidationDataset(_common.BaseModel):
8724
8731
  default=None,
8725
8732
  description="""GCS URI of the file containing validation dataset in JSONL format.""",
8726
8733
  )
8734
+ vertex_dataset_resource: Optional[str] = Field(
8735
+ default=None,
8736
+ description="""The resource name of the Vertex Multimodal Dataset that is used as training dataset. Example: 'projects/my-project-id-or-number/locations/my-location/datasets/my-dataset-id'.""",
8737
+ )
8727
8738
 
8728
8739
 
8729
8740
  class TuningValidationDatasetDict(TypedDict, total=False):
@@ -8731,6 +8742,9 @@ class TuningValidationDatasetDict(TypedDict, total=False):
8731
8742
  gcs_uri: Optional[str]
8732
8743
  """GCS URI of the file containing validation dataset in JSONL format."""
8733
8744
 
8745
+ vertex_dataset_resource: Optional[str]
8746
+ """The resource name of the Vertex Multimodal Dataset that is used as training dataset. Example: 'projects/my-project-id-or-number/locations/my-location/datasets/my-dataset-id'."""
8747
+
8734
8748
 
8735
8749
  TuningValidationDatasetOrDict = Union[
8736
8750
  TuningValidationDataset, TuningValidationDatasetDict
@@ -12801,7 +12815,7 @@ class AudioChunk(_common.BaseModel):
12801
12815
  """Representation of an audio chunk."""
12802
12816
 
12803
12817
  data: Optional[bytes] = Field(
12804
- default=None, description="""Raw byets of audio data."""
12818
+ default=None, description="""Raw bytes of audio data."""
12805
12819
  )
12806
12820
  mime_type: Optional[str] = Field(
12807
12821
  default=None, description="""MIME type of the audio chunk."""
@@ -12816,7 +12830,7 @@ class AudioChunkDict(TypedDict, total=False):
12816
12830
  """Representation of an audio chunk."""
12817
12831
 
12818
12832
  data: Optional[bytes]
12819
- """Raw byets of audio data."""
12833
+ """Raw bytes of audio data."""
12820
12834
 
12821
12835
  mime_type: Optional[str]
12822
12836
  """MIME type of the audio chunk."""
google/genai/version.py CHANGED
@@ -13,4 +13,4 @@
13
13
  # limitations under the License.
14
14
  #
15
15
 
16
- __version__ = '1.22.0' # x-release-please-version
16
+ __version__ = '1.23.0' # x-release-please-version
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: google-genai
3
- Version: 1.22.0
3
+ Version: 1.23.0
4
4
  Summary: GenAI Python SDK
5
5
  Author-email: Google LLC <googleapis-packages@google.com>
6
6
  License: Apache-2.0
@@ -743,6 +743,53 @@ response = client.models.generate_content(
743
743
  ),
744
744
  )
745
745
  ```
746
+
747
+ #### Model Context Protocol (MCP) support (experimental)
748
+
749
+ Built-in [MCP](https://modelcontextprotocol.io/introduction) support is an
750
+ experimental feature. You can pass a local MCP server as a tool directly.
751
+
752
+ ```python
753
+ import os
754
+ import asyncio
755
+ from datetime import datetime
756
+ from mcp import ClientSession, StdioServerParameters
757
+ from mcp.client.stdio import stdio_client
758
+ from google import genai
759
+
760
+ client = genai.Client()
761
+
762
+ # Create server parameters for stdio connection
763
+ server_params = StdioServerParameters(
764
+ command="npx", # Executable
765
+ args=["-y", "@philschmid/weather-mcp"], # MCP Server
766
+ env=None, # Optional environment variables
767
+ )
768
+
769
+ async def run():
770
+ async with stdio_client(server_params) as (read, write):
771
+ async with ClientSession(read, write) as session:
772
+ # Prompt to get the weather for the current day in London.
773
+ prompt = f"What is the weather in London in {datetime.now().strftime('%Y-%m-%d')}?"
774
+
775
+ # Initialize the connection between client and server
776
+ await session.initialize()
777
+
778
+ # Send request to the model with MCP function declarations
779
+ response = await client.aio.models.generate_content(
780
+ model="gemini-2.5-flash",
781
+ contents=prompt,
782
+ config=genai.types.GenerateContentConfig(
783
+ temperature=0,
784
+ tools=[session], # uses the session, will automatically call the tool using automatic function calling
785
+ ),
786
+ )
787
+ print(response.text)
788
+
789
+ # Start the asyncio event loop and run the main function
790
+ asyncio.run(run())
791
+ ```
792
+
746
793
  ### JSON Response Schema
747
794
 
748
795
  However you define your schema, don't duplicate it in your input prompt,
@@ -1260,7 +1307,7 @@ client.
1260
1307
 
1261
1308
  ### Tune
1262
1309
 
1263
- - Vertex AI supports tuning from GCS source
1310
+ - Vertex AI supports tuning from GCS source or from a Vertex Multimodal Dataset
1264
1311
  - Gemini Developer API supports tuning from inline examples
1265
1312
 
1266
1313
  ```python
@@ -1269,10 +1316,12 @@ from google.genai import types
1269
1316
  if client.vertexai:
1270
1317
  model = 'gemini-2.0-flash-001'
1271
1318
  training_dataset = types.TuningDataset(
1319
+ # or gcs_uri=my_vertex_multimodal_dataset
1272
1320
  gcs_uri='gs://cloud-samples-data/ai-platform/generative_ai/gemini-1_5/text/sft_train_data.jsonl',
1273
1321
  )
1274
1322
  else:
1275
1323
  model = 'models/gemini-2.0-flash-001'
1324
+ # or gcs_uri=my_vertex_multimodal_dataset.resource_name
1276
1325
  training_dataset = types.TuningDataset(
1277
1326
  examples=[
1278
1327
  types.TuningExample(
@@ -1,6 +1,6 @@
1
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=AoCuJp2lI9zHvybCzcki2IK2DyUhfUDly4s9CGxUmoc,49887
3
+ google/genai/_api_client.py,sha256=bbuu6LUCEf6MH-DzhEg-w7iv84SqRy5vIluXDKOn1UU,52447
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
@@ -18,18 +18,18 @@ google/genai/chats.py,sha256=0QdOUeWEYDQgAWBy1f7a3z3yY9S8tXSowUzNrzazzj4,16651
18
18
  google/genai/client.py,sha256=wXnfZBSv9p-yKtX_gabUrfBXoYHuqHhzK_VgwRttMgY,10777
19
19
  google/genai/errors.py,sha256=UebysH1cDeIdtp1O06CW7lQjnNrWtuqZTeVgEJylX48,5589
20
20
  google/genai/files.py,sha256=MCcHRXiMFKjbnt78yVPejszEgGz_MHRXfJyDi5-07Gs,39655
21
- google/genai/live.py,sha256=6zKjLZKnd4J-t0XQ61yuzRJv8TwbhTW96Wr8dqeFPI8,38891
21
+ google/genai/live.py,sha256=8Vts1bX8ZWOa0PiS3zmYhG1QMhLtBPC_ATLTSAIrazs,38945
22
22
  google/genai/live_music.py,sha256=3GG9nsto8Vhkohcs-4CPMS4DFp1ZtMuLYzHfvEPYAeg,6971
23
23
  google/genai/models.py,sha256=7gBXwg6_RdW_HRD_iRFIAdqu0bkUkW0fAp_PMEPVwoU,239309
24
24
  google/genai/operations.py,sha256=99zs__fTWsyQu7rMGmBI1_4tuAJxLR0g3yp7ymsUsBA,19609
25
25
  google/genai/pagers.py,sha256=nyVYxp92rS-UaewO_oBgP593knofeLU6yOn6RolNoGQ,6797
26
26
  google/genai/py.typed,sha256=RsMFoLwBkAvY05t6izop4UHZtqOPLiKp3GkIEizzmQY,40
27
27
  google/genai/tokens.py,sha256=oew9I0fGuGLlXMdCn2Ot9Lv_herHMnvZOYjjdOz2CAM,12324
28
- google/genai/tunings.py,sha256=aUtotVuvn66vtsYiqwH3Av3arZ-sl3ooHCwkLijb6K4,48298
29
- google/genai/types.py,sha256=UmIBjW0qFbA2LN113DUtz8ZtgG1_7I0vIAPE0fVmTrc,459025
30
- google/genai/version.py,sha256=KWnZScqB5t3VtG5YNrQoE4_p36-OUcoYjWaKRdmLbQc,627
31
- google_genai-1.22.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
32
- google_genai-1.22.0.dist-info/METADATA,sha256=bOufmJ0EEGx9pwhd0hPURu6MM7sR6WQnLGZiM7KY0hs,37123
33
- google_genai-1.22.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- google_genai-1.22.0.dist-info/top_level.txt,sha256=_1QvSJIhFAGfxb79D6DhB7SUw2X6T4rwnz_LLrbcD3c,7
35
- google_genai-1.22.0.dist-info/RECORD,,
28
+ google/genai/tunings.py,sha256=u6Hcfkc-nUYB_spSfY98h7EIiSz0dH3wVH0b2feMh1M,49072
29
+ google/genai/types.py,sha256=LGLNmXkmnCLU-narShhG_1rtglfARNm6pvvZOiO5_lA,460023
30
+ google/genai/version.py,sha256=Snuhu088Fxuibck04VaPaZ69LvJ5pKNdicb9oZYwcrY,627
31
+ google_genai-1.23.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
32
+ google_genai-1.23.0.dist-info/METADATA,sha256=Yylx0cTXrq87K2-VIbM9e1RT80LHMcvUC5ZB8Y0YTqc,38902
33
+ google_genai-1.23.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ google_genai-1.23.0.dist-info/top_level.txt,sha256=_1QvSJIhFAGfxb79D6DhB7SUw2X6T4rwnz_LLrbcD3c,7
35
+ google_genai-1.23.0.dist-info/RECORD,,