Skip to content

Commit 0baba2e

Browse files
Merge pull request #271 from askui/feat/automation-error
Adds Error Tool Handling Concept
2 parents 7355cba + c4de478 commit 0baba2e

6 files changed

Lines changed: 57 additions & 11 deletions

File tree

docs/07_tools.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,46 @@ class GreetingTool(Tool):
251251
return f"{base_greeting}, {name}! How are you today?"
252252
```
253253

254+
### Error Handling in Tools
255+
256+
When a tool raises an exception, the agent distinguishes between **fixable** and **unfixable** errors:
257+
258+
- **Fixable errors** (regular exceptions): The error message is returned to the model, which can auto-correct and retry with different parameters. This is the default behavior for any `Exception` raised inside `__call__`.
259+
- **Unfixable errors** (`AutomationError`): The error propagates immediately to the caller, terminating the agent's execution. Use this for errors where retrying cannot help (e.g., missing credentials, unreachable services, invalid environment state).
260+
261+
```python
262+
from askui import AutomationError
263+
from askui.models.shared.tools import Tool
264+
265+
266+
class DatabaseQueryTool(Tool):
267+
"""Queries a database."""
268+
269+
def __init__(self):
270+
super().__init__(
271+
name="database_query",
272+
description="Executes a read-only SQL query",
273+
input_schema={
274+
"type": "object",
275+
"properties": {
276+
"query": {"type": "string", "description": "SQL query to execute"}
277+
},
278+
"required": ["query"],
279+
},
280+
)
281+
282+
def __call__(self, query: str) -> str:
283+
if not self._is_connected():
284+
# Unfixable: no amount of retrying will help
285+
raise AutomationError("Database connection is not available")
286+
287+
if "DROP" in query.upper():
288+
# Fixable: the agent can rephrase the query
289+
raise ValueError("Only SELECT queries are allowed")
290+
291+
return self._execute(query)
292+
```
293+
254294
To use this tool with the ComputerAgent, you can run
255295
```python
256296
from askui import ComputerAgent

src/askui/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
ToolUseBlockParam,
3232
UrlImageSourceParam,
3333
)
34+
from .models.exceptions import AutomationError
3435
from .models.shared.settings import (
3536
DEFAULT_GET_RESOLUTION,
3637
DEFAULT_LOCATE_RESOLUTION,
@@ -69,6 +70,7 @@
6970

7071
__all__ = [
7172
"Agent",
73+
"AutomationError",
7274
"ComputerAgent",
7375
"VisionAgent",
7476
"AgentSettings",

src/askui/agent_base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ def act(
159159
None
160160
161161
Raises:
162+
AutomationError: If a tool raises an unfixable error that cannot be
163+
auto-corrected by the agent.
162164
MaxTokensExceededError: If the model reaches the maximum token limit
163165
defined in the agent settings.
164166
ModelRefusalError: If the model refuses to process the request.

src/askui/models/shared/conversation.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,10 +177,11 @@ def execute_conversation(
177177
self._setup_control_loop(messages, tools, settings, reporters)
178178

179179
self._on_conversation_start()
180-
self._execute_control_loop()
181-
self._on_conversation_end()
182-
183-
self._teardown_control_loop()
180+
try:
181+
self._execute_control_loop()
182+
finally:
183+
self._on_conversation_end()
184+
self._teardown_control_loop()
184185

185186
@tracer.start_as_current_span("_setup_control_loop")
186187
def _setup_control_loop(

src/askui/models/shared/tools.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
2121
from typing_extensions import Self
2222

23+
from askui.models.exceptions import AutomationError
2324
from askui.models.shared.agent_message_param import (
2425
Base64ImageSourceParam,
2526
CacheControlEphemeralParam,
@@ -384,10 +385,8 @@ def is_agent_os_initialized(self) -> bool:
384385
return self._agent_os is not None
385386

386387

387-
class AgentException(Exception):
388-
"""
389-
Exception raised by the agent.
390-
"""
388+
class AgentError(Exception):
389+
"""Unfixable error raised by the agent that terminates execution immediately."""
391390

392391
def __init__(self, message: str):
393392
self.message = message
@@ -647,7 +646,7 @@ def _run_regular_tool(
647646
content=_convert_to_content(tool_result),
648647
tool_use_id=tool_use_block_param.id,
649648
)
650-
except AgentException:
649+
except (AgentError, AutomationError):
651650
raise
652651
except Exception as e: # noqa: BLE001
653652
error_message = getattr(e, "message", str(e))
@@ -691,6 +690,8 @@ def _run_mcp_tool(
691690
content=_convert_to_content(result),
692691
tool_use_id=tool_use_block_param.id,
693692
)
693+
except AutomationError:
694+
raise
694695
except Exception as e: # noqa: BLE001
695696
logger.warning(
696697
"MCP tool failed",

src/askui/tools/exception_tool.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from askui.models.shared.tools import AgentException, Tool
1+
from askui.models.shared.tools import AgentError, Tool
22

33

44
class ExceptionTool(Tool):
@@ -28,4 +28,4 @@ def __init__(self) -> None:
2828
)
2929

3030
def __call__(self, text: str) -> None:
31-
raise AgentException(text)
31+
raise AgentError(text)

0 commit comments

Comments
 (0)