From 5a418e4fab5cda12a4813760cd3da136cc00581e Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Mon, 8 Dec 2025 11:26:18 -0800 Subject: [PATCH 1/2] 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 --- .../devui/agent_framework_devui/_mapper.py | 14 ++++++++--- python/packages/devui/tests/test_mapper.py | 25 +++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/python/packages/devui/agent_framework_devui/_mapper.py b/python/packages/devui/agent_framework_devui/_mapper.py index f68fea9d01..de4655c3d0 100644 --- a/python/packages/devui/agent_framework_devui/_mapper.py +++ b/python/packages/devui/agent_framework_devui/_mapper.py @@ -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 @@ -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( diff --git a/python/packages/devui/tests/test_mapper.py b/python/packages/devui/tests/test_mapper.py index eaaec77313..6557dea527 100644 --- a/python/packages/devui/tests/test_mapper.py +++ b/python/packages/devui/tests/test_mapper.py @@ -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: From 6f8b62e0674ce53fdfc25ff55e527d2d9629c11a Mon Sep 17 00:00:00 2001 From: Victor Dibia Date: Mon, 8 Dec 2025 12:15:08 -0800 Subject: [PATCH 2/2] improve error handling consistency --- python/packages/devui/agent_framework_devui/_mapper.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/packages/devui/agent_framework_devui/_mapper.py b/python/packages/devui/agent_framework_devui/_mapper.py index de4655c3d0..5adff1cd2f 100644 --- a/python/packages/devui/agent_framework_devui/_mapper.py +++ b/python/packages/devui/agent_framework_devui/_mapper.py @@ -953,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(