Skip to content

Commit 6162b9b

Browse files
committed
fix(sdk): normalize failed experiment scores
1 parent 83248d1 commit 6162b9b

4 files changed

Lines changed: 64 additions & 7 deletions

File tree

sdks/python/src/opik/evaluation/evaluation_result.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,19 @@ def normalize_experiment_score(
8585
metadata={**(base_metadata or {}), "_fabricated": True},
8686
)
8787

88+
if score.metadata is not None and not isinstance(score.metadata, Mapping):
89+
return score_result.ScoreResult(
90+
name=effective_name,
91+
value=0.0,
92+
reason=(
93+
"ScoreResult.metadata must be a mapping or None, got "
94+
f"{type(score.metadata).__name__}."
95+
),
96+
scoring_failed=True,
97+
category_name=score.category_name,
98+
metadata={"_fabricated": True},
99+
)
100+
88101
if score.scoring_failed:
89102
value = (
90103
score.value

sdks/python/src/opik/evaluation/evaluator.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def _deduplicate_experiment_scores(
6565
scores: List[object],
6666
) -> List[score_result.ScoreResult]:
6767
deduplicated: List[score_result.ScoreResult] = []
68-
successful_positions: Dict[str, int] = {}
68+
score_positions: Dict[str, int] = {}
6969
for raw_score in scores:
7070
if not isinstance(raw_score, score_result.ScoreResult):
7171
LOGGER.warning(
@@ -75,12 +75,9 @@ def _deduplicate_experiment_scores(
7575
score = evaluation_result.normalize_experiment_score(
7676
raw_score, default_name="invalid_experiment_score"
7777
)
78-
if score.scoring_failed:
79-
deduplicated.append(score)
80-
continue
81-
position = successful_positions.get(score.name)
78+
position = score_positions.get(score.name)
8279
if position is None:
83-
successful_positions[score.name] = len(deduplicated)
80+
score_positions[score.name] = len(deduplicated)
8481
deduplicated.append(score)
8582
else:
8683
deduplicated[position] = score

sdks/python/tests/unit/evaluation/test_evaluate_experiment.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55

66
from opik import evaluation, exceptions, url_helpers
77
from opik.api_objects import opik_client
8-
from opik.evaluation import metrics, rest_operations, test_case
8+
from opik.evaluation import (
9+
evaluator as evaluator_module,
10+
metrics,
11+
rest_operations,
12+
test_case,
13+
)
914
from opik.evaluation.engine import engine
1015
from opik.evaluation.metrics import score_result
1116

@@ -348,6 +353,33 @@ def scorer_b(_):
348353
)
349354

350355

356+
@pytest.mark.parametrize(
357+
"scores, expected",
358+
[
359+
(
360+
[
361+
score_result.ScoreResult(name="accuracy", value=0.5),
362+
score_result.ScoreResult(
363+
name="accuracy", value=0.0, scoring_failed=True
364+
),
365+
],
366+
[score_result.ScoreResult(name="accuracy", value=0.0, scoring_failed=True)],
367+
),
368+
(
369+
[
370+
score_result.ScoreResult(
371+
name="accuracy", value=0.0, scoring_failed=True
372+
),
373+
score_result.ScoreResult(name="accuracy", value=0.9),
374+
],
375+
[score_result.ScoreResult(name="accuracy", value=0.9)],
376+
),
377+
],
378+
)
379+
def test_deduplicate_experiment_scores__last_result_wins(scores, expected):
380+
assert evaluator_module._deduplicate_experiment_scores(scores) == expected
381+
382+
351383
def test_evaluate_experiment__with_experiment_id__uses_get_by_id(fake_backend):
352384
mock_experiment = _make_mock_experiment(id="explicit-exp-id")
353385
mock_dataset = _make_mock_dataset()

sdks/python/tests/unit/evaluation/test_evaluation_result.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,21 @@ def test_normalize_experiment_score__invalid_metadata_type(bad_metadata):
796796
assert res.metadata == {"_fabricated": True}
797797

798798

799+
@pytest.mark.parametrize("bad_metadata", ["invalid_str", [1, 2], 123, ("a", "b")])
800+
def test_normalize_experiment_score__valid_score_with_invalid_metadata(bad_metadata):
801+
score = score_result.ScoreResult(
802+
name="accuracy",
803+
value=0.5,
804+
metadata=bad_metadata, # type: ignore
805+
)
806+
res = evaluation_result.normalize_experiment_score(score, default_name="fallback")
807+
assert res.name == "accuracy"
808+
assert res.value == 0.0
809+
assert res.scoring_failed is True
810+
assert res.metadata == {"_fabricated": True}
811+
assert "metadata must be a mapping" in res.reason
812+
813+
799814
@pytest.mark.parametrize("bad_flag", ["false", "true", 0, 1, None, []])
800815
def test_normalize_experiment_score__invalid_scoring_failed_flag(bad_flag):
801816
score = score_result.ScoreResult(

0 commit comments

Comments
 (0)