Skip to content

Commit f11d19d

Browse files
haranrkcopybara-github
authored andcommitted
feat(tools): resolve built-in tools for managed-agent requests
Add an is_managed_agent flag to LlmRequest so the google_search and url_context built-in tools resolve their server-side config even when no Gemini model is set. The flag defaults to False, so existing flows are unchanged. Co-authored-by: Haran Rajkumar <haranrk@google.com> PiperOrigin-RevId: 939997610
1 parent 46a2181 commit f11d19d

8 files changed

Lines changed: 98 additions & 2 deletions

File tree

src/google/adk/models/llm_request.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from pydantic import BaseModel
2323
from pydantic import ConfigDict
2424
from pydantic import Field
25+
from pydantic import PrivateAttr
2526

2627
from ..agents.context_cache_config import ContextCacheConfig
2728
from ..tools.base_tool import BaseTool
@@ -100,6 +101,15 @@ class LlmRequest(BaseModel):
100101
the full history.
101102
"""
102103

104+
_is_managed_agent: bool = PrivateAttr(default=False)
105+
"""Internal flag: whether this request was built by a ManagedAgent.
106+
107+
This is an internal implementation detail (not part of the serialized request
108+
schema). It is set by ``ManagedAgent`` and read only through the
109+
``_is_managed_agent`` helper in ``model_name_utils`` so built-in tools resolve
110+
server-side without a Gemini model.
111+
"""
112+
103113
def append_instructions(
104114
self, instructions: Union[list[str], types.Content]
105115
) -> list[types.Content]:

src/google/adk/tools/google_search_tool.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from google.genai import types
2020
from typing_extensions import override
2121

22+
from ..utils.model_name_utils import _is_managed_agent
2223
from ..utils.model_name_utils import is_gemini_1_model
2324
from ..utils.model_name_utils import is_gemini_model
2425
from ..utils.model_name_utils import is_gemini_model_id_check_disabled
@@ -79,7 +80,11 @@ async def process_llm_request(
7980
llm_request.config.tools.append(
8081
types.Tool(google_search_retrieval=types.GoogleSearchRetrieval())
8182
)
82-
elif is_gemini_model(llm_request.model) or model_check_disabled:
83+
elif (
84+
is_gemini_model(llm_request.model)
85+
or model_check_disabled
86+
or _is_managed_agent(llm_request)
87+
):
8388
llm_request.config.tools.append(
8489
types.Tool(google_search=types.GoogleSearch())
8590
)

src/google/adk/tools/url_context_tool.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from google.genai import types
2020
from typing_extensions import override
2121

22+
from ..utils.model_name_utils import _is_managed_agent
2223
from ..utils.model_name_utils import is_gemini_1_model
2324
from ..utils.model_name_utils import is_gemini_eap_or_2_or_above
2425
from ..utils.model_name_utils import is_gemini_model_id_check_disabled
@@ -52,7 +53,11 @@ async def process_llm_request(
5253
llm_request.config.tools = llm_request.config.tools or []
5354
if is_gemini_1_model(llm_request.model):
5455
raise ValueError('Url context tool cannot be used in Gemini 1.x.')
55-
elif is_gemini_eap_or_2_or_above(llm_request.model) or model_check_disabled:
56+
elif (
57+
is_gemini_eap_or_2_or_above(llm_request.model)
58+
or model_check_disabled
59+
or _is_managed_agent(llm_request)
60+
):
5661
llm_request.config.tools.append(
5762
types.Tool(url_context=types.UrlContext())
5863
)

src/google/adk/utils/model_name_utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,16 @@
1818

1919
import re
2020
from typing import Optional
21+
from typing import TYPE_CHECKING
2122

2223
from packaging.version import InvalidVersion
2324
from packaging.version import Version
2425

2526
from .env_utils import is_env_enabled
2627

28+
if TYPE_CHECKING:
29+
from ..models.llm_request import LlmRequest
30+
2731
_DISABLE_GEMINI_MODEL_ID_CHECK_ENV_VAR = 'ADK_DISABLE_GEMINI_MODEL_ID_CHECK'
2832

2933

@@ -36,6 +40,11 @@ def is_gemini_model_id_check_disabled() -> bool:
3640
return is_env_enabled(_DISABLE_GEMINI_MODEL_ID_CHECK_ENV_VAR)
3741

3842

43+
def _is_managed_agent(llm_request: LlmRequest) -> bool:
44+
"""Whether the request was built by a ManagedAgent."""
45+
return llm_request._is_managed_agent
46+
47+
3948
def extract_model_name(model_string: str) -> str:
4049
"""Extract the actual model name from either simple or path-based format.
4150

tests/unittests/models/test_llm_request.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -834,3 +834,16 @@ def test_append_instructions_with_only_text_parts():
834834

835835
# Should return empty list since no non-text parts
836836
assert user_contents == []
837+
838+
839+
def test_is_managed_agent_defaults_false():
840+
"""_is_managed_agent defaults to False for ordinary requests."""
841+
request = LlmRequest()
842+
assert request._is_managed_agent is False
843+
844+
845+
def test_is_managed_agent_can_be_set_true():
846+
"""_is_managed_agent is an internal flag set after construction."""
847+
request = LlmRequest()
848+
request._is_managed_agent = True
849+
assert request._is_managed_agent is True

tests/unittests/tools/test_google_search_tool.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,3 +516,23 @@ async def test_process_llm_request_custom_model_behavior(
516516
assert llm_request.model == expected_model
517517
assert llm_request.config.tools is not None
518518
assert len(llm_request.config.tools) == 1
519+
520+
@pytest.mark.asyncio
521+
async def test_process_llm_request_managed_agent_no_model(self):
522+
"""Managed-agent requests resolve google_search even with no model."""
523+
tool = GoogleSearchTool()
524+
tool_context = await _create_tool_context()
525+
526+
llm_request = LlmRequest(
527+
model=None,
528+
config=types.GenerateContentConfig(),
529+
)
530+
llm_request._is_managed_agent = True
531+
532+
await tool.process_llm_request(
533+
tool_context=tool_context, llm_request=llm_request
534+
)
535+
536+
assert llm_request.config.tools is not None
537+
assert len(llm_request.config.tools) == 1
538+
assert llm_request.config.tools[0].google_search is not None

tests/unittests/tools/test_url_context_tool.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,23 @@ async def test_process_llm_request_edge_cases(self):
322322
await tool.process_llm_request(
323323
tool_context=tool_context, llm_request=llm_request
324324
)
325+
326+
@pytest.mark.asyncio
327+
async def test_process_llm_request_managed_agent_no_model(self):
328+
"""Managed-agent requests resolve url_context even with no model."""
329+
tool = UrlContextTool()
330+
tool_context = await _create_tool_context()
331+
332+
llm_request = LlmRequest(
333+
model=None,
334+
config=types.GenerateContentConfig(),
335+
)
336+
llm_request._is_managed_agent = True
337+
338+
await tool.process_llm_request(
339+
tool_context=tool_context, llm_request=llm_request
340+
)
341+
342+
assert llm_request.config.tools is not None
343+
assert len(llm_request.config.tools) == 1
344+
assert llm_request.config.tools[0].url_context is not None

tests/unittests/utils/test_model_name_utils.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
"""Tests for model name utility functions."""
1616

17+
from google.adk.models.llm_request import LlmRequest
18+
from google.adk.utils.model_name_utils import _is_managed_agent
1719
from google.adk.utils.model_name_utils import extract_model_name
1820
from google.adk.utils.model_name_utils import is_gemini_1_model
1921
from google.adk.utils.model_name_utils import is_gemini_3_1_flash_live
@@ -429,3 +431,15 @@ def test_is_gemini_3_5_live_translate_edge_cases(self):
429431
"""Test edge cases."""
430432
assert is_gemini_3_5_live_translate(None) is False
431433
assert is_gemini_3_5_live_translate('') is False
434+
435+
436+
class TestIsManagedAgent:
437+
"""Tests for the _is_managed_agent predicate."""
438+
439+
def test_true_when_flag_set(self):
440+
request = LlmRequest()
441+
request._is_managed_agent = True
442+
assert _is_managed_agent(request) is True
443+
444+
def test_false_by_default(self):
445+
assert _is_managed_agent(LlmRequest()) is False

0 commit comments

Comments
 (0)