Skip to content

Commit 66bfedc

Browse files
GWealecopybara-github
authored andcommitted
perf: lazy-load optional providers and auth chain to cut cold start ~25%
Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 905227934
1 parent 0447e93 commit 66bfedc

9 files changed

Lines changed: 172 additions & 68 deletions

File tree

src/google/adk/agents/__init__.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import importlib
16+
from typing import TYPE_CHECKING
17+
1518
from .base_agent import BaseAgent
1619
from .context import Context
1720
from .invocation_context import InvocationContext
@@ -20,11 +23,13 @@
2023
from .llm_agent import Agent
2124
from .llm_agent import LlmAgent
2225
from .loop_agent import LoopAgent
23-
from .mcp_instruction_provider import McpInstructionProvider
2426
from .parallel_agent import ParallelAgent
2527
from .run_config import RunConfig
2628
from .sequential_agent import SequentialAgent
2729

30+
if TYPE_CHECKING:
31+
from .mcp_instruction_provider import McpInstructionProvider
32+
2833
__all__ = [
2934
'Agent',
3035
'BaseAgent',
@@ -39,3 +44,16 @@
3944
'LiveRequestQueue',
4045
'RunConfig',
4146
]
47+
48+
49+
def __getattr__(name: str):
50+
if name == 'McpInstructionProvider':
51+
try:
52+
module = importlib.import_module(f'{__name__}.mcp_instruction_provider')
53+
except ImportError as e:
54+
raise ImportError(
55+
'`McpInstructionProvider` requires the `mcp` package.'
56+
' Install with: pip install google-adk[extensions]'
57+
) from e
58+
return module.McpInstructionProvider
59+
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')

src/google/adk/auth/__init__.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,25 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from __future__ import annotations
16+
17+
import importlib
18+
from typing import TYPE_CHECKING
19+
1520
from .auth_credential import AuthCredential
1621
from .auth_credential import AuthCredentialTypes
1722
from .auth_credential import OAuth2Auth
18-
from .auth_handler import AuthHandler
1923
from .auth_schemes import AuthScheme
2024
from .auth_schemes import AuthSchemeType
2125
from .auth_schemes import OpenIdConnectWithConfig
2226
from .auth_tool import AuthConfig
2327
from .base_auth_provider import BaseAuthProvider
28+
29+
if TYPE_CHECKING:
30+
from .auth_handler import AuthHandler
31+
32+
33+
def __getattr__(name: str):
34+
if name == 'AuthHandler':
35+
return importlib.import_module(f'{__name__}.auth_handler').AuthHandler
36+
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')

