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.
- google/genai/_api_client.py +49 -26
- google/genai/_interactions/__init__.py +3 -0
- google/genai/_interactions/_base_client.py +1 -1
- google/genai/_interactions/_client.py +57 -3
- google/genai/_interactions/_client_adapter.py +48 -0
- google/genai/_interactions/types/__init__.py +6 -0
- google/genai/_interactions/types/audio_content.py +2 -0
- google/genai/_interactions/types/audio_content_param.py +2 -0
- google/genai/_interactions/types/content.py +65 -0
- google/genai/_interactions/types/content_delta.py +10 -2
- google/genai/_interactions/types/content_param.py +63 -0
- google/genai/_interactions/types/content_start.py +5 -46
- google/genai/_interactions/types/content_stop.py +1 -2
- google/genai/_interactions/types/document_content.py +2 -0
- google/genai/_interactions/types/document_content_param.py +2 -0
- google/genai/_interactions/types/error_event.py +1 -2
- google/genai/_interactions/types/file_search_call_content.py +32 -0
- google/genai/_interactions/types/file_search_call_content_param.py +31 -0
- google/genai/_interactions/types/generation_config.py +4 -0
- google/genai/_interactions/types/generation_config_param.py +4 -0
- google/genai/_interactions/types/image_config.py +31 -0
- google/genai/_interactions/types/image_config_param.py +30 -0
- google/genai/_interactions/types/image_content.py +2 -0
- google/genai/_interactions/types/image_content_param.py +2 -0
- google/genai/_interactions/types/interaction.py +6 -52
- google/genai/_interactions/types/interaction_create_params.py +4 -22
- google/genai/_interactions/types/interaction_event.py +1 -2
- google/genai/_interactions/types/interaction_sse_event.py +5 -3
- google/genai/_interactions/types/interaction_status_update.py +1 -2
- google/genai/_interactions/types/model.py +1 -0
- google/genai/_interactions/types/model_param.py +1 -0
- google/genai/_interactions/types/turn.py +3 -44
- google/genai/_interactions/types/turn_param.py +4 -40
- google/genai/_interactions/types/usage.py +1 -1
- google/genai/_interactions/types/usage_param.py +1 -1
- google/genai/_interactions/types/video_content.py +2 -0
- google/genai/_interactions/types/video_content_param.py +2 -0
- google/genai/_live_converters.py +118 -34
- google/genai/_local_tokenizer_loader.py +1 -0
- google/genai/_tokens_converters.py +14 -14
- google/genai/_transformers.py +15 -21
- google/genai/batches.py +27 -22
- google/genai/caches.py +42 -42
- google/genai/chats.py +0 -2
- google/genai/client.py +61 -55
- google/genai/files.py +224 -0
- google/genai/live.py +1 -1
- google/genai/models.py +56 -44
- google/genai/tests/__init__.py +21 -0
- google/genai/tests/afc/__init__.py +21 -0
- google/genai/tests/afc/test_convert_if_exist_pydantic_model.py +309 -0
- google/genai/tests/afc/test_convert_number_values_for_function_call_args.py +63 -0
- google/genai/tests/afc/test_find_afc_incompatible_tool_indexes.py +240 -0
- google/genai/tests/afc/test_generate_content_stream_afc.py +530 -0
- google/genai/tests/afc/test_generate_content_stream_afc_thoughts.py +77 -0
- google/genai/tests/afc/test_get_function_map.py +176 -0
- google/genai/tests/afc/test_get_function_response_parts.py +277 -0
- google/genai/tests/afc/test_get_max_remote_calls_for_afc.py +130 -0
- google/genai/tests/afc/test_invoke_function_from_dict_args.py +241 -0
- google/genai/tests/afc/test_raise_error_for_afc_incompatible_config.py +159 -0
- google/genai/tests/afc/test_should_append_afc_history.py +53 -0
- google/genai/tests/afc/test_should_disable_afc.py +214 -0
- google/genai/tests/batches/__init__.py +17 -0
- google/genai/tests/batches/test_cancel.py +77 -0
- google/genai/tests/batches/test_create.py +78 -0
- google/genai/tests/batches/test_create_with_bigquery.py +113 -0
- google/genai/tests/batches/test_create_with_file.py +82 -0
- google/genai/tests/batches/test_create_with_gcs.py +125 -0
- google/genai/tests/batches/test_create_with_inlined_requests.py +255 -0
- google/genai/tests/batches/test_delete.py +86 -0
- google/genai/tests/batches/test_embedding.py +157 -0
- google/genai/tests/batches/test_get.py +78 -0
- google/genai/tests/batches/test_list.py +79 -0
- google/genai/tests/caches/__init__.py +17 -0
- google/genai/tests/caches/constants.py +29 -0
- google/genai/tests/caches/test_create.py +210 -0
- google/genai/tests/caches/test_create_custom_url.py +105 -0
- google/genai/tests/caches/test_delete.py +54 -0
- google/genai/tests/caches/test_delete_custom_url.py +52 -0
- google/genai/tests/caches/test_get.py +94 -0
- google/genai/tests/caches/test_get_custom_url.py +52 -0
- google/genai/tests/caches/test_list.py +68 -0
- google/genai/tests/caches/test_update.py +70 -0
- google/genai/tests/caches/test_update_custom_url.py +58 -0
- google/genai/tests/chats/__init__.py +1 -0
- google/genai/tests/chats/test_get_history.py +598 -0
- google/genai/tests/chats/test_send_message.py +844 -0
- google/genai/tests/chats/test_validate_response.py +90 -0
- google/genai/tests/client/__init__.py +17 -0
- google/genai/tests/client/test_async_stream.py +427 -0
- google/genai/tests/client/test_client_close.py +197 -0
- google/genai/tests/client/test_client_initialization.py +1687 -0
- google/genai/tests/client/test_client_requests.py +221 -0
- google/genai/tests/client/test_custom_client.py +104 -0
- google/genai/tests/client/test_http_options.py +178 -0
- google/genai/tests/client/test_replay_client_equality.py +168 -0
- google/genai/tests/client/test_retries.py +846 -0
- google/genai/tests/client/test_upload_errors.py +136 -0
- google/genai/tests/common/__init__.py +17 -0
- google/genai/tests/common/test_common.py +954 -0
- google/genai/tests/conftest.py +162 -0
- google/genai/tests/documents/__init__.py +17 -0
- google/genai/tests/documents/test_delete.py +51 -0
- google/genai/tests/documents/test_get.py +85 -0
- google/genai/tests/documents/test_list.py +72 -0
- google/genai/tests/errors/__init__.py +1 -0
- google/genai/tests/errors/test_api_error.py +417 -0
- google/genai/tests/file_search_stores/__init__.py +17 -0
- google/genai/tests/file_search_stores/test_create.py +66 -0
- google/genai/tests/file_search_stores/test_delete.py +64 -0
- google/genai/tests/file_search_stores/test_get.py +94 -0
- google/genai/tests/file_search_stores/test_import_file.py +112 -0
- google/genai/tests/file_search_stores/test_list.py +57 -0
- google/genai/tests/file_search_stores/test_upload_to_file_search_store.py +141 -0
- google/genai/tests/files/__init__.py +17 -0
- google/genai/tests/files/test_delete.py +46 -0
- google/genai/tests/files/test_download.py +85 -0
- google/genai/tests/files/test_get.py +46 -0
- google/genai/tests/files/test_list.py +72 -0
- google/genai/tests/files/test_register.py +272 -0
- google/genai/tests/files/test_register_table.py +70 -0
- google/genai/tests/files/test_upload.py +255 -0
- google/genai/tests/imports/test_no_optional_imports.py +28 -0
- google/genai/tests/interactions/test_auth.py +476 -0
- google/genai/tests/interactions/test_integration.py +84 -0
- google/genai/tests/interactions/test_paths.py +105 -0
- google/genai/tests/live/__init__.py +16 -0
- google/genai/tests/live/test_live.py +2143 -0
- google/genai/tests/live/test_live_music.py +362 -0
- google/genai/tests/live/test_live_response.py +163 -0
- google/genai/tests/live/test_send_client_content.py +147 -0
- google/genai/tests/live/test_send_realtime_input.py +268 -0
- google/genai/tests/live/test_send_tool_response.py +222 -0
- google/genai/tests/local_tokenizer/__init__.py +17 -0
- google/genai/tests/local_tokenizer/test_local_tokenizer.py +343 -0
- google/genai/tests/local_tokenizer/test_local_tokenizer_loader.py +235 -0
- google/genai/tests/mcp/__init__.py +17 -0
- google/genai/tests/mcp/test_has_mcp_tool_usage.py +89 -0
- google/genai/tests/mcp/test_mcp_to_gemini_tools.py +191 -0
- google/genai/tests/mcp/test_parse_config_for_mcp_sessions.py +201 -0
- google/genai/tests/mcp/test_parse_config_for_mcp_usage.py +130 -0
- google/genai/tests/mcp/test_set_mcp_usage_header.py +72 -0
- google/genai/tests/models/__init__.py +17 -0
- google/genai/tests/models/constants.py +8 -0
- google/genai/tests/models/test_compute_tokens.py +120 -0
- google/genai/tests/models/test_count_tokens.py +159 -0
- google/genai/tests/models/test_delete.py +107 -0
- google/genai/tests/models/test_edit_image.py +264 -0
- google/genai/tests/models/test_embed_content.py +94 -0
- google/genai/tests/models/test_function_call_streaming.py +442 -0
- google/genai/tests/models/test_generate_content.py +2501 -0
- google/genai/tests/models/test_generate_content_cached_content.py +132 -0
- google/genai/tests/models/test_generate_content_config_zero_value.py +103 -0
- google/genai/tests/models/test_generate_content_from_apikey.py +44 -0
- google/genai/tests/models/test_generate_content_http_options.py +40 -0
- google/genai/tests/models/test_generate_content_image_generation.py +143 -0
- google/genai/tests/models/test_generate_content_mcp.py +343 -0
- google/genai/tests/models/test_generate_content_media_resolution.py +97 -0
- google/genai/tests/models/test_generate_content_model.py +139 -0
- google/genai/tests/models/test_generate_content_part.py +821 -0
- google/genai/tests/models/test_generate_content_thought.py +76 -0
- google/genai/tests/models/test_generate_content_tools.py +1761 -0
- google/genai/tests/models/test_generate_images.py +191 -0
- google/genai/tests/models/test_generate_videos.py +759 -0
- google/genai/tests/models/test_get.py +104 -0
- google/genai/tests/models/test_list.py +233 -0
- google/genai/tests/models/test_recontext_image.py +189 -0
- google/genai/tests/models/test_segment_image.py +148 -0
- google/genai/tests/models/test_update.py +95 -0
- google/genai/tests/models/test_upscale_image.py +157 -0
- google/genai/tests/operations/__init__.py +17 -0
- google/genai/tests/operations/test_get.py +38 -0
- google/genai/tests/public_samples/__init__.py +17 -0
- google/genai/tests/public_samples/test_gemini_text_only.py +34 -0
- google/genai/tests/pytest_helper.py +246 -0
- google/genai/tests/shared/__init__.py +16 -0
- google/genai/tests/shared/batches/__init__.py +14 -0
- google/genai/tests/shared/batches/test_create_delete.py +57 -0
- google/genai/tests/shared/batches/test_create_get_cancel.py +56 -0
- google/genai/tests/shared/batches/test_list.py +40 -0
- google/genai/tests/shared/caches/__init__.py +14 -0
- google/genai/tests/shared/caches/test_create_get_delete.py +67 -0
- google/genai/tests/shared/caches/test_create_update_get.py +71 -0
- google/genai/tests/shared/caches/test_list.py +40 -0
- google/genai/tests/shared/chats/__init__.py +14 -0
- google/genai/tests/shared/chats/test_send_message.py +48 -0
- google/genai/tests/shared/chats/test_send_message_stream.py +50 -0
- google/genai/tests/shared/files/__init__.py +14 -0
- google/genai/tests/shared/files/test_list.py +41 -0
- google/genai/tests/shared/files/test_upload_get_delete.py +54 -0
- google/genai/tests/shared/models/__init__.py +14 -0
- google/genai/tests/shared/models/test_compute_tokens.py +41 -0
- google/genai/tests/shared/models/test_count_tokens.py +40 -0
- google/genai/tests/shared/models/test_edit_image.py +67 -0
- google/genai/tests/shared/models/test_embed.py +40 -0
- google/genai/tests/shared/models/test_generate_content.py +39 -0
- google/genai/tests/shared/models/test_generate_content_stream.py +54 -0
- google/genai/tests/shared/models/test_generate_images.py +40 -0
- google/genai/tests/shared/models/test_generate_videos.py +38 -0
- google/genai/tests/shared/models/test_list.py +37 -0
- google/genai/tests/shared/models/test_recontext_image.py +55 -0
- google/genai/tests/shared/models/test_segment_image.py +52 -0
- google/genai/tests/shared/models/test_upscale_image.py +52 -0
- google/genai/tests/shared/tunings/__init__.py +16 -0
- google/genai/tests/shared/tunings/test_create.py +46 -0
- google/genai/tests/shared/tunings/test_create_get_cancel.py +56 -0
- google/genai/tests/shared/tunings/test_list.py +39 -0
- google/genai/tests/tokens/__init__.py +16 -0
- google/genai/tests/tokens/test_create.py +154 -0
- google/genai/tests/transformers/__init__.py +17 -0
- google/genai/tests/transformers/test_blobs.py +84 -0
- google/genai/tests/transformers/test_bytes.py +15 -0
- google/genai/tests/transformers/test_duck_type.py +96 -0
- google/genai/tests/transformers/test_function_responses.py +72 -0
- google/genai/tests/transformers/test_schema.py +653 -0
- google/genai/tests/transformers/test_t_batch.py +286 -0
- google/genai/tests/transformers/test_t_content.py +160 -0
- google/genai/tests/transformers/test_t_contents.py +398 -0
- google/genai/tests/transformers/test_t_part.py +85 -0
- google/genai/tests/transformers/test_t_parts.py +87 -0
- google/genai/tests/transformers/test_t_tool.py +157 -0
- google/genai/tests/transformers/test_t_tools.py +195 -0
- google/genai/tests/tunings/__init__.py +16 -0
- google/genai/tests/tunings/test_cancel.py +39 -0
- google/genai/tests/tunings/test_end_to_end.py +106 -0
- google/genai/tests/tunings/test_get.py +67 -0
- google/genai/tests/tunings/test_list.py +75 -0
- google/genai/tests/tunings/test_tune.py +268 -0
- google/genai/tests/types/__init__.py +16 -0
- google/genai/tests/types/test_bytes_internal.py +271 -0
- google/genai/tests/types/test_bytes_type.py +152 -0
- google/genai/tests/types/test_future.py +101 -0
- google/genai/tests/types/test_optional_types.py +36 -0
- google/genai/tests/types/test_part_type.py +616 -0
- google/genai/tests/types/test_schema_from_json_schema.py +417 -0
- google/genai/tests/types/test_schema_json_schema.py +468 -0
- google/genai/tests/types/test_types.py +2903 -0
- google/genai/types.py +631 -488
- google/genai/version.py +1 -1
- {google_genai-1.56.0.dist-info → google_genai-1.58.0.dist-info}/METADATA +6 -11
- google_genai-1.58.0.dist-info/RECORD +358 -0
- google_genai-1.56.0.dist-info/RECORD +0 -162
- /google/genai/{_interactions/py.typed → tests/interactions/__init__.py} +0 -0
- {google_genai-1.56.0.dist-info → google_genai-1.58.0.dist-info}/WHEEL +0 -0
- {google_genai-1.56.0.dist-info → google_genai-1.58.0.dist-info}/licenses/LICENSE +0 -0
- {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."""
|