Skip to content

Commit eac456e

Browse files
committed
fix(adk): include validation errors in recoverable wrapping logic
1 parent 91795ed commit eac456e

2 files changed

Lines changed: 40 additions & 11 deletions

File tree

packages/toolbox-adk/src/toolbox_adk/tool.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import inspect
1616
import logging
1717
from typing import Any, Dict, Mapping, Optional
18-
from pydantic import ValidationError
1918

2019
import toolbox_core
2120
from fastapi.openapi.models import (
@@ -32,6 +31,7 @@
3231
from google.adk.tools.base_tool import BaseTool
3332
from google.adk.tools.tool_context import ToolContext
3433
from google.genai.types import FunctionDeclaration, Schema, Type
34+
from pydantic import ValidationError
3535
from toolbox_core.protocol import AdditionalPropertiesSchema, ParameterSchema
3636
from toolbox_core.tool import ToolboxTool as CoreToolboxTool
3737
from typing_extensions import override
@@ -50,9 +50,9 @@ def __init__(
5050
adk_token_getters: Optional[Mapping[str, Any]] = None,
5151
):
5252
"""Args:
53-
core_tool: The underlying toolbox_core.py tool instance.
54-
auth_config: Credential configuration to handle interactive flows.
55-
adk_token_getters: Tool-specific auth token getters.
53+
core_tool: The underlying toolbox_core.py tool instance.
54+
auth_config: Credential configuration to handle interactive flows.
55+
adk_token_getters: Tool-specific auth token getters.
5656
"""
5757
name = getattr(core_tool, "__name__", None)
5858
if not name:
@@ -216,8 +216,7 @@ async def run_async(
216216
):
217217
self._core_tool = self._core_tool.add_auth_token_getter(
218218
s,
219-
lambda t=creds.oauth2.id_token
220-
or creds.oauth2.access_token: t,
219+
lambda t=creds.oauth2.id_token or creds.oauth2.access_token: t,
221220
)
222221
try:
223222
if tool_context._invocation_context.credential_service:
@@ -270,12 +269,12 @@ async def run_async(
270269

271270
try:
272271
return await self._core_tool(**args)
273-
except (TypeError, PermissionError, ValueError, ValidationError) as e:
274-
# Propagate framework-level errors to ensure compatibility with existing tests
272+
except (TypeError, PermissionError) as e:
273+
# Propagate system-level errors
275274
raise e
276-
except Exception as e:
277-
# Catch unexpected tool execution errors and return as a structured dictionary
278-
# This handles cases like remote tool crashes or server errors gracefully for LLMs
275+
except (ValueError, ValidationError, Exception) as e:
276+
# Catch tool execution and validation errors and return as a structured dictionary
277+
# This handles cases like invalid parameters, tool crashes, or server errors
279278
logging.warning(
280279
"Toolbox tool '%s' execution failed: %s", self.name, e, exc_info=True
281280
)

packages/toolbox-adk/tests/unit/test_tool.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,36 @@ async def test_run_async_returns_error_on_exception(self):
7171
assert result.get("is_error") is True
7272
assert "RuntimeError" in result["error"]
7373

74+
@pytest.mark.asyncio
75+
async def test_run_async_returns_error_on_validation_error(self):
76+
# Setup mock to raise a ValidationError
77+
mock_core = AsyncMock()
78+
mock_core.__name__ = "my_tool"
79+
mock_core.__doc__ = "my description"
80+
81+
# We simulate a ValidationError by raising it from the mock
82+
mock_core.side_effect = ValidationError.from_exception_data(
83+
"MyModel",
84+
[
85+
{
86+
"type": "missing",
87+
"loc": ("param",),
88+
"msg": "field required",
89+
"input": {},
90+
}
91+
],
92+
)
93+
94+
tool = ToolboxTool(mock_core)
95+
ctx = MagicMock()
96+
97+
result = await tool.run_async({"arg": 1}, ctx)
98+
99+
assert isinstance(result, dict) and "error" in result
100+
assert result.get("is_error") is True
101+
assert "ValidationError" in result["error"]
102+
assert "field required" in result["error"]
103+
74104
@pytest.mark.asyncio
75105
async def test_bind_params(self):
76106
mock_core = MagicMock()

0 commit comments

Comments
 (0)