src/google/adk/flows/llm_flows/base_llm_flow.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@
3636
from ...agents.live_request_queue import LiveRequestQueue
3737
from ...agents.readonly_context import ReadonlyContext
3838
from ...agents.run_config import StreamingMode
39-
from ...auth.auth_handler import AuthHandler
4039
from ...auth.auth_tool import AuthConfig
41-
from ...auth.credential_manager import CredentialManager
4240
from ...events.event import Event
4341
from ...models.base_llm_connection import BaseLlmConnection
4442
from ...models.llm_request import LlmRequest
@@ -144,6 +142,8 @@ async def _resolve_toolset_auth(
144142
continue
145143

146144
auth_config_copy = auth_config.model_copy(deep=True)
145+
from ...auth.credential_manager import CredentialManager
146+
147147
try:
148148
credential = await CredentialManager(
149149
auth_config_copy
@@ -173,7 +173,8 @@ async def _resolve_toolset_auth(
173173
if not pending_auth_requests:
174174
return
175175

176-
# Build auth requests dict with generated auth requests
176+
from ...auth.auth_handler import AuthHandler
177+
177178
auth_requests = {
178179
credential_id: AuthHandler(auth_config).generate_auth_request()
179180
for credential_id, auth_config in pending_auth_requests.items()

src/google/adk/flows/llm_flows/single_flow.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,15 @@
2929
from . import instructions
3030
from . import interactions_processor
3131
from . import request_confirmation
32-
from ...auth import auth_preprocessor
3332
from .base_llm_flow import BaseLlmFlow
3433

3534
logger = logging.getLogger('google_adk.' + __name__)
3635

3736

3837
def _create_request_processors():
3938
"""Create the standard request processor list for a single-agent flow."""
39+
from ...auth import auth_preprocessor
40+
4041
return [
4142
basic.request_processor,
4243
auth_preprocessor.request_processor,

src/google/adk/models/__init__.py

Lines changed: 67 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,52 +14,85 @@
1414

1515
"""Defines the interface to support a model."""
1616

17-
from .apigee_llm import ApigeeLlm
17+
from __future__ import annotations
18+
19+
import importlib
20+
from typing import TYPE_CHECKING
21+
1822
from .base_llm import BaseLlm
19-
from .gemma_llm import Gemma
20-
from .google_llm import Gemini
2123
from .llm_request import LlmRequest
2224
from .llm_response import LlmResponse
2325
from .registry import LLMRegistry
2426

27+
if TYPE_CHECKING:
28+
from .anthropic_llm import Claude
29+
from .apigee_llm import ApigeeLlm
30+
from .gemma_llm import Gemma
31+
from .gemma_llm import Gemma3Ollama
32+
from .google_llm import Gemini
33+
from .lite_llm import LiteLlm
34+
2535
__all__ = [
36+
'ApigeeLlm',
2637
'BaseLlm',
38+
'Claude',
2739
'Gemini',
2840
'Gemma',
41+
'Gemma3Ollama',
2942
'LLMRegistry',
43+
'LiteLlm',
3044
]
3145

46+
_LAZY_PROVIDERS: dict[str, tuple[list[str], str]] = {
47+
'Gemini': (
48+
[
49+
r'gemini-.*',
50+
r'model-optimizer-.*',
51+
r'projects\/.+\/locations\/.+\/endpoints\/.+',
52+
r'projects\/.+\/locations\/.+\/publishers\/google\/models\/gemini.+',
53+
],
54+
'google_llm',
55+
),
56+
'Gemma': ([r'gemma-.*'], 'gemma_llm'),
57+
'ApigeeLlm': ([r'.*-apigee$'], 'apigee_llm'),
58+
'Claude': ([r'claude-3-.*', r'claude-.*-4.*'], 'anthropic_llm'),
59+
'Gemma3Ollama': ([r'ollama/gemma3.*'], 'gemma_llm'),
60+
'LiteLlm': (
61+
[
62+
r'openai/.*',
63+
r'azure/.*',
64+
r'azure_ai/.*',
65+
r'groq/.*',
66+
r'anthropic/.*',
67+
r'bedrock/.*',
68+
r'ollama/(?!gemma3).*',
69+
r'ollama_chat/.*',
70+
r'together_ai/.*',
71+
r'vertex_ai/.*',
72+
r'mistral/.*',
73+
r'deepseek/.*',
74+
r'fireworks_ai/.*',
75+
r'cohere/.*',
76+
r'databricks/.*',
77+
r'ai21/.*',
78+
],
79+
'lite_llm',
80+
),
81+
}
3282

33-
LLMRegistry.register(Gemini)
34-
LLMRegistry.register(Gemma)
35-
LLMRegistry.register(ApigeeLlm)
83+
for _name, (_patterns, _module) in _LAZY_PROVIDERS.items():
84+
LLMRegistry._register_lazy(_patterns, f'{__name__}.{_module}', _name)
3685

37-
# Optionally register Claude if anthropic package is installed
38-
try:
39-
from .anthropic_llm import Claude
40-
41-
LLMRegistry.register(Claude)
42-
__all__.append('Claude')
43-
except Exception:
44-
# Claude support requires: pip install google-adk[extensions]
45-
pass
46-
47-
# Optionally register LiteLlm if litellm package is installed
48-
try:
49-
from .lite_llm import LiteLlm
50-
51-
LLMRegistry.register(LiteLlm)
52-
__all__.append('LiteLlm')
53-
except Exception:
54-
# LiteLLM support requires: pip install google-adk[extensions]
55-
pass
56-
57-
# Optionally register Gemma3Ollama if litellm package is installed
58-
try:
59-
from .gemma_llm import Gemma3Ollama
6086

61-
LLMRegistry.register(Gemma3Ollama)
62-
__all__.append('Gemma3Ollama')
63-
except Exception:
64-
# Gemma3Ollama requires LiteLLM: pip install google-adk[extensions]
65-
pass
87+
def __getattr__(name: str):
88+
if name in _LAZY_PROVIDERS:
89+
module_name = _LAZY_PROVIDERS[name][1]
90+
try:
91+
module = importlib.import_module(f'{__name__}.{module_name}')
92+
except ImportError as e:
93+
raise ImportError(
94+
f'`{name}` requires an optional dependency that is not installed.'
95+
' Install with: pip install google-adk[extensions]'
96+
) from e
97+
return getattr(module, name)
98+
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')

src/google/adk/models/registry.py

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,20 @@
1717
from __future__ import annotations
1818

1919
from functools import lru_cache
20+
import importlib
2021
import logging
2122
import re
2223
from typing import TYPE_CHECKING
24+
from typing import Union
2325

2426
if TYPE_CHECKING:
2527
from .base_llm import BaseLlm
2628

2729
logger = logging.getLogger('google_adk.' + __name__)
2830

2931

30-
_llm_registry_dict: dict[str, type[BaseLlm]] = {}
31-
"""Registry for LLMs.
32-
33-
Key is the regex that matches the model name.
34-
Value is the class that implements the model.
35-
"""
32+
_LazyEntry = tuple[str, str]
33+
_llm_registry_dict: dict[str, Union[type['BaseLlm'], _LazyEntry]] = {}
3634

3735

3836
class LLMRegistry:
@@ -81,6 +79,14 @@ def register(llm_cls: type[BaseLlm]):
8179
for regex in llm_cls.supported_models():
8280
LLMRegistry._register(regex, llm_cls)
8381

82+
@staticmethod
83+
def _register_lazy(
84+
model_name_regexes: list[str], module_path: str, class_name: str
85+
):
86+
"""Pre-registers a lazily-imported LLM class."""
87+
for regex in model_name_regexes:
88+
_llm_registry_dict[regex] = (module_path, class_name)
89+
8490
@staticmethod
8591
@lru_cache(maxsize=32)
8692
def resolve(model: str) -> type[BaseLlm]:
@@ -95,9 +101,20 @@ def resolve(model: str) -> type[BaseLlm]:
95101
ValueError: If the model is not found.
96102
"""
97103

98-
for regex, llm_class in _llm_registry_dict.items():
99-
if re.compile(regex).fullmatch(model):
104+
for regex, entry in list(_llm_registry_dict.items()):
105+
if not re.compile(regex).fullmatch(model):
106+
continue
107+
if isinstance(entry, tuple):
108+
module_path, class_name = entry
109+
try:
110+
module = importlib.import_module(module_path)
111+
except ImportError:
112+
_llm_registry_dict.pop(regex, None)
113+
continue
114+
llm_class = getattr(module, class_name)
115+
_llm_registry_dict[regex] = llm_class
100116
return llm_class
117+
return entry
101118

102119
# Provide helpful error messages for known patterns
103120
error_msg = f'Model {model} not found.'

src/google/adk/telemetry/_experimental_semconv.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,19 @@
2222
import contextvars
2323
import json
2424
import os
25+
import sys
2526
from typing import Any
2627
from typing import Literal
28+
from typing import TYPE_CHECKING
2729
from typing import TypedDict
2830

2931
from google.genai import types
3032
from google.genai.models import t as transformers
31-
from mcp import ClientSession as McpClientSession
32-
from mcp import Tool as McpTool
3333
from opentelemetry._logs import Logger
34+
35+
if TYPE_CHECKING:
36+
from mcp import ClientSession as McpClientSession
37+
from mcp import Tool as McpTool
3438
from opentelemetry._logs import LogRecord
3539
from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import GEN_AI_INPUT_MESSAGES
3640
from opentelemetry.semconv._incubating.attributes.gen_ai_attributes import GEN_AI_OUTPUT_MESSAGES
@@ -268,12 +272,16 @@ async def _to_tool_definitions(
268272
if callable(tool):
269273
return [_tool_definition_from_callable_tool(tool)]
270274

271-
if isinstance(tool, McpTool):
272-
return [_tool_definition_from_mcp_tool(tool)]
275+
if 'mcp' in sys.modules:
276+
from mcp import ClientSession as McpClientSession
277+
from mcp import Tool as McpTool
278+
279+
if isinstance(tool, McpTool):
280+
return [_tool_definition_from_mcp_tool(tool)]
273281

274-
if isinstance(tool, McpClientSession):
275-
result = await tool.list_tools()
276-
return [_model_dump_to_tool_definition(t) for t in result.tools]
282+
if isinstance(tool, McpClientSession):
283+
result = await tool.list_tools()
284+
return [_model_dump_to_tool_definition(t) for t in result.tools]
277285

278286
return [
279287
GenericToolDefinition(

src/google/adk/tools/tool_context.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,30 @@
1414

1515
from __future__ import annotations
1616

17-
# Keep CallbackContext for backward compatibility
17+
import importlib
18+
from typing import TYPE_CHECKING
19+
1820
from ..agents.callback_context import CallbackContext
1921
from ..agents.context import Context
20-
# Keep AuthCredential for backward compatibility
21-
from ..auth.auth_credential import AuthCredential
22-
# Keep AuthHandler for backward compatibility
23-
from ..auth.auth_handler import AuthHandler
24-
# Keep AuthConfig for backward compatibility
25-
from ..auth.auth_tool import AuthConfig
26-
# Keep ToolConfirmation for backward compatibility
2722
from .tool_confirmation import ToolConfirmation
2823

29-
# ToolContext is unified into Context
24+
if TYPE_CHECKING:
25+
from ..auth.auth_credential import AuthCredential
26+
from ..auth.auth_handler import AuthHandler
27+
from ..auth.auth_tool import AuthConfig
28+
3029
ToolContext = Context
30+
31+
_LAZY_REEXPORTS: dict[str, tuple[str, str]] = {
32+
'AuthCredential': ('google.adk.auth.auth_credential', 'AuthCredential'),
33+
'AuthHandler': ('google.adk.auth.auth_handler', 'AuthHandler'),
34+
'AuthConfig': ('google.adk.auth.auth_tool', 'AuthConfig'),
35+
}
36+
37+
38+
def __getattr__(name: str):
39+
if name in _LAZY_REEXPORTS:
40+
module_path, attr = _LAZY_REEXPORTS[name]
41+
module = importlib.import_module(module_path)
42+
return getattr(module, attr)
43+
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')

0 commit comments

Comments
 (0)