Skip to content

Bug: AutomaticFunctionCallingConfig(disable=True) is ignored - Planner hook bypassed #4133

@sajanlamsal

Description

@sajanlamsal

Bug: AutomaticFunctionCallingConfig(disable=True) is ignored - Planner hook bypassed

Describe the bug

When setting automatic_function_calling=AutomaticFunctionCallingConfig(disable=True) in the generate_content_config of an Agent, the ADK ignores this configuration. The custom Planner.process_planning_response() hook is never called, even when using maximum_remote_calls=0 as a workaround.

To Reproduce

1. Install

pip install google-adk==1.22.1

2. Create a custom planner with logging (planner.py):

import logging
from typing import List, Optional
from google.adk.planners import BuiltInPlanner
from google.adk.agents.callback_context import CallbackContext
from google.genai import types

# File logger for guaranteed visibility
logger = logging.getLogger("planner_debug")
file_handler = logging.FileHandler("planner_debug.log")
file_handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s'))
logger.addHandler(file_handler)
logger.setLevel(logging.DEBUG)

class StrictLimitPlanner(BuiltInPlanner):
    def __init__(self, max_iterations: int, key_name: str, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.max_iterations = max_iterations
        self._state_key = f"_counter_{key_name}"

    def process_planning_response(
            self,
            callback_context: CallbackContext,
            response_parts: List[types.Part],
    ) -> Optional[List[types.Part]]:
        # THIS LOG NEVER APPEARS - proving the hook is bypassed
        logger.warning(f"[PLANNER CALLED] {self._state_key}")
        
        processed_parts = super().process_planning_response(callback_context, response_parts)
        return processed_parts if processed_parts else response_parts

3. Create an agent with AFC disabled (agent.py):

from google.adk.agents.llm_agent import Agent
from google.adk.apps import App
from google.adk.tools.google_search_tool import GoogleSearchTool
from google.genai.types import GenerateContentConfig, AutomaticFunctionCallingConfig, ThinkingConfig
from planner import StrictLimitPlanner

strict_config = GenerateContentConfig(
    automatic_function_calling=AutomaticFunctionCallingConfig(
        disable=True,
        maximum_remote_calls=0  # Workaround that stops loop but still bypasses planner
    ),
)

agent = Agent(
    name="test_agent",
    model="gemini-2.0-flash-exp",
    instruction="You are a helpful assistant.",
    tools=[GoogleSearchTool()],
    planner=StrictLimitPlanner(max_iterations=3, key_name="test", thinking_config=ThinkingConfig(include_thoughts=False)),
    generate_content_config=strict_config,
)

app = App(name="test_app", root_agent=agent)

4. Run and send a message that triggers tool use:

adk api_server
# Then send: "query ~~~~"

5. Check logs:

# Server log shows:
# WARNING - max_remote_calls in automatic_function_calling_config 0 is less than or equal to 0. Disabling automatic function calling.

# BUT planner_debug.log is EMPTY (0 bytes):
ls -la planner_debug.log
# -rw-r--r--  1 user  staff  0 Jan 13 11:39 planner_debug.log

Expected behavior

  1. disable=True should disable AFC without requiring maximum_remote_calls=0
  2. Custom Planner.process_planning_response() should be called after each model response, regardless of AFC settings
  3. The planner should be able to intercept, count, and modify tool calls

Actual behavior

  1. disable=True alone does nothing - logs still show "AFC is enabled with max remote calls: 10"
  2. maximum_remote_calls=0 stops the loop but completely bypasses the planner hook
  3. process_planning_response() is never called (proven by empty log file)

Desktop

  • OS: macOS (Apple Silicon)
  • Python version: 3.14.2
  • ADK version: 1.22.1 (pip show google-adk)
  • google-genai version: 1.56.0

Model Information

  • Are you using LiteLLM: No
  • Which model is being used: gemini-2.0-flash-exp, gemini-3-flash-preview (Vertex AI backend)

Additional context

Workaround Status

  • Setting maximum_remote_calls=0 successfully stops the infinite loop (acting as a circuit breaker)
  • BUT it still bypasses Planner.process_planning_response() entirely
  • Result: Planners cannot count iterations, modify tool inputs, or inject thoughts

Impact

  • Custom planners are completely non-functional when tools are present
  • Cost control via iteration limits is impossible at the planner level
  • The documented Planner API is effectively broken for agents with tools

Suggested Fix

The Agent class should:

  1. Respect disable=True in AutomaticFunctionCallingConfig
  2. Always call Planner.process_planning_response() after each model response, regardless of AFC settings

Metadata

Metadata

Assignees

Labels

core[Component] This issue is related to the core interface and implementationgood first issue[Community] This issue is good for newcomers to participaterequest clarification[Status] The maintainer need clarification or more information from the authorstale[Status] Issues which have been marked inactive since there is no user response

Type

No fields configured for Bug.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions