Skip to content
Open
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
25 changes: 21 additions & 4 deletions packages/uipath/src/uipath/eval/evaluators/output_evaluator.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,19 @@ def _normalize_numbers(self, obj: Any) -> Any:
return float(obj)
return obj

def _describe_key_lookup_failure(
self, error: Exception, source: Any, key: "str | list[str]"
) -> str:
"""Describe which target output key(s) failed to resolve and what was available."""
requested = (
", ".join(f"'{k}'" for k in key) if isinstance(key, list) else f"'{key}'"
)
message = f"Could not resolve target output key {requested}: {error}"
if isinstance(source, dict):
available = ", ".join(f"'{k}'" for k in source.keys()) or "none"
message += f". Available top-level keys: {available}"
return message

def _get_actual_output(self, workload_execution: WorkloadExecution) -> Any:
"""Get the actual output from the workload execution.

Expand All @@ -153,7 +166,9 @@ def _get_actual_output(self, workload_execution: WorkloadExecution) -> Any:
raise UiPathEvaluationError(
code="TARGET_OUTPUT_KEY_NOT_FOUND",
title="One or more target output keys not found in actual output",
detail=f"Error: {e}",
detail=self._describe_key_lookup_failure(
e, workload_execution.workload_output, key
),
category=UiPathEvaluationErrorCategory.USER,
) from e
for k, v in list_result.items():
Expand All @@ -168,7 +183,9 @@ def _get_actual_output(self, workload_execution: WorkloadExecution) -> Any:
raise UiPathEvaluationError(
code="TARGET_OUTPUT_KEY_NOT_FOUND",
title="Target output key not found in actual output",
detail=f"Error: {e}",
detail=self._describe_key_lookup_failure(
e, workload_execution.workload_output, key
),
category=UiPathEvaluationErrorCategory.USER,
) from e
else:
Expand Down Expand Up @@ -216,7 +233,7 @@ def _resolve_list_key_expected(
raise UiPathEvaluationError(
code="TARGET_OUTPUT_KEY_NOT_FOUND",
title="One or more target output keys not found in expected output",
detail=f"Error: {e}",
detail=self._describe_key_lookup_failure(e, expected_output, keys),
category=UiPathEvaluationErrorCategory.USER,
) from e

Expand All @@ -238,7 +255,7 @@ def _resolve_scalar_key_expected(self, expected_output: Any, key: str) -> Any:
raise UiPathEvaluationError(
code="TARGET_OUTPUT_KEY_NOT_FOUND",
title="Target output key not found in expected output",
detail=f"Error: {e}",
detail=self._describe_key_lookup_failure(e, expected_output, key),
category=UiPathEvaluationErrorCategory.USER,
) from e

Expand Down
9 changes: 6 additions & 3 deletions packages/uipath/src/uipath/eval/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,20 +370,23 @@ def __init__(
):
"""Initialize the UiPathEvaluationError."""
# Get the current traceback as a string
full_detail = detail
if include_traceback:
tb = traceback.format_exc()
if (
tb and tb.strip() != "NoneType: None"
): # Ensure there's an actual traceback
detail = f"{detail}\n\n{tb}"
full_detail = f"{detail}\n\n{tb}"

self.error_info = UiPathEvaluationErrorContract(
code=f"{prefix}.{code}",
title=title,
detail=detail,
detail=full_detail,
category=category,
)
super().__init__(detail)
# str(exc) stays human-readable (title + detail); the raw traceback is
# still available via error_info.detail for logs/support.
super().__init__(f"{title}: {detail}")

@property
def as_dict(self) -> dict[str, Any]:
Expand Down
5 changes: 5 additions & 0 deletions packages/uipath/tests/evaluators/test_evaluator_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,8 @@ async def test_scalar_key_missing_in_actual_raises(self) -> None:
result = await evaluator.evaluate(execution, criteria)
assert isinstance(result, ErrorEvaluationResult)
assert result.score == 0.0
assert "'missing_key'" in result.details
assert "'other'" in result.details

@pytest.mark.asyncio
async def test_scalar_key_missing_in_expected_raises(self) -> None:
Expand All @@ -807,6 +809,9 @@ async def test_scalar_key_missing_in_expected_raises(self) -> None:
result = await evaluator.evaluate(execution, criteria)
assert isinstance(result, ErrorEvaluationResult)
assert result.score == 0.0
assert "'status'" in result.details
assert "'other_key'" in result.details
assert "Traceback" not in result.details

@pytest.mark.asyncio
async def test_scalar_key_invalid_json_expected_raises(self) -> None:
Expand Down
Loading