diff --git a/slime/rollout/rm_hub/math_dapo_utils.py b/slime/rollout/rm_hub/math_dapo_utils.py index 915c3aa480..1cab28ca0a 100644 --- a/slime/rollout/rm_hub/math_dapo_utils.py +++ b/slime/rollout/rm_hub/math_dapo_utils.py @@ -15,6 +15,7 @@ import re import signal +from decimal import Decimal, InvalidOperation def last_boxed_only_string(string: str) -> str | None: @@ -63,7 +64,6 @@ def remove_boxed(s: str) -> str: class timeout: - def __init__(self, seconds=1, error_message="Timeout"): self.seconds = seconds self.error_message = error_message @@ -207,7 +207,15 @@ def is_correct_minerva( else: gt = normalize_final_answer(gt) - gt = str(int(float(gt))) # in dapo, all answers are integers + try: + numeric_gt = Decimal(gt) + except InvalidOperation as exc: + raise ValueError(f"DAPO ground-truth label must be an integer, got {gt!r}") from exc + + if not numeric_gt.is_finite() or numeric_gt != numeric_gt.to_integral_value(): + raise ValueError(f"DAPO ground-truth label must be an integer, got {gt!r}") + + gt = str(int(numeric_gt)) return (pred == gt), pred diff --git a/tests/test_rm_math_dapo.py b/tests/test_rm_math_dapo.py index 3a5e9a28b9..9d241b34ce 100644 --- a/tests/test_rm_math_dapo.py +++ b/tests/test_rm_math_dapo.py @@ -153,14 +153,37 @@ def test_is_correct_strict_box_no_box_returns_minus_one(): @pytest.mark.unit def test_is_correct_minerva_matches_int_answer(): - """Minerva path expects gt to coerce via ``int(float(gt))`` (line 210), - so floats / int-strings collapse to canonical int strings before - comparison.""" correct, pred = is_correct_minerva("Long solution. Answer: 42", "42") assert correct is True assert pred == "42" +@pytest.mark.unit +@pytest.mark.parametrize("ground_truth", ["42.0", "4.2e1"]) +def test_is_correct_minerva_accepts_integral_numeric_labels(ground_truth): + correct, pred = is_correct_minerva("Answer: 42", ground_truth) + assert correct is True + assert pred == "42" + + +@pytest.mark.unit +def test_is_correct_minerva_preserves_large_integer_precision(): + ground_truth = "9007199254740993" + correct, pred = is_correct_minerva(f"Answer: {ground_truth}", ground_truth) + assert correct is True + assert pred == ground_truth + + +@pytest.mark.unit +@pytest.mark.parametrize("ground_truth", ["0.5", "1/2", "1+2", "NaN", "Infinity"]) +def test_is_correct_minerva_rejects_non_integer_labels(ground_truth): + with pytest.raises( + ValueError, + match="DAPO ground-truth label must be an integer", + ): + is_correct_minerva("Answer: 0", ground_truth) + + @pytest.mark.unit def test_is_correct_minerva_takes_last_answer_match(): """Multiple ``Answer:`` lines → ``re.findall`` returns the list and