Files
2026-07-13 13:22:34 +08:00

192 lines
6.1 KiB
Python

import numpy as np
import pandas as pd
from mlflow.entities.dataset_record_source import DatasetRecordSource, DatasetRecordSourceType
from mlflow.genai.evaluation.entities import EvalItem, EvaluationResult
from mlflow.genai.judges import CategoricalRating
def test_eval_item_from_dataset_row_extracts_source():
source = DatasetRecordSource(
source_type=DatasetRecordSourceType.TRACE,
source_data={"trace_id": "tr-123", "session_id": "session_1"},
)
row = {
"inputs": {"question": "test"},
"outputs": "answer",
"expectations": {},
"source": source,
}
eval_item = EvalItem.from_dataset_row(row)
assert eval_item.source == source
assert eval_item.source.source_data["session_id"] == "session_1"
assert eval_item.inputs == {"question": "test"}
assert eval_item.outputs == "answer"
def test_eval_item_from_dataset_row_handles_missing_source():
row = {
"inputs": {"question": "test"},
"outputs": "answer",
"expectations": {},
}
eval_item = EvalItem.from_dataset_row(row)
assert eval_item.source is None
assert eval_item.inputs == {"question": "test"}
assert eval_item.outputs == "answer"
def test_all_passing():
df = pd.DataFrame([
{"scorer_a/value": True, "scorer_a/rationale": None},
{"scorer_a/value": True, "scorer_a/rationale": None},
])
result = EvaluationResult(run_id="r1", metrics={}, result_df=df)
assert result.passed
assert result.reason == ""
def test_with_failures():
df = pd.DataFrame([
{
"scorer_a/value": True,
"scorer_a/rationale": None,
"scorer_b/value": False,
"scorer_b/rationale": "bad output",
}
])
result = EvaluationResult(run_id="r1", metrics={}, result_df=df)
assert not result.passed
assert "scorer_b" in result.reason
def test_string_yes_no():
df = pd.DataFrame([
{"scorer_a/value": "yes", "scorer_a/rationale": None},
{"scorer_a/value": "no", "scorer_a/rationale": "failed check"},
])
result = EvaluationResult(run_id="r1", metrics={}, result_df=df)
assert not result.passed
assert "scorer_a" in result.reason
def test_no_rating_fails_cleanly_without_pass_if_hint():
# "no" is a recognized rating, so it fails without nagging about pass_if.
df = pd.DataFrame([{"scorer_a/value": "no", "scorer_a/rationale": None}])
result = EvaluationResult(run_id="r1", metrics={}, result_df=df)
assert not result.passed
assert "value='no'" in result.reason
assert "pass_if" not in result.reason
def test_unrecognized_string_gets_pass_if_hint():
# A non-yes/no string is not a recognized rating; surface the pass_if hint.
df = pd.DataFrame([{"scorer_a/value": "pass", "scorer_a/rationale": None}])
result = EvaluationResult(run_id="r1", metrics={}, result_df=df)
assert not result.passed
assert "'pass'" in result.reason
assert "pass_if" in result.reason
def test_none_result_df():
result = EvaluationResult(run_id="r1", metrics={}, result_df=None)
assert result.passed
def test_categorical_rating_value():
df = pd.DataFrame([
{"scorer_a/value": CategoricalRating.YES, "scorer_a/rationale": None},
{"scorer_a/value": CategoricalRating.NO, "scorer_a/rationale": "nope"},
])
result = EvaluationResult(run_id="r1", metrics={}, result_df=df)
assert not result.passed
assert "scorer_a" in result.reason
def test_error_message_fails_with_detail():
df = pd.DataFrame([
{
"scorer_a/value": None,
"scorer_a/rationale": None,
"scorer_a/error_message": "scorer blew up",
}
])
result = EvaluationResult(run_id="r1", metrics={}, result_df=df)
assert not result.passed
assert "scorer blew up" in result.reason
def test_numeric_value_without_pass_if_fails_loudly():
df = pd.DataFrame([{"scorer_a/value": 0.7, "scorer_a/rationale": None}])
result = EvaluationResult(run_id="r1", metrics={}, result_df=df)
assert not result.passed
assert "pass_if" in result.reason
def test_numeric_value_rationale_does_not_suppress_pass_if_hint():
df = pd.DataFrame([{"scorer_a/value": 0.7, "scorer_a/rationale": "looks good"}])
result = EvaluationResult(run_id="r1", metrics={}, result_df=df)
assert not result.passed
assert "looks good" in result.reason
assert "pass_if" in result.reason
def test_pass_if_predicate_gates_numeric_value():
df = pd.DataFrame([{"scorer_a/value": 0.7, "scorer_a/rationale": None}])
lenient = EvaluationResult(
run_id="r1", metrics={}, result_df=df, pass_criteria={"scorer_a": lambda v: v >= 0.6}
)
assert lenient.passed
strict = EvaluationResult(
run_id="r1", metrics={}, result_df=df, pass_criteria={"scorer_a": lambda v: v >= 0.8}
)
assert not strict.passed
assert "scorer_a" in strict.reason
def test_pass_if_raising_is_reported_not_propagated():
df = pd.DataFrame([{"scorer_a/value": "weird", "scorer_a/rationale": None}])
def boom(v):
raise RuntimeError("bad predicate")
result = EvaluationResult(
run_id="r1", metrics={}, result_df=df, pass_criteria={"scorer_a": boom}
)
assert not result.passed
assert "pass_if raised" in result.reason
def test_numpy_scalar_values():
# Regression for np.bool_ / np.float64 scalars from DataFrame.iterrows().
df = pd.DataFrame([
{
"flag/value": np.bool_(True),
"score/value": np.float64(0.95),
}
])
result = EvaluationResult(
run_id="r1", metrics={}, result_df=df, pass_criteria={"score": lambda v: v >= 0.9}
)
assert result.passed, result.reason
df_fail = pd.DataFrame([{"flag/value": np.bool_(False)}])
assert not EvaluationResult(run_id="r1", metrics={}, result_df=df_fail).passed
def test_sparse_columns_are_skipped():
# Different rows run different scorers, so each row has NaN for the other's column.
df = pd.DataFrame([
{"scorer_a/value": True},
{"scorer_b/value": True},
])
result = EvaluationResult(run_id="r1", metrics={}, result_df=df)
assert result.passed, result.reason