google-genai 1.14.0__py3-none-any.whl → 1.16.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/__init__.py +5 -3
- google/genai/_adapters.py +55 -0
- google/genai/_api_client.py +24 -20
- google/genai/_api_module.py +1 -1
- google/genai/_automatic_function_calling_util.py +2 -2
- google/genai/_common.py +1 -1
- google/genai/_extra_utils.py +117 -9
- google/genai/_live_converters.py +2127 -758
- google/genai/_mcp_utils.py +117 -0
- google/genai/_replay_api_client.py +1 -1
- google/genai/_test_api_client.py +1 -1
- google/genai/_tokens_converters.py +1701 -0
- google/genai/_transformers.py +66 -33
- google/genai/caches.py +277 -26
- google/genai/chats.py +1 -1
- google/genai/client.py +12 -1
- google/genai/errors.py +1 -1
- google/genai/live.py +218 -35
- google/genai/live_music.py +201 -0
- google/genai/models.py +670 -56
- google/genai/pagers.py +1 -1
- google/genai/tokens.py +357 -0
- google/genai/tunings.py +53 -0
- google/genai/types.py +4892 -3606
- google/genai/version.py +2 -2
- {google_genai-1.14.0.dist-info → google_genai-1.16.0.dist-info}/METADATA +111 -22
- google_genai-1.16.0.dist-info/RECORD +35 -0
- {google_genai-1.14.0.dist-info → google_genai-1.16.0.dist-info}/WHEEL +1 -1
- google_genai-1.14.0.dist-info/RECORD +0 -30
- {google_genai-1.14.0.dist-info → google_genai-1.16.0.dist-info}/licenses/LICENSE +0 -0
- {google_genai-1.14.0.dist-info → google_genai-1.16.0.dist-info}/top_level.txt +0 -0
google/genai/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2025 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -15,9 +15,11 @@
|
|
15
15
|
|
16
16
|
"""Google Gen AI SDK"""
|
17
17
|
|
18
|
+
from . import version
|
18
19
|
from .client import Client
|
19
|
-
from . import
|
20
|
+
from .live import live_ephemeral_connect
|
21
|
+
|
20
22
|
|
21
23
|
__version__ = version.__version__
|
22
24
|
|
23
|
-
__all__ = ['Client']
|
25
|
+
__all__ = ['Client', 'live_ephemeral_connect']
|
@@ -0,0 +1,55 @@
|
|
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
|
+
import typing
|
17
|
+
|
18
|
+
from ._mcp_utils import mcp_to_gemini_tools
|
19
|
+
from .types import FunctionCall, Tool
|
20
|
+
|
21
|
+
if typing.TYPE_CHECKING:
|
22
|
+
from mcp import types as mcp_types
|
23
|
+
from mcp import ClientSession
|
24
|
+
|
25
|
+
|
26
|
+
class McpToGenAiToolAdapter:
|
27
|
+
"""Adapter for working with MCP tools in a GenAI client."""
|
28
|
+
|
29
|
+
def __init__(
|
30
|
+
self,
|
31
|
+
session: "mcp.ClientSession", # type: ignore # noqa: F821
|
32
|
+
list_tools_result: "mcp_types.ListToolsResult", # type: ignore
|
33
|
+
) -> None:
|
34
|
+
self._mcp_session = session
|
35
|
+
self._list_tools_result = list_tools_result
|
36
|
+
|
37
|
+
async def call_tool(
|
38
|
+
self, function_call: FunctionCall
|
39
|
+
) -> "mcp_types.CallToolResult": # type: ignore
|
40
|
+
"""Calls a function on the MCP server."""
|
41
|
+
name = function_call.name if function_call.name else ""
|
42
|
+
arguments = dict(function_call.args) if function_call.args else {}
|
43
|
+
|
44
|
+
return typing.cast(
|
45
|
+
"mcp_types.CallToolResult",
|
46
|
+
await self._mcp_session.call_tool(
|
47
|
+
name=name,
|
48
|
+
arguments=arguments,
|
49
|
+
),
|
50
|
+
)
|
51
|
+
|
52
|
+
@property
|
53
|
+
def tools(self) -> list[Tool]:
|
54
|
+
"""Returns a list of Google GenAI tools."""
|
55
|
+
return mcp_to_gemini_tools(self._list_tools_result.tools)
|
google/genai/_api_client.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2025 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -32,6 +32,7 @@ import math
|
|
32
32
|
import os
|
33
33
|
import ssl
|
34
34
|
import sys
|
35
|
+
import threading
|
35
36
|
import time
|
36
37
|
from typing import Any, AsyncIterator, Optional, Tuple, Union
|
37
38
|
from urllib.parse import urlparse
|
@@ -71,14 +72,14 @@ def _append_library_version_headers(headers: dict[str, str]) -> None:
|
|
71
72
|
'user-agent' in headers
|
72
73
|
and version_header_value not in headers['user-agent']
|
73
74
|
):
|
74
|
-
headers['user-agent']
|
75
|
+
headers['user-agent'] = f'{version_header_value} ' + headers['user-agent']
|
75
76
|
elif 'user-agent' not in headers:
|
76
77
|
headers['user-agent'] = version_header_value
|
77
78
|
if (
|
78
79
|
'x-goog-api-client' in headers
|
79
80
|
and version_header_value not in headers['x-goog-api-client']
|
80
81
|
):
|
81
|
-
headers['x-goog-api-client']
|
82
|
+
headers['x-goog-api-client'] = f'{version_header_value} ' + headers['x-goog-api-client']
|
82
83
|
elif 'x-goog-api-client' not in headers:
|
83
84
|
headers['x-goog-api-client'] = version_header_value
|
84
85
|
|
@@ -376,10 +377,12 @@ class BaseApiClient:
|
|
376
377
|
# credentials. This is crucial for thread safety when multiple coroutines
|
377
378
|
# might be accessing the credentials at the same time.
|
378
379
|
try:
|
379
|
-
self.
|
380
|
+
self._sync_auth_lock = threading.Lock()
|
381
|
+
self._async_auth_lock = asyncio.Lock()
|
380
382
|
except RuntimeError:
|
381
383
|
asyncio.set_event_loop(asyncio.new_event_loop())
|
382
|
-
self.
|
384
|
+
self._sync_auth_lock = threading.Lock()
|
385
|
+
self._async_auth_lock = asyncio.Lock()
|
383
386
|
|
384
387
|
# Handle when to use Vertex AI in express mode (api key).
|
385
388
|
# Explicit initializer arguments are already validated above.
|
@@ -519,25 +522,26 @@ class BaseApiClient:
|
|
519
522
|
|
520
523
|
def _access_token(self) -> str:
|
521
524
|
"""Retrieves the access token for the credentials."""
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
self.project
|
526
|
-
|
527
|
-
|
528
|
-
if self._credentials
|
529
|
-
|
530
|
-
|
531
|
-
|
525
|
+
with self._sync_auth_lock:
|
526
|
+
if not self._credentials:
|
527
|
+
self._credentials, project = _load_auth(project=self.project)
|
528
|
+
if not self.project:
|
529
|
+
self.project = project
|
530
|
+
|
531
|
+
if self._credentials:
|
532
|
+
if self._credentials.expired or not self._credentials.token:
|
533
|
+
# Only refresh when it needs to. Default expiration is 3600 seconds.
|
534
|
+
_refresh_auth(self._credentials)
|
535
|
+
if not self._credentials.token:
|
536
|
+
raise RuntimeError('Could not resolve API token from the environment')
|
537
|
+
return self._credentials.token # type: ignore[no-any-return]
|
538
|
+
else:
|
532
539
|
raise RuntimeError('Could not resolve API token from the environment')
|
533
|
-
return self._credentials.token # type: ignore[no-any-return]
|
534
|
-
else:
|
535
|
-
raise RuntimeError('Could not resolve API token from the environment')
|
536
540
|
|
537
541
|
async def _async_access_token(self) -> Union[str, Any]:
|
538
542
|
"""Retrieves the access token for the credentials asynchronously."""
|
539
543
|
if not self._credentials:
|
540
|
-
async with self.
|
544
|
+
async with self._async_auth_lock:
|
541
545
|
# This ensures that only one coroutine can execute the auth logic at a
|
542
546
|
# time for thread safety.
|
543
547
|
if not self._credentials:
|
@@ -551,7 +555,7 @@ class BaseApiClient:
|
|
551
555
|
if self._credentials:
|
552
556
|
if self._credentials.expired or not self._credentials.token:
|
553
557
|
# Only refresh when it needs to. Default expiration is 3600 seconds.
|
554
|
-
async with self.
|
558
|
+
async with self._async_auth_lock:
|
555
559
|
if self._credentials.expired or not self._credentials.token:
|
556
560
|
# Double check that the credentials expired before refreshing.
|
557
561
|
await asyncio.to_thread(_refresh_auth, self._credentials)
|
google/genai/_api_module.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2025 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -269,7 +269,7 @@ def _parse_schema_from_parameter(
|
|
269
269
|
raise ValueError(
|
270
270
|
f'Failed to parse the parameter {param} of function {func_name} for'
|
271
271
|
' automatic function calling.Automatic function calling works best with'
|
272
|
-
' simpler function signature schema,consider manually
|
272
|
+
' simpler function signature schema, consider manually parsing your'
|
273
273
|
f' function declaration for function {func_name}.'
|
274
274
|
)
|
275
275
|
|
google/genai/_common.py
CHANGED
google/genai/_extra_utils.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright
|
1
|
+
# Copyright 2025 Google LLC
|
2
2
|
#
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
4
|
# you may not use this file except in compliance with the License.
|
@@ -24,14 +24,30 @@ from typing import Any, Callable, Dict, Optional, Union, get_args, get_origin
|
|
24
24
|
import pydantic
|
25
25
|
|
26
26
|
from . import _common
|
27
|
+
from . import _mcp_utils
|
27
28
|
from . import errors
|
28
29
|
from . import types
|
30
|
+
from ._adapters import McpToGenAiToolAdapter
|
31
|
+
|
29
32
|
|
30
33
|
if sys.version_info >= (3, 10):
|
31
34
|
from types import UnionType
|
32
35
|
else:
|
33
36
|
UnionType = typing._UnionGenericAlias # type: ignore[attr-defined]
|
34
37
|
|
38
|
+
if typing.TYPE_CHECKING:
|
39
|
+
from mcp import ClientSession as McpClientSession
|
40
|
+
from mcp.types import Tool as McpTool
|
41
|
+
else:
|
42
|
+
McpClientSession: typing.Type = Any
|
43
|
+
McpTool: typing.Type = Any
|
44
|
+
try:
|
45
|
+
from mcp import ClientSession as McpClientSession
|
46
|
+
from mcp.types import Tool as McpTool
|
47
|
+
except ImportError:
|
48
|
+
McpClientSession = None
|
49
|
+
McpTool = None
|
50
|
+
|
35
51
|
_DEFAULT_MAX_REMOTE_CALLS_AFC = 10
|
36
52
|
|
37
53
|
logger = logging.getLogger('google_genai.models')
|
@@ -78,10 +94,13 @@ def format_destination(
|
|
78
94
|
|
79
95
|
def get_function_map(
|
80
96
|
config: Optional[types.GenerateContentConfigOrDict] = None,
|
97
|
+
mcp_to_genai_tool_adapters: Optional[
|
98
|
+
dict[str, McpToGenAiToolAdapter]
|
99
|
+
] = None,
|
81
100
|
is_caller_method_async: bool = False,
|
82
|
-
) -> dict[str, Callable[..., Any]]:
|
101
|
+
) -> dict[str, Union[Callable[..., Any], McpToGenAiToolAdapter]]:
|
83
102
|
"""Returns a function map from the config."""
|
84
|
-
function_map: dict[str, Callable[..., Any]] = {}
|
103
|
+
function_map: dict[str, Union[Callable[..., Any], McpToGenAiToolAdapter]] = {}
|
85
104
|
if not config:
|
86
105
|
return function_map
|
87
106
|
config_model = _create_generate_content_config_model(config)
|
@@ -95,6 +114,17 @@ def get_function_map(
|
|
95
114
|
f' invoke {tool.__name__} to get the function response.'
|
96
115
|
)
|
97
116
|
function_map[tool.__name__] = tool
|
117
|
+
if mcp_to_genai_tool_adapters:
|
118
|
+
if not is_caller_method_async:
|
119
|
+
raise errors.UnsupportedFunctionError(
|
120
|
+
'MCP tools are not supported in synchronous methods.'
|
121
|
+
)
|
122
|
+
for tool_name, _ in mcp_to_genai_tool_adapters.items():
|
123
|
+
if function_map.get(tool_name):
|
124
|
+
raise ValueError(
|
125
|
+
f'Tool {tool_name} is already defined for the request.'
|
126
|
+
)
|
127
|
+
function_map.update(mcp_to_genai_tool_adapters)
|
98
128
|
return function_map
|
99
129
|
|
100
130
|
|
@@ -247,7 +277,7 @@ async def invoke_function_from_dict_args_async(
|
|
247
277
|
|
248
278
|
def get_function_response_parts(
|
249
279
|
response: types.GenerateContentResponse,
|
250
|
-
function_map: dict[str, Callable[..., Any]],
|
280
|
+
function_map: dict[str, Union[Callable[..., Any], McpToGenAiToolAdapter]],
|
251
281
|
) -> list[types.Part]:
|
252
282
|
"""Returns the function response parts from the response."""
|
253
283
|
func_response_parts = []
|
@@ -267,9 +297,10 @@ def get_function_response_parts(
|
|
267
297
|
)
|
268
298
|
func_response: dict[str, Any]
|
269
299
|
try:
|
270
|
-
|
271
|
-
|
272
|
-
|
300
|
+
if not isinstance(func, McpToGenAiToolAdapter):
|
301
|
+
func_response = {
|
302
|
+
'result': invoke_function_from_dict_args(args, func)
|
303
|
+
}
|
273
304
|
except Exception as e: # pylint: disable=broad-except
|
274
305
|
func_response = {'error': str(e)}
|
275
306
|
func_response_part = types.Part.from_function_response(
|
@@ -278,9 +309,10 @@ def get_function_response_parts(
|
|
278
309
|
func_response_parts.append(func_response_part)
|
279
310
|
return func_response_parts
|
280
311
|
|
312
|
+
|
281
313
|
async def get_function_response_parts_async(
|
282
314
|
response: types.GenerateContentResponse,
|
283
|
-
function_map: dict[str, Callable[..., Any]],
|
315
|
+
function_map: dict[str, Union[Callable[..., Any], McpToGenAiToolAdapter]],
|
284
316
|
) -> list[types.Part]:
|
285
317
|
"""Returns the function response parts from the response."""
|
286
318
|
func_response_parts = []
|
@@ -300,7 +332,15 @@ async def get_function_response_parts_async(
|
|
300
332
|
)
|
301
333
|
func_response: dict[str, Any]
|
302
334
|
try:
|
303
|
-
if
|
335
|
+
if isinstance(func, McpToGenAiToolAdapter):
|
336
|
+
mcp_tool_response = await func.call_tool(
|
337
|
+
types.FunctionCall(name=func_name, args=args)
|
338
|
+
)
|
339
|
+
if mcp_tool_response.isError:
|
340
|
+
func_response = {'error': mcp_tool_response}
|
341
|
+
else:
|
342
|
+
func_response = {'result': mcp_tool_response}
|
343
|
+
elif inspect.iscoroutinefunction(func):
|
304
344
|
func_response = {
|
305
345
|
'result': await invoke_function_from_dict_args_async(args, func)
|
306
346
|
}
|
@@ -401,3 +441,71 @@ def should_append_afc_history(
|
|
401
441
|
if not config_model.automatic_function_calling:
|
402
442
|
return True
|
403
443
|
return not config_model.automatic_function_calling.ignore_call_history
|
444
|
+
|
445
|
+
|
446
|
+
def parse_config_for_mcp_usage(
|
447
|
+
config: Optional[types.GenerateContentConfigOrDict] = None,
|
448
|
+
) -> Optional[types.GenerateContentConfig]:
|
449
|
+
"""Returns a parsed config with an appended MCP header if MCP tools or sessions are used."""
|
450
|
+
if not config:
|
451
|
+
return None
|
452
|
+
config_model = _create_generate_content_config_model(config)
|
453
|
+
# Create a copy of the config model with the tools field cleared since some
|
454
|
+
# tools may not be pickleable.
|
455
|
+
config_model_copy = config_model.model_copy(update={'tools': None})
|
456
|
+
config_model_copy.tools = config_model.tools
|
457
|
+
if config_model.tools and _mcp_utils.has_mcp_tool_usage(config_model.tools):
|
458
|
+
if config_model_copy.http_options is None:
|
459
|
+
config_model_copy.http_options = types.HttpOptions(headers={})
|
460
|
+
if config_model_copy.http_options.headers is None:
|
461
|
+
config_model_copy.http_options.headers = {}
|
462
|
+
_mcp_utils.set_mcp_usage_header(config_model_copy.http_options.headers)
|
463
|
+
|
464
|
+
return config_model_copy
|
465
|
+
|
466
|
+
|
467
|
+
async def parse_config_for_mcp_sessions(
|
468
|
+
config: Optional[types.GenerateContentConfigOrDict] = None,
|
469
|
+
) -> tuple[
|
470
|
+
Optional[types.GenerateContentConfig],
|
471
|
+
dict[str, McpToGenAiToolAdapter],
|
472
|
+
]:
|
473
|
+
"""Returns a parsed config with MCP sessions converted to GenAI tools.
|
474
|
+
|
475
|
+
Also returns a map of MCP tools to GenAI tool adapters to be used for AFC.
|
476
|
+
"""
|
477
|
+
mcp_to_genai_tool_adapters: dict[str, McpToGenAiToolAdapter] = {}
|
478
|
+
parsed_config = parse_config_for_mcp_usage(config)
|
479
|
+
if not parsed_config:
|
480
|
+
return None, mcp_to_genai_tool_adapters
|
481
|
+
# Create a copy of the config model with the tools field cleared as they will
|
482
|
+
# be replaced with the MCP tools converted to GenAI tools.
|
483
|
+
parsed_config_copy = parsed_config.model_copy(update={'tools': None})
|
484
|
+
if parsed_config.tools:
|
485
|
+
parsed_config_copy.tools = []
|
486
|
+
for tool in parsed_config.tools:
|
487
|
+
if McpClientSession is not None and isinstance(tool, McpClientSession):
|
488
|
+
mcp_to_genai_tool_adapter = McpToGenAiToolAdapter(
|
489
|
+
tool, await tool.list_tools()
|
490
|
+
)
|
491
|
+
# Extend the config with the MCP session tools converted to GenAI tools.
|
492
|
+
parsed_config_copy.tools.extend(mcp_to_genai_tool_adapter.tools)
|
493
|
+
for genai_tool in mcp_to_genai_tool_adapter.tools:
|
494
|
+
if genai_tool.function_declarations:
|
495
|
+
for function_declaration in genai_tool.function_declarations:
|
496
|
+
if function_declaration.name:
|
497
|
+
if mcp_to_genai_tool_adapters.get(function_declaration.name):
|
498
|
+
raise ValueError(
|
499
|
+
f'Tool {function_declaration.name} is already defined for'
|
500
|
+
' the request.'
|
501
|
+
)
|
502
|
+
mcp_to_genai_tool_adapters[function_declaration.name] = (
|
503
|
+
mcp_to_genai_tool_adapter
|
504
|
+
)
|
505
|
+
if McpClientSession is not None:
|
506
|
+
parsed_config_copy.tools.extend(
|
507
|
+
tool
|
508
|
+
for tool in parsed_config.tools
|
509
|
+
if not isinstance(tool, McpClientSession)
|
510
|
+
)
|
511
|
+
return parsed_config_copy, mcp_to_genai_tool_adapters
|