Skip to content

AnthropicLlm ignores tool_config: function calling mode NONE or ANY never reaches Claude #7633

Closed

Description

@Somay-kousis

🔴 Required Information

Describe the Bug:

AnthropicLlm (and Claude) always send tool_choice={"type": "auto"} when the agent has tools. The function calling mode set in generate_content_config.tool_config is dropped, so with a Claude model:

  • mode="NONE" does not stop tool calls.
  • mode="ANY" does not force a tool call.
  • mode="ANY", allowed_function_names=["x"] does not force tool x.

tool_config is how ADK sets tool choice (the answer on #773). Gemini and LiteLlm honor it (LiteLlm maps ANY to required and NONE to none). ADK's own model_consult force plugin relies on it too (_force_escalation_plugin.py:310-313 sets ANY with allowed_function_names=[tool]), so that forcing has no effect on Claude either.

Steps to Reproduce:

  1. Create an Agent with model=AnthropicLlm(...), a function tool, and generate_content_config=GenerateContentConfig(tool_config=ToolConfig(function_calling_config=FunctionCallingConfig(mode="NONE"))).
  2. Run it with InMemoryRunner.run_async.
  3. Look at the tool_choice sent to messages.create (the script below fakes the SDK call to record it).
  4. Repeat with mode="ANY" and with mode="ANY", allowed_function_names=["get_weather"].

Expected Behavior:

NONE None -> {'type': 'none'}
ANY None -> {'type': 'any'}
ANY ['get_weather'] -> {'type': 'tool', 'name': 'get_weather'}

Observed Behavior:

NONE None -> {'type': 'auto'}
ANY None -> {'type': 'auto'}
ANY ['get_weather'] -> {'type': 'auto'}

Environment Details:

  • ADK Library Version (pip show google-adk): 2.12.0, main at 662b017
  • Desktop OS: macOS 26.6.2
  • Python Version (python -V): Python 3.12.13

Model Information:

  • Are you using LiteLLM: No
  • Which model is being used: AnthropicLlm(model="claude-sonnet-4-5"), with AsyncMessages.create faked (anthropic 1.14.0), no real call

🟡 Optional Information

Root cause:

src/google/adk/models/anthropic_llm.py:1255-1259 in AnthropicLlm.generate_content_async hardcodes the choice:

tool_choice = (
    anthropic_types.ToolChoiceAutoParam(type="auto")
    if llm_request.tools_dict
    else NOT_GIVEN
)

llm_request.config.tool_config is not read anywhere in the file. The streaming and non-streaming paths both use this value.

Minimal Reproduction Code:

import asyncio, os
from unittest import mock
from anthropic import types as at
from anthropic.resources.messages import AsyncMessages
from google.adk.agents import Agent
from google.adk.models.anthropic_llm import AnthropicLlm
from google.adk.runners import InMemoryRunner
from google.genai import types

os.environ.setdefault("ANTHROPIC_API_KEY", "fake")
sent = []

async def fake_create(self, **kwargs):
  sent.append(kwargs)
  return at.Message(
      id="m", content=[at.TextBlock(text="ok", type="text")],
      model=kwargs["model"], role="assistant", stop_reason="end_turn",
      stop_sequence=None, type="message",
      usage=at.Usage(input_tokens=1, output_tokens=1))

def get_weather(city: str) -> str:
  """Returns the weather for a city."""
  return f"Sunny in {city}"

async def run(fc):
  agent = Agent(
      name="weather", model=AnthropicLlm(model="claude-sonnet-4-5"),
      tools=[get_weather],
      generate_content_config=types.GenerateContentConfig(
          tool_config=types.ToolConfig(function_calling_config=fc)))
  runner = InMemoryRunner(agent=agent, app_name="r")
  s = await runner.session_service.create_session(app_name="r", user_id="u")
  sent.clear()
  async for _ in runner.run_async(
      user_id="u", session_id=s.id,
      new_message=types.Content(role="user", parts=[types.Part(text="Paris?")])):
    pass
  print(fc.mode.value, fc.allowed_function_names, "->", sent[0]["tool_choice"])

async def main():
  with mock.patch.object(AsyncMessages, "create", fake_create):
    await run(types.FunctionCallingConfig(mode="NONE"))
    await run(types.FunctionCallingConfig(mode="ANY"))
    await run(types.FunctionCallingConfig(
        mode="ANY", allowed_function_names=["get_weather"]))

asyncio.run(main())

How often has this issue occurred?:

  • Always (100%)

Activity

  1. added
    models[Component] This issue is related to model support
    on Oct 22, 2026
  2. sanketpatil06 commented on Oct 22, 2026

    @sanketpatil06

    Hi @Somay-kousis, thanks for the report and for opening PR.

    AnthropicLlm hardcodes tool_choice={"type": "auto"} and ignores tool_config.function_calling_config. Your PR fixes this for NONE, ANY, a single allowed function name, and the extended-thinking fallback.

    Our team is reviewing #7634. We appreciate your contribution!

  3. Somay-kousis commented on Oct 25, 2026

    @Somay-kousis
    ContributorAuthor

    Thanks! Happy to adjust #7634 if the team wants a different shape for any of the modes.

  4. added a commit that references this issue on Oct 27, 2026
    da786ed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

models[Component] This issue is related to model support

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions