Repository navigation
Bug: AutomaticFunctionCallingConfig(disable=True) is ignored - Planner hook bypassed #4133
Copy link
Copy link
Closed
Closed
Copy link
Labels
core[Component] This issue is related to the core interface and implementation[Component] This issue is related to the core interface and implementationgood first issue[Community] This issue is good for newcomers to participate[Community] This issue is good for newcomers to participaterequest clarification[Status] The maintainer need clarification or more information from the author[Status] The maintainer need clarification or more information from the authorstale[Status] Issues which have been marked inactive since there is no user response[Status] Issues which have been marked inactive since there is no user response
Description
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.12. 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_parts3. 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.logExpected behavior
disable=Trueshould disable AFC without requiringmaximum_remote_calls=0- Custom
Planner.process_planning_response()should be called after each model response, regardless of AFC settings - The planner should be able to intercept, count, and modify tool calls
Actual behavior
disable=Truealone does nothing - logs still show "AFC is enabled with max remote calls: 10"maximum_remote_calls=0stops the loop but completely bypasses the planner hookprocess_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=0successfully 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:
- Respect
disable=TrueinAutomaticFunctionCallingConfig - Always call
Planner.process_planning_response()after each model response, regardless of AFC settings
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
core[Component] This issue is related to the core interface and implementation[Component] This issue is related to the core interface and implementationgood first issue[Community] This issue is good for newcomers to participate[Community] This issue is good for newcomers to participaterequest clarification[Status] The maintainer need clarification or more information from the author[Status] The maintainer need clarification or more information from the authorstale[Status] Issues which have been marked inactive since there is no user response[Status] Issues which have been marked inactive since there is no user response
Activity
fix(planners): allow BuiltInPlanner subclasses to override process_pl…
maru0804 commentedon Jan 13, 2026
Hi @sajanlamsal,
I've submitted PR #4141 which complements your PR #4137.
base_llm_flow.pyprocess_planning_responsebypass in_nl_planning.pyThe planner hook issue is caused by
isinstance(planner, BuiltInPlanner)returningTruefor 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.
sajanlamsal commentedon Jan 14, 2026
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!
llalitkumarrr commentedon Feb 11, 2026
Hello @sajanlamsal ,
Please note that not all fields in
GenerateContentConfigare currently supported. For example,toolsmust 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.
adk-bot commentedon Feb 28, 2026
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.
adk-bot commentedon Mar 7, 2026
This has been automatically closed because it has been marked as stale for over 7 days.
fix(planners): allow BuiltInPlanner subclasses to override process_pl…
fix(planners): allow BuiltInPlanner subclasses to override process_pl…