diff --git a/rlm/utils/parsing.py b/rlm/utils/parsing.py index 2a9aa0965..d9ed767aa 100644 --- a/rlm/utils/parsing.py +++ b/rlm/utils/parsing.py @@ -10,7 +10,8 @@ def find_code_blocks(text: str) -> list[str]: """ Find REPL code blocks in text wrapped in triple backticks and return List of content(s). - Returns None if no code blocks are found. + If there are none, fall back to XML tool-use blocks (``), which + Claude models often emit instead of fences. Returns an empty list if nothing is found. """ pattern = r"```repl\s*\n(.*?)\n```" results = [] @@ -19,6 +20,11 @@ def find_code_blocks(text: str) -> list[str]: code_content = match.group(1).strip() results.append(code_content) + if not results: + xml_pattern = r'\s*(.*?)' + for match in re.finditer(xml_pattern, text, re.DOTALL): + results.append(match.group(1).strip()) + return results diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 12e79df09..bfbed9b68 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -74,6 +74,52 @@ def factorial(n): assert "def factorial(n):" in blocks[0] assert "return n * factorial(n - 1)" in blocks[0] + def test_xml_tool_call_block(self): + """Claude models may emit native XML tool-use syntax instead of ```repl``` fences.""" + text = """I'll inspect the context first. + + + + +print("Length of context:", len(context)) +print(context[:500]) + + +""" + blocks = find_code_blocks(text) + assert blocks == ['print("Length of context:", len(context))\nprint(context[:500])'] + + def test_multiple_xml_tool_call_blocks(self): + text = """ + +a = 1 + + + +b = a + 1 + + +""" + blocks = find_code_blocks(text) + assert blocks == ["a = 1", "b = a + 1"] + + def test_xml_tool_call_for_other_tool_ignored(self): + text = """ + +x = 1 + +""" + assert find_code_blocks(text) == [] + + def test_fenced_blocks_take_precedence_over_xml(self): + text = """```repl +y = 2 +``` + +x = 1 +""" + assert find_code_blocks(text) == ["y = 2"] + class TestAnswerDictFinalAnswer: """Tests for the ``answer`` dict completion signal surfaced via REPLResult.final_answer."""