google-genai 1.55.0__py3-none-any.whl → 1.57.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 (88) hide show
  1. google/genai/_api_client.py +37 -18
  2. google/genai/_interactions/_base_client.py +8 -2
  3. google/genai/_interactions/resources/interactions.py +6 -6
  4. google/genai/_interactions/types/__init__.py +4 -0
  5. google/genai/_interactions/types/audio_content.py +0 -1
  6. google/genai/_interactions/types/audio_content_param.py +0 -1
  7. google/genai/_interactions/types/code_execution_call_content.py +0 -1
  8. google/genai/_interactions/types/code_execution_call_content_param.py +0 -1
  9. google/genai/_interactions/types/code_execution_result_content.py +0 -1
  10. google/genai/_interactions/types/code_execution_result_content_param.py +0 -1
  11. google/genai/_interactions/types/content.py +63 -0
  12. google/genai/_interactions/types/content_delta.py +7 -23
  13. google/genai/_interactions/types/content_param.py +61 -0
  14. google/genai/_interactions/types/content_start.py +4 -44
  15. google/genai/_interactions/types/deep_research_agent_config.py +0 -1
  16. google/genai/_interactions/types/deep_research_agent_config_param.py +0 -1
  17. google/genai/_interactions/types/document_content.py +3 -2
  18. google/genai/_interactions/types/document_content_param.py +3 -2
  19. google/genai/_interactions/types/document_mime_type.py +23 -0
  20. google/genai/_interactions/types/document_mime_type_param.py +25 -0
  21. google/genai/_interactions/types/dynamic_agent_config.py +0 -1
  22. google/genai/_interactions/types/dynamic_agent_config_param.py +0 -1
  23. google/genai/_interactions/types/file_search_result_content.py +0 -1
  24. google/genai/_interactions/types/file_search_result_content_param.py +0 -1
  25. google/genai/_interactions/types/function_call_content.py +0 -1
  26. google/genai/_interactions/types/function_call_content_param.py +0 -1
  27. google/genai/_interactions/types/function_result_content.py +1 -2
  28. google/genai/_interactions/types/function_result_content_param.py +1 -2
  29. google/genai/_interactions/types/google_search_call_content.py +0 -1
  30. google/genai/_interactions/types/google_search_call_content_param.py +0 -1
  31. google/genai/_interactions/types/google_search_result_content.py +0 -1
  32. google/genai/_interactions/types/google_search_result_content_param.py +0 -1
  33. google/genai/_interactions/types/image_content.py +1 -2
  34. google/genai/_interactions/types/image_content_param.py +1 -2
  35. google/genai/_interactions/types/interaction.py +4 -52
  36. google/genai/_interactions/types/interaction_create_params.py +2 -22
  37. google/genai/_interactions/types/mcp_server_tool_call_content.py +0 -1
  38. google/genai/_interactions/types/mcp_server_tool_call_content_param.py +0 -1
  39. google/genai/_interactions/types/mcp_server_tool_result_content.py +1 -2
  40. google/genai/_interactions/types/mcp_server_tool_result_content_param.py +1 -2
  41. google/genai/_interactions/types/model.py +1 -0
  42. google/genai/_interactions/types/model_param.py +1 -0
  43. google/genai/_interactions/types/text_content.py +0 -1
  44. google/genai/_interactions/types/text_content_param.py +0 -1
  45. google/genai/_interactions/types/thinking_level.py +1 -1
  46. google/genai/_interactions/types/thought_content.py +0 -1
  47. google/genai/_interactions/types/thought_content_param.py +0 -1
  48. google/genai/_interactions/types/turn.py +3 -44
  49. google/genai/_interactions/types/turn_param.py +4 -40
  50. google/genai/_interactions/types/url_context_call_content.py +0 -1
  51. google/genai/_interactions/types/url_context_call_content_param.py +0 -1
  52. google/genai/_interactions/types/url_context_result_content.py +0 -1
  53. google/genai/_interactions/types/url_context_result_content_param.py +0 -1
  54. google/genai/_interactions/types/usage.py +1 -1
  55. google/genai/_interactions/types/usage_param.py +1 -1
  56. google/genai/_interactions/types/video_content.py +1 -2
  57. google/genai/_interactions/types/video_content_param.py +1 -2
  58. google/genai/_live_converters.py +36 -64
  59. google/genai/_local_tokenizer_loader.py +1 -0
  60. google/genai/_tokens_converters.py +14 -14
  61. google/genai/batches.py +27 -22
  62. google/genai/caches.py +42 -42
  63. google/genai/chats.py +0 -2
  64. google/genai/client.py +3 -1
  65. google/genai/files.py +224 -0
  66. google/genai/models.py +57 -72
  67. google/genai/tests/chats/test_get_history.py +9 -8
  68. google/genai/tests/chats/test_validate_response.py +1 -1
  69. google/genai/tests/client/test_client_requests.py +1 -135
  70. google/genai/tests/files/test_register.py +272 -0
  71. google/genai/tests/files/test_register_table.py +70 -0
  72. google/genai/tests/interactions/test_auth.py +479 -0
  73. google/genai/tests/interactions/test_integration.py +2 -0
  74. google/genai/tests/interactions/test_paths.py +105 -0
  75. google/genai/tests/live/test_live.py +2 -36
  76. google/genai/tests/local_tokenizer/test_local_tokenizer.py +1 -1
  77. google/genai/tests/models/test_function_call_streaming.py +90 -90
  78. google/genai/tests/models/test_generate_content.py +1 -2
  79. google/genai/tests/models/test_recontext_image.py +1 -1
  80. google/genai/tests/pytest_helper.py +17 -0
  81. google/genai/tunings.py +1 -27
  82. google/genai/types.py +603 -518
  83. google/genai/version.py +1 -1
  84. {google_genai-1.55.0.dist-info → google_genai-1.57.0.dist-info}/METADATA +224 -22
  85. {google_genai-1.55.0.dist-info → google_genai-1.57.0.dist-info}/RECORD +88 -80
  86. {google_genai-1.55.0.dist-info → google_genai-1.57.0.dist-info}/WHEEL +0 -0
  87. {google_genai-1.55.0.dist-info → google_genai-1.57.0.dist-info}/licenses/LICENSE +0 -0
  88. {google_genai-1.55.0.dist-info → google_genai-1.57.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,479 @@
1
+ # Copyright 2025 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+
16
+
17
+ """Tests for Interactions API."""
18
+
19
+ from ... import Client
20
+ from ... import _base_url
21
+ from unittest import mock
22
+ import pytest
23
+ from httpx import Request, Response
24
+ from ..._api_client import AsyncHttpxClient, BaseApiClient
25
+ from httpx import Client as HTTPClient
26
+ import os
27
+
28
+ ENV_VARS = [
29
+ "GOOGLE_CLOUD_PROJECT",
30
+ "GEMINI_API_KEY",
31
+ "GOOGLE_API_KEY",
32
+ "GOOGLE_CLOUD_LOCATION",
33
+ ]
34
+
35
+ @pytest.fixture(autouse=True)
36
+ def clear_env_vars(monkeypatch):
37
+ for var in ENV_VARS:
38
+ monkeypatch.delenv(var, raising=False)
39
+
40
+ def test_interactions_gemini_url(monkeypatch):
41
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
42
+ client = Client()
43
+
44
+
45
+ with mock.patch.object(HTTPClient, "send") as mock_send:
46
+ mock_send.return_value = Response(200, request=Request('POST', ''))
47
+ client.interactions.create(
48
+ model='gemini-1.5-flash',
49
+ input='Hello',
50
+ )
51
+ mock_send.assert_called_once()
52
+ request = mock_send.call_args[0][0]
53
+ assert str(request.url).endswith('/v1beta/interactions')
54
+ assert request.headers['x-goog-api-key'] == 'test-api-key'
55
+
56
+
57
+ def test_interactions_gemini_no_vertex_auth(monkeypatch):
58
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
59
+ client = Client()
60
+
61
+ with (
62
+ mock.patch.object(BaseApiClient, "_access_token") as mock_access_token,
63
+ mock.patch.object(HTTPClient, "send") as mock_send,
64
+ ):
65
+ mock_send.return_value = Response(200, request=Request('POST', ''))
66
+ client.interactions.create(
67
+ model='gemini-1.5-flash',
68
+ input='Hello',
69
+ )
70
+ mock_access_token.assert_not_called()
71
+
72
+ def test_interactions_gemini_retry(monkeypatch):
73
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
74
+ client = Client()
75
+ client._api_client.max_retries = 2
76
+
77
+ with mock.patch.object(HTTPClient, "send") as mock_send:
78
+ mock_send.side_effect = [
79
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
80
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
81
+ Response(200, request=Request('POST', '')),
82
+ ]
83
+ client.interactions.create(model='gemini-1.5-flash', input='Hello')
84
+ assert mock_send.call_count == 3
85
+
86
+ def test_interactions_gemini_extra_headers(monkeypatch):
87
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
88
+ client = Client()
89
+
90
+ with mock.patch.object(HTTPClient, "send") as mock_send:
91
+ mock_send.return_value = Response(200, request=Request('POST', ''))
92
+ client.interactions.create(
93
+ model='gemini-1.5-flash',
94
+ input='Hello',
95
+ extra_headers={'X-Custom-Header': 'TestValue'}
96
+ )
97
+ mock_send.assert_called_once()
98
+ request = mock_send.call_args[0][0]
99
+ assert request.headers['x-custom-header'] == 'TestValue'
100
+ assert request.headers['x-goog-api-key'] == 'test-api-key'
101
+
102
+
103
+ def test_interactions_vertex_auth_header():
104
+ from ..._api_client import BaseApiClient
105
+ from ..._interactions._base_client import SyncAPIClient
106
+ from httpx import Client as HTTPClient
107
+
108
+ creds = mock.Mock()
109
+ creds.quota_project_id = "test-quota-project"
110
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
111
+
112
+ with (
113
+ mock.patch.object(
114
+ BaseApiClient, "_access_token", return_value='fake-vertex-token'
115
+ ) as mock_access_token,
116
+ mock.patch.object(
117
+ HTTPClient, "send",
118
+ return_value=mock.Mock(),
119
+ ) as mock_send,
120
+ ):
121
+
122
+ response = client.interactions.create(
123
+ model='gemini-2.5-flash',
124
+ input='What is the largest planet in our solar system?',
125
+ )
126
+
127
+ mock_send.assert_called_once()
128
+ mock_access_token.assert_called_once()
129
+ args, kwargs = mock_send.call_args
130
+ headers = args[0].headers
131
+ assert any(
132
+ key == "authorization" and value == 'Bearer fake-vertex-token'
133
+ for key, value in headers.items())
134
+ assert any(
135
+ key == "x-goog-user-project" and value == 'test-quota-project'
136
+ for key, value in headers.items())
137
+
138
+ def test_interactions_vertex_key_no_auth_header():
139
+ from ..._api_client import BaseApiClient
140
+ from httpx import Client as HTTPClient
141
+
142
+ creds = mock.Mock()
143
+ client = Client(vertexai=True, api_key='test-api-key')
144
+
145
+ with (
146
+ mock.patch.object(
147
+ BaseApiClient, "_access_token", return_value='fake-vertex-token'
148
+ ) as mock_access_token,
149
+ mock.patch.object(
150
+ HTTPClient, "send",
151
+ return_value=mock.Mock(),
152
+ ) as mock_send,
153
+ ):
154
+
155
+ response = client.interactions.create(
156
+ model='gemini-2.5-flash',
157
+ input='What is the largest planet in our solar system?',
158
+ )
159
+
160
+ mock_send.assert_called_once()
161
+ mock_access_token.assert_not_called()
162
+ args, kwargs = mock_send.call_args
163
+ headers = args[0].headers
164
+ assert any(
165
+ key == "x-goog-api-key" and value == 'test-api-key'
166
+ for key, value in headers.items())
167
+
168
+ def test_interactions_vertex_url():
169
+ creds = mock.Mock()
170
+ creds.quota_project_id = "test-quota-project"
171
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
172
+
173
+ with mock.patch("httpx.Client.send") as mock_send:
174
+ mock_send.return_value = Response(200, request=Request('POST', ''))
175
+ client.interactions.create(
176
+ model='gemini-1.5-flash',
177
+ input='Hello',
178
+ )
179
+ mock_send.assert_called_once()
180
+ request = mock_send.call_args[0][0]
181
+ assert str(request.url) == 'https://us-central1-aiplatform.googleapis.com/v1beta1/projects/test-project/locations/us-central1/interactions'
182
+
183
+ def test_interactions_vertex_auth_refresh_on_retry():
184
+ from ..._api_client import BaseApiClient
185
+ from httpx import Client as HTTPClient
186
+
187
+ creds = mock.Mock()
188
+ creds.quota_project_id = "test-quota-project"
189
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
190
+ client._api_client.max_retries = 2
191
+
192
+ token_values = ['token1', 'token2', 'token3']
193
+ token_iter = iter(token_values)
194
+
195
+ def get_token():
196
+ return next(token_iter)
197
+
198
+ with (
199
+ mock.patch.object(BaseApiClient, "_access_token", side_effect=get_token) as mock_access_token,
200
+ mock.patch.object(HTTPClient, "send") as mock_send,
201
+ ):
202
+ mock_send.side_effect = [
203
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
204
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
205
+ Response(200, request=Request('POST', '')),
206
+ ]
207
+
208
+ client.interactions.create(model='gemini-1.5-flash', input='Hello')
209
+
210
+ assert mock_access_token.call_count == 3
211
+ assert mock_send.call_count == 3
212
+ # Check headers of each call
213
+ for i in range(3):
214
+ headers = mock_send.call_args_list[i][0][0].headers
215
+ assert headers['authorization'] == f'Bearer {token_values[i]}'
216
+
217
+ @pytest.mark.xfail(reason="extra_headers don't override default auth")
218
+ def test_interactions_vertex_extra_headers_override():
219
+ from ..._api_client import BaseApiClient
220
+ from httpx import Client as HTTPClient
221
+
222
+ creds = mock.Mock()
223
+ creds.quota_project_id = "test-quota-project"
224
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
225
+
226
+ with (
227
+ mock.patch.object(BaseApiClient, "_access_token", return_value='default-token') as mock_access_token,
228
+ mock.patch.object(HTTPClient, "send") as mock_send,
229
+ ):
230
+ mock_send.return_value = Response(200, request=Request('POST', ''))
231
+
232
+ # Override Authorization
233
+ client.interactions.create(
234
+ model='gemini-1.5-flash',
235
+ input='Hello',
236
+ extra_headers={'Authorization': 'Bearer manual-token'}
237
+ )
238
+ mock_send.assert_called_once()
239
+ headers = mock_send.call_args[0][0].headers
240
+ assert headers['authorization'] == 'Bearer manual-token'
241
+ mock_access_token.assert_not_called() # Should not fetch default token
242
+
243
+ mock_send.reset_mock()
244
+ mock_access_token.reset_mock()
245
+
246
+ # Provide API Key
247
+ client.interactions.create(
248
+ model='gemini-1.5-flash',
249
+ input='Hello',
250
+ extra_headers={'x-goog-api-key': 'manual-key'}
251
+ )
252
+ mock_send.assert_called_once()
253
+ headers = mock_send.call_args[0][0].headers
254
+ assert headers['x-goog-api-key'] == 'manual-key'
255
+ assert 'authorization' not in headers
256
+ mock_access_token.assert_not_called()
257
+
258
+ @pytest.mark.asyncio
259
+ async def test_async_interactions_gemini_url(monkeypatch):
260
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
261
+ client = Client()
262
+
263
+ with mock.patch.object(AsyncHttpxClient, "send") as mock_send:
264
+ mock_send.return_value = Response(200, request=Request('POST', ''))
265
+ await client.aio.interactions.create(
266
+ model='gemini-1.5-flash',
267
+ input='Hello',
268
+ )
269
+ mock_send.assert_called_once()
270
+ request = mock_send.call_args[0][0]
271
+ assert str(request.url).endswith('/v1beta/interactions')
272
+ assert request.headers['x-goog-api-key'] == 'test-api-key'
273
+
274
+ @pytest.mark.asyncio
275
+ async def test_async_interactions_gemini_no_vertex_auth(monkeypatch):
276
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
277
+ client = Client()
278
+
279
+ with (
280
+ mock.patch.object(BaseApiClient, "_async_access_token") as mock_access_token,
281
+ mock.patch.object(AsyncHttpxClient, "send") as mock_send,
282
+ ):
283
+ mock_send.return_value = Response(200, request=Request('POST', ''))
284
+ await client.aio.interactions.create(
285
+ model='gemini-1.5-flash',
286
+ input='Hello',
287
+ )
288
+ mock_access_token.assert_not_called()
289
+
290
+ @pytest.mark.asyncio
291
+ async def test_async_interactions_gemini_retry(monkeypatch):
292
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
293
+ client = Client()
294
+ client.aio._api_client.max_retries = 2
295
+
296
+ with mock.patch.object(AsyncHttpxClient, "send") as mock_send:
297
+ mock_send.side_effect = [
298
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
299
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
300
+ Response(200, request=Request('POST', '')),
301
+ ]
302
+ await client.aio.interactions.create(model='gemini-1.5-flash', input='Hello')
303
+ assert mock_send.call_count == 3
304
+
305
+ @pytest.mark.asyncio
306
+ async def test_async_interactions_gemini_extra_headers(monkeypatch):
307
+ monkeypatch.setenv('GOOGLE_API_KEY', 'test-api-key')
308
+ client = Client()
309
+
310
+ with mock.patch.object(AsyncHttpxClient, "send") as mock_send:
311
+ mock_send.return_value = Response(200, request=Request('POST', ''))
312
+ await client.aio.interactions.create(
313
+ model='gemini-1.5-flash',
314
+ input='Hello',
315
+ extra_headers={'X-Custom-Header': 'TestValue'}
316
+ )
317
+ mock_send.assert_called_once()
318
+ request = mock_send.call_args[0][0]
319
+ assert request.headers['x-custom-header'] == 'TestValue'
320
+ assert request.headers['x-goog-api-key'] == 'test-api-key'
321
+
322
+ @pytest.mark.asyncio
323
+ async def test_async_interactions_vertex_auth_header():
324
+ from ..._api_client import BaseApiClient
325
+ from ..._interactions._base_client import SyncAPIClient
326
+ from ..._api_client import AsyncHttpxClient
327
+
328
+ creds = mock.Mock()
329
+ creds.quota_project_id = "test-quota-project"
330
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
331
+
332
+ with (
333
+ mock.patch.object(
334
+ BaseApiClient, "_async_access_token", return_value='fake-vertex-token'
335
+ ) as mock_access_token,
336
+ mock.patch.object(
337
+ AsyncHttpxClient, "send",
338
+ return_value=mock.Mock(),
339
+ ) as mock_send,
340
+ ):
341
+
342
+ response = await client.aio.interactions.create(
343
+ model='gemini-2.5-flash',
344
+ input='What is the largest planet in our solar system?',
345
+ )
346
+
347
+ mock_send.assert_called_once()
348
+ mock_access_token.assert_called_once()
349
+ args, kwargs = mock_send.call_args
350
+ headers = args[0].headers
351
+ assert any(
352
+ key == "authorization" and value == 'Bearer fake-vertex-token'
353
+ for key, value in headers.items())
354
+ assert any(
355
+ key == "x-goog-user-project" and value == 'test-quota-project'
356
+ for key, value in headers.items())
357
+
358
+ @pytest.mark.asyncio
359
+ async def test_async_interactions_vertex_key_no_auth_header():
360
+ from ..._api_client import BaseApiClient
361
+ from ..._api_client import AsyncHttpxClient
362
+ creds = mock.Mock()
363
+ client = Client(vertexai=True, api_key='test-api-key')
364
+
365
+ with (
366
+ mock.patch.object(
367
+ BaseApiClient, "_async_access_token", return_value='fake-vertex-token'
368
+ ) as mock_access_token,
369
+ mock.patch.object(
370
+ AsyncHttpxClient, "send",
371
+ return_value=mock.Mock(),
372
+ ) as mock_send,
373
+ ):
374
+
375
+ response = await client.aio.interactions.create(
376
+ model='gemini-2.5-flash',
377
+ input='What is the largest planet in our solar system?',
378
+ )
379
+
380
+ mock_send.assert_called_once()
381
+ mock_access_token.assert_not_called()
382
+ args, kwargs = mock_send.call_args
383
+ headers = args[0].headers
384
+ assert any(
385
+ key == "x-goog-api-key" and value == 'test-api-key'
386
+ for key, value in headers.items())
387
+
388
+ @pytest.mark.asyncio
389
+ async def test_async_interactions_vertex_url():
390
+ from ..._api_client import AsyncHttpxClient
391
+ creds = mock.Mock()
392
+ creds.quota_project_id = "test-quota-project"
393
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
394
+
395
+ with mock.patch.object(AsyncHttpxClient, "send") as mock_send:
396
+ mock_send.return_value = Response(200, request=Request('POST', ''))
397
+ await client.aio.interactions.create(
398
+ model='gemini-1.5-flash',
399
+ input='Hello',
400
+ )
401
+ mock_send.assert_called_once()
402
+ request = mock_send.call_args[0][0]
403
+ assert str(request.url) == 'https://us-central1-aiplatform.googleapis.com/v1beta1/projects/test-project/locations/us-central1/interactions'
404
+
405
+ @pytest.mark.asyncio
406
+ async def test_async_interactions_vertex_auth_refresh_on_retry():
407
+ from ..._api_client import BaseApiClient
408
+ from ..._api_client import AsyncHttpxClient
409
+
410
+ creds = mock.Mock()
411
+ creds.quota_project_id = "test-quota-project"
412
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
413
+ client.aio._api_client.max_retries = 2
414
+
415
+ token_values = ['token1', 'token2', 'token3']
416
+ token_iter = iter(token_values)
417
+
418
+ async def get_token():
419
+ return next(token_iter)
420
+
421
+ with (
422
+ mock.patch.object(BaseApiClient, "_async_access_token", side_effect=get_token) as mock_access_token,
423
+ mock.patch.object(AsyncHttpxClient, "send") as mock_send,
424
+ ):
425
+ mock_send.side_effect = [
426
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
427
+ Response(500, request=Request('POST', ''), headers={"retry-after-ms": "1"}),
428
+ Response(200, request=Request('POST', '')),
429
+ ]
430
+
431
+ await client.aio.interactions.create(model='gemini-1.5-flash', input='Hello')
432
+
433
+ assert mock_access_token.call_count == 3
434
+ assert mock_send.call_count == 3
435
+ for i in range(3):
436
+ headers = mock_send.call_args_list[i][0][0].headers
437
+ assert headers['authorization'] == f'Bearer {token_values[i]}'
438
+
439
+ @pytest.mark.xfail(reason="extra_headers don't override default auth")
440
+ @pytest.mark.asyncio
441
+ async def test_async_interactions_vertex_extra_headers_override():
442
+ from ..._api_client import BaseApiClient
443
+ from ..._api_client import AsyncHttpxClient
444
+
445
+ creds = mock.Mock()
446
+ creds.quota_project_id = "test-quota-project"
447
+ client = Client(vertexai=True, project='test-project', location='us-central1', credentials=creds)
448
+
449
+ with (
450
+ mock.patch.object(BaseApiClient, "_async_access_token", return_value='default-token') as mock_access_token,
451
+ mock.patch.object(AsyncHttpxClient, "send") as mock_send,
452
+ ):
453
+ mock_send.return_value = Response(200, request=Request('POST', ''))
454
+
455
+ # Override Authorization
456
+ await client.aio.interactions.create(
457
+ model='gemini-1.5-flash',
458
+ input='Hello',
459
+ extra_headers={'Authorization': 'Bearer manual-token'}
460
+ )
461
+ mock_send.assert_called_once()
462
+ headers = mock_send.call_args[0][0].headers
463
+ assert headers['authorization'] == 'Bearer manual-token'
464
+ mock_access_token.assert_not_called()
465
+
466
+ mock_send.reset_mock()
467
+ mock_access_token.reset_mock()
468
+
469
+ # Provide API Key
470
+ await client.aio.interactions.create(
471
+ model='gemini-1.5-flash',
472
+ input='Hello',
473
+ extra_headers={'x-goog-api-key': 'manual-key'}
474
+ )
475
+ mock_send.assert_called_once()
476
+ headers = mock_send.call_args[0][0].headers
477
+ assert headers['x-goog-api-key'] == 'manual-key'
478
+ assert 'authorization' not in headers
479
+ mock_access_token.assert_not_called()
@@ -50,6 +50,7 @@ def test_client_timeout():
50
50
  mock_nextgen_client.assert_called_once_with(
51
51
  base_url=mock.ANY,
52
52
  api_key="placeholder",
53
+ api_version="v1alpha",
53
54
  default_headers=mock.ANY,
54
55
  http_client=mock.ANY,
55
56
  timeout=5.0,
@@ -73,6 +74,7 @@ async def test_async_client_timeout():
73
74
  mock_nextgen_client.assert_called_once_with(
74
75
  base_url=mock.ANY,
75
76
  api_key="placeholder",
77
+ api_version="v1alpha",
76
78
  default_headers=mock.ANY,
77
79
  http_client=mock.ANY,
78
80
  timeout=5.0,
@@ -0,0 +1,105 @@
1
+
2
+ # Copyright 2025 Google LLC
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+
18
+ """Tests for Interactions API URL paths."""
19
+
20
+ from unittest import mock
21
+ import pytest
22
+ from httpx import Request, Response
23
+ from ..._api_client import AsyncHttpxClient
24
+ from httpx import Client as HTTPClient
25
+ from .. import pytest_helper
26
+ import google.auth
27
+
28
+ @mock.patch.object(google.auth, "default", autospec=True)
29
+ def test_interactions_paths(mock_auth_default, client):
30
+ interaction_id = "test-interaction-id"
31
+
32
+ mock_creds = mock.Mock()
33
+ mock_creds.token = "test-token"
34
+ mock_creds.expired = False
35
+ mock_creds.quota_project_id = "test-quota-project"
36
+ mock_auth_default.return_value = (mock_creds, "test-project")
37
+
38
+ if client._api_client.vertexai:
39
+ expected_base_url = f'https://{client._api_client.location}-aiplatform.googleapis.com/v1beta1/projects/{client._api_client.project}/locations/{client._api_client.location}'
40
+ else:
41
+ expected_base_url = "https://generativelanguage.googleapis.com/v1beta"
42
+
43
+ with mock.patch.object(HTTPClient, "send") as mock_send:
44
+ mock_send.return_value = Response(200, request=Request('GET', ''))
45
+ client.interactions.get(id=interaction_id)
46
+ mock_send.assert_called_once()
47
+ request = mock_send.call_args[0][0]
48
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}'
49
+
50
+ mock_send.reset_mock()
51
+ mock_send.return_value = Response(200, request=Request('POST', ''))
52
+ client.interactions.cancel(id=interaction_id)
53
+ mock_send.assert_called_once()
54
+ request = mock_send.call_args[0][0]
55
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}/cancel'
56
+
57
+ mock_send.reset_mock()
58
+ mock_send.return_value = Response(200, request=Request('DELETE', ''))
59
+ client.interactions.delete(id=interaction_id)
60
+ mock_send.assert_called_once()
61
+ request = mock_send.call_args[0][0]
62
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}'
63
+
64
+ @pytest.mark.asyncio
65
+ @mock.patch.object(google.auth, "default", autospec=True)
66
+ async def test_async_interactions_paths(mock_auth_default, client):
67
+ interaction_id = "test-interaction-id"
68
+
69
+ mock_creds = mock.Mock()
70
+ mock_creds.token = "test-token"
71
+ mock_creds.expired = False
72
+ mock_creds.quota_project_id = "test-quota-project"
73
+ mock_auth_default.return_value = (mock_creds, "test-project")
74
+
75
+ if client._api_client.vertexai:
76
+ expected_base_url = f'https://{client._api_client.location}-aiplatform.googleapis.com/v1beta1/projects/{client._api_client.project}/locations/{client._api_client.location}'
77
+ else:
78
+ expected_base_url = "https://generativelanguage.googleapis.com/v1beta"
79
+
80
+ with mock.patch.object(AsyncHttpxClient, "send") as mock_send:
81
+ mock_send.return_value = Response(200, request=Request('GET', ''))
82
+ await client.aio.interactions.get(id=interaction_id)
83
+ mock_send.assert_called_once()
84
+ request = mock_send.call_args[0][0]
85
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}'
86
+
87
+ mock_send.reset_mock()
88
+ mock_send.return_value = Response(200, request=Request('POST', ''))
89
+ await client.aio.interactions.cancel(id=interaction_id)
90
+ mock_send.assert_called_once()
91
+ request = mock_send.call_args[0][0]
92
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}/cancel'
93
+
94
+ mock_send.reset_mock()
95
+ mock_send.return_value = Response(200, request=Request('DELETE', ''))
96
+ await client.aio.interactions.delete(id=interaction_id)
97
+ mock_send.assert_called_once()
98
+ request = mock_send.call_args[0][0]
99
+ assert str(request.url) == f'{expected_base_url}/interactions/{interaction_id}'
100
+
101
+ pytestmark = pytest_helper.setup(
102
+ file=__file__,
103
+ globals_for_file=globals(),
104
+ test_table=[],
105
+ )
@@ -839,42 +839,6 @@ async def test_bidi_setup_error_if_multispeaker_voice_config(vertexai):
839
839
  )
840
840
 
841
841
 
842
- @pytest.mark.parametrize('vertexai', [True, False])
843
- @pytest.mark.asyncio
844
- async def test_replicated_voice_config(vertexai):
845
- # Config is a dict
846
- config_dict = {
847
- 'speech_config': {
848
- 'voice_config': {
849
- 'replicated_voice_config': {
850
- 'mime_type': 'audio/pcm',
851
- 'voice_sample_audio': bytes([0, 0, 0]),
852
- },
853
- },
854
- },
855
- }
856
- result = await get_connect_message(
857
- mock_api_client(vertexai=vertexai),
858
- model='test_model',
859
- config=config_dict,
860
- )
861
- if vertexai:
862
- try:
863
- replicated_voice_config = result['setup']['generationConfig'][
864
- 'speechConfig'
865
- ]['voiceConfig']['replicatedVoiceConfig']
866
- except KeyError:
867
- replicated_voice_config = result['setup']['generationConfig'][
868
- 'speechConfig'
869
- ]['voiceConfig']['replicated_voice_config']
870
- assert replicated_voice_config == {
871
- 'mime_type': 'audio/pcm',
872
- 'voice_sample_audio': 'AAAA',
873
- }
874
- else:
875
- return
876
-
877
-
878
842
  @pytest.mark.parametrize('vertexai', [True, False])
879
843
  @pytest.mark.asyncio
880
844
  async def test_explicit_vad(vertexai):
@@ -1690,6 +1654,8 @@ async def test_bidi_setup_to_api_with_thinking_config(vertexai):
1690
1654
  result = await get_connect_message(
1691
1655
  mock_api_client(vertexai=vertexai), model='test_model', config=config_dict
1692
1656
  )
1657
+ result = pytest_helper.camel_to_snake_all_keys(result)
1658
+ expected_result = pytest_helper.camel_to_snake_all_keys(expected_result)
1693
1659
  assert result == expected_result
1694
1660
 
1695
1661
 
@@ -37,7 +37,7 @@ class TestLocalTokenizer(unittest.TestCase):
37
37
  self.mock_tokenizer = MagicMock()
38
38
  self.mock_get_sentencepiece.return_value = self.mock_tokenizer
39
39
 
40
- self.tokenizer = local_tokenizer.LocalTokenizer(model_name='gemini-2.5-flash')
40
+ self.tokenizer = local_tokenizer.LocalTokenizer(model_name='gemini-3-pro-preview')
41
41
 
42
42
  def tearDown(self):
43
43
  patch.stopall()