Skip to content

ApigeeLlm (Chat Completions) ignores FunctionCallingConfig.allowed_function_names and the VALIDATED mode #8100

Description

@Android-Login-Analysis

Describe the Bug:

ApigeeLlm (Chat Completions) ignores FunctionCallingConfig.allowed_function_names and the VALIDATED mode. _map_tools in src/google/adk/models/apigee_llm.py (lines 765-789 at b8836bdb, 815-822 on current main) maps only mode: ANY -> "required", NONE -> "none", AUTO -> "auto". So "call lookup_order" becomes "call any tool", and "only these tools" becomes no restriction. No warning is logged. We found this while testing how SDKs carry tool-call constraints to the provider.

At the same commit, AnthropicLlm ignored tool_config entirely and always sent tool_choice: {"type": "auto"} (anthropic_llm.py:1068-1072). This looks fixed on main by _build_tool_choice. One case may remain: when llm_request.tools_dict is empty (a direct generate_content_async call with tools only in config.tools), tool_choice is still NOT_GIVEN even if tool_config is set. We have not rerun that on main.

Steps to Reproduce:

  1. Start a local HTTP server that logs request bodies.
  2. Create an ApigeeLlm with proxy_url pointing at it and api_type="chat_completions".
  3. Run an LlmAgent with two tools and the config below.
  4. Look at tool_choice in the captured request.

Expected Behavior:

mode=ANY, allowed_function_names=["lookup_order"] -> "tool_choice": {"type": "function", "function": {"name": "lookup_order"}}. mode=VALIDATED (or AUTO) with allowed_function_names -> an allowed_tools choice, or the other tools removed from the request, or an error saying the restriction cannot be sent.

Observed Behavior:

  • ANY + ["lookup_order"]: "tool_choice": "required", both tools declared. In our test the server answered with issue_refund and the agent ran it.
  • VALIDATED + ["lookup_order"]: no tool_choice, both tools declared. The agent ran issue_refund.

Environment Details:

  • ADK Library Version: google-adk 2.9.2 (commit b8836bdb). Apigee code unchanged on main at 9687bae1.
  • Desktop OS: Linux (Ubuntu 24.04)
  • Python Version: 3.12

Model Information:

  • Are you using LiteLLM: No
  • Which model is being used: apigee/openai/gpt-4o (Chat Completions) and claude-sonnet-4-5 through AnthropicLlm

Minimal Reproduction Code:

from google.adk.agents import LlmAgent
from google.adk.models.apigee_llm import ApigeeLlm
from google.adk.runners import InMemoryRunner
from google.genai import types

def lookup_order(order_id: str) -> str:
    return "delivered"

def issue_refund(order_id: str, amount: int) -> str:
    return "refunded"

cfg = types.GenerateContentConfig(tool_config=types.ToolConfig(
    function_calling_config=types.FunctionCallingConfig(
        mode=types.FunctionCallingConfigMode.ANY, allowed_function_names=["lookup_order"])))

agent = LlmAgent(name="app", instruction="Use the tools.",
                 model=ApigeeLlm(model="apigee/openai/gpt-4o", proxy_url="http://127.0.0.1:8080/v1",
                                 api_type="chat_completions"),
                 tools=[lookup_order, issue_refund], generate_content_config=cfg)
runner = InMemoryRunner(agent=agent)
# run one turn with runner.run_async(...) and inspect the captured request

How often has this issue occurred?: Always (deterministic, request construction)

Additional Context:

Impact: an application that forces or limits tool calls through FunctionCallingConfig expects the model to be held to that. On these backends the restriction is not sent, so any declared tool can be called.

Suggested fix: map allowed_function_names with one name to a named function choice, map a subset to allowed_tools (or filter tools), and raise or warn for combinations that cannot be expressed.

Activity

  1. Binary-Refinery commented on Nov 12, 2026

    @Binary-Refinery

    I can extend the local HTTP reproduction to cover two allowed tools and one excluded tool, then check VALIDATED separately. I’ll report the outbound payload and leave the existing single-tool fix in #8101 scoped as it is.

  2. surajksharma07 commented on Nov 12, 2026

    @surajksharma07
    Collaborator

    @Android-Login-Analysis, easy to follow. Reproduced on 2.14.0 and current main: ANY + ["lookup_order"] goes out as "required", VALIDATED sends no tool_choice, and issue_refund runs in both. On the Claude side the main fix shipped in 2.14.0, but the empty tools_dict case you mentioned is still there.

    @prassu017 #8101 looks good for the single-name case. For the rest, this worked locally: for ANY/VALIDATED, trim payload["tools"] down to allowed_function_names, map VALIDATED to "auto", and raise if an allowed name isn’t a declared tool. Could you try that on your side, including two allowed names plus an excluded one? Please test it thoroughly before folding it into the PR. Maintainers may still prefer allowed_tools over filtering.

    @Binary-Refinery your two-allowed/one-excluded payload check would be a good way to validate this. Could you share the outbound request bodies once you have them?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions