google-genai 1.56.0__py3-none-any.whl → 1.58.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 (246) hide show
  1. google/genai/_api_client.py +49 -26
  2. google/genai/_interactions/__init__.py +3 -0
  3. google/genai/_interactions/_base_client.py +1 -1
  4. google/genai/_interactions/_client.py +57 -3
  5. google/genai/_interactions/_client_adapter.py +48 -0
  6. google/genai/_interactions/types/__init__.py +6 -0
  7. google/genai/_interactions/types/audio_content.py +2 -0
  8. google/genai/_interactions/types/audio_content_param.py +2 -0
  9. google/genai/_interactions/types/content.py +65 -0
  10. google/genai/_interactions/types/content_delta.py +10 -2
  11. google/genai/_interactions/types/content_param.py +63 -0
  12. google/genai/_interactions/types/content_start.py +5 -46
  13. google/genai/_interactions/types/content_stop.py +1 -2
  14. google/genai/_interactions/types/document_content.py +2 -0
  15. google/genai/_interactions/types/document_content_param.py +2 -0
  16. google/genai/_interactions/types/error_event.py +1 -2
  17. google/genai/_interactions/types/file_search_call_content.py +32 -0
  18. google/genai/_interactions/types/file_search_call_content_param.py +31 -0
  19. google/genai/_interactions/types/generation_config.py +4 -0
  20. google/genai/_interactions/types/generation_config_param.py +4 -0
  21. google/genai/_interactions/types/image_config.py +31 -0
  22. google/genai/_interactions/types/image_config_param.py +30 -0
  23. google/genai/_interactions/types/image_content.py +2 -0
  24. google/genai/_interactions/types/image_content_param.py +2 -0
  25. google/genai/_interactions/types/interaction.py +6 -52
  26. google/genai/_interactions/types/interaction_create_params.py +4 -22
  27. google/genai/_interactions/types/interaction_event.py +1 -2
  28. google/genai/_interactions/types/interaction_sse_event.py +5 -3
  29. google/genai/_interactions/types/interaction_status_update.py +1 -2
  30. google/genai/_interactions/types/model.py +1 -0
  31. google/genai/_interactions/types/model_param.py +1 -0
  32. google/genai/_interactions/types/turn.py +3 -44
  33. google/genai/_interactions/types/turn_param.py +4 -40
  34. google/genai/_interactions/types/usage.py +1 -1
  35. google/genai/_interactions/types/usage_param.py +1 -1
  36. google/genai/_interactions/types/video_content.py +2 -0
  37. google/genai/_interactions/types/video_content_param.py +2 -0
  38. google/genai/_live_converters.py +118 -34
  39. google/genai/_local_tokenizer_loader.py +1 -0
  40. google/genai/_tokens_converters.py +14 -14
  41. google/genai/_transformers.py +15 -21
  42. google/genai/batches.py +27 -22
  43. google/genai/caches.py +42 -42
  44. google/genai/chats.py +0 -2
  45. google/genai/client.py +61 -55
  46. google/genai/files.py +224 -0
  47. google/genai/live.py +1 -1
  48. google/genai/models.py +56 -44
  49. google/genai/tests/__init__.py +21 -0
  50. google/genai/tests/afc/__init__.py +21 -0
  51. google/genai/tests/afc/test_convert_if_exist_pydantic_model.py +309 -0
  52. google/genai/tests/afc/test_convert_number_values_for_function_call_args.py +63 -0
  53. google/genai/tests/afc/test_find_afc_incompatible_tool_indexes.py +240 -0
  54. google/genai/tests/afc/test_generate_content_stream_afc.py +530 -0
  55. google/genai/tests/afc/test_generate_content_stream_afc_thoughts.py +77 -0
  56. google/genai/tests/afc/test_get_function_map.py +176 -0
  57. google/genai/tests/afc/test_get_function_response_parts.py +277 -0
  58. google/genai/tests/afc/test_get_max_remote_calls_for_afc.py +130 -0
  59. google/genai/tests/afc/test_invoke_function_from_dict_args.py +241 -0
  60. google/genai/tests/afc/test_raise_error_for_afc_incompatible_config.py +159 -0
  61. google/genai/tests/afc/test_should_append_afc_history.py +53 -0
  62. google/genai/tests/afc/test_should_disable_afc.py +214 -0
  63. google/genai/tests/batches/__init__.py +17 -0
  64. google/genai/tests/batches/test_cancel.py +77 -0
  65. google/genai/tests/batches/test_create.py +78 -0
  66. google/genai/tests/batches/test_create_with_bigquery.py +113 -0
  67. google/genai/tests/batches/test_create_with_file.py +82 -0
  68. google/genai/tests/batches/test_create_with_gcs.py +125 -0
  69. google/genai/tests/batches/test_create_with_inlined_requests.py +255 -0
  70. google/genai/tests/batches/test_delete.py +86 -0
  71. google/genai/tests/batches/test_embedding.py +157 -0
  72. google/genai/tests/batches/test_get.py +78 -0
  73. google/genai/tests/batches/test_list.py +79 -0
  74. google/genai/tests/caches/__init__.py +17 -0
  75. google/genai/tests/caches/constants.py +29 -0
  76. google/genai/tests/caches/test_create.py +210 -0
  77. google/genai/tests/caches/test_create_custom_url.py +105 -0
  78. google/genai/tests/caches/test_delete.py +54 -0
  79. google/genai/tests/caches/test_delete_custom_url.py +52 -0
  80. google/genai/tests/caches/test_get.py +94 -0
  81. google/genai/tests/caches/test_get_custom_url.py +52 -0
  82. google/genai/tests/caches/test_list.py +68 -0
  83. google/genai/tests/caches/test_update.py +70 -0
  84. google/genai/tests/caches/test_update_custom_url.py +58 -0
  85. google/genai/tests/chats/__init__.py +1 -0
  86. google/genai/tests/chats/test_get_history.py +598 -0
  87. google/genai/tests/chats/test_send_message.py +844 -0
  88. google/genai/tests/chats/test_validate_response.py +90 -0
  89. google/genai/tests/client/__init__.py +17 -0
  90. google/genai/tests/client/test_async_stream.py +427 -0
  91. google/genai/tests/client/test_client_close.py +197 -0
  92. google/genai/tests/client/test_client_initialization.py +1687 -0
  93. google/genai/tests/client/test_client_requests.py +221 -0
  94. google/genai/tests/client/test_custom_client.py +104 -0
  95. google/genai/tests/client/test_http_options.py +178 -0
  96. google/genai/tests/client/test_replay_client_equality.py +168 -0
  97. google/genai/tests/client/test_retries.py +846 -0
  98. google/genai/tests/client/test_upload_errors.py +136 -0
  99. google/genai/tests/common/__init__.py +17 -0
  100. google/genai/tests/common/test_common.py +954 -0
  101. google/genai/tests/conftest.py +162 -0
  102. google/genai/tests/documents/__init__.py +17 -0
  103. google/genai/tests/documents/test_delete.py +51 -0
  104. google/genai/tests/documents/test_get.py +85 -0
  105. google/genai/tests/documents/test_list.py +72 -0
  106. google/genai/tests/errors/__init__.py +1 -0
  107. google/genai/tests/errors/test_api_error.py +417 -0
  108. google/genai/tests/file_search_stores/__init__.py +17 -0
  109. google/genai/tests/file_search_stores/test_create.py +66 -0
  110. google/genai/tests/file_search_stores/test_delete.py +64 -0
  111. google/genai/tests/file_search_stores/test_get.py +94 -0
  112. google/genai/tests/file_search_stores/test_import_file.py +112 -0
  113. google/genai/tests/file_search_stores/test_list.py +57 -0
  114. google/genai/tests/file_search_stores/test_upload_to_file_search_store.py +141 -0
  115. google/genai/tests/files/__init__.py +17 -0
  116. google/genai/tests/files/test_delete.py +46 -0
  117. google/genai/tests/files/test_download.py +85 -0
  118. google/genai/tests/files/test_get.py +46 -0
  119. google/genai/tests/files/test_list.py +72 -0
  120. google/genai/tests/files/test_register.py +272 -0
  121. google/genai/tests/files/test_register_table.py +70 -0
  122. google/genai/tests/files/test_upload.py +255 -0
  123. google/genai/tests/imports/test_no_optional_imports.py +28 -0
  124. google/genai/tests/interactions/test_auth.py +476 -0
  125. google/genai/tests/interactions/test_integration.py +84 -0
  126. google/genai/tests/interactions/test_paths.py +105 -0
  127. google/genai/tests/live/__init__.py +16 -0
  128. google/genai/tests/live/test_live.py +2143 -0
  129. google/genai/tests/live/test_live_music.py +362 -0
  130. google/genai/tests/live/test_live_response.py +163 -0
  131. google/genai/tests/live/test_send_client_content.py +147 -0
  132. google/genai/tests/live/test_send_realtime_input.py +268 -0
  133. google/genai/tests/live/test_send_tool_response.py +222 -0
  134. google/genai/tests/local_tokenizer/__init__.py +17 -0
  135. google/genai/tests/local_tokenizer/test_local_tokenizer.py +343 -0
  136. google/genai/tests/local_tokenizer/test_local_tokenizer_loader.py +235 -0
  137. google/genai/tests/mcp/__init__.py +17 -0
  138. google/genai/tests/mcp/test_has_mcp_tool_usage.py +89 -0
  139. google/genai/tests/mcp/test_mcp_to_gemini_tools.py +191 -0
  140. google/genai/tests/mcp/test_parse_config_for_mcp_sessions.py +201 -0
  141. google/genai/tests/mcp/test_parse_config_for_mcp_usage.py +130 -0
  142. google/genai/tests/mcp/test_set_mcp_usage_header.py +72 -0
  143. google/genai/tests/models/__init__.py +17 -0
  144. google/genai/tests/models/constants.py +8 -0
  145. google/genai/tests/models/test_compute_tokens.py +120 -0
  146. google/genai/tests/models/test_count_tokens.py +159 -0
  147. google/genai/tests/models/test_delete.py +107 -0
  148. google/genai/tests/models/test_edit_image.py +264 -0
  149. google/genai/tests/models/test_embed_content.py +94 -0
  150. google/genai/tests/models/test_function_call_streaming.py +442 -0
  151. google/genai/tests/models/test_generate_content.py +2501 -0
  152. google/genai/tests/models/test_generate_content_cached_content.py +132 -0
  153. google/genai/tests/models/test_generate_content_config_zero_value.py +103 -0
  154. google/genai/tests/models/test_generate_content_from_apikey.py +44 -0
  155. google/genai/tests/models/test_generate_content_http_options.py +40 -0
  156. google/genai/tests/models/test_generate_content_image_generation.py +143 -0
  157. google/genai/tests/models/test_generate_content_mcp.py +343 -0
  158. google/genai/tests/models/test_generate_content_media_resolution.py +97 -0
  159. google/genai/tests/models/test_generate_content_model.py +139 -0
  160. google/genai/tests/models/test_generate_content_part.py +821 -0
  161. google/genai/tests/models/test_generate_content_thought.py +76 -0
  162. google/genai/tests/models/test_generate_content_tools.py +1761 -0
  163. google/genai/tests/models/test_generate_images.py +191 -0
  164. google/genai/tests/models/test_generate_videos.py +759 -0
  165. google/genai/tests/models/test_get.py +104 -0
  166. google/genai/tests/models/test_list.py +233 -0
  167. google/genai/tests/models/test_recontext_image.py +189 -0
  168. google/genai/tests/models/test_segment_image.py +148 -0
  169. google/genai/tests/models/test_update.py +95 -0
  170. google/genai/tests/models/test_upscale_image.py +157 -0
  171. google/genai/tests/operations/__init__.py +17 -0
  172. google/genai/tests/operations/test_get.py +38 -0
  173. google/genai/tests/public_samples/__init__.py +17 -0
  174. google/genai/tests/public_samples/test_gemini_text_only.py +34 -0
  175. google/genai/tests/pytest_helper.py +246 -0
  176. google/genai/tests/shared/__init__.py +16 -0
  177. google/genai/tests/shared/batches/__init__.py +14 -0
  178. google/genai/tests/shared/batches/test_create_delete.py +57 -0
  179. google/genai/tests/shared/batches/test_create_get_cancel.py +56 -0
  180. google/genai/tests/shared/batches/test_list.py +40 -0
  181. google/genai/tests/shared/caches/__init__.py +14 -0
  182. google/genai/tests/shared/caches/test_create_get_delete.py +67 -0
  183. google/genai/tests/shared/caches/test_create_update_get.py +71 -0
  184. google/genai/tests/shared/caches/test_list.py +40 -0
  185. google/genai/tests/shared/chats/__init__.py +14 -0
  186. google/genai/tests/shared/chats/test_send_message.py +48 -0
  187. google/genai/tests/shared/chats/test_send_message_stream.py +50 -0
  188. google/genai/tests/shared/files/__init__.py +14 -0
  189. google/genai/tests/shared/files/test_list.py +41 -0
  190. google/genai/tests/shared/files/test_upload_get_delete.py +54 -0
  191. google/genai/tests/shared/models/__init__.py +14 -0
  192. google/genai/tests/shared/models/test_compute_tokens.py +41 -0
  193. google/genai/tests/shared/models/test_count_tokens.py +40 -0
  194. google/genai/tests/shared/models/test_edit_image.py +67 -0
  195. google/genai/tests/shared/models/test_embed.py +40 -0
  196. google/genai/tests/shared/models/test_generate_content.py +39 -0
  197. google/genai/tests/shared/models/test_generate_content_stream.py +54 -0
  198. google/genai/tests/shared/models/test_generate_images.py +40 -0
  199. google/genai/tests/shared/models/test_generate_videos.py +38 -0
  200. google/genai/tests/shared/models/test_list.py +37 -0
  201. google/genai/tests/shared/models/test_recontext_image.py +55 -0
  202. google/genai/tests/shared/models/test_segment_image.py +52 -0
  203. google/genai/tests/shared/models/test_upscale_image.py +52 -0
  204. google/genai/tests/shared/tunings/__init__.py +16 -0
  205. google/genai/tests/shared/tunings/test_create.py +46 -0
  206. google/genai/tests/shared/tunings/test_create_get_cancel.py +56 -0
  207. google/genai/tests/shared/tunings/test_list.py +39 -0
  208. google/genai/tests/tokens/__init__.py +16 -0
  209. google/genai/tests/tokens/test_create.py +154 -0
  210. google/genai/tests/transformers/__init__.py +17 -0
  211. google/genai/tests/transformers/test_blobs.py +84 -0
  212. google/genai/tests/transformers/test_bytes.py +15 -0
  213. google/genai/tests/transformers/test_duck_type.py +96 -0
  214. google/genai/tests/transformers/test_function_responses.py +72 -0
  215. google/genai/tests/transformers/test_schema.py +653 -0
  216. google/genai/tests/transformers/test_t_batch.py +286 -0
  217. google/genai/tests/transformers/test_t_content.py +160 -0
  218. google/genai/tests/transformers/test_t_contents.py +398 -0
  219. google/genai/tests/transformers/test_t_part.py +85 -0
  220. google/genai/tests/transformers/test_t_parts.py +87 -0
  221. google/genai/tests/transformers/test_t_tool.py +157 -0
  222. google/genai/tests/transformers/test_t_tools.py +195 -0
  223. google/genai/tests/tunings/__init__.py +16 -0
  224. google/genai/tests/tunings/test_cancel.py +39 -0
  225. google/genai/tests/tunings/test_end_to_end.py +106 -0
  226. google/genai/tests/tunings/test_get.py +67 -0
  227. google/genai/tests/tunings/test_list.py +75 -0
  228. google/genai/tests/tunings/test_tune.py +268 -0
  229. google/genai/tests/types/__init__.py +16 -0
  230. google/genai/tests/types/test_bytes_internal.py +271 -0
  231. google/genai/tests/types/test_bytes_type.py +152 -0
  232. google/genai/tests/types/test_future.py +101 -0
  233. google/genai/tests/types/test_optional_types.py +36 -0
  234. google/genai/tests/types/test_part_type.py +616 -0
  235. google/genai/tests/types/test_schema_from_json_schema.py +417 -0
  236. google/genai/tests/types/test_schema_json_schema.py +468 -0
  237. google/genai/tests/types/test_types.py +2903 -0
  238. google/genai/types.py +631 -488
  239. google/genai/version.py +1 -1
  240. {google_genai-1.56.0.dist-info → google_genai-1.58.0.dist-info}/METADATA +6 -11
  241. google_genai-1.58.0.dist-info/RECORD +358 -0
  242. google_genai-1.56.0.dist-info/RECORD +0 -162
  243. /google/genai/{_interactions/py.typed → tests/interactions/__init__.py} +0 -0
  244. {google_genai-1.56.0.dist-info → google_genai-1.58.0.dist-info}/WHEEL +0 -0
  245. {google_genai-1.56.0.dist-info → google_genai-1.58.0.dist-info}/licenses/LICENSE +0 -0
  246. {google_genai-1.56.0.dist-info → google_genai-1.58.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,268 @@
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 live.py."""
18
+ import json
19
+ import os
20
+ from unittest import mock
21
+
22
+ import pytest
23
+ from websockets import client
24
+
25
+ from ... import client as gl_client
26
+ from ... import live
27
+ from ... import types
28
+ from .. import pytest_helper
29
+
30
+
31
+ IMAGE_FILE_PATH = os.path.abspath(
32
+ os.path.join(os.path.dirname(__file__), '../data/google.jpg')
33
+ )
34
+
35
+
36
+ def mock_api_client(vertexai=False):
37
+ api_client = mock.MagicMock(spec=gl_client.BaseApiClient)
38
+ api_client.api_key = 'TEST_API_KEY'
39
+ api_client._host = lambda: 'test_host'
40
+ api_client._http_options = {'headers': {}} # Ensure headers exist
41
+ api_client.vertexai = vertexai
42
+ api_client._api_client = api_client
43
+ return api_client
44
+
45
+
46
+ @pytest.fixture
47
+ def mock_websocket():
48
+ websocket = mock.AsyncMock(spec=client.ClientConnection)
49
+ websocket.send = mock.AsyncMock()
50
+ websocket.recv = mock.AsyncMock(
51
+ return_value='{"serverContent": {"turnComplete": true}}'
52
+ ) # Default response
53
+ websocket.close = mock.AsyncMock()
54
+ return websocket
55
+
56
+
57
+ @pytest.mark.parametrize('vertexai', [True, False])
58
+ @pytest.mark.asyncio
59
+ async def test_send_media_blob_dict(mock_websocket, vertexai):
60
+ session = live.AsyncSession(
61
+ api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket
62
+ )
63
+ content = {'data': bytes([0, 0, 0, 0, 0, 0]), 'mime_type': 'audio/pcm'}
64
+
65
+ await session.send_realtime_input(media=content)
66
+ mock_websocket.send.assert_called_once()
67
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
68
+ assert 'realtime_input' in sent_data
69
+
70
+ assert sent_data['realtime_input']['mediaChunks'][0]['data'] == 'AAAAAAAA'
71
+ assert pytest_helper.get_value_ignore_key_case(
72
+ sent_data['realtime_input']['mediaChunks'][0], 'mime_type'
73
+ ) == 'audio/pcm'
74
+
75
+
76
+ @pytest.mark.parametrize('vertexai', [True, False])
77
+ @pytest.mark.asyncio
78
+ async def test_send_media_blob(mock_websocket, vertexai):
79
+ session = live.AsyncSession(
80
+ api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket
81
+ )
82
+ content = types.Blob(data=bytes([0, 0, 0, 0, 0, 0]), mime_type='audio/pcm')
83
+
84
+ await session.send_realtime_input(media=content)
85
+ mock_websocket.send.assert_called_once()
86
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
87
+ assert 'realtime_input' in sent_data
88
+
89
+ assert sent_data['realtime_input']['mediaChunks'][0]['data'] == 'AAAAAAAA'
90
+ assert pytest_helper.get_value_ignore_key_case(
91
+ sent_data['realtime_input']['mediaChunks'][0], 'mime_type'
92
+ ) == 'audio/pcm'
93
+
94
+
95
+ @pytest.mark.parametrize('vertexai', [True, False])
96
+ @pytest.mark.asyncio
97
+ async def test_send_media_image(mock_websocket, vertexai, image_jpeg):
98
+ session = live.AsyncSession(
99
+ api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket
100
+ )
101
+
102
+ await session.send_realtime_input(media=image_jpeg)
103
+ mock_websocket.send.assert_called_once()
104
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
105
+ assert 'realtime_input' in sent_data
106
+
107
+ assert pytest_helper.get_value_ignore_key_case(
108
+ sent_data['realtime_input']['mediaChunks'][0], 'mime_type'
109
+ ) == 'image/jpeg'
110
+
111
+
112
+ @pytest.mark.parametrize('vertexai', [True, False])
113
+ @pytest.mark.parametrize(
114
+ 'content',
115
+ [
116
+ {'data': bytes([0, 0, 0, 0, 0, 0]), 'mime_type': 'audio/pcm'},
117
+ types.Blob(data=bytes([0, 0, 0, 0, 0, 0]), mime_type='audio/pcm'),
118
+ ],
119
+ )
120
+ @pytest.mark.asyncio
121
+ async def test_send_audio(mock_websocket, vertexai, content):
122
+ api_client = mock_api_client(vertexai=vertexai)
123
+ session = live.AsyncSession(api_client=api_client, websocket=mock_websocket)
124
+
125
+ await session.send_realtime_input(audio=content)
126
+ mock_websocket.send.assert_called_once()
127
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
128
+ assert 'realtime_input' in sent_data
129
+
130
+ assert sent_data['realtime_input']['audio']['data'] == 'AAAAAAAA'
131
+ assert pytest_helper.get_value_ignore_key_case(
132
+ sent_data['realtime_input']['audio'], 'mime_type'
133
+ ) == 'audio/pcm'
134
+
135
+
136
+ @pytest.mark.parametrize('vertexai', [True, False])
137
+ @pytest.mark.asyncio
138
+ async def test_send_bad_audio_blob(mock_websocket, vertexai):
139
+ api_client = mock_api_client(vertexai=vertexai)
140
+ session = live.AsyncSession(api_client=api_client, websocket=mock_websocket)
141
+ content = types.Blob(data=bytes([0, 0, 0, 0, 0, 0]), mime_type='image/png')
142
+
143
+ with pytest.raises(ValueError, match='.*Unsupported mime type.*'):
144
+ await session.send_realtime_input(audio=content)
145
+
146
+
147
+ @pytest.mark.parametrize('vertexai', [True, False])
148
+ @pytest.mark.asyncio
149
+ async def test_send_bad_video_blob(mock_websocket, vertexai):
150
+ api_client = mock_api_client(vertexai=vertexai)
151
+ session = live.AsyncSession(api_client=api_client, websocket=mock_websocket)
152
+ content = types.Blob(data=bytes([0, 0, 0, 0, 0, 0]), mime_type='audio/pcm')
153
+
154
+ with pytest.raises(ValueError, match='.*Unsupported mime type.*'):
155
+ await session.send_realtime_input(video=content)
156
+
157
+
158
+ @pytest.mark.parametrize('vertexai', [True, False])
159
+ @pytest.mark.asyncio
160
+ async def test_send_audio_stream_end(mock_websocket, vertexai):
161
+ session = live.AsyncSession(
162
+ api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket
163
+ )
164
+
165
+ await session.send_realtime_input(audio_stream_end=True)
166
+ mock_websocket.send.assert_called_once()
167
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
168
+ assert 'realtime_input' in sent_data
169
+
170
+ assert sent_data['realtime_input']['audioStreamEnd'] == True
171
+
172
+
173
+ @pytest.mark.parametrize('vertexai', [True, False])
174
+ @pytest.mark.parametrize(
175
+ 'content',
176
+ [
177
+ {'data': bytes([0, 0, 0, 0, 0, 0]), 'mime_type': 'image/png'},
178
+ types.Blob(data=bytes([0, 0, 0, 0, 0, 0]), mime_type='image/png'),
179
+ ],
180
+ )
181
+ @pytest.mark.asyncio
182
+ async def test_send_video(mock_websocket, vertexai, content):
183
+ api_client = mock_api_client(vertexai=vertexai)
184
+ session = live.AsyncSession(api_client=api_client, websocket=mock_websocket)
185
+
186
+ await session.send_realtime_input(video=content)
187
+ mock_websocket.send.assert_called_once()
188
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
189
+ assert 'realtime_input' in sent_data
190
+
191
+ assert sent_data['realtime_input']['video']['data'] == 'AAAAAAAA'
192
+ assert pytest_helper.get_value_ignore_key_case(
193
+ sent_data['realtime_input']['video'], 'mime_type'
194
+ ) == 'image/png'
195
+
196
+
197
+ @pytest.mark.parametrize('vertexai', [True, False])
198
+ @pytest.mark.asyncio
199
+ async def test_send_video_image(mock_websocket, vertexai, image_jpeg):
200
+ api_client = mock_api_client(vertexai=vertexai)
201
+ session = live.AsyncSession(api_client=api_client, websocket=mock_websocket)
202
+
203
+ await session.send_realtime_input(video=image_jpeg)
204
+ mock_websocket.send.assert_called_once()
205
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
206
+ assert 'realtime_input' in sent_data
207
+
208
+ assert pytest_helper.get_value_ignore_key_case(
209
+ sent_data['realtime_input']['video'], 'mime_type'
210
+ ) == 'image/jpeg'
211
+
212
+
213
+ @pytest.mark.parametrize('vertexai', [True, False])
214
+ @pytest.mark.asyncio
215
+ async def test_send_text(mock_websocket, vertexai):
216
+ api_client = mock_api_client(vertexai=vertexai)
217
+ session = live.AsyncSession(api_client=api_client, websocket=mock_websocket)
218
+
219
+ await session.send_realtime_input(text='Hello?')
220
+ mock_websocket.send.assert_called_once()
221
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
222
+ assert 'realtime_input' in sent_data
223
+
224
+ assert sent_data['realtime_input']['text'] == 'Hello?'
225
+
226
+
227
+ @pytest.mark.parametrize('vertexai', [True, False])
228
+ @pytest.mark.parametrize('activity', [{}, types.ActivityStart()])
229
+ @pytest.mark.asyncio
230
+ async def test_send_activity_start(mock_websocket, vertexai, activity):
231
+ session = live.AsyncSession(
232
+ api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket
233
+ )
234
+
235
+ await session.send_realtime_input(activity_start=activity)
236
+ mock_websocket.send.assert_called_once()
237
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
238
+ assert 'realtime_input' in sent_data
239
+
240
+ assert sent_data['realtime_input']['activityStart'] == {}
241
+
242
+
243
+ @pytest.mark.parametrize('vertexai', [True, False])
244
+ @pytest.mark.parametrize('activity', [{}, types.ActivityEnd()])
245
+ @pytest.mark.asyncio
246
+ async def test_send_activity_end(mock_websocket, vertexai, activity):
247
+ session = live.AsyncSession(
248
+ api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket
249
+ )
250
+
251
+ await session.send_realtime_input(activity_end=activity)
252
+ mock_websocket.send.assert_called_once()
253
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
254
+ assert 'realtime_input' in sent_data
255
+
256
+ assert sent_data['realtime_input']['activityEnd'] == {}
257
+
258
+
259
+ @pytest.mark.parametrize('vertexai', [True, False])
260
+ @pytest.mark.asyncio
261
+ async def test_send_multiple_args(mock_websocket, vertexai):
262
+ session = live.AsyncSession(
263
+ api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket
264
+ )
265
+ with pytest.raises(ValueError, match='.*one argument.*'):
266
+ await session.send_realtime_input(
267
+ text='Hello?', activity_start=types.ActivityStart()
268
+ )
@@ -0,0 +1,222 @@
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 live.py."""
18
+
19
+ import contextlib
20
+ import json
21
+ from unittest import mock
22
+
23
+ import pytest
24
+ from websockets import client
25
+
26
+ from .. import pytest_helper
27
+ from ... import client as gl_client
28
+ from ... import live
29
+ from ... import types
30
+
31
+
32
+ def exception_if_mldev(vertexai, exception_type: type[Exception]):
33
+ if vertexai:
34
+ return contextlib.nullcontext()
35
+ else:
36
+ return pytest.raises(exception_type)
37
+
38
+
39
+ def mock_api_client(vertexai=False):
40
+ api_client = mock.MagicMock(spec=gl_client.BaseApiClient)
41
+ api_client.api_key = 'TEST_API_KEY'
42
+ api_client._host = lambda: 'test_host'
43
+ api_client._http_options = {'headers': {}} # Ensure headers exist
44
+ api_client.vertexai = vertexai
45
+ api_client._api_client = api_client
46
+ return api_client
47
+
48
+
49
+ @pytest.fixture
50
+ def mock_websocket():
51
+ websocket = mock.AsyncMock(spec=client.ClientConnection)
52
+ websocket.send = mock.AsyncMock()
53
+ websocket.recv = mock.AsyncMock(
54
+ return_value='{"serverContent": {"turnComplete": true}}'
55
+ ) # Default response
56
+ websocket.close = mock.AsyncMock()
57
+ return websocket
58
+
59
+
60
+ @pytest.mark.parametrize('vertexai', [True, False])
61
+ @pytest.mark.asyncio
62
+ async def test_function_response_dict(mock_websocket, vertexai):
63
+ api_client = mock_api_client(vertexai=vertexai)
64
+ session = live.AsyncSession(
65
+ api_client=api_client, websocket=mock_websocket
66
+ )
67
+
68
+ input = {
69
+ 'name': 'get_current_weather',
70
+ 'response': {'temperature': 14.5, 'unit': 'C'},
71
+ }
72
+
73
+ if not vertexai:
74
+ input['id'] = 'some-id'
75
+
76
+ await session.send_tool_response(function_responses=input)
77
+
78
+ mock_websocket.send.assert_called_once()
79
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
80
+ assert 'tool_response' in sent_data
81
+
82
+ assert (
83
+ sent_data['tool_response']['functionResponses'][0]['name']
84
+ == 'get_current_weather'
85
+ )
86
+ assert (
87
+ sent_data['tool_response']['functionResponses'][0]['response'][
88
+ 'temperature'
89
+ ]
90
+ == 14.5
91
+ )
92
+ assert (
93
+ sent_data['tool_response']['functionResponses'][0]['response']['unit']
94
+ == 'C'
95
+ )
96
+
97
+
98
+ @pytest.mark.parametrize('vertexai', [True, False])
99
+ @pytest.mark.asyncio
100
+ async def test_function_response(mock_websocket, vertexai):
101
+ session = live.AsyncSession(
102
+ api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket
103
+ )
104
+
105
+ input = types.FunctionResponse(
106
+ name='get_current_weather',
107
+ response={
108
+ 'temperature': 14.5,
109
+ 'unit': 'C',
110
+ 'user_name': 'test_user_name',
111
+ 'userEmail': 'test_user_email',
112
+ },
113
+ )
114
+ if not vertexai:
115
+ input.id = 'some-id'
116
+
117
+ await session.send_tool_response(function_responses=input)
118
+ mock_websocket.send.assert_called_once()
119
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
120
+ assert 'tool_response' in sent_data
121
+
122
+ assert (
123
+ sent_data['tool_response']['functionResponses'][0]['name']
124
+ == 'get_current_weather'
125
+ )
126
+ assert (
127
+ sent_data['tool_response']['functionResponses'][0]['response']
128
+ == input.response
129
+ )
130
+
131
+
132
+ @pytest.mark.parametrize('vertexai', [True, False])
133
+ @pytest.mark.asyncio
134
+ async def test_function_response_scheduling(mock_websocket, vertexai):
135
+ api_client = mock_api_client(vertexai=vertexai)
136
+ session = live.AsyncSession(api_client=api_client, websocket=mock_websocket)
137
+
138
+ input = types.FunctionResponse(
139
+ name='get_current_weather',
140
+ response={'temperature': 14.5, 'unit': 'C'},
141
+ will_continue=True,
142
+ scheduling=types.FunctionResponseScheduling.SILENT,
143
+ )
144
+ if not vertexai:
145
+ input.id = 'some-id'
146
+
147
+ await session.send_tool_response(function_responses=input)
148
+
149
+ mock_websocket.send.assert_called_once()
150
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
151
+ assert 'tool_response' in sent_data
152
+
153
+ assert pytest_helper.get_value_ignore_key_case(
154
+ sent_data['tool_response']['functionResponses'][0], 'will_continue'
155
+ )
156
+ assert (
157
+ sent_data['tool_response']['functionResponses'][0]['scheduling']
158
+ == 'SILENT'
159
+ )
160
+
161
+
162
+ @pytest.mark.parametrize('vertexai', [True, False])
163
+ @pytest.mark.asyncio
164
+ async def test_function_response_list(mock_websocket, vertexai):
165
+ session = live.AsyncSession(
166
+ api_client=mock_api_client(vertexai=vertexai), websocket=mock_websocket
167
+ )
168
+
169
+ input1 = {
170
+ 'name': 'get_current_weather',
171
+ 'response': {'temperature': 14.5, 'unit': 'C'},
172
+ }
173
+ input2 = {
174
+ 'name': 'get_current_weather',
175
+ 'response': {'temperature': 99.9, 'unit': 'C'},
176
+ }
177
+
178
+ if not vertexai:
179
+ input1['id'] = '1'
180
+ input2['id'] = '2'
181
+
182
+ await session.send_tool_response(function_responses=[input1, input2])
183
+ mock_websocket.send.assert_called_once()
184
+ sent_data = json.loads(mock_websocket.send.call_args[0][0])
185
+ assert 'tool_response' in sent_data
186
+
187
+ assert len(sent_data['tool_response']['functionResponses']) == 2
188
+ assert (
189
+ sent_data['tool_response']['functionResponses'][0]['response'][
190
+ 'temperature'
191
+ ]
192
+ == 14.5
193
+ )
194
+ assert (
195
+ sent_data['tool_response']['functionResponses'][1]['response'][
196
+ 'temperature'
197
+ ]
198
+ == 99.9
199
+ )
200
+
201
+
202
+ @pytest.mark.parametrize('vertexai', [True, False])
203
+ @pytest.mark.asyncio
204
+ async def test_missing_id(mock_websocket, vertexai):
205
+ api_client = mock_api_client(vertexai=vertexai)
206
+ session = live.AsyncSession(
207
+ api_client=api_client, websocket=mock_websocket
208
+ )
209
+
210
+ input1 = {
211
+ 'name': 'get_current_weather',
212
+ 'response': {'temperature': 14.5, 'unit': 'C'},
213
+ 'id': '1',
214
+ }
215
+ input2 = {
216
+ 'name': 'get_current_weather',
217
+ 'response': {'temperature': 99.9, 'unit': 'C'},
218
+ }
219
+
220
+ if not vertexai:
221
+ with pytest.raises(ValueError, match=".*must have.*"):
222
+ await session.send_tool_response(function_responses=[input1, input2])
@@ -0,0 +1,17 @@
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 the Google GenAI SDK's local_tokenizer module."""