Skip to content

ToolConfirmation.from_response_dict crashes with TypeError on parsed dicts聽#7681

Description

@aryanpatil-07

馃敶 Required Information

Describe the Bug:
ToolConfirmation.from_response_dict parses tool confirmations from a response dict and intends to support both raw dicts and the wrapper format {"response": ...}. However, it assumes response["response"] is always a serialized JSON string and unconditionally calls json.loads(response["response"]). If the payload is already deserialized (e.g., by FastAPI, an HTTP client, or an in-memory runner) into {"response": {"confirmed": True}}, calling json.loads(dict) raises TypeError: the JSON object must be str, bytes or bytearray, not dict.

In Human-In-The-Loop workflows (src/google/adk/workflow/utils/_workflow_hitl_utils.py), the resume handler wraps from_response_dict in try: ... except ValueError as e:. Because TypeError does not inherit from ValueError, resumes with pre-parsed dictionary payloads crash unexpectedly.

Steps to Reproduce:

  1. Call ToolConfirmation.from_response_dict({"response": {"confirmed": True}})
  2. Observe TypeError: the JSON object must be str, bytes or bytearray, not dict

Expected Behavior:
ToolConfirmation.from_response_dict should accept pre-parsed dictionary payloads inside response["response"] and validate them directly via model_validate(raw).

Observed Behavior:

TypeError: the JSON object must be str, bytes or bytearray, not dict

Environment Details:

Desktop OS: Windows / Linux / macOS
Python Version: 3.11+
ADK Library Version: main branch

Model Information:

Are you using LiteLLM: No
Which model is being used: N/A

馃煛 Optional Information

Minimal Reproduction Code:

python
from google.adk.tools.tool_confirmation import ToolConfirmation
Crashes with TypeError:
ToolConfirmation.from_response_dict({"response": {"confirmed": True}})

How often has this issue occurred?: Always (100%)

Activity

  1. surajksharma07 commented on Oct 23, 2026

    @surajksharma07
    Collaborator

    Reproduced this on main @aryanpatil-07 and the TypeError also leaks out of parse_tool_confirmation() in the workflow HITL resume path instead of becoming a WorkflowDataError.

    The approach in #7682 looks right as a workaround: only call json.loads() when the inner value is a str/bytes, otherwise pass it straight to model_validate(). Invalid values like {"response": 123} then raise a ValidationError which the HITL code already catches.

    Could you confirm this covers your real case too? Also which caller sent you the pre-parsed dict (FastAPI, a custom client, workflow resume)?

    Before it goes for review please test it well and add a test for the HITL resume path and for a non-dict inner value so we cover more than the direct call.

  2. aryanpatil-07 commented on Oct 23, 2026

    @aryanpatil-07
    Author

    Hi @surajksharma07 , thanks for reproducing and reviewing!

    Yes, this covers our real case completely.

    The pre-parsed dict came from a FastAPI / REST API server setup. The incoming HTTP request body containing the resume payload was parsed by FastAPI into Python dictionaries before being passed to runner.run_async(..., resume_inputs=...) / workflow resume. Because the client payload used the wrapper shape {"response": {"confirmed": True, ...}}, response["response"] was already a dict rather than a JSON string, which triggered the TypeError in json.loads().

    As requested, I have updated the PR with:

    1. Non-dict inner value test: Added test_single_response_key_with_invalid_inner_value_raises_validation_error in test_tool_confirmation.py, verifying that {"response": 123} raises ValidationError.
    2. Workflow HITL resume path tests: Added tests in test_workflow_hitl_utils.py verifying that:
      • Resuming with a wrapped pre-parsed dict ({"response": {"confirmed": True}}) successfully sets ctx.tool_confirmation.
      • Invalid inner values (like {"response": 123}) properly get converted to WorkflowDataError instead of leaking TypeError.

    All checks are passing and it is ready for another review!

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