diff --git a/py/src/braintrust/logger.py b/py/src/braintrust/logger.py index 41dbb027..8d7e4cf2 100644 --- a/py/src/braintrust/logger.py +++ b/py/src/braintrust/logger.py @@ -3557,7 +3557,7 @@ def _validate_and_sanitize_experiment_log_partial_args(event: Mapping[str, Any]) if not isinstance(score, (int, float)): raise ValueError("score values must be numbers") - if score < 0 or score > 1: + if not 0 <= score <= 1: raise ValueError("score values must be between 0 and 1") if "metadata" in sanitized_event and sanitized_event["metadata"] is not None: diff --git a/py/src/braintrust/score.py b/py/src/braintrust/score.py index c9395c49..614ac818 100644 --- a/py/src/braintrust/score.py +++ b/py/src/braintrust/score.py @@ -45,7 +45,7 @@ def as_dict(self): } def __post_init__(self): - if self.score is not None and (self.score < 0 or self.score > 1): + if self.score is not None and not 0 <= self.score <= 1: raise ValueError(f"score ({self.score}) must be between 0 and 1") if self.error is not None: warnings.warn( diff --git a/py/src/braintrust/test_logger.py b/py/src/braintrust/test_logger.py index 9bb585fa..df380a8f 100644 --- a/py/src/braintrust/test_logger.py +++ b/py/src/braintrust/test_logger.py @@ -3829,3 +3829,16 @@ def test_proxy_conn_base_url(proxy_url, expected_base_url): state = BraintrustState() state.proxy_url = proxy_url assert state.proxy_conn().base_url == expected_base_url + + +@pytest.mark.parametrize("value", [float("nan"), float("inf"), float("-inf")]) +def test_log_score_validation_rejects_nonfinite_values(value): + # Exercise validation without constructing a logger or making requests. + with pytest.raises(ValueError, match="between 0 and 1"): + logger._validate_and_sanitize_experiment_log_partial_args({"scores": {"accuracy": value}}) + + +@pytest.mark.parametrize("value", [None, 0, 0.5, 1, False, True]) +def test_log_score_validation_preserves_valid_values(value): + result = logger._validate_and_sanitize_experiment_log_partial_args({"scores": {"accuracy": value}}) + assert result["scores"]["accuracy"] == value diff --git a/py/src/braintrust/test_score.py b/py/src/braintrust/test_score.py index 77cca41b..28013151 100644 --- a/py/src/braintrust/test_score.py +++ b/py/src/braintrust/test_score.py @@ -135,6 +135,14 @@ def test_score_validation_enforces_bounds(self): with self.assertRaises(ValueError): Score(name="invalid_over_one", score=1.1) + def test_score_rejects_nonfinite_values(self): + for value in (float("nan"), float("inf"), float("-inf")): + with self.subTest(value=value): + with self.assertRaises(ValueError): + Score(name="nonfinite", score=value) + with self.assertRaises(ValueError): + Score.from_dict({"name": "nonfinite", "score": value}) + def test_score_does_not_include_deprecated_error_field(self): """Test that as_dict() does not include the deprecated error field.""" score = Score(name="test_scorer", score=0.5)