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,530 @@
|
|
|
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
|
+
from unittest import mock
|
|
16
|
+
import pytest
|
|
17
|
+
from ... import _api_client
|
|
18
|
+
from ... import _extra_utils
|
|
19
|
+
from ... import client
|
|
20
|
+
from ... import models
|
|
21
|
+
from ... import types
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
TEST_NO_AFC_PART = types.Part(
|
|
25
|
+
text=(
|
|
26
|
+
'Okay, here is the weather in San Francisco'
|
|
27
|
+
' as of approximately 8:10 pm PST on May'
|
|
28
|
+
' 10, 2023. Please note that the weather'
|
|
29
|
+
' can change rapidly.'
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
TEST_FUNCTION_CALL_PART = types.Part(
|
|
34
|
+
function_call=types.FunctionCall(
|
|
35
|
+
name='get_current_weather',
|
|
36
|
+
args={'location': 'San Francisco'},
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
TEST_FUNCTION_RESPONSE_PART = types.Part(
|
|
41
|
+
function_response=types.FunctionResponse(
|
|
42
|
+
name='get_current_weather',
|
|
43
|
+
response={'result': 'sunny'},
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
TEST_AFC_TEXT_PART = types.Part(text='San Francisco weather is sunny.')
|
|
48
|
+
|
|
49
|
+
TEST_NO_AFC_CONTENT = types.Content(
|
|
50
|
+
parts=[TEST_NO_AFC_PART],
|
|
51
|
+
role='model',
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
TEST_FUNCTION_CALL_CONTENT = types.Content(
|
|
55
|
+
parts=[TEST_FUNCTION_CALL_PART],
|
|
56
|
+
role='model',
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
TEST_FUNCTION_RESPONSE_CONTENT = types.Content(
|
|
60
|
+
parts=[TEST_FUNCTION_RESPONSE_PART],
|
|
61
|
+
role='user',
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
TEST_AFC_TEXT_CONTENT = types.Content(
|
|
65
|
+
parts=[TEST_AFC_TEXT_PART],
|
|
66
|
+
role='model',
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
TEST_AFC_HISTORY = [
|
|
70
|
+
types.Content(
|
|
71
|
+
parts=[types.Part(text='what is the weather in San Francisco?')],
|
|
72
|
+
role='user',
|
|
73
|
+
),
|
|
74
|
+
TEST_FUNCTION_CALL_CONTENT,
|
|
75
|
+
TEST_FUNCTION_RESPONSE_CONTENT,
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def get_current_weather(location: str) -> str:
|
|
80
|
+
"""Returns the current weather.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
location: The location of a city and state, e.g. "San Francisco, CA".
|
|
84
|
+
"""
|
|
85
|
+
return 'windy'
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
async def get_current_weather_async(location: str) -> str:
|
|
89
|
+
"""Returns the current weather.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
location: The location of a city and state, e.g. "San Francisco, CA".
|
|
93
|
+
"""
|
|
94
|
+
return 'windy'
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def get_aqi_from_city(location: str) -> str:
|
|
98
|
+
"""Returns the aqi index of a city.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
location: The city and State, e.g. San Francisco, CA.
|
|
102
|
+
"""
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@pytest.fixture
|
|
107
|
+
def mock_api_client(vertexai=False):
|
|
108
|
+
api_client = mock.MagicMock(spec=client.ApiClient)
|
|
109
|
+
api_client.api_key = 'TEST_API_KEY'
|
|
110
|
+
api_client._host = lambda: 'test_host'
|
|
111
|
+
api_client._http_options = {'headers': {}} # Ensure headers exist
|
|
112
|
+
api_client.vertexai = vertexai
|
|
113
|
+
return api_client
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@pytest.fixture
|
|
117
|
+
def mock_get_function_response_parts_none():
|
|
118
|
+
with mock.patch.object(
|
|
119
|
+
_extra_utils,
|
|
120
|
+
'get_function_response_parts',
|
|
121
|
+
) as mock_get_function_response_parts_none:
|
|
122
|
+
mock_get_function_response_parts_none.return_value = None
|
|
123
|
+
yield mock_get_function_response_parts_none
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.fixture
|
|
127
|
+
def mock_get_function_response_parts_none_async():
|
|
128
|
+
with mock.patch.object(
|
|
129
|
+
_extra_utils,
|
|
130
|
+
'get_function_response_parts_async',
|
|
131
|
+
) as mock_get_function_response_parts_none_async:
|
|
132
|
+
mock_get_function_response_parts_none_async.return_value = None
|
|
133
|
+
yield mock_get_function_response_parts_none_async
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@pytest.fixture
|
|
137
|
+
def mock_get_function_response_parts() -> list[types.Part]:
|
|
138
|
+
with mock.patch.object(
|
|
139
|
+
_extra_utils, 'get_function_response_parts'
|
|
140
|
+
) as mock_get_function_response_parts:
|
|
141
|
+
mock_get_function_response_parts.side_effect = [
|
|
142
|
+
[TEST_FUNCTION_RESPONSE_PART],
|
|
143
|
+
[], # Breaks when the function response is not returned.
|
|
144
|
+
]
|
|
145
|
+
yield mock_get_function_response_parts
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@pytest.fixture
|
|
149
|
+
def mock_get_function_response_parts_async() -> list[types.Part]:
|
|
150
|
+
with mock.patch.object(
|
|
151
|
+
_extra_utils, 'get_function_response_parts_async'
|
|
152
|
+
) as mock_get_function_response_parts_async:
|
|
153
|
+
mock_get_function_response_parts_async.side_effect = [
|
|
154
|
+
[TEST_FUNCTION_RESPONSE_PART],
|
|
155
|
+
[], # Breaks when the function response is not returned.
|
|
156
|
+
]
|
|
157
|
+
yield mock_get_function_response_parts_async
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@pytest.fixture
|
|
161
|
+
def mock_generate_content_stream_no_afc():
|
|
162
|
+
with mock.patch.object(
|
|
163
|
+
models.Models, '_generate_content_stream'
|
|
164
|
+
) as mock_stream_no_afc:
|
|
165
|
+
mock_stream_no_afc.return_value = [
|
|
166
|
+
types.GenerateContentResponse(
|
|
167
|
+
candidates=[types.Candidate(content=TEST_NO_AFC_CONTENT)]
|
|
168
|
+
)
|
|
169
|
+
]
|
|
170
|
+
yield mock_stream_no_afc
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@pytest.fixture
|
|
174
|
+
def mock_generate_content_stream_with_afc():
|
|
175
|
+
with mock.patch.object(
|
|
176
|
+
models.Models, '_generate_content_stream'
|
|
177
|
+
) as mock_stream_with_afc:
|
|
178
|
+
mock_stream_with_afc.side_effect = [
|
|
179
|
+
[
|
|
180
|
+
types.GenerateContentResponse(
|
|
181
|
+
candidates=[types.Candidate(content=TEST_FUNCTION_CALL_CONTENT)]
|
|
182
|
+
)
|
|
183
|
+
],
|
|
184
|
+
[
|
|
185
|
+
types.GenerateContentResponse(
|
|
186
|
+
candidates=[types.Candidate(content=TEST_AFC_TEXT_CONTENT)]
|
|
187
|
+
)
|
|
188
|
+
],
|
|
189
|
+
]
|
|
190
|
+
yield mock_stream_with_afc
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@pytest.fixture
|
|
194
|
+
def mock_generate_content_stream_no_afc_async():
|
|
195
|
+
with mock.patch.object(
|
|
196
|
+
models.AsyncModels, '_generate_content_stream'
|
|
197
|
+
) as mock_stream_no_afc:
|
|
198
|
+
|
|
199
|
+
async def async_generator():
|
|
200
|
+
yield types.GenerateContentResponse(
|
|
201
|
+
candidates=[types.Candidate(content=TEST_NO_AFC_CONTENT)]
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
mock_stream_no_afc.return_value = async_generator()
|
|
205
|
+
yield mock_stream_no_afc
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@pytest.fixture
|
|
209
|
+
def mock_generate_content_stream_with_afc_async():
|
|
210
|
+
with mock.patch.object(
|
|
211
|
+
models.AsyncModels, '_generate_content_stream'
|
|
212
|
+
) as mock_stream_with_afc:
|
|
213
|
+
|
|
214
|
+
async def async_generator_1():
|
|
215
|
+
yield types.GenerateContentResponse(
|
|
216
|
+
candidates=[types.Candidate(content=TEST_FUNCTION_CALL_CONTENT)]
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
async def async_generator_2():
|
|
220
|
+
yield types.GenerateContentResponse(
|
|
221
|
+
candidates=[types.Candidate(content=TEST_AFC_TEXT_CONTENT)]
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
mock_stream_with_afc.side_effect = [
|
|
225
|
+
async_generator_1(),
|
|
226
|
+
async_generator_2(),
|
|
227
|
+
]
|
|
228
|
+
yield mock_stream_with_afc
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_generate_content_stream_no_function_map(
|
|
232
|
+
mock_generate_content_stream_no_afc,
|
|
233
|
+
mock_get_function_response_parts_none,
|
|
234
|
+
):
|
|
235
|
+
"""Test when no function tools are provided.
|
|
236
|
+
|
|
237
|
+
Expected to answer past weather.
|
|
238
|
+
"""
|
|
239
|
+
models_instance = models.Models(api_client_=mock_api_client)
|
|
240
|
+
stream = models_instance.generate_content_stream(
|
|
241
|
+
model='test_model', contents='what is the weather in San Francisco?'
|
|
242
|
+
)
|
|
243
|
+
for chunk in stream:
|
|
244
|
+
assert chunk.text == TEST_NO_AFC_PART.text
|
|
245
|
+
|
|
246
|
+
assert mock_generate_content_stream_no_afc.call_count == 1
|
|
247
|
+
assert mock_get_function_response_parts_none.call_count == 0
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def test_generate_content_stream_afc_disabled(
|
|
251
|
+
mock_generate_content_stream_with_afc,
|
|
252
|
+
mock_get_function_response_parts_none,
|
|
253
|
+
):
|
|
254
|
+
"""Test when function tools are provided but AFC is disabled.
|
|
255
|
+
|
|
256
|
+
Expected to respond with function call.
|
|
257
|
+
"""
|
|
258
|
+
models_instance = models.Models(api_client_=mock_api_client)
|
|
259
|
+
stream = models_instance.generate_content_stream(
|
|
260
|
+
model='test_model',
|
|
261
|
+
contents='what is the weather in San Francisco?',
|
|
262
|
+
config=types.GenerateContentConfig(
|
|
263
|
+
tools=[get_current_weather],
|
|
264
|
+
automatic_function_calling=types.AutomaticFunctionCallingConfig(
|
|
265
|
+
disable=True
|
|
266
|
+
),
|
|
267
|
+
),
|
|
268
|
+
)
|
|
269
|
+
for chunk in stream:
|
|
270
|
+
# Work as manual function calling.
|
|
271
|
+
assert chunk.candidates[0].content.parts[0].function_call
|
|
272
|
+
|
|
273
|
+
assert mock_generate_content_stream_with_afc.call_count == 1
|
|
274
|
+
assert mock_get_function_response_parts_none.call_count == 0
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_generate_content_stream_no_function_response(
|
|
278
|
+
mock_generate_content_stream_no_afc,
|
|
279
|
+
mock_get_function_response_parts_none,
|
|
280
|
+
):
|
|
281
|
+
"""Test when function tools are provided and function responses are not returned.
|
|
282
|
+
|
|
283
|
+
Expected to answer past weather.
|
|
284
|
+
"""
|
|
285
|
+
models_instance = models.Models(api_client_=mock_api_client)
|
|
286
|
+
config = types.GenerateContentConfig(tools=[get_aqi_from_city])
|
|
287
|
+
stream = models_instance.generate_content_stream(
|
|
288
|
+
model='test_model',
|
|
289
|
+
contents='what is the weather in San Francisco?',
|
|
290
|
+
config=config,
|
|
291
|
+
)
|
|
292
|
+
for chunk in stream:
|
|
293
|
+
assert chunk.text == TEST_NO_AFC_PART.text
|
|
294
|
+
|
|
295
|
+
assert mock_generate_content_stream_no_afc.call_count == 1
|
|
296
|
+
assert mock_get_function_response_parts_none.call_count == 1
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def test_generate_content_stream_with_function_tools_used(
|
|
300
|
+
mock_generate_content_stream_with_afc,
|
|
301
|
+
mock_get_function_response_parts,
|
|
302
|
+
):
|
|
303
|
+
"""Test when function tools are provided and function responses are returned.
|
|
304
|
+
|
|
305
|
+
Expected to answer weather based on function response.
|
|
306
|
+
"""
|
|
307
|
+
models_instance = models.Models(api_client_=mock_api_client)
|
|
308
|
+
config = types.GenerateContentConfig(tools=[get_current_weather])
|
|
309
|
+
stream = models_instance.generate_content_stream(
|
|
310
|
+
model='test_model',
|
|
311
|
+
contents='what is the weather in San Francisco?',
|
|
312
|
+
config=config,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
chunk = None
|
|
316
|
+
for chunk in stream:
|
|
317
|
+
assert chunk.text == TEST_AFC_TEXT_PART.text
|
|
318
|
+
|
|
319
|
+
assert mock_generate_content_stream_with_afc.call_count == 2
|
|
320
|
+
assert mock_get_function_response_parts.call_count == 2
|
|
321
|
+
|
|
322
|
+
assert chunk is not None
|
|
323
|
+
for i in range(len(chunk.automatic_function_calling_history)):
|
|
324
|
+
assert chunk.automatic_function_calling_history[i].model_dump(
|
|
325
|
+
exclude_none=True
|
|
326
|
+
) == TEST_AFC_HISTORY[i].model_dump(exclude_none=True)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def test_generate_content_stream_with_thought_summaries(
|
|
330
|
+
mock_generate_content_stream_with_afc,
|
|
331
|
+
mock_get_function_response_parts,
|
|
332
|
+
):
|
|
333
|
+
"""Test when function tools are provided and thought summaries are enabled.
|
|
334
|
+
|
|
335
|
+
Expected to answer weather based on function response.
|
|
336
|
+
"""
|
|
337
|
+
models_instance = models.Models(api_client_=mock_api_client)
|
|
338
|
+
config = types.GenerateContentConfig(
|
|
339
|
+
tools=[get_current_weather],
|
|
340
|
+
thinking_config=types.ThinkingConfig(include_thoughts=True),
|
|
341
|
+
)
|
|
342
|
+
stream = models_instance.generate_content_stream(
|
|
343
|
+
model='test_model',
|
|
344
|
+
contents='what is the weather in San Francisco?',
|
|
345
|
+
config=config,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
chunk = None
|
|
349
|
+
for chunk in stream:
|
|
350
|
+
assert chunk.text == TEST_AFC_TEXT_PART.text
|
|
351
|
+
|
|
352
|
+
assert mock_generate_content_stream_with_afc.call_count == 2
|
|
353
|
+
assert mock_get_function_response_parts.call_count == 2
|
|
354
|
+
|
|
355
|
+
assert chunk is not None
|
|
356
|
+
for i in range(len(chunk.automatic_function_calling_history)):
|
|
357
|
+
assert chunk.automatic_function_calling_history[i].model_dump(
|
|
358
|
+
exclude_none=True
|
|
359
|
+
) == TEST_AFC_HISTORY[i].model_dump(exclude_none=True)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@pytest.mark.asyncio
|
|
363
|
+
async def test_generate_content_stream_no_function_map_async(
|
|
364
|
+
mock_generate_content_stream_no_afc,
|
|
365
|
+
mock_get_function_response_parts_none,
|
|
366
|
+
):
|
|
367
|
+
"""Test when no function tools are provided.
|
|
368
|
+
|
|
369
|
+
Expected to answer past weather.
|
|
370
|
+
"""
|
|
371
|
+
models_instance = models.Models(api_client_=mock_api_client)
|
|
372
|
+
stream = models_instance.generate_content_stream(
|
|
373
|
+
model='test_model', contents='what is the weather in San Francisco?'
|
|
374
|
+
)
|
|
375
|
+
for chunk in stream:
|
|
376
|
+
assert chunk.text == TEST_NO_AFC_PART.text
|
|
377
|
+
|
|
378
|
+
assert mock_generate_content_stream_no_afc.call_count == 1
|
|
379
|
+
assert mock_get_function_response_parts_none.call_count == 0
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@pytest.mark.asyncio
|
|
383
|
+
async def test_generate_content_stream_afc_disabled_async(
|
|
384
|
+
mock_generate_content_stream_with_afc_async,
|
|
385
|
+
mock_get_function_response_parts_none,
|
|
386
|
+
):
|
|
387
|
+
"""Test when function tools are provided but AFC is disabled.
|
|
388
|
+
|
|
389
|
+
Expected to respond with function call.
|
|
390
|
+
"""
|
|
391
|
+
models_instance = models.AsyncModels(api_client_=mock_api_client)
|
|
392
|
+
stream = await models_instance.generate_content_stream(
|
|
393
|
+
model='test_model',
|
|
394
|
+
contents='what is the weather in San Francisco?',
|
|
395
|
+
config=types.GenerateContentConfig(
|
|
396
|
+
tools=[get_current_weather],
|
|
397
|
+
automatic_function_calling=types.AutomaticFunctionCallingConfig(
|
|
398
|
+
disable=True
|
|
399
|
+
),
|
|
400
|
+
),
|
|
401
|
+
)
|
|
402
|
+
async for chunk in stream:
|
|
403
|
+
# Work as manual function calling.
|
|
404
|
+
assert chunk.candidates[0].content.parts[0].function_call
|
|
405
|
+
|
|
406
|
+
assert mock_generate_content_stream_with_afc_async.call_count == 1
|
|
407
|
+
assert mock_get_function_response_parts_none.call_count == 0
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
@pytest.mark.asyncio
|
|
411
|
+
async def test_generate_content_stream_no_function_response_async(
|
|
412
|
+
mock_generate_content_stream_no_afc_async,
|
|
413
|
+
mock_get_function_response_parts_none_async,
|
|
414
|
+
):
|
|
415
|
+
"""Test when function tools are provided and function responses are not returned.
|
|
416
|
+
|
|
417
|
+
Expected to answer past weather.
|
|
418
|
+
"""
|
|
419
|
+
models_instance = models.AsyncModels(api_client_=mock_api_client)
|
|
420
|
+
config = types.GenerateContentConfig(tools=[get_aqi_from_city])
|
|
421
|
+
stream = await models_instance.generate_content_stream(
|
|
422
|
+
model='test_model',
|
|
423
|
+
contents='what is the weather in San Francisco?',
|
|
424
|
+
config=config,
|
|
425
|
+
)
|
|
426
|
+
async for chunk in stream:
|
|
427
|
+
assert chunk.text == TEST_NO_AFC_PART.text
|
|
428
|
+
|
|
429
|
+
assert mock_generate_content_stream_no_afc_async.call_count == 1
|
|
430
|
+
|
|
431
|
+
assert mock_get_function_response_parts_none_async.call_count == 1
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
@pytest.mark.asyncio
|
|
435
|
+
async def test_generate_content_stream_with_function_tools_used_async(
|
|
436
|
+
mock_generate_content_stream_with_afc_async,
|
|
437
|
+
mock_get_function_response_parts_async,
|
|
438
|
+
):
|
|
439
|
+
"""Test when function tools are provided and function responses are returned.
|
|
440
|
+
|
|
441
|
+
Expected to answer weather based on function response.
|
|
442
|
+
"""
|
|
443
|
+
models_instance = models.AsyncModels(api_client_=mock_api_client)
|
|
444
|
+
config = types.GenerateContentConfig(tools=[get_current_weather])
|
|
445
|
+
stream = await models_instance.generate_content_stream(
|
|
446
|
+
model='test_model',
|
|
447
|
+
contents='what is the weather in San Francisco?',
|
|
448
|
+
config=config,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
chunk = None
|
|
452
|
+
async for chunk in stream:
|
|
453
|
+
assert chunk.text == TEST_AFC_TEXT_PART.text
|
|
454
|
+
|
|
455
|
+
assert mock_generate_content_stream_with_afc_async.call_count == 2
|
|
456
|
+
|
|
457
|
+
assert mock_get_function_response_parts_async.call_count == 2
|
|
458
|
+
|
|
459
|
+
assert chunk is not None
|
|
460
|
+
for i in range(len(chunk.automatic_function_calling_history)):
|
|
461
|
+
assert chunk.automatic_function_calling_history[i].model_dump(
|
|
462
|
+
exclude_none=True
|
|
463
|
+
) == TEST_AFC_HISTORY[i].model_dump(exclude_none=True)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
@pytest.mark.asyncio
|
|
467
|
+
async def test_generate_content_stream_with_function_async_function_used_async(
|
|
468
|
+
mock_generate_content_stream_with_afc_async,
|
|
469
|
+
mock_get_function_response_parts_async,
|
|
470
|
+
):
|
|
471
|
+
"""Test when function tools are provided and function responses are returned.
|
|
472
|
+
|
|
473
|
+
Expected to answer weather based on function response.
|
|
474
|
+
"""
|
|
475
|
+
models_instance = models.AsyncModels(api_client_=mock_api_client)
|
|
476
|
+
config = types.GenerateContentConfig(tools=[get_current_weather_async])
|
|
477
|
+
stream = await models_instance.generate_content_stream(
|
|
478
|
+
model='test_model',
|
|
479
|
+
contents='what is the weather in San Francisco?',
|
|
480
|
+
config=config,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
chunk = None
|
|
484
|
+
async for chunk in stream:
|
|
485
|
+
assert chunk.text == TEST_AFC_TEXT_PART.text
|
|
486
|
+
|
|
487
|
+
assert mock_generate_content_stream_with_afc_async.call_count == 2
|
|
488
|
+
|
|
489
|
+
assert mock_get_function_response_parts_async.call_count == 2
|
|
490
|
+
|
|
491
|
+
assert chunk is not None
|
|
492
|
+
for i in range(len(chunk.automatic_function_calling_history)):
|
|
493
|
+
assert chunk.automatic_function_calling_history[i].model_dump(
|
|
494
|
+
exclude_none=True
|
|
495
|
+
) == TEST_AFC_HISTORY[i].model_dump(exclude_none=True)
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
@pytest.mark.asyncio
|
|
499
|
+
async def test_generate_content_stream_with_thought_summaries_async(
|
|
500
|
+
mock_generate_content_stream_with_afc_async,
|
|
501
|
+
mock_get_function_response_parts_async,
|
|
502
|
+
):
|
|
503
|
+
"""Test when function tools are provided and thought summaries are enabled.
|
|
504
|
+
|
|
505
|
+
Expected to answer weather based on function response.
|
|
506
|
+
"""
|
|
507
|
+
models_instance = models.AsyncModels(api_client_=mock_api_client)
|
|
508
|
+
config = types.GenerateContentConfig(
|
|
509
|
+
tools=[get_current_weather],
|
|
510
|
+
thinking_config=types.ThinkingConfig(include_thoughts=True),
|
|
511
|
+
)
|
|
512
|
+
stream = await models_instance.generate_content_stream(
|
|
513
|
+
model='test_model',
|
|
514
|
+
contents='what is the weather in San Francisco?',
|
|
515
|
+
config=config,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
chunk = None
|
|
519
|
+
async for chunk in stream:
|
|
520
|
+
assert chunk.text == TEST_AFC_TEXT_PART.text
|
|
521
|
+
|
|
522
|
+
assert mock_generate_content_stream_with_afc_async.call_count == 2
|
|
523
|
+
|
|
524
|
+
assert mock_get_function_response_parts_async.call_count == 2
|
|
525
|
+
|
|
526
|
+
assert chunk is not None
|
|
527
|
+
for i in range(len(chunk.automatic_function_calling_history)):
|
|
528
|
+
assert chunk.automatic_function_calling_history[i].model_dump(
|
|
529
|
+
exclude_none=True
|
|
530
|
+
) == TEST_AFC_HISTORY[i].model_dump(exclude_none=True)
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
import pytest
|
|
16
|
+
from ... import types
|
|
17
|
+
from .. import pytest_helper
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_current_weather(location: str) -> str:
|
|
21
|
+
"""Returns the current weather.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
location: The location of a city and state, e.g. "San Francisco, CA".
|
|
25
|
+
"""
|
|
26
|
+
return 'windy'
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
pytestmark = pytest_helper.setup(
|
|
30
|
+
file=__file__,
|
|
31
|
+
globals_for_file=globals(),
|
|
32
|
+
test_method='models.generate_content',
|
|
33
|
+
)
|
|
34
|
+
pytest_plugins = ('pytest_asyncio',)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_generate_content_stream_with_function_and_thought_summaries(client):
|
|
38
|
+
"""Test when function tools are provided and thought summaries are enabled.
|
|
39
|
+
|
|
40
|
+
Expected to answer weather based on function response.
|
|
41
|
+
"""
|
|
42
|
+
config = types.GenerateContentConfig(
|
|
43
|
+
tools=[get_current_weather],
|
|
44
|
+
thinking_config=types.ThinkingConfig(include_thoughts=True),
|
|
45
|
+
)
|
|
46
|
+
stream = client.models.generate_content_stream(
|
|
47
|
+
model='gemini-2.5-flash',
|
|
48
|
+
contents='what is the weather in San Francisco, CA?',
|
|
49
|
+
config=config,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
chunk = None
|
|
53
|
+
for chunk in stream:
|
|
54
|
+
assert chunk is not None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.asyncio
|
|
58
|
+
async def test_generate_content_stream_with_function_and_thought_summaries_async(
|
|
59
|
+
client,
|
|
60
|
+
):
|
|
61
|
+
"""Test when function tools are provided and thought summaries are enabled.
|
|
62
|
+
|
|
63
|
+
Expected to answer weather based on function response.
|
|
64
|
+
"""
|
|
65
|
+
config = types.GenerateContentConfig(
|
|
66
|
+
tools=[get_current_weather],
|
|
67
|
+
thinking_config=types.ThinkingConfig(include_thoughts=True),
|
|
68
|
+
)
|
|
69
|
+
stream = await client.aio.models.generate_content_stream(
|
|
70
|
+
model='gemini-2.5-flash',
|
|
71
|
+
contents='what is the weather in San Francisco, CA?',
|
|
72
|
+
config=config,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
chunk = None
|
|
76
|
+
async for chunk in stream:
|
|
77
|
+
assert chunk is not None
|