Skip to content
Merged
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
40 changes: 40 additions & 0 deletions docs/07_tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,46 @@ class GreetingTool(Tool):
return f"{base_greeting}, {name}! How are you today?"
```

### Error Handling in Tools

When a tool raises an exception, the agent distinguishes between **fixable** and **unfixable** errors:

- **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__`.
- **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).

```python
from askui import AutomationError
from askui.models.shared.tools import Tool


class DatabaseQueryTool(Tool):
"""Queries a database."""

def __init__(self):
super().__init__(
name="database_query",
description="Executes a read-only SQL query",
input_schema={
"type": "object",
"properties": {
"query": {"type": "string", "description": "SQL query to execute"}
},
"required": ["query"],
},
)

def __call__(self, query: str) -> str:
if not self._is_connected():
# Unfixable: no amount of retrying will help
raise AutomationError("Database connection is not available")

if "DROP" in query.upper():
# Fixable: the agent can rephrase the query
raise ValueError("Only SELECT queries are allowed")

return self._execute(query)
```

To use this tool with the ComputerAgent, you can run
```python
from askui import ComputerAgent
Expand Down
2 changes: 2 additions & 0 deletions src/askui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
ToolUseBlockParam,
UrlImageSourceParam,
)
from .models.exceptions import AutomationError
from .models.shared.settings import (
DEFAULT_GET_RESOLUTION,
DEFAULT_LOCATE_RESOLUTION,
Expand Down Expand Up @@ -69,6 +70,7 @@

__all__ = [
"Agent",
"AutomationError",
"ComputerAgent",
"VisionAgent",
"AgentSettings",
Expand Down
2 changes: 2 additions & 0 deletions src/askui/agent_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ def act(
None

Raises:
AutomationError: If a tool raises an unfixable error that cannot be
auto-corrected by the agent.
MaxTokensExceededError: If the model reaches the maximum token limit
defined in the agent settings.
ModelRefusalError: If the model refuses to process the request.
Expand Down
9 changes: 5 additions & 4 deletions src/askui/models/shared/conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,11 @@ def execute_conversation(
self._setup_control_loop(messages, tools, settings, reporters)

self._on_conversation_start()
self._execute_control_loop()
self._on_conversation_end()

self._teardown_control_loop()
try:
self._execute_control_loop()
finally:
self._on_conversation_end()
self._teardown_control_loop()

@tracer.start_as_current_span("_setup_control_loop")
def _setup_control_loop(
Expand Down
11 changes: 6 additions & 5 deletions src/askui/models/shared/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
from typing_extensions import Self

from askui.models.exceptions import AutomationError
from askui.models.shared.agent_message_param import (
Base64ImageSourceParam,
CacheControlEphemeralParam,
Expand Down Expand Up @@ -384,10 +385,8 @@ def is_agent_os_initialized(self) -> bool:
return self._agent_os is not None


class AgentException(Exception):
"""
Exception raised by the agent.
"""
class AgentError(Exception):
"""Unfixable error raised by the agent that terminates execution immediately."""

def __init__(self, message: str):
self.message = message
Expand Down Expand Up @@ -647,7 +646,7 @@ def _run_regular_tool(
content=_convert_to_content(tool_result),
tool_use_id=tool_use_block_param.id,
)
except AgentException:
except (AgentError, AutomationError):
raise
except Exception as e: # noqa: BLE001
error_message = getattr(e, "message", str(e))
Expand Down Expand Up @@ -691,6 +690,8 @@ def _run_mcp_tool(
content=_convert_to_content(result),
tool_use_id=tool_use_block_param.id,
)
except AutomationError:
raise
except Exception as e: # noqa: BLE001
logger.warning(
"MCP tool failed",
Expand Down
4 changes: 2 additions & 2 deletions src/askui/tools/exception_tool.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from askui.models.shared.tools import AgentException, Tool
from askui.models.shared.tools import AgentError, Tool


class ExceptionTool(Tool):
Expand Down Expand Up @@ -28,4 +28,4 @@ def __init__(self) -> None:
)

def __call__(self, text: str) -> None:
raise AgentException(text)
raise AgentError(text)
Loading