google-genai 0.7.0__tar.gz → 1.0.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.
Files changed (32) hide show
  1. {google_genai-0.7.0/google_genai.egg-info → google_genai-1.0.0}/PKG-INFO +90 -48
  2. {google_genai-0.7.0 → google_genai-1.0.0}/README.md +89 -47
  3. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/_api_client.py +26 -25
  4. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/_automatic_function_calling_util.py +19 -20
  5. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/_common.py +33 -2
  6. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/_extra_utils.py +12 -6
  7. google_genai-1.0.0/google/genai/_operations.py +365 -0
  8. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/_replay_api_client.py +7 -0
  9. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/_transformers.py +32 -14
  10. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/errors.py +4 -0
  11. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/files.py +79 -71
  12. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/live.py +5 -0
  13. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/models.py +344 -35
  14. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/tunings.py +288 -61
  15. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/types.py +191 -20
  16. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/version.py +1 -1
  17. {google_genai-0.7.0 → google_genai-1.0.0/google_genai.egg-info}/PKG-INFO +90 -48
  18. {google_genai-0.7.0 → google_genai-1.0.0}/google_genai.egg-info/SOURCES.txt +1 -0
  19. {google_genai-0.7.0 → google_genai-1.0.0}/pyproject.toml +1 -1
  20. {google_genai-0.7.0 → google_genai-1.0.0}/LICENSE +0 -0
  21. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/__init__.py +0 -0
  22. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/_api_module.py +0 -0
  23. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/_test_api_client.py +0 -0
  24. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/batches.py +0 -0
  25. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/caches.py +0 -0
  26. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/chats.py +0 -0
  27. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/client.py +0 -0
  28. {google_genai-0.7.0 → google_genai-1.0.0}/google/genai/pagers.py +0 -0
  29. {google_genai-0.7.0 → google_genai-1.0.0}/google_genai.egg-info/dependency_links.txt +0 -0
  30. {google_genai-0.7.0 → google_genai-1.0.0}/google_genai.egg-info/requires.txt +0 -0
  31. {google_genai-0.7.0 → google_genai-1.0.0}/google_genai.egg-info/top_level.txt +0 -0
  32. {google_genai-0.7.0 → google_genai-1.0.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: google-genai
3
- Version: 0.7.0
3
+ Version: 1.0.0
4
4
  Summary: GenAI Python SDK
5
5
  Author-email: Google LLC <googleapis-packages@google.com>
6
6
  License: Apache-2.0
@@ -34,7 +34,7 @@ Requires-Dist: websockets<15.0dev,>=13.0
34
34
 
35
35
  -----
36
36
 
37
- Google Gen AI Python SDK provides an interface for developers to integrate Google's generative models into their Python applications. It supports the [Gemini Developer API](https://ai.google.dev/gemini-api/docs) and [Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/overview) APIs. This is an early release. API is subject to change. Please do not use this SDK in production environments at this stage.
37
+ Google Gen AI Python SDK provides an interface for developers to integrate Google's generative models into their Python applications. It supports the [Gemini Developer API](https://ai.google.dev/gemini-api/docs) and [Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/overview) APIs.
38
38
 
39
39
  ## Installation
40
40
 
@@ -66,6 +66,23 @@ client = genai.Client(
66
66
  )
67
67
  ```
68
68
 
69
+ To set the API version use `http_options`. For example, to set the API version
70
+ to `v1` for Vertex AI:
71
+
72
+ ```python
73
+ client = genai.Client(
74
+ vertexai=True, project='your-project-id', location='us-central1',
75
+ http_options={'api_version': 'v1'}
76
+ )
77
+ ```
78
+
79
+ To set the API version to `v1alpha` for the Gemini API:
80
+
81
+ ```python
82
+ client = genai.Client(api_key='GEMINI_API_KEY',
83
+ http_options={'api_version': 'v1alpha'})
84
+ ```
85
+
69
86
  ## Types
70
87
 
71
88
  Parameter types can be specified as either dictionaries(`TypedDict`) or
@@ -97,9 +114,10 @@ download the file in console.
97
114
  python code.
98
115
 
99
116
  ```python
100
- file = client.files.upload(path="a11.text")
117
+ file = client.files.upload(path="a11.txt")
101
118
  response = client.models.generate_content(
102
- model="gemini-2.0-flash-exp", contents=["Summarize this file", file]
119
+ model="gemini-2.0-flash-exp",
120
+ contents=["Could you summarize this file?", file]
103
121
  )
104
122
  print(response.text)
105
123
  ```
@@ -143,53 +161,17 @@ response = client.models.generate_content(
143
161
  print(response.text)
144
162
  ```
145
163
 
146
- ### Thinking
147
-
148
- The Gemini 2.0 Flash Thinking model is an experimental model that could return
149
- "thoughts" as part of its response.
150
-
151
- #### Gemini Developer API
152
-
153
- Thinking config is only available in v1alpha for Gemini AI API.
154
-
155
- ```python
156
- response = client.models.generate_content(
157
- model='gemini-2.0-flash-thinking-exp',
158
- contents='What is the sum of natural numbers from 1 to 100?',
159
- config=types.GenerateContentConfig(
160
- thinking_config=types.ThinkingConfig(include_thoughts=True),
161
- http_options=types.HttpOptions(api_version='v1alpha'),
162
- )
163
- )
164
- for part in response.candidates[0].content.parts:
165
- print(part)
166
- ```
167
-
168
- #### Vertex AI API
169
-
170
- ```python
171
- response = client.models.generate_content(
172
- model='gemini-2.0-flash-thinking-exp-01-21',
173
- contents='What is the sum of natural numbers from 1 to 100?',
174
- config=types.GenerateContentConfig(
175
- thinking_config=types.ThinkingConfig(include_thoughts=True),
176
- )
177
- )
178
- for part in response.candidates[0].content.parts:
179
- print(part)
180
- ```
181
-
182
164
  ### List Base Models
183
165
 
184
166
  To retrieve tuned models, see [list tuned models](#list-tuned-models).
185
167
 
186
168
  ```python
187
- for model in client.models.list(config={'query_base':True}):
169
+ for model in client.models.list():
188
170
  print(model)
189
171
  ```
190
172
 
191
173
  ```python
192
- pager = client.models.list(config={"page_size": 10, 'query_base':True})
174
+ pager = client.models.list(config={"page_size": 10})
193
175
  print(pager.page_size)
194
176
  print(pager[0])
195
177
  pager.next_page()
@@ -199,12 +181,12 @@ print(pager[0])
199
181
  #### Async
200
182
 
201
183
  ```python
202
- async for job in await client.aio.models.list(config={'query_base':True}):
184
+ async for job in await client.aio.models.list():
203
185
  print(job)
204
186
  ```
205
187
 
206
188
  ```python
207
- async_pager = await client.aio.models.list(config={"page_size": 10, 'query_base':True})
189
+ async_pager = await client.aio.models.list(config={"page_size": 10})
208
190
  print(async_pager.page_size)
209
191
  print(async_pager[0])
210
192
  await async_pager.next_page()
@@ -337,6 +319,66 @@ response = client.models.generate_content(
337
319
  print(response.text)
338
320
  ```
339
321
 
322
+ #### Function calling with `ANY` tools config mode
323
+
324
+ If you configure function calling mode to be `ANY`, then the model will always
325
+ return function call parts. If you also pass a python function as a tool, by
326
+ default the SDK will perform automatic function calling until the remote calls exceed the
327
+ maximum remote call for automatic function calling (default to 10 times).
328
+
329
+ If you'd like to disable automatic function calling in `ANY` mode:
330
+
331
+ ```python
332
+ def get_current_weather(location: str) -> str:
333
+ """Returns the current weather.
334
+
335
+ Args:
336
+ location: The city and state, e.g. San Francisco, CA
337
+ """
338
+ return "sunny"
339
+
340
+ response = client.models.generate_content(
341
+ model="gemini-2.0-flash-exp",
342
+ contents="What is the weather like in Boston?",
343
+ config=types.GenerateContentConfig(
344
+ tools=[get_current_weather],
345
+ automatic_function_calling=types.AutomaticFunctionCallingConfig(
346
+ disable=True
347
+ ),
348
+ tool_config=types.ToolConfig(
349
+ function_calling_config=types.FunctionCallingConfig(mode='ANY')
350
+ ),
351
+ ),
352
+ )
353
+ ```
354
+
355
+ If you'd like to set `x` number of automatic function call turns, you can
356
+ configure the maximum remote calls to be `x + 1`.
357
+ Assuming you prefer `1` turn for automatic function calling.
358
+
359
+ ```python
360
+ def get_current_weather(location: str) -> str:
361
+ """Returns the current weather.
362
+
363
+ Args:
364
+ location: The city and state, e.g. San Francisco, CA
365
+ """
366
+ return "sunny"
367
+
368
+ response = client.models.generate_content(
369
+ model="gemini-2.0-flash-exp",
370
+ contents="What is the weather like in Boston?",
371
+ config=types.GenerateContentConfig(
372
+ tools=[get_current_weather],
373
+ automatic_function_calling=types.AutomaticFunctionCallingConfig(
374
+ maximum_remote_calls=2
375
+ ),
376
+ tool_config=types.ToolConfig(
377
+ function_calling_config=types.FunctionCallingConfig(mode='ANY')
378
+ ),
379
+ ),
380
+ )
381
+ ```
340
382
  ### JSON Response Schema
341
383
 
342
384
  #### Pydantic Model Schema support
@@ -863,12 +905,12 @@ print(tuned_model)
863
905
  To retrieve base models, see [list base models](#list-base-models).
864
906
 
865
907
  ```python
866
- for model in client.models.list(config={"page_size": 10}):
908
+ for model in client.models.list(config={"page_size": 10, "query_base": False}):
867
909
  print(model)
868
910
  ```
869
911
 
870
912
  ```python
871
- pager = client.models.list(config={"page_size": 10})
913
+ pager = client.models.list(config={"page_size": 10, "query_base": False})
872
914
  print(pager.page_size)
873
915
  print(pager[0])
874
916
  pager.next_page()
@@ -878,12 +920,12 @@ print(pager[0])
878
920
  #### Async
879
921
 
880
922
  ```python
881
- async for job in await client.aio.models.list(config={"page_size": 10}):
923
+ async for job in await client.aio.models.list(config={"page_size": 10, "query_base": False}):
882
924
  print(job)
883
925
  ```
884
926
 
885
927
  ```python
886
- async_pager = await client.aio.models.list(config={"page_size": 10})
928
+ async_pager = await client.aio.models.list(config={"page_size": 10, "query_base": False})
887
929
  print(async_pager.page_size)
888
930
  print(async_pager[0])
889
931
  await async_pager.next_page()
@@ -7,7 +7,7 @@
7
7
 
8
8
  -----
9
9
 
10
- Google Gen AI Python SDK provides an interface for developers to integrate Google's generative models into their Python applications. It supports the [Gemini Developer API](https://ai.google.dev/gemini-api/docs) and [Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/overview) APIs. This is an early release. API is subject to change. Please do not use this SDK in production environments at this stage.
10
+ Google Gen AI Python SDK provides an interface for developers to integrate Google's generative models into their Python applications. It supports the [Gemini Developer API](https://ai.google.dev/gemini-api/docs) and [Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/overview) APIs.
11
11
 
12
12
  ## Installation
13
13
 
@@ -39,6 +39,23 @@ client = genai.Client(
39
39
  )
40
40
  ```
41
41
 
42
+ To set the API version use `http_options`. For example, to set the API version
43
+ to `v1` for Vertex AI:
44
+
45
+ ```python
46
+ client = genai.Client(
47
+ vertexai=True, project='your-project-id', location='us-central1',
48
+ http_options={'api_version': 'v1'}
49
+ )
50
+ ```
51
+
52
+ To set the API version to `v1alpha` for the Gemini API:
53
+
54
+ ```python
55
+ client = genai.Client(api_key='GEMINI_API_KEY',
56
+ http_options={'api_version': 'v1alpha'})
57
+ ```
58
+
42
59
  ## Types
43
60
 
44
61
  Parameter types can be specified as either dictionaries(`TypedDict`) or
@@ -70,9 +87,10 @@ download the file in console.
70
87
  python code.
71
88
 
72
89
  ```python
73
- file = client.files.upload(path="a11.text")
90
+ file = client.files.upload(path="a11.txt")
74
91
  response = client.models.generate_content(
75
- model="gemini-2.0-flash-exp", contents=["Summarize this file", file]
92
+ model="gemini-2.0-flash-exp",
93
+ contents=["Could you summarize this file?", file]
76
94
  )
77
95
  print(response.text)
78
96
  ```
@@ -116,53 +134,17 @@ response = client.models.generate_content(
116
134
  print(response.text)
117
135
  ```
118
136
 
119
- ### Thinking
120
-
121
- The Gemini 2.0 Flash Thinking model is an experimental model that could return
122
- "thoughts" as part of its response.
123
-
124
- #### Gemini Developer API
125
-
126
- Thinking config is only available in v1alpha for Gemini AI API.
127
-
128
- ```python
129
- response = client.models.generate_content(
130
- model='gemini-2.0-flash-thinking-exp',
131
- contents='What is the sum of natural numbers from 1 to 100?',
132
- config=types.GenerateContentConfig(
133
- thinking_config=types.ThinkingConfig(include_thoughts=True),
134
- http_options=types.HttpOptions(api_version='v1alpha'),
135
- )
136
- )
137
- for part in response.candidates[0].content.parts:
138
- print(part)
139
- ```
140
-
141
- #### Vertex AI API
142
-
143
- ```python
144
- response = client.models.generate_content(
145
- model='gemini-2.0-flash-thinking-exp-01-21',
146
- contents='What is the sum of natural numbers from 1 to 100?',
147
- config=types.GenerateContentConfig(
148
- thinking_config=types.ThinkingConfig(include_thoughts=True),
149
- )
150
- )
151
- for part in response.candidates[0].content.parts:
152
- print(part)
153
- ```
154
-
155
137
  ### List Base Models
156
138
 
157
139
  To retrieve tuned models, see [list tuned models](#list-tuned-models).
158
140
 
159
141
  ```python
160
- for model in client.models.list(config={'query_base':True}):
142
+ for model in client.models.list():
161
143
  print(model)
162
144
  ```
163
145
 
164
146
  ```python
165
- pager = client.models.list(config={"page_size": 10, 'query_base':True})
147
+ pager = client.models.list(config={"page_size": 10})
166
148
  print(pager.page_size)
167
149
  print(pager[0])
168
150
  pager.next_page()
@@ -172,12 +154,12 @@ print(pager[0])
172
154
  #### Async
173
155
 
174
156
  ```python
175
- async for job in await client.aio.models.list(config={'query_base':True}):
157
+ async for job in await client.aio.models.list():
176
158
  print(job)
177
159
  ```
178
160
 
179
161
  ```python
180
- async_pager = await client.aio.models.list(config={"page_size": 10, 'query_base':True})
162
+ async_pager = await client.aio.models.list(config={"page_size": 10})
181
163
  print(async_pager.page_size)
182
164
  print(async_pager[0])
183
165
  await async_pager.next_page()
@@ -310,6 +292,66 @@ response = client.models.generate_content(
310
292
  print(response.text)
311
293
  ```
312
294
 
295
+ #### Function calling with `ANY` tools config mode
296
+
297
+ If you configure function calling mode to be `ANY`, then the model will always
298
+ return function call parts. If you also pass a python function as a tool, by
299
+ default the SDK will perform automatic function calling until the remote calls exceed the
300
+ maximum remote call for automatic function calling (default to 10 times).
301
+
302
+ If you'd like to disable automatic function calling in `ANY` mode:
303
+
304
+ ```python
305
+ def get_current_weather(location: str) -> str:
306
+ """Returns the current weather.
307
+
308
+ Args:
309
+ location: The city and state, e.g. San Francisco, CA
310
+ """
311
+ return "sunny"
312
+
313
+ response = client.models.generate_content(
314
+ model="gemini-2.0-flash-exp",
315
+ contents="What is the weather like in Boston?",
316
+ config=types.GenerateContentConfig(
317
+ tools=[get_current_weather],
318
+ automatic_function_calling=types.AutomaticFunctionCallingConfig(
319
+ disable=True
320
+ ),
321
+ tool_config=types.ToolConfig(
322
+ function_calling_config=types.FunctionCallingConfig(mode='ANY')
323
+ ),
324
+ ),
325
+ )
326
+ ```
327
+
328
+ If you'd like to set `x` number of automatic function call turns, you can
329
+ configure the maximum remote calls to be `x + 1`.
330
+ Assuming you prefer `1` turn for automatic function calling.
331
+
332
+ ```python
333
+ def get_current_weather(location: str) -> str:
334
+ """Returns the current weather.
335
+
336
+ Args:
337
+ location: The city and state, e.g. San Francisco, CA
338
+ """
339
+ return "sunny"
340
+
341
+ response = client.models.generate_content(
342
+ model="gemini-2.0-flash-exp",
343
+ contents="What is the weather like in Boston?",
344
+ config=types.GenerateContentConfig(
345
+ tools=[get_current_weather],
346
+ automatic_function_calling=types.AutomaticFunctionCallingConfig(
347
+ maximum_remote_calls=2
348
+ ),
349
+ tool_config=types.ToolConfig(
350
+ function_calling_config=types.FunctionCallingConfig(mode='ANY')
351
+ ),
352
+ ),
353
+ )
354
+ ```
313
355
  ### JSON Response Schema
314
356
 
315
357
  #### Pydantic Model Schema support
@@ -836,12 +878,12 @@ print(tuned_model)
836
878
  To retrieve base models, see [list base models](#list-base-models).
837
879
 
838
880
  ```python
839
- for model in client.models.list(config={"page_size": 10}):
881
+ for model in client.models.list(config={"page_size": 10, "query_base": False}):
840
882
  print(model)
841
883
  ```
842
884
 
843
885
  ```python
844
- pager = client.models.list(config={"page_size": 10})
886
+ pager = client.models.list(config={"page_size": 10, "query_base": False})
845
887
  print(pager.page_size)
846
888
  print(pager[0])
847
889
  pager.next_page()
@@ -851,12 +893,12 @@ print(pager[0])
851
893
  #### Async
852
894
 
853
895
  ```python
854
- async for job in await client.aio.models.list(config={"page_size": 10}):
896
+ async for job in await client.aio.models.list(config={"page_size": 10, "query_base": False}):
855
897
  print(job)
856
898
  ```
857
899
 
858
900
  ```python
859
- async_pager = await client.aio.models.list(config={"page_size": 10})
901
+ async_pager = await client.aio.models.list(config={"page_size": 10, "query_base": False})
860
902
  print(async_pager.page_size)
861
903
  print(async_pager[0])
862
904
  await async_pager.next_page()
@@ -99,6 +99,19 @@ class HttpRequest:
99
99
  timeout: Optional[float] = None
100
100
 
101
101
 
102
+ # TODO(b/394358912): Update this class to use a SDKResponse class that can be
103
+ # generated and used for all languages.
104
+ @dataclass
105
+ class BaseResponse:
106
+ http_headers: dict[str, str]
107
+
108
+ @property
109
+ def dict(self) -> dict[str, Any]:
110
+ if isinstance(self, dict):
111
+ return self
112
+ return {'httpHeaders': self.http_headers}
113
+
114
+
102
115
  class HttpResponse:
103
116
 
104
117
  def __init__(
@@ -255,7 +268,7 @@ class ApiClient:
255
268
  'Project and location or API key must be set when using the Vertex '
256
269
  'AI API.'
257
270
  )
258
- if self.api_key:
271
+ if self.api_key or self.location == 'global':
259
272
  self._http_options['base_url'] = (
260
273
  f'https://aiplatform.googleapis.com/'
261
274
  )
@@ -273,7 +286,7 @@ class ApiClient:
273
286
  self._http_options['api_version'] = 'v1beta'
274
287
  # Default options for both clients.
275
288
  self._http_options['headers'] = {'Content-Type': 'application/json'}
276
- if self.api_key and not self.vertexai:
289
+ if self.api_key:
277
290
  self._http_options['headers']['x-goog-api-key'] = self.api_key
278
291
  # Update the http options with the user provided http options.
279
292
  if http_options:
@@ -323,8 +336,6 @@ class ApiClient:
323
336
  and not self.api_key
324
337
  ):
325
338
  path = f'projects/{self.project}/locations/{self.location}/' + path
326
- elif self.vertexai and self.api_key:
327
- path = f'{path}?key={self.api_key}'
328
339
  url = _join_url_path(
329
340
  patched_http_options['base_url'],
330
341
  patched_http_options['api_version'] + '/' + path,
@@ -436,18 +447,12 @@ class ApiClient:
436
447
  http_method, path, request_dict, http_options
437
448
  )
438
449
  response = self._request(http_request, stream=False)
439
- if http_options:
440
- if (
441
- isinstance(http_options, HttpOptions)
442
- and http_options.deprecated_response_payload is not None
443
- ):
444
- response._copy_to_dict(http_options.deprecated_response_payload)
445
- elif (
446
- isinstance(http_options, dict)
447
- and 'deprecated_response_payload' in http_options
448
- ):
449
- response._copy_to_dict(http_options['deprecated_response_payload'])
450
- return response.json
450
+ json_response = response.json
451
+ if not json_response:
452
+ base_response = BaseResponse(response.headers).dict
453
+ return base_response
454
+
455
+ return json_response
451
456
 
452
457
  def request_streamed(
453
458
  self,
@@ -461,10 +466,6 @@ class ApiClient:
461
466
  )
462
467
 
463
468
  session_response = self._request(http_request, stream=True)
464
- if http_options and 'deprecated_response_payload' in http_options:
465
- session_response._copy_to_dict(
466
- http_options['deprecated_response_payload']
467
- )
468
469
  for chunk in session_response.segments():
469
470
  yield chunk
470
471
 
@@ -480,9 +481,11 @@ class ApiClient:
480
481
  )
481
482
 
482
483
  result = await self._async_request(http_request=http_request, stream=False)
483
- if http_options and 'deprecated_response_payload' in http_options:
484
- result._copy_to_dict(http_options['deprecated_response_payload'])
485
- return result.json
484
+ json_response = result.json
485
+ if not json_response:
486
+ base_response = BaseResponse(result.headers).dict
487
+ return base_response
488
+ return json_response
486
489
 
487
490
  async def async_request_streamed(
488
491
  self,
@@ -497,8 +500,6 @@ class ApiClient:
497
500
 
498
501
  response = await self._async_request(http_request=http_request, stream=True)
499
502
 
500
- if http_options and 'deprecated_response_payload' in http_options:
501
- response._copy_to_dict(http_options['deprecated_response_payload'])
502
503
  async def async_generator():
503
504
  async for chunk in response:
504
505
  yield chunk
@@ -17,14 +17,18 @@ import inspect
17
17
  import sys
18
18
  import types as builtin_types
19
19
  import typing
20
- from typing import Any, Callable, Literal, Union, _GenericAlias, get_args, get_origin
20
+ from typing import _GenericAlias, Any, Callable, get_args, get_origin, Literal, Union
21
+
21
22
  import pydantic
23
+
24
+ from . import _extra_utils
22
25
  from . import types
23
26
 
27
+
24
28
  if sys.version_info >= (3, 10):
25
- UnionType = builtin_types.UnionType
29
+ VersionedUnionType = builtin_types.UnionType
26
30
  else:
27
- UnionType = typing._UnionGenericAlias
31
+ VersionedUnionType = typing._UnionGenericAlias
28
32
 
29
33
  _py_builtin_type_to_schema_type = {
30
34
  str: 'STRING',
@@ -45,7 +49,8 @@ def _is_builtin_primitive_or_compound(
45
49
  def _raise_for_any_of_if_mldev(schema: types.Schema):
46
50
  if schema.any_of:
47
51
  raise ValueError(
48
- 'AnyOf is not supported in function declaration schema for Google AI.'
52
+ 'AnyOf is not supported in function declaration schema for'
53
+ ' the Gemini API.'
49
54
  )
50
55
 
51
56
 
@@ -53,15 +58,7 @@ def _raise_for_default_if_mldev(schema: types.Schema):
53
58
  if schema.default is not None:
54
59
  raise ValueError(
55
60
  'Default value is not supported in function declaration schema for'
56
- ' Google AI.'
57
- )
58
-
59
-
60
- def _raise_for_nullable_if_mldev(schema: types.Schema):
61
- if schema.nullable:
62
- raise ValueError(
63
- 'Nullable is not supported in function declaration schema for'
64
- ' Google AI.'
61
+ ' the Gemini API.'
65
62
  )
66
63
 
67
64
 
@@ -69,7 +66,6 @@ def _raise_if_schema_unsupported(client, schema: types.Schema):
69
66
  if not client.vertexai:
70
67
  _raise_for_any_of_if_mldev(schema)
71
68
  _raise_for_default_if_mldev(schema)
72
- _raise_for_nullable_if_mldev(schema)
73
69
 
74
70
 
75
71
  def _is_default_value_compatible(
@@ -82,10 +78,10 @@ def _is_default_value_compatible(
82
78
  if (
83
79
  isinstance(annotation, _GenericAlias)
84
80
  or isinstance(annotation, builtin_types.GenericAlias)
85
- or isinstance(annotation, UnionType)
81
+ or isinstance(annotation, VersionedUnionType)
86
82
  ):
87
83
  origin = get_origin(annotation)
88
- if origin in (Union, UnionType):
84
+ if origin in (Union, VersionedUnionType):
89
85
  return any(
90
86
  _is_default_value_compatible(default_value, arg)
91
87
  for arg in get_args(annotation)
@@ -141,7 +137,7 @@ def _parse_schema_from_parameter(
141
137
  _raise_if_schema_unsupported(client, schema)
142
138
  return schema
143
139
  if (
144
- isinstance(param.annotation, UnionType)
140
+ isinstance(param.annotation, VersionedUnionType)
145
141
  # only parse simple UnionType, example int | str | float | bool
146
142
  # complex UnionType will be invoked in raise branch
147
143
  and all(
@@ -229,7 +225,11 @@ def _parse_schema_from_parameter(
229
225
  schema.type = 'OBJECT'
230
226
  unique_types = set()
231
227
  for arg in args:
232
- if arg.__name__ == 'NoneType': # Optional type
228
+ # The first check is for NoneType in Python 3.9, since the __name__
229
+ # attribute is not available in Python 3.9
230
+ if type(arg) is type(None) or (
231
+ hasattr(arg, '__name__') and arg.__name__ == 'NoneType'
232
+ ): # Optional type
233
233
  schema.nullable = True
234
234
  continue
235
235
  schema_in_any_of = _parse_schema_from_parameter(
@@ -272,9 +272,8 @@ def _parse_schema_from_parameter(
272
272
  return schema
273
273
  # all other generic alias will be invoked in raise branch
274
274
  if (
275
- inspect.isclass(param.annotation)
276
275
  # for user defined class, we only support pydantic model
277
- and issubclass(param.annotation, pydantic.BaseModel)
276
+ _extra_utils.is_annotation_pydantic_model(param.annotation)
278
277
  ):
279
278
  if (
280
279
  param.default is not inspect.Parameter.empty
@@ -18,14 +18,17 @@
18
18
  import base64
19
19
  import datetime
20
20
  import enum
21
+ import functools
21
22
  import typing
22
23
  from typing import Union
23
24
  import uuid
25
+ import warnings
24
26
 
25
27
  import pydantic
26
28
  from pydantic import alias_generators
27
29
 
28
30
  from . import _api_client
31
+ from . import errors
29
32
 
30
33
 
31
34
  def set_value_by_path(data, keys, value):
@@ -213,8 +216,16 @@ class CaseInSensitiveEnum(str, enum.Enum):
213
216
  except KeyError:
214
217
  try:
215
218
  return cls[value.lower()] # Try to access directly with lowercase
216
- except KeyError as e:
217
- raise ValueError(f"{value} is not a valid {cls.__name__}") from e
219
+ except KeyError:
220
+ warnings.warn(f"{value} is not a valid {cls.__name__}")
221
+ try:
222
+ # Creating a enum instance based on the value
223
+ unknown_enum_val = cls._new_member_(cls) # pylint: disable=protected-access,attribute-error
224
+ unknown_enum_val._name_ = str(value) # pylint: disable=protected-access
225
+ unknown_enum_val._value_ = value # pylint: disable=protected-access
226
+ return unknown_enum_val
227
+ except:
228
+ return None
218
229
 
219
230
 
220
231
  def timestamped_unique_name() -> str:
@@ -264,3 +275,23 @@ def encode_unserializable_types(data: dict[str, object]) -> dict[str, object]:
264
275
  else:
265
276
  processed_data[key] = value
266
277
  return processed_data
278
+
279
+
280
+ def experimental_warning(message: str):
281
+ """Experimental warning, only warns once."""
282
+ def decorator(func):
283
+ warning_done = False
284
+ @functools.wraps(func)
285
+ def wrapper(*args, **kwargs):
286
+ nonlocal warning_done
287
+ if not warning_done:
288
+ warning_done = True
289
+ warnings.warn(
290
+ message=message,
291
+ category=errors.ExperimentalWarning,
292
+ stacklevel=2,
293
+ )
294
+ return func(*args, **kwargs)
295
+ return wrapper
296
+ return decorator
297
+