Skip to content

FunctionDeclaration doesn't support dicts #5364

Closed

Description

@pp-eyushin

Describe the Bug:
Python methods with dicts in signature raises issues when FunctionTool tries to come up with schema.
Complex types as list/dict are handled in fallback branch relying on pydantic.TypeAdapter.json_schema(), that emits additionalProperties for typed dicts.

Follow-up for #4798

Steps to Reproduce:
Please provide a numbered list of steps to reproduce the behavior:

import asyncio
import os
from datetime import datetime, timezone

from google.adk import Agent, Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types as genai_types

APP_NAME = "repro"
USER_ID = "u1"
MODEL = "gemini-2.5-flash"


async def get_utc_timestamp(
    options: dict[str, str] | None = None,
    # options: list[str] | None = None,
) -> str:
    _ = options
    return datetime.now(timezone.utc).isoformat()


async def main() -> None:
    agent = Agent(
        name="time_agent",
        model=MODEL,
        instruction="You are a helpful assistant. Use tools when asked for the time.",
        tools=[get_utc_timestamp],
    )

    session_service = InMemorySessionService()
    session = await session_service.create_session(app_name=APP_NAME, user_id=USER_ID)
    runner = Runner(app_name=APP_NAME, agent=agent, session_service=session_service)

    user_msg = genai_types.Content(
        role="user",
        parts=[genai_types.Part(text="What's the current UTC timestamp?")],
    )

    async for event in runner.run_async(
        user_id=USER_ID, session_id=session.id, new_message=user_msg
    ):
        if event.content and event.content.parts and event.is_final_response():
            for part in event.content.parts:
                print(part.text)


if __name__ == "__main__":
    os.environ.setdefault("GOOGLE_API_KEY", "<your key here>")
    asyncio.run(main())

Expected Behavior:
dict should be recognized in Schema.

Observed Behavior:

google.genai.errors.ClientError: 400 INVALID_ARGUMENT. {'error': {'code': 400, 'message': 'Invalid JSON payload received. Unknown name "additional_properties" at \'tools[0].function_declarations[0].parameters.properties[0].value.any_of[0]\': Cannot find field.', 'status': 'INVALID_ARGUMENT', 'details': [{'@type': 'type.googleapis.com/google.rpc.BadRequest', 'fieldViolations': [{'field': 'tools[0].function_declarations[0].parameters.properties[0].value.any_of[0]', 'description': 'Invalid JSON payload received. Unknown name "additional_properties" at \'tools[0].function_declarations[0].parameters.properties[0].value.any_of[0]\': Cannot find field.'}]}]}}

Environment Details:

  • ADK Library Version (pip show google-adk): 1.31.0
  • Desktop OS: macOS
  • Python Version (python -V): Python 3.11.14

Model Information:

  • Are you using LiteLLM: No
  • Which model is being used: gemini-2.5-flash

How often has this issue occurred?:

  • Always (100%)

Activity

  1. added
    tools[Component] This issue is related to tools
    on Apr 17, 2026
  2. added theissue type on Apr 17, 2026
  3. perrymanuk commented on Apr 18, 2026

    @perrymanuk

    Confirming this reproduces in a production setting (google-adk==2.0.0a3, google-genai==1.72.0, Python 3.14) with a few extra data points that may help narrow it down:

    Model-dependent strictness

    The failure only surfaces on gemini-2.5-flash and gemini-3.1-flash. The identical agent + tools + schema ran against gemini-3.1-pro-preview for weeks without issue — pro was tolerating the unknown additional_properties field silently.

    In our case the bug was latent after the ADK 2.x upgrade but only started 400-ing once we downgraded our orchestrator from pro to flash for cost reasons. That means other users may be sitting on this without realising, and any model-tier downshift in a multi-agent app is likely to surface it.

    Also triggered by Optional[List[...]]

    Not just dicts — Optional[List[str]] parameters hit the same fallback branch and produce the same snake_case leak. Our failing schemas included:

    • attendees: Optional[List[str]] (calendar event tool)
    • related_refs: Optional[List[str]]
    • metadata: Optional[Dict[str, Any]]
    • metadata_merge: Optional[Dict[str, Any]]
    • related_info: Optional[Dict[str, Any]]

    All five shapes produced the same Unknown name "additional_properties" at '...any_of[0]' 400 when routed to flash.

    Workaround for anyone blocked

    A small before_model_callback that recursively strips the non-standard key from every outgoing llm_request.config.tools[*].function_declarations[*].parameters is working reliably as a holdover. The camelCase additionalProperties (standards-compliant) is left untouched; only the snake_case one is removed:

    https://github.com/perrymanuk/radbot/blob/main/radbot/callbacks/sanitize_tool_schemas.py

    Wired onto the root agent + every sub-agent via before_model_callback so the scrub runs on every LLM call. Idempotent and becomes a harmless no-op once the upstream fix lands.

    Note on #5052

    FWIW #5052 appears to explicitly emit the snake_case additional_properties key in the dict branch, which is the opposite direction from what the Gemini API accepts (it requires camelCase additionalProperties per standard JSON Schema). May be worth double-checking that PR's output against the REST validator before merging — the repro in this issue would presumably still 400 after #5052 lands.

  4. EugeneYushin commented on Apr 20, 2026

    @EugeneYushin
    Contributor

    Thanks @perrymanuk for a thorough review.

    The identical agent + tools + schema ran against gemini-3.1-pro-preview for weeks without issue — pro was tolerating the unknown additional_properties field silently.

    It fails for me with gemini-3.1-pro-preview as well. I'm able to reproduce the issue putting pro model in repro steps.

    I'm calling _sanitize_schema_formats_for_gemini in my PR fix, but most probably we will loose key type hints that way (judging by #5052).

  5. klateefa commented on Apr 23, 2026

    @klateefa

    @pp-eyushin This seems to be a schema formatting issue, not a limitation with dict itself. The generated schema includes additional_properties (snake_case), which isn’t valid JSON Schema, so Gemini rejects it. It expects additionalProperties (camelCase).Fix is to clean or normalize the schema before sending it, or avoid raw dict types and use explicit models. PR #5366 looks like it addresses this by sanitizing the schema. Can you try this and let me know if the issue persists?

  6. pp-eyushin commented on Apr 23, 2026

    @pp-eyushin
    Author

    @klateefa yes, PR you mention fixes the issue.

  7. surajksharma07 commented on Jun 9, 2026

    @surajksharma07
    Collaborator

    /adk-issue-analyze

  8. self-assigned this
    on Jun 17, 2026
  9. GWeale commented on Jun 17, 2026

    @GWeale
    Collaborator

    Fixed on main via #5366 (commit 9808451): the generated schema for complex union/dict types is now sanitized, so dict-typed FunctionDeclaration params no longer 400 on Gemini. Closing as fixed.

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

Metadata

Metadata

Labels

tools[Component] This issue is related to tools

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions