Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(autofix): Correctly format chained exceptions #915

Merged
merged 2 commits into from
Jul 16, 2024
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
12 changes: 11 additions & 1 deletion src/seer/automation/agent/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@
logger = logging.getLogger(__name__)


def get_full_exception_string(exc):
result = str(exc)
if exc.__cause__:
if result:
result += f"\n\nThe above exception was the direct cause of the following exception:\n\n{str(exc.__cause__)}"
else:
result = str(exc.__cause__)
return result


class FunctionTool(BaseModel):
name: str
description: str
Expand All @@ -18,7 +28,7 @@ def call(self, **kwargs):
return self.fn(**kwargs)
except Exception as e:
logger.exception(e)
return f"Error: {e}"
return f"Error: {get_full_exception_string(e)}"

def to_dict(self):
return {
Expand Down
87 changes: 87 additions & 0 deletions tests/automation/agent/test_agent_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from unittest.mock import Mock, patch

import pytest

from seer.automation.agent.tools import FunctionTool, get_full_exception_string


class TestGetFullExceptionString:
def test_simple_exception(self):
exc = ValueError("Simple error")
assert get_full_exception_string(exc) == "Simple error"

def test_chained_exception(self):
try:
raise RuntimeError("Main error") from ValueError("Root cause")
except RuntimeError as exc:
assert (
get_full_exception_string(exc)
== "Main error\n\nThe above exception was the direct cause of the following exception:\n\nRoot cause"
)

def test_empty_main_exception(self):
try:
raise RuntimeError() from ValueError("Root cause")
except RuntimeError as exc:
assert get_full_exception_string(exc) == "Root cause"


class TestFunctionTool:
@pytest.fixture
def mock_function(self):
return Mock(return_value="Success")

@pytest.fixture
def function_tool(self, mock_function):
return FunctionTool(
name="test_tool",
description="A test tool",
fn=mock_function,
parameters=[{"name": "param1", "type": "string"}],
)

def test_successful_call(self, function_tool):
result = function_tool.call(param1="test")
assert result == "Success"

def test_exception_handling(self, function_tool):
function_tool.fn.side_effect = ValueError("Test error")

with patch("seer.automation.agent.tools.logger") as mock_logger:
result = function_tool.call(param1="test")

assert result.startswith("Error: Test error")
mock_logger.exception.assert_called_once()

def test_chained_exception_handling(self, function_tool):
cause = ValueError("Root cause")
main_error = RuntimeError("Main error")
main_error.__cause__ = cause
function_tool.fn.side_effect = main_error

with patch("seer.automation.agent.tools.logger") as mock_logger:
result = function_tool.call(param1="test")

expected = "Error: Main error\n\nThe above exception was the direct cause of the following exception:\n\nRoot cause"
assert result == expected
mock_logger.exception.assert_called_once()

def test_to_dict(self, function_tool):
expected = {
"type": "function",
"function": {
"name": "test_tool",
"description": "A test tool",
"parameters": {
"type": "object",
"properties": {
"param1": {
"type": "string",
"description": "",
}
},
"required": [],
},
},
}
assert function_tool.to_dict() == expected
Loading