Skip to content

Commit 250e065

Browse files
committed
fix(eval): emit string justification for coded evaluators
Coded evaluators (tool-call count/args/output/order, json-similarity) stored their explanation under per-evaluator keys (explained_tool_calls_*, lcs, matched_leaves) with no 'justification' key, so the eval worker's d.get('justification') always resolved to null while the structured detail was still present. Add a computed 'justification' string field to each coded justification model, derived from its existing structured detail via a shared format_explained_tool_calls helper. model_dump() now emits a string justification for every evaluator, matching LLMJudgeJustification, without changing the structured fields or the worker.
1 parent d31ef42 commit 250e065

6 files changed

Lines changed: 72 additions & 0 deletions

File tree

packages/uipath/src/uipath/eval/_helpers/evaluators_helpers.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,28 @@
2424
COMMUNITY_agents_SUFFIX = "-community-agents"
2525

2626

27+
def format_explained_tool_calls(explanations: Mapping[str, str]) -> str:
28+
"""Render an `explained_tool_calls_*` mapping as a human-readable justification.
29+
30+
The score helpers produce a mapping keyed by tool name with values like
31+
"Actual: X, Expected: Y, Score: Z", or a single `_result` sentinel entry
32+
describing an empty/short-circuit case. This collapses either shape into a
33+
single string suitable for the `justification` field surfaced to users.
34+
35+
Args:
36+
explanations: Mapping of tool name (or `_result`) to its per-tool explanation.
37+
38+
Returns:
39+
A human-readable justification string (empty if there is nothing to explain).
40+
"""
41+
if not explanations:
42+
return ""
43+
return "; ".join(
44+
value if key == "_result" else f"{key} -> {value}"
45+
for key, value in explanations.items()
46+
)
47+
48+
2749
def extract_tool_calls_names(spans: Sequence[ReadableSpan]) -> list[str]:
2850
"""Extract the tool call names from execution spans IN ORDER.
2951

packages/uipath/src/uipath/eval/evaluators/json_similarity_evaluator.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import math
44
from typing import Any, Tuple
55

6+
from pydantic import computed_field
7+
68
from ..models import (
79
AgentExecution,
810
EvaluationResult,
@@ -23,6 +25,12 @@ class JsonSimilarityJustification(BaseEvaluatorJustification):
2325
matched_leaves: float
2426
total_leaves: float
2527

28+
@computed_field # type: ignore[prop-decorator]
29+
@property
30+
def justification(self) -> str:
31+
"""Human-readable justification derived from the matched JSON leaves."""
32+
return f"Matched {self.matched_leaves} of {self.total_leaves} JSON leaf values."
33+
2634

2735
class JsonSimilarityEvaluatorConfig(OutputEvaluatorConfig[OutputEvaluationCriteria]):
2836
"""Configuration for the json similarity evaluator."""

packages/uipath/src/uipath/eval/evaluators/tool_call_args_evaluator.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""Tool call order evaluator for validating correct sequence of tool calls."""
22

3+
from pydantic import computed_field
4+
35
from .._helpers.evaluators_helpers import (
46
extract_tool_calls,
7+
format_explained_tool_calls,
58
tool_calls_args_score,
69
)
710
from ..models import AgentExecution, EvaluationResult, NumericEvaluationResult, ToolCall
@@ -34,6 +37,12 @@ class ToolCallArgsEvaluatorJustification(BaseEvaluatorJustification):
3437

3538
explained_tool_calls_args: dict[str, str]
3639

40+
@computed_field # type: ignore[prop-decorator]
41+
@property
42+
def justification(self) -> str:
43+
"""Human-readable justification derived from the per-tool arg matches."""
44+
return format_explained_tool_calls(self.explained_tool_calls_args)
45+
3746

3847
class ToolCallArgsEvaluator(
3948
BaseEvaluator[

packages/uipath/src/uipath/eval/evaluators/tool_call_count_evaluator.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
from collections import Counter
44

5+
from pydantic import computed_field
6+
57
from .._helpers.evaluators_helpers import (
68
extract_tool_calls_names,
9+
format_explained_tool_calls,
710
tool_calls_count_score,
811
)
912
from ..models import AgentExecution, EvaluationResult, NumericEvaluationResult
@@ -37,6 +40,12 @@ class ToolCallCountEvaluatorJustification(BaseEvaluatorJustification):
3740

3841
explained_tool_calls_count: dict[str, str]
3942

43+
@computed_field # type: ignore[prop-decorator]
44+
@property
45+
def justification(self) -> str:
46+
"""Human-readable justification derived from the per-tool counts."""
47+
return format_explained_tool_calls(self.explained_tool_calls_count)
48+
4049

4150
class ToolCallCountEvaluator(
4251
BaseEvaluator[

packages/uipath/src/uipath/eval/evaluators/tool_call_order_evaluator.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Tool call order evaluator for validating correct sequence of tool calls."""
22

3+
from pydantic import computed_field
4+
35
from .._helpers.evaluators_helpers import (
46
extract_tool_calls_names,
57
tool_calls_order_score,
@@ -35,6 +37,19 @@ class ToolCallOrderEvaluatorJustification(BaseEvaluatorJustification):
3537

3638
lcs: list[str]
3739

40+
@computed_field # type: ignore[prop-decorator]
41+
@property
42+
def justification(self) -> str:
43+
"""Human-readable justification derived from the matched call order."""
44+
if not self.lcs:
45+
return (
46+
"No common ordered subsequence between expected and actual tool calls."
47+
)
48+
return (
49+
"Longest common ordered subsequence between expected and actual "
50+
f"tool calls: {self.lcs}."
51+
)
52+
3853

3954
class ToolCallOrderEvaluator(
4055
BaseEvaluator[

packages/uipath/src/uipath/eval/evaluators/tool_call_output_evaluator.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""Tool call order evaluator for validating correct sequence of tool calls."""
22

3+
from pydantic import computed_field
4+
35
from .._helpers.evaluators_helpers import (
46
extract_tool_calls_outputs,
7+
format_explained_tool_calls,
58
tool_calls_output_score,
69
)
710
from ..models import (
@@ -40,6 +43,12 @@ class ToolCallOutputEvaluatorJustification(BaseEvaluatorJustification):
4043

4144
explained_tool_calls_outputs: dict[str, str]
4245

46+
@computed_field # type: ignore[prop-decorator]
47+
@property
48+
def justification(self) -> str:
49+
"""Human-readable justification derived from the per-tool output matches."""
50+
return format_explained_tool_calls(self.explained_tool_calls_outputs)
51+
4352

4453
class ToolCallOutputEvaluator(
4554
BaseEvaluator[

0 commit comments

Comments
 (0)