Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions src/google/adk/flows/llm_flows/request_confirmation.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from typing import TYPE_CHECKING

from google.genai import types
from pydantic import ValidationError
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The ValidationError import is correctly added to handle Pydantic validation errors, which is crucial for robust parsing of tool confirmation payloads.

from typing_extensions import override

from . import functions
Expand Down Expand Up @@ -81,13 +82,35 @@ async def run_async(
# ADK client must send a resuming run request with a function response
# that always encapsulate the confirmation result with a 'response'
# key
tool_confirmation = ToolConfirmation.model_validate(
json.loads(function_response.response['response'])
)
try:
tool_confirmation = ToolConfirmation.model_validate(
json.loads(function_response.response['response'])
)
except (
json.JSONDecodeError,
TypeError,
ValidationError,
) as parse_err:
logger.warning(
'Malformed tool confirmation payload for'
' function_response_id=%s: %s',
function_response.id,
parse_err,
)
tool_confirmation = ToolConfirmation(confirmed=False)
Comment on lines +85 to +100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This try-except block correctly catches potential json.JSONDecodeError, TypeError, and ValidationError during the parsing of the tool confirmation payload. In case of an error, it logs a warning and defaults tool_confirmation to ToolConfirmation(confirmed=False), ensuring a fail-closed behavior as intended. This is a critical improvement for handling malformed inputs.

else:
tool_confirmation = ToolConfirmation.model_validate(
function_response.response
)
try:
tool_confirmation = ToolConfirmation.model_validate(
function_response.response
)
except ValidationError as parse_err:
logger.warning(
'Malformed tool confirmation payload for'
' function_response_id=%s: %s',
function_response.id,
parse_err,
)
tool_confirmation = ToolConfirmation(confirmed=False)
Comment on lines +102 to +113
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similarly, this try-except block handles ValidationError when the function_response.response is directly validated. This ensures that even if the payload is not a nested JSON string, Pydantic validation errors are caught, a warning is logged, and the confirmation defaults to confirmed=False.

request_confirmation_function_responses[function_response.id] = (
tool_confirmation
)
Expand Down
31 changes: 31 additions & 0 deletions tests/unittests/runners/test_run_tool_confirmation.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,37 @@ async def test_confirmation_flow(
== expected_parts_final
)

@pytest.mark.asyncio
async def test_malformed_confirmation_payload_is_rejected_fail_closed(
self,
runner: testing_utils.InMemoryRunner,
agent: LlmAgent,
):
"""Malformed confirmation payloads must fail closed and reject tool calls."""
initial_events = await runner.run_async(testing_utils.UserContent("test"))
ask_for_confirmation_function_call_id = (
initial_events[1].content.parts[0].function_call.id
)

malformed_confirmation = testing_utils.UserContent(
Part(
function_response=FunctionResponse(
id=ask_for_confirmation_function_call_id,
name=REQUEST_CONFIRMATION_FUNCTION_CALL_NAME,
response={"response": "{not-json"},
)
)
)

events = await runner.run_async(malformed_confirmation)
simplified = testing_utils.simplify_events(copy.deepcopy(events))
assert simplified[0][1] == Part(
function_response=FunctionResponse(
name=agent.tools[0].name,
response={"error": "This tool call is rejected."},
)
)
Comment on lines +227 to +256
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This new test case effectively verifies the fail-closed behavior when a malformed JSON payload is provided for tool confirmation. It correctly asserts that the tool execution is rejected, which is a good regression test for the implemented fix.



class TestHITLConfirmationFlowWithCustomPayloadSchema(BaseHITLTest):
"""Tests the HITL confirmation flow with a single agent, for custom confirmation payload schema."""
Expand Down