Python: Title: DevUI Fix WorkflowFailedEvent error extraction (#2706)

* Title: Fix WorkflowFailedEvent error extraction to use details instead of error Body:
Summary
Fixed WorkflowFailedEvent mapping to extract error message from details.message instead of non-existent error attribute
Added support for including details.extra context in error messages when present
Problem
The WorkflowFailedEvent handler in _mapper.py was reading event.error, but WorkflowFailedEvent uses a details attribute (of type WorkflowErrorDetails), not error. This caused all workflow failures to display "Unknown error" in the UI instead of the actual error message.
Fix
Updated the handler to match the pattern already used by ExecutorFailedEvent:
Read from event.details instead of event.error
Extract details.message for the error text
Include details.extra context when available

* improve error handling consistency
This commit is contained in:
Victor Dibia
2025-12-08 13:44:32 -08:00
committed by GitHub
Unverified
parent 1e6aedb3f9
commit d889fa6f87
2 changed files with 43 additions and 4 deletions
@@ -840,7 +840,9 @@ class MessageMapper:
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
@@ -849,8 +851,14 @@ class MessageMapper:
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(
@@ -945,7 +953,13 @@ class MessageMapper:
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(
@@ -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: