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
8 changes: 7 additions & 1 deletion rlm/utils/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (`<invoke name="repl">`), which
Claude models often emit instead of fences. Returns an empty list if nothing is found.
"""
pattern = r"```repl\s*\n(.*?)\n```"
results = []
Expand All @@ -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'<invoke\s+name="repl">\s*<parameter\s+name="code">(.*?)</parameter>'
for match in re.finditer(xml_pattern, text, re.DOTALL):
results.append(match.group(1).strip())

return results


Expand Down
46 changes: 46 additions & 0 deletions tests/test_parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

<function_calls>
<invoke name="repl">
<parameter name="code">
print("Length of context:", len(context))
print(context[:500])
</parameter>
</invoke>
</function_calls>"""
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 = """<function_calls>
<invoke name="repl">
<parameter name="code">a = 1</parameter>
</invoke>
<invoke name="repl">
<parameter name="code">
b = a + 1
</parameter>
</invoke>
</function_calls>"""
blocks = find_code_blocks(text)
assert blocks == ["a = 1", "b = a + 1"]

def test_xml_tool_call_for_other_tool_ignored(self):
text = """<function_calls>
<invoke name="search">
<parameter name="code">x = 1</parameter>
</invoke>
</function_calls>"""
assert find_code_blocks(text) == []

def test_fenced_blocks_take_precedence_over_xml(self):
text = """```repl
y = 2
```
<invoke name="repl">
<parameter name="code">x = 1</parameter>
</invoke>"""
assert find_code_blocks(text) == ["y = 2"]


class TestAnswerDictFinalAnswer:
"""Tests for the ``answer`` dict completion signal surfaced via REPLResult.final_answer."""
Expand Down