feat: Allow thinking_config in generate_content_config

Merge https://github.com/google/adk-python/pull/4117

**Overview**
 This PR implements the feature request in #4108 to allow `thinking_config` to be set directly within `generate_content_config`, bringing the Python SDK in line with the Go implementation.

 **Changes**
 - **llm_agent.py**: Relaxed the validation logic in `validate_generate_content_config` to remove the `ValueError` for `thinking_config`.
 - **Precedence Warning**: Added an override of `model_post_init` in `LlmAgent` to issue a `UserWarning` if both a `planner` and a manual `thinking_config` are provided.
 - **built_in_planner.py**: Updated `apply_thinking_config` to log an `INFO` message when the planner overwrites an existing configuration on the `LlmRequest`.

 **Testing**
 Verified with a reproduction script covering:
 1. Successful initialization of an agent with direct `thinking_config`.
 2. Validation of `UserWarning` during initialization when conflicting configurations are present.
 3. Confirmation of logger output when the planner performs an overwrite.

 Closes: #4108
 Tagging @invictus2010 for visibility.

Co-authored-by: Liang Wu <wuliang@google.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/4117 from Akshat8510:feat/allow-thinking-config-4108 5deeb893799379c681d6822dc4a1e42f86d3ed01
PiperOrigin-RevId: 856821447
This commit is contained in:
Akshat8510
2026-01-15 14:32:06 -08:00
committed by Copybara-Service
parent 19315fe557
commit e162bb8832
3 changed files with 84 additions and 11 deletions
@@ -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
@@ -27,6 +28,7 @@ from google.adk.models.google_llm import Gemini
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
@@ -234,17 +236,35 @@ def test_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',
@@ -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',
@@ -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
@@ -471,3 +494,28 @@ 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 DEBUG 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.DEBUG, logger='google_adk.google.adk.planners.built_in_planner'
):
planner.apply_thinking_config(req)
assert (
'Overwriting `thinking_config` from `generate_content_config`'
in caplog.text
)