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
22 changes: 18 additions & 4 deletions python/packages/devui/agent_framework_devui/_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,9 @@ async def _convert_workflow_event(self, event: Any, context: dict[str, Any]) ->

if event_class == "WorkflowFailedEvent":
workflow_id = context.get("workflow_id", str(uuid4()))
error_info = getattr(event, "error", None)
# WorkflowFailedEvent uses 'details' field (WorkflowErrorDetails), not 'error'
# This matches ExecutorFailedEvent which also uses 'details'
details = getattr(event, "details", None)

# Import Response and ResponseError types
from openai.types.responses import Response, ResponseError
Expand All @@ -849,8 +851,14 @@ async def _convert_workflow_event(self, event: Any, context: dict[str, Any]) ->
request_obj = context.get("request")
model_name = request_obj.model if request_obj and request_obj.model else "devui"

# Create error object
error_message = str(error_info) if error_info else "Unknown error"
# Extract error message from WorkflowErrorDetails
if details:
error_message = getattr(details, "message", None) or str(details)
extra = getattr(details, "extra", None)
if extra:
error_message = f"{error_message} (extra: {extra})"
else:
error_message = "Unknown error"

# Create ResponseError object (code must be one of the allowed values)
response_error = ResponseError(
Expand Down Expand Up @@ -945,7 +953,13 @@ async def _convert_workflow_event(self, event: Any, context: dict[str, Any]) ->
item_id = context.get(f"exec_item_{executor_id}", f"exec_{executor_id}_unknown")
# ExecutorFailedEvent uses 'details' field (WorkflowErrorDetails), not 'error'
details = getattr(event, "details", None)
err_msg: str | None = str(getattr(details, "message", details)) if details else None
if details:
err_msg = getattr(details, "message", None) or str(details)
extra = getattr(details, "extra", None)
if extra:
err_msg = f"{err_msg} (extra: {extra})"
else:
err_msg = None

# Create ExecutorActionItem with failed status
executor_item = ExecutorActionItem(
Expand Down
25 changes: 25 additions & 0 deletions python/packages/devui/tests/test_mapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,31 @@ async def test_workflow_failed_event(mapper: MessageMapper, test_request: AgentF
response = failed_events[0].response
assert response.status == "failed"
assert response.error is not None
# Verify error message is correctly extracted from details.message (not "Unknown error")
assert "Workflow failed due to test error" in response.error.message
assert "Unknown error" not in response.error.message


async def test_workflow_failed_event_with_extra(mapper: MessageMapper, test_request: AgentFrameworkRequest) -> None:
"""Test WorkflowFailedEvent includes extra context when available."""
from agent_framework._workflows._events import WorkflowErrorDetails, WorkflowFailedEvent

details = WorkflowErrorDetails(
error_type="ValidationError",
message="Input validation failed",
executor_id="validation_executor",
extra={"field": "email", "reason": "invalid format"},
)
event = WorkflowFailedEvent(details=details)
events = await mapper.convert_event(event, test_request)

assert len(events) == 1
assert events[0].type == "response.failed"
response = events[0].response
# Verify both the message and extra context are included
assert "Input validation failed" in response.error.message
assert "extra:" in response.error.message
assert "email" in response.error.message


async def test_workflow_failed_event_with_traceback(mapper: MessageMapper, test_request: AgentFrameworkRequest) -> None:
Expand Down
Loading