Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions tests/utils/reward_score/test_gsm8k_on_cpu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright 2024 Bytedance Ltd. and/or its affiliates
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import pytest

from verl.utils.reward_score import gsm8k


@pytest.mark.parametrize(
"solution_str, expected",
[
("The answer is 42", "42"),
("first 7 then 13", "13"),
("negative -5 here", "-5"),
# Only invalid tokens (a stray period): there is no real number, so the
# extraction must return None rather than the invalid "." token.
("The result is just a period .", None),
("no digits at all", None),
],
)
def test_extract_solution_flexible(solution_str, expected):
assert gsm8k.extract_solution(solution_str, method="flexible") == expected


def test_flexible_no_valid_number_scores_zero():
# A stray "." used to be extracted as the answer, so a non-zero format_score
# leaked to outputs that contain no real number. With no valid number the
# answer is None and the score is 0.
assert gsm8k.compute_score("just a period .", "42", method="flexible", format_score=0.1) == 0
5 changes: 3 additions & 2 deletions verl/utils/reward_score/gsm8k.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ def extract_solution(solution_str, method="strict"):
else:
invalid_str = ["", "."]
# find the last number that is not '.'
for final_answer in reversed(answer):
if final_answer not in invalid_str:
for candidate in reversed(answer):
if candidate not in invalid_str:
final_answer = candidate
Comment on lines +46 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate candidates as numbers before assigning final_answer.

The regex at Line 38 also matches punctuation-only strings such as "..", ",", and "-.". The current condition rejects only exact "" and ".", so the loop can still return an invalid token when no real number exists. Validate each candidate before breaking.

Proposed fix
-            invalid_str = ["", "."]
-            # find the last number that is not '.'
+            # find the last parseable number
             for candidate in reversed(answer):
-                if candidate not in invalid_str:
-                    final_answer = candidate
-                    break
+                try:
+                    float(candidate.replace(",", ""))
+                except ValueError:
+                    continue
+                final_answer = candidate
+                break
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@verl/utils/reward_score/gsm8k.py` around lines 46 - 48, Update the
candidate-selection loop around final_answer to accept a candidate only when it
represents a valid number, rather than checking only invalid_str membership;
preserve the reversed(answer) search and stop assigning once the first valid
numeric candidate is found, leaving final_answer unset or at its existing
default when none qualifies.

break
return final_answer

Expand Down