google-genai 1.33.0__py3-none-any.whl → 1.53.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.
@@ -15,12 +15,15 @@
15
15
 
16
16
  """Extra utils depending on types that are shared between sync and async modules."""
17
17
 
18
+ import asyncio
18
19
  import inspect
20
+ import io
19
21
  import logging
20
22
  import sys
21
23
  import typing
22
24
  from typing import Any, Callable, Dict, Optional, Union, get_args, get_origin
23
-
25
+ import mimetypes
26
+ import os
24
27
  import pydantic
25
28
 
26
29
  from . import _common
@@ -90,14 +93,12 @@ def _get_bigquery_uri(
90
93
 
91
94
 
92
95
  def format_destination(
93
- src: Union[str, types.BatchJobSourceOrDict],
94
- config: Optional[types.CreateBatchJobConfigOrDict] = None,
96
+ src: Union[str, types.BatchJobSource],
97
+ config: Optional[types.CreateBatchJobConfig] = None,
95
98
  ) -> types.CreateBatchJobConfig:
96
99
  """Formats the destination uri based on the source uri for Vertex AI."""
97
- config = (
98
- types._CreateBatchJobParameters(config=config).config
99
- or types.CreateBatchJobConfig()
100
- )
100
+ if config is None:
101
+ config = types.CreateBatchJobConfig()
101
102
 
102
103
  unique_name = None
103
104
  if not config.display_name:
@@ -113,11 +114,39 @@ def format_destination(
113
114
  elif bigquery_source_uri:
114
115
  unique_name = unique_name or _common.timestamped_unique_name()
115
116
  config.dest = f'{bigquery_source_uri}_dest_{unique_name}'
116
- else:
117
- raise ValueError(f'The source {src} is not supported.')
117
+
118
118
  return config
119
119
 
120
120
 
121
+ def find_afc_incompatible_tool_indexes(
122
+ config: Optional[types.GenerateContentConfigOrDict] = None,
123
+ ) -> list[int]:
124
+ """Checks if the config contains any AFC incompatible tools.
125
+
126
+ A `types.Tool` object that contains `function_declarations` is considered a
127
+ non-AFC tool for this execution path.
128
+
129
+ Args:
130
+ config: The GenerateContentConfig to check for incompatible tools.
131
+
132
+ Returns:
133
+ A list of indexes of the incompatible tools in the config.
134
+ """
135
+ if not config:
136
+ return []
137
+ config_model = _create_generate_content_config_model(config)
138
+ incompatible_tools_indexes: list[int] = []
139
+
140
+ if not config_model or not config_model.tools:
141
+ return incompatible_tools_indexes
142
+
143
+ for index, tool in enumerate(config_model.tools):
144
+ if isinstance(tool, types.Tool) and tool.function_declarations:
145
+ incompatible_tools_indexes.append(index)
146
+
147
+ return incompatible_tools_indexes
148
+
149
+
121
150
  def get_function_map(
122
151
  config: Optional[types.GenerateContentConfigOrDict] = None,
123
152
  mcp_to_genai_tool_adapters: Optional[
@@ -372,7 +401,9 @@ async def get_function_response_parts_async(
372
401
  }
373
402
  else:
374
403
  func_response = {
375
- 'result': invoke_function_from_dict_args(args, func)
404
+ 'result': await asyncio.to_thread(
405
+ invoke_function_from_dict_args, args, func
406
+ )
376
407
  }
377
408
  except Exception as e: # pylint: disable=broad-except
378
409
  func_response = {'error': str(e)}
@@ -458,6 +489,31 @@ def get_max_remote_calls_afc(
458
489
  return int(config_model.automatic_function_calling.maximum_remote_calls)
459
490
 
460
491
 
492
+ def raise_error_for_afc_incompatible_config(config: Optional[types.GenerateContentConfig]
493
+ ) -> None:
494
+ """Raises an error if the config is not compatible with AFC."""
495
+ if (
496
+ not config
497
+ or not config.tool_config
498
+ or not config.tool_config.function_calling_config
499
+ ):
500
+ return
501
+ afc_config = config.automatic_function_calling
502
+ disable_afc_config = afc_config.disable if afc_config else False
503
+ stream_function_call = (
504
+ config.tool_config.function_calling_config.stream_function_call_arguments
505
+ )
506
+
507
+ if stream_function_call and not disable_afc_config:
508
+ raise ValueError(
509
+ 'Running in streaming mode with stream_function_call_arguments'
510
+ ' enabled, this feature is not compatible with automatic function'
511
+ ' calling (AFC). Please set config.automatic_function_calling.disable'
512
+ ' to True to disable AFC or leave config.tool_config.'
513
+ ' function_calling_config.stream_function_call_arguments to be empty'
514
+ ' or set to False to disable streaming function call arguments.'
515
+ )
516
+
461
517
  def should_append_afc_history(
462
518
  config: Optional[types.GenerateContentConfigOrDict] = None,
463
519
  ) -> bool:
@@ -537,10 +593,84 @@ async def parse_config_for_mcp_sessions(
537
593
  def append_chunk_contents(
538
594
  contents: Union[types.ContentListUnion, types.ContentListUnionDict],
539
595
  chunk: types.GenerateContentResponse,
540
- ) -> None:
541
- """Appends the contents of the chunk to the contents list."""
596
+ ) -> Union[types.ContentListUnion, types.ContentListUnionDict]:
597
+ """Appends the contents of the chunk to the contents list and returns it."""
542
598
  if chunk is not None and chunk.candidates is not None:
543
599
  chunk_content = chunk.candidates[0].content
544
600
  contents = t.t_contents(contents) # type: ignore[assignment]
545
601
  if isinstance(contents, list) and chunk_content is not None:
546
602
  contents.append(chunk_content) # type: ignore[arg-type]
603
+ return contents
604
+
605
+
606
+ def prepare_resumable_upload(
607
+ file: Union[str, os.PathLike[str], io.IOBase],
608
+ user_http_options: Optional[types.HttpOptionsOrDict] = None,
609
+ user_mime_type: Optional[str] = None,
610
+ ) -> tuple[
611
+ types.HttpOptions,
612
+ int,
613
+ str,
614
+ ]:
615
+ """Prepares the HTTP options, file bytes size and mime type for a resumable upload.
616
+
617
+ This function inspects a file (from a path or an in-memory object) to
618
+ determine its size and MIME type. It then constructs the necessary HTTP
619
+ headers and options required to initiate a resumable upload session.
620
+ """
621
+ size_bytes = None
622
+ mime_type = user_mime_type
623
+ if isinstance(file, io.IOBase):
624
+ if mime_type is None:
625
+ raise ValueError(
626
+ 'Unknown mime type: Could not determine the mimetype for your'
627
+ ' file\n please set the `mime_type` argument'
628
+ )
629
+ if hasattr(file, 'mode'):
630
+ if 'b' not in file.mode:
631
+ raise ValueError('The file must be opened in binary mode.')
632
+ offset = file.tell()
633
+ file.seek(0, os.SEEK_END)
634
+ size_bytes = file.tell() - offset
635
+ file.seek(offset, os.SEEK_SET)
636
+ else:
637
+ fs_path = os.fspath(file)
638
+ if not fs_path or not os.path.isfile(fs_path):
639
+ raise FileNotFoundError(f'{file} is not a valid file path.')
640
+ size_bytes = os.path.getsize(fs_path)
641
+ if mime_type is None:
642
+ mime_type, _ = mimetypes.guess_type(fs_path)
643
+ if mime_type is None:
644
+ raise ValueError(
645
+ 'Unknown mime type: Could not determine the mimetype for your'
646
+ ' file\n please set the `mime_type` argument'
647
+ )
648
+ http_options: types.HttpOptions
649
+ if user_http_options:
650
+ if isinstance(user_http_options, dict):
651
+ user_http_options = types.HttpOptions(**user_http_options)
652
+ http_options = user_http_options
653
+ http_options.api_version = ''
654
+ http_options.headers = {
655
+ 'Content-Type': 'application/json',
656
+ 'X-Goog-Upload-Protocol': 'resumable',
657
+ 'X-Goog-Upload-Command': 'start',
658
+ 'X-Goog-Upload-Header-Content-Length': f'{size_bytes}',
659
+ 'X-Goog-Upload-Header-Content-Type': f'{mime_type}',
660
+ }
661
+ else:
662
+ http_options = types.HttpOptions(
663
+ api_version='',
664
+ headers={
665
+ 'Content-Type': 'application/json',
666
+ 'X-Goog-Upload-Protocol': 'resumable',
667
+ 'X-Goog-Upload-Command': 'start',
668
+ 'X-Goog-Upload-Header-Content-Length': f'{size_bytes}',
669
+ 'X-Goog-Upload-Header-Content-Type': f'{mime_type}',
670
+ },
671
+ )
672
+ if isinstance(file, (str, os.PathLike)):
673
+ if http_options.headers is None:
674
+ http_options.headers = {}
675
+ http_options.headers['X-Goog-Upload-File-Name'] = os.path.basename(file)
676
+ return http_options, size_bytes, mime_type