mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
feat: Add a tool confirmation flow that can guard tool execution with explicit confirmation and custom input
The existing `LongRunningTool` does not define a programmatic way to provide & validate structured input, also it relies on LLM to reason and parse the user's response. For a quick start, annotate the function with `FunctionTool(my_function, require_confirmation=True)`. A more advanced flow is shown in the `human_tool_confirmation` sample. The new flow is similar to the existing Auth flow: - User request a tool confirmation by calling `tool_context.request_confirmation()` in the tool or `before_tool_callback`, or just using the `require_confirmation` shortcut in FunctionTool. - User can provide custom validation logic before tool call proceeds. - ADK creates corresponding RequestConfirmation FunctionCall Event to ask user for confirmation - User needs to provide the expected tool confirmation to a RequestConfirmation FunctionResponse Event. - ADK then checks the response and continues the tool call. PiperOrigin-RevId: 801019917
This commit is contained in:
committed by
Copybara-Service
parent
3ed9097983
commit
a17bcbb2aa
@@ -17,6 +17,7 @@ from unittest.mock import MagicMock
|
||||
from google.adk.agents.invocation_context import InvocationContext
|
||||
from google.adk.sessions.session import Session
|
||||
from google.adk.tools.function_tool import FunctionTool
|
||||
from google.adk.tools.tool_confirmation import ToolConfirmation
|
||||
from google.adk.tools.tool_context import ToolContext
|
||||
import pytest
|
||||
|
||||
@@ -345,3 +346,51 @@ async def test_run_async_with_tool_context_and_unexpected_argument():
|
||||
"received_arg": "world",
|
||||
"context_present": True,
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_async_with_require_confirmation():
|
||||
"""Test that run_async handles require_confirmation flag."""
|
||||
|
||||
def sample_func(arg1: str):
|
||||
return {"received_arg": arg1}
|
||||
|
||||
tool = FunctionTool(sample_func, require_confirmation=True)
|
||||
mock_invocation_context = MagicMock(spec=InvocationContext)
|
||||
mock_invocation_context.session = MagicMock(spec=Session)
|
||||
mock_invocation_context.session.state = MagicMock()
|
||||
mock_invocation_context.agent = MagicMock()
|
||||
mock_invocation_context.agent.name = "test_agent"
|
||||
tool_context_mock = ToolContext(invocation_context=mock_invocation_context)
|
||||
tool_context_mock.function_call_id = "test_function_call_id"
|
||||
|
||||
# First call, should request confirmation
|
||||
result = await tool.run_async(
|
||||
args={"arg1": "hello"},
|
||||
tool_context=tool_context_mock,
|
||||
)
|
||||
assert result == {
|
||||
"error": "This tool call requires confirmation, please approve or reject."
|
||||
}
|
||||
assert tool_context_mock._event_actions.requested_tool_confirmations[
|
||||
"test_function_call_id"
|
||||
].hint == (
|
||||
"Please approve or reject the tool call sample_func() by responding with"
|
||||
" a FunctionResponse with an expected ToolConfirmation payload."
|
||||
)
|
||||
|
||||
# Second call, user rejects
|
||||
tool_context_mock.tool_confirmation = ToolConfirmation(confirmed=False)
|
||||
result = await tool.run_async(
|
||||
args={"arg1": "hello"},
|
||||
tool_context=tool_context_mock,
|
||||
)
|
||||
assert result == {"error": "This tool call is rejected."}
|
||||
|
||||
# Third call, user approves
|
||||
tool_context_mock.tool_confirmation = ToolConfirmation(confirmed=True)
|
||||
result = await tool.run_async(
|
||||
args={"arg1": "hello"},
|
||||
tool_context=tool_context_mock,
|
||||
)
|
||||
assert result == {"received_arg": "hello"}
|
||||
|
||||
Reference in New Issue
Block a user