From 49c7c119c2ea49953bd1ad835acd03f33b10f44d Mon Sep 17 00:00:00 2001 From: yahocan Date: Wed, 28 Jan 2026 11:18:00 +0300 Subject: [PATCH 1/2] fix: use greedy matching in FINAL pattern to handle nested parentheses --- rlm/utils/parsing.py | 3 ++- tests/test_parsing.py | 39 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/rlm/utils/parsing.py b/rlm/utils/parsing.py index e4c235068..f0bdd40dc 100644 --- a/rlm/utils/parsing.py +++ b/rlm/utils/parsing.py @@ -54,7 +54,8 @@ def find_final_answer(text: str, environment: "BaseEnv | None" = None) -> str | return None # Check for FINAL pattern - must be at start of line - final_pattern = r"^\s*FINAL\((.*?)\)" + # Use greedy matching to capture content with nested parentheses + final_pattern = r"^\s*FINAL\((.*)\)\s*$" match = re.search(final_pattern, text, re.MULTILINE | re.DOTALL) if match: return match.group(1).strip() diff --git a/tests/test_parsing.py b/tests/test_parsing.py index a42065aa1..91d5347a7 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -90,7 +90,9 @@ def test_final_var_answer(self): text = "Check the variable:\nFINAL_VAR(result)" # Create a mock environment that returns the variable value mock_env = Mock() - mock_env.execute_code.return_value = REPLResult(stdout="42", stderr="", locals={}) + mock_env.execute_code.return_value = REPLResult( + stdout="42", stderr="", locals={} + ) result = find_final_answer(text, environment=mock_env) assert result == "42" # Verify execute_code was called with the correct code @@ -128,6 +130,33 @@ def test_final_with_whitespace(self): result = find_final_answer(text) assert result == "answer with spaces" + def test_final_with_nested_parentheses_greedy_matching(self): + """Test that greedy matching captures content with nested parentheses correctly. + Greedy matching (.*) matches to the last closing parenthesis, correctly handling + nested parentheses in FINAL() content. Non-greedy (.*?) would incorrectly stop + at the first closing parenthesis, breaking functions, tuples, and nested structures. + """ + # Function call with nested parentheses + text = "FINAL(func(arg1, arg2))" + result = find_final_answer(text) + assert result == "func(arg1, arg2)" + + # List and tuple with multiple closing parentheses + text = "FINAL([1, 2, 3], (4, 5))" + result = find_final_answer(text) + assert result == "[1, 2, 3], (4, 5)" + + # Complex nested dictionary + text = "FINAL({'key': 'value', 'nested': {'a': 1, 'b': 2}})" + result = find_final_answer(text) + assert "'key': 'value'" in result + assert "'nested':" in result + + # Multiple function calls with nested parentheses + text = "FINAL(calculate(10, 20) + process(data))" + result = find_final_answer(text) + assert result == "calculate(10, 20) + process(data)" + def test_final_and_final_var_parsing(self): """Test that both FINAL and FINAL_VAR patterns are parsed correctly.""" # Test FINAL with various content types @@ -170,7 +199,9 @@ def test_final_and_final_var_parsing(self): def test_final_var_takes_precedence_over_final(self): """Test that FINAL_VAR is checked first and takes precedence over FINAL.""" mock_env = Mock() - mock_env.execute_code.return_value = REPLResult(stdout="var_value", stderr="", locals={}) + mock_env.execute_code.return_value = REPLResult( + stdout="var_value", stderr="", locals={} + ) # If both appear, FINAL_VAR should be found first (checked first in the function) text = "FINAL_VAR(result)\nFINAL(direct_answer)" @@ -213,7 +244,9 @@ def test_final_var_retrieves_actual_variables_from_environment(self): # Test retrieving list variable text = "FINAL_VAR(answer)" result = find_final_answer(text, environment=env) - assert result == "[1, 2, 3, 4, 5]", f"Expected '[1, 2, 3, 4, 5]', got '{result}'" + assert ( + result == "[1, 2, 3, 4, 5]" + ), f"Expected '[1, 2, 3, 4, 5]', got '{result}'" # Test retrieving computed variable text = "FINAL_VAR(computed)" From 59cc9887e58f3d98e2774eec1a0826e614aed554 Mon Sep 17 00:00:00 2001 From: Alex Zhang Date: Thu, 29 Jan 2026 11:21:58 -0500 Subject: [PATCH 2/2] parsing on test --- tests/test_parsing.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 91d5347a7..0ff910faa 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -90,9 +90,7 @@ def test_final_var_answer(self): text = "Check the variable:\nFINAL_VAR(result)" # Create a mock environment that returns the variable value mock_env = Mock() - mock_env.execute_code.return_value = REPLResult( - stdout="42", stderr="", locals={} - ) + mock_env.execute_code.return_value = REPLResult(stdout="42", stderr="", locals={}) result = find_final_answer(text, environment=mock_env) assert result == "42" # Verify execute_code was called with the correct code @@ -199,9 +197,7 @@ def test_final_and_final_var_parsing(self): def test_final_var_takes_precedence_over_final(self): """Test that FINAL_VAR is checked first and takes precedence over FINAL.""" mock_env = Mock() - mock_env.execute_code.return_value = REPLResult( - stdout="var_value", stderr="", locals={} - ) + mock_env.execute_code.return_value = REPLResult(stdout="var_value", stderr="", locals={}) # If both appear, FINAL_VAR should be found first (checked first in the function) text = "FINAL_VAR(result)\nFINAL(direct_answer)" @@ -244,9 +240,7 @@ def test_final_var_retrieves_actual_variables_from_environment(self): # Test retrieving list variable text = "FINAL_VAR(answer)" result = find_final_answer(text, environment=env) - assert ( - result == "[1, 2, 3, 4, 5]" - ), f"Expected '[1, 2, 3, 4, 5]', got '{result}'" + assert result == "[1, 2, 3, 4, 5]", f"Expected '[1, 2, 3, 4, 5]', got '{result}'" # Test retrieving computed variable text = "FINAL_VAR(computed)"