Skip to content

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

Closed

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

Activity

  1. added
    core[Component] This issue is related to the core interface and implementation
    on Jan 13, 2026
  2. added theissue type on Jan 13, 2026
  3. added a commit that references this issue on Jan 13, 2026
    d09d67d
  4. maru0804 commented on Jan 13, 2026

    @maru0804
    Contributor

    Hi @sajanlamsal,

    I've submitted PR #4141 which complements your PR #4137.

    The planner hook issue is caused by isinstance(planner, BuiltInPlanner) returning True for subclasses. My PR changes this to check if the method is actually overridden.

    Since both PRs modify different files, they could be merged separately. Let me know if you'd prefer to combine them.

  5. sajanlamsal commented on Jan 14, 2026

    @sajanlamsal
    Author

    Hi @maru0804

    Thanks for submitting PR #4141! That's a nice fix for the planner hook issue.

    Quick update: PR #4137 has been expanded since your advice. It now includes extra fix for guardrail:

    Part 1: AFC configuration fix (original scope)
    Part 2: Guardrail mechanism to prevent infinite loops when LLMs persistently return FCs after reaching maximum_remote_calls

    Both features are tightly coupled—they modify the same file (base_llm_flow.py) and share the same helper function (_should_stop_afc_loop()), so I've combined them into a single PR.

    Regarding the planner hooks: My PR includes tests (test_planner_hook_called_with_maximum_remote_calls_zero, test_planner_hooks_in_live_mode) that verify planner hooks still work correctly with AFC config enforcement.

    Your PR #4141 fixes the root cause of why process_planning_response overrides weren't being called, which is excellent.

    I think these PRs are independent and can be merged separately:

    PR #4137: Modifies base_llm_flow.py (AFC loop logic)
    PR #4141: Modifies _nl_planning.py (planner method resolution)

    No conflicts expected. Thanks again for the fix!

  6. llalitkumarrr commented on Feb 11, 2026

    @llalitkumarrr
    Collaborator

    Hello @sajanlamsal ,

    Please note that not all fields in GenerateContentConfig are currently supported. For example, tools must be configured separately viatools. This section is primarily recommended for adjusting model temperature and safety settings etc.
    And thanks for @maru0804, we already know issue with custom class for planner.

    Additionally the PR raising are under review, if all checks are successfully these will merged.

    Thank you for your patience.

  7. adk-bot commented on Feb 28, 2026

    @adk-bot
    Collaborator

    This issue has been automatically marked as stale because it has not had recent activity for 7 days after a maintainer requested clarification. It will be closed if no further activity occurs within 7 days.

  8. added
    stale[Status] Issues which have been marked inactive since there is no user response
    request clarification[Status] The maintainer need clarification or more information from the author
    on Feb 28, 2026
  9. adk-bot commented on Mar 7, 2026

    @adk-bot
    Collaborator

    This has been automatically closed because it has been marked as stale for over 7 days.

  10. added a commit that references this issue on Jun 16, 2026
    f8e9195
  11. added a commit that references this issue on Aug 11, 2026
    5a7ef9d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions