Skip to content

Commit 2f8dc6b

Browse files
committed
chore: add JsonSimilarityEvaluator
1 parent 093c0ff commit 2f8dc6b

8 files changed

Lines changed: 536 additions & 150 deletions

src/uipath/_cli/_evals/_evaluators/__init__.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,20 @@
33
This package contains all evaluator types and the factory for creating them.
44
"""
55

6-
from ._agent_scorer_evaluator import AgentScorerEvaluator
7-
from ._deterministic_evaluator import DeterministicEvaluator
6+
from ._deterministic_evaluator_base import DeterministicEvaluatorBase
87
from ._evaluator_base import EvaluatorBase
98
from ._evaluator_factory import EvaluatorFactory
9+
from ._exact_match_evaluator import ExactMatchEvaluator
10+
from ._json_similarity_evaluator import JsonSimilarityEvaluator
1011
from ._llm_as_judge_evaluator import LlmAsAJudgeEvaluator
1112
from ._trajectory_evaluator import TrajectoryEvaluator
1213

1314
__all__ = [
1415
"EvaluatorBase",
16+
"DeterministicEvaluatorBase",
1517
"EvaluatorFactory",
16-
"DeterministicEvaluator",
18+
"JsonSimilarityEvaluator",
19+
"ExactMatchEvaluator",
1720
"LlmAsAJudgeEvaluator",
18-
"AgentScorerEvaluator",
1921
"TrajectoryEvaluator",
2022
]

src/uipath/_cli/_evals/_evaluators/_agent_scorer_evaluator.py

Lines changed: 0 additions & 48 deletions
This file was deleted.

src/uipath/_cli/_evals/_evaluators/_deterministic_evaluator.py

Lines changed: 0 additions & 76 deletions
This file was deleted.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import copy
2+
import json
3+
from abc import ABC
4+
from typing import Any, Dict, Tuple
5+
6+
from ._evaluator_base import EvaluatorBase
7+
8+
9+
class DeterministicEvaluatorBase(EvaluatorBase, ABC):
10+
def __init__(self, target_output_key: str = "*"):
11+
super().__init__()
12+
self.target_output_key = target_output_key
13+
14+
def _select_targets(
15+
self, expected_output: Dict[str, Any], actual_output: Dict[str, Any]
16+
) -> Tuple[Any, Any]:
17+
actual_output_copy = copy.deepcopy(actual_output)
18+
expected_output_copy = copy.deepcopy(expected_output)
19+
if self.target_output_key != "*":
20+
if (
21+
self.target_output_key not in actual_output
22+
or self.target_output_key not in expected_output
23+
):
24+
raise ValueError(
25+
f"Field '{self.target_output_key}' missing from expected or actual output"
26+
)
27+
actual_output_copy = actual_output_copy[self.target_output_key]
28+
expected_output_copy = expected_output[self.target_output_key]
29+
return actual_output_copy, expected_output_copy
30+
31+
def _canonical_json(self, obj: Any) -> str:
32+
return json.dumps(
33+
self._normalize_numbers(obj),
34+
sort_keys=True,
35+
separators=(",", ":"),
36+
ensure_ascii=False,
37+
)
38+
39+
def _normalize_numbers(self, obj: Any) -> Any:
40+
if isinstance(obj, dict):
41+
return {k: self._normalize_numbers(v) for k, v in obj.items()}
42+
if isinstance(obj, (list, tuple)):
43+
return [self._normalize_numbers(v) for v in obj]
44+
if isinstance(obj, (int, float)) and not isinstance(obj, bool):
45+
return float(obj)
46+
return obj

src/uipath/_cli/_evals/_evaluators/_evaluator_factory.py

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from typing import Any, Dict
22

33
from .._models import EvaluatorCategory, EvaluatorType
4-
from ._agent_scorer_evaluator import AgentScorerEvaluator
5-
from ._deterministic_evaluator import DeterministicEvaluator
64
from ._evaluator_base import EvaluatorBase, EvaluatorBaseParams
5+
from ._exact_match_evaluator import ExactMatchEvaluator
6+
from ._json_similarity_evaluator import JsonSimilarityEvaluator
77
from ._llm_as_judge_evaluator import LlmAsAJudgeEvaluator
88
from ._trajectory_evaluator import TrajectoryEvaluator
99

@@ -50,23 +50,47 @@ def create_evaluator(data: Dict[str, Any]) -> EvaluatorBase:
5050
)
5151

5252
# Create evaluator based on category
53-
if category == EvaluatorCategory.Deterministic:
54-
return EvaluatorFactory._create_deterministic_evaluator(base_params, data)
55-
elif category == EvaluatorCategory.LlmAsAJudge:
56-
return EvaluatorFactory._create_llm_as_judge_evaluator(base_params, data)
57-
elif category == EvaluatorCategory.AgentScorer:
58-
return EvaluatorFactory._create_agent_scorer_evaluator(base_params, data)
59-
elif category == EvaluatorCategory.Trajectory:
60-
return EvaluatorFactory._create_trajectory_evaluator(base_params, data)
61-
else:
62-
raise ValueError(f"Unknown evaluator category: {category}")
53+
match category:
54+
case EvaluatorCategory.Deterministic:
55+
if evaluator_type == evaluator_type.Equals:
56+
return EvaluatorFactory._create_exact_match_evaluator(
57+
base_params, data
58+
)
59+
elif evaluator_type == evaluator_type.JsonSimilarity:
60+
return EvaluatorFactory._create_json_similarity_evaluator(
61+
base_params, data
62+
)
63+
else:
64+
raise ValueError(
65+
f"Unknown evaluator type {evaluator_type} for category {category}"
66+
)
67+
case EvaluatorCategory.LlmAsAJudge:
68+
return EvaluatorFactory._create_llm_as_judge_evaluator(
69+
base_params, data
70+
)
71+
case EvaluatorCategory.AgentScorer:
72+
raise NotImplementedError()
73+
case EvaluatorCategory.Trajectory:
74+
return EvaluatorFactory._create_trajectory_evaluator(base_params, data)
75+
case _:
76+
raise ValueError(f"Unknown evaluator category: {category}")
6377

6478
@staticmethod
65-
def _create_deterministic_evaluator(
79+
def _create_exact_match_evaluator(
6680
base_params: EvaluatorBaseParams, data: Dict[str, Any]
67-
) -> DeterministicEvaluator:
81+
) -> ExactMatchEvaluator:
6882
"""Create a deterministic evaluator."""
69-
return DeterministicEvaluator.from_params(
83+
return ExactMatchEvaluator.from_params(
84+
base_params,
85+
target_output_key=data.get("targetOutputKey", "*"),
86+
)
87+
88+
@staticmethod
89+
def _create_json_similarity_evaluator(
90+
base_params: EvaluatorBaseParams, data: Dict[str, Any]
91+
) -> JsonSimilarityEvaluator:
92+
"""Create a deterministic evaluator."""
93+
return JsonSimilarityEvaluator.from_params(
7094
base_params,
7195
target_output_key=data.get("targetOutputKey", "*"),
7296
)
@@ -91,13 +115,6 @@ def _create_llm_as_judge_evaluator(
91115
target_output_key=data.get("targetOutputKey", "*"),
92116
)
93117

94-
@staticmethod
95-
def _create_agent_scorer_evaluator(
96-
base_params: EvaluatorBaseParams, data: Dict[str, Any]
97-
) -> AgentScorerEvaluator:
98-
"""Create an agent scorer evaluator."""
99-
raise NotImplementedError()
100-
101118
@staticmethod
102119
def _create_trajectory_evaluator(
103120
base_params: EvaluatorBaseParams, data: Dict[str, Any]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import copy
2+
from typing import Any, Dict
3+
4+
from uipath._cli._evals._evaluators._deterministic_evaluator_base import (
5+
DeterministicEvaluatorBase,
6+
)
7+
from uipath._cli._evals._models import EvaluationResult
8+
from uipath._cli._evals._models._evaluators import ScoreType
9+
10+
11+
class ExactMatchEvaluator(DeterministicEvaluatorBase):
12+
async def evaluate(
13+
self,
14+
evaluation_id: str,
15+
evaluation_name: str,
16+
input_data: Dict[str, Any],
17+
expected_output: Dict[str, Any],
18+
actual_output: Dict[str, Any],
19+
) -> EvaluationResult:
20+
actual_output_copy = copy.deepcopy(actual_output)
21+
expected_output_copy = copy.deepcopy(expected_output)
22+
23+
actual_output, expected_output = self._select_targets(
24+
expected_output, actual_output
25+
)
26+
are_equal = self._canonical_json(actual_output) == self._canonical_json(
27+
expected_output
28+
)
29+
30+
return EvaluationResult(
31+
evaluation_id=evaluation_id,
32+
evaluation_name=evaluation_name,
33+
evaluator_id=self.id,
34+
evaluator_name=self.name,
35+
score=are_equal,
36+
input=input_data,
37+
expected_output=expected_output_copy,
38+
actual_output=actual_output_copy,
39+
score_type=ScoreType.BOOLEAN,
40+
)

0 commit comments

Comments
 (0)