Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0883d5f
feat: allow thinking_config in generate_content_config (#4108)
Akshat8510 Jan 11, 2026
2d485cb
fix: address review comments regarding imports and logging
Akshat8510 Jan 11, 2026
25a9186
style: improve readability of precedence check in LlmAgent
Akshat8510 Jan 11, 2026
c5aa50e
docs: update LlmAgent docstring and use explicit None checks
Akshat8510 Jan 11, 2026
abce588
docs: update doc
Akshat8510 Jan 11, 2026
3a492da
docs: update doc
Akshat8510 Jan 11, 2026
d8dd6c9
test: update unit tests to allow thinking_config and verify precedenc…
Akshat8510 Jan 11, 2026
bec52bb
test: remove obsolete failing test and add new verification tests
Akshat8510 Jan 11, 2026
a631439
test: cleanup duplicate test definitions and fix imports
Akshat8510 Jan 11, 2026
412447a
refactor: simplify precedence check and remove duplicate tests
Akshat8510 Jan 11, 2026
8438ed6
test: cleanup unit tests and fix logging test placement
Akshat8510 Jan 11, 2026
4b23903
test:clean up
Akshat8510 Jan 11, 2026
072f8ad
style: move test imports to top level per PEP 8
Akshat8510 Jan 11, 2026
74600d8
refactor: simplify apply_thinking_config and cleanup test imports
Akshat8510 Jan 11, 2026
f137045
fix: address maintainer feedback on logic bug and redundant comments
Akshat8510 Jan 13, 2026
56526d9
Merge branch 'main' into feat/allow-thinking-config-4108
Akshat8510 Jan 13, 2026
978d7d1
fix: resolve logic bug in planner and refactor for consistency
Akshat8510 Jan 13, 2026
01462c4
Merge branch 'main' into feat/allow-thinking-config-4108
wuliang229 Jan 13, 2026
fdaed09
Merge branch 'main' into feat/allow-thinking-config-4108
Akshat8510 Jan 15, 2026
cd2c510
style: automatic cleanup of imports and formatting via isort and pyink
Akshat8510 Jan 15, 2026
7d3c06f
style: final manual fix for import grouping and hierarchical logging
Akshat8510 Jan 15, 2026
39b55ec
fix: address bot suggestions for stacklevel and redundant config init…
Akshat8510 Jan 15, 2026
c98e51f
Merge branch 'main' into feat/allow-thinking-config-4108
Akshat8510 Jan 15, 2026
98dfe12
Merge branch 'main' into feat/allow-thinking-config-4108
Akshat8510 Jan 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions src/google/adk/agents/llm_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ class LlmAgent(BaseAgent):
"""The additional content generation configurations.

NOTE: not all fields are usable, e.g. tools must be configured via `tools`,
thinking_config must be configured via `planner` in LlmAgent.
thinking_config can be configured here or via the `planner`. If both are set, the planner's configuration takes precedence.

For example: use this config to adjust model temperature, configure safety
settings, etc.
Expand Down Expand Up @@ -849,8 +849,6 @@ def validate_generate_content_config(
) -> types.GenerateContentConfig:
if not generate_content_config:
return types.GenerateContentConfig()
if generate_content_config.thinking_config:
raise ValueError('Thinking config should be set via LlmAgent.planner.')
if generate_content_config.tools:
raise ValueError('All tools must be set via LlmAgent.tools.')
if generate_content_config.system_instruction:
Expand All @@ -863,6 +861,23 @@ def validate_generate_content_config(
)
return generate_content_config

@override
def model_post_init(self, __context: Any) -> None:
"""Provides a warning if multiple thinking configurations are found."""
super().model_post_init(__context)

# Note: Using getattr to check both locations for thinking_config
if getattr(
self.generate_content_config, 'thinking_config', None
) and getattr(self.planner, 'thinking_config', None):
warnings.warn(
'Both `thinking_config` in `generate_content_config` and a '
'planner with `thinking_config` are provided. The '
"planner's configuration will take precedence.",
UserWarning,
stacklevel=5,
)

@classmethod
@experimental
def _resolve_tools(
Expand Down
11 changes: 10 additions & 1 deletion src/google/adk/planners/built_in_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

import logging
from typing import List
from typing import Optional

Expand All @@ -23,6 +26,8 @@
from ..models.llm_request import LlmRequest
from .base_planner import BasePlanner

_LOGGER = logging.getLogger(__name__)


class BuiltInPlanner(BasePlanner):
"""The built-in planner that uses model's built-in thinking features.
Expand Down Expand Up @@ -56,7 +61,11 @@ def apply_thinking_config(self, llm_request: LlmRequest) -> None:
llm_request: The LLM request to apply the thinking config to.
"""
if self.thinking_config:
llm_request.config = llm_request.config or types.GenerateContentConfig()
if llm_request.config.thinking_config:
_LOGGER.info(
'Overwriting `thinking_config` from `generate_content_config` with '
'the one provided by the `BuiltInPlanner`.'
)
llm_request.config.thinking_config = self.thinking_config

@override
Expand Down
63 changes: 55 additions & 8 deletions tests/unittests/agents/test_llm_agent_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Unit tests for canonical_xxx fields in LlmAgent."""

import logging
from typing import Any
from typing import Optional
from unittest import mock
Expand All @@ -27,6 +28,7 @@
from google.adk.models.lite_llm import LiteLlm
from google.adk.models.llm_request import LlmRequest
from google.adk.models.registry import LLMRegistry
from google.adk.planners.built_in_planner import BuiltInPlanner
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.adk.tools.google_search_tool import google_search
from google.adk.tools.google_search_tool import GoogleSearchTool
Expand Down Expand Up @@ -234,17 +236,35 @@ def _before_model_callback(
assert agent.before_model_callback is not None


def test_validate_generate_content_config_thinking_config_throw():
with pytest.raises(ValueError):
_ = LlmAgent(
name='test_agent',
generate_content_config=types.GenerateContentConfig(
thinking_config=types.ThinkingConfig()
),
)
def test_validate_generate_content_config_thinking_config_allow():
"""Tests that thinking_config is now allowed directly in the agent init."""
agent = LlmAgent(
name='test_agent',
generate_content_config=types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(include_thoughts=True)
),
)
assert agent.generate_content_config.thinking_config.include_thoughts is True


def test_thinking_config_precedence_warning():
"""Tests that a UserWarning is issued when both manual config and planner exist."""

config = types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(include_thoughts=True)
)
planner = BuiltInPlanner(
thinking_config=types.ThinkingConfig(include_thoughts=True)
)

with pytest.warns(
UserWarning, match="planner's configuration will take precedence"
):
LlmAgent(name='test_agent', generate_content_config=config, planner=planner)


def test_validate_generate_content_config_tools_throw():
"""Tests that tools cannot be set directly in config."""
with pytest.raises(ValueError):
_ = LlmAgent(
name='test_agent',
Expand All @@ -255,6 +275,7 @@ def test_validate_generate_content_config_tools_throw():


def test_validate_generate_content_config_system_instruction_throw():
"""Tests that system instructions cannot be set directly in config."""
with pytest.raises(ValueError):
_ = LlmAgent(
name='test_agent',
Expand All @@ -265,6 +286,8 @@ def test_validate_generate_content_config_system_instruction_throw():


def test_validate_generate_content_config_response_schema_throw():
"""Tests that response schema cannot be set directly in config."""

class Schema(BaseModel):
pass

Expand Down Expand Up @@ -471,3 +494,27 @@ def test_agent_with_litellm_string_model(model_name):
agent = LlmAgent(name='test_agent', model=model_name)
assert isinstance(agent.canonical_model, LiteLlm)
assert agent.canonical_model.model == model_name


def test_builtin_planner_overwrite_logging(caplog):
"""Tests that the planner logs an INFO message when overwriting a config."""

planner = BuiltInPlanner(
thinking_config=types.ThinkingConfig(include_thoughts=True)
)

# Create a request that already has a thinking_config
req = LlmRequest(
contents=[],
config=types.GenerateContentConfig(
thinking_config=types.ThinkingConfig(include_thoughts=True)
),
)

with caplog.at_level(logging.INFO):
planner.apply_thinking_config(req)

assert (
'Overwriting `thinking_config` from `generate_content_config`'
in caplog.text
)