Skip to content

Commit 61d0dfe

Browse files
halleriteclaude
andcommitted
fix(v1): the proposer contract tolerates LaTeX escapes
First live multi-agent training run: Qwen3-4B proposers reliably end with a well-formed contract whose problem text carries LaTeX (\( S \), \frac) — off-spec JSON escapes that failed every such episode. One-retry fixup doubles any backslash that isn't a valid JSON escape (valid escapes are consumed whole, so \\ stays itself). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 8586dd7 commit 61d0dfe

2 files changed

Lines changed: 30 additions & 1 deletion

File tree

environments/proposer_solver_v1/proposer_solver_v1/taskset.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,22 @@ class SolveData(vf.TaskData):
5353
"""The proposer's verified answer — the minted task's ground truth."""
5454

5555

56+
def _loads_contract(chunk: str) -> object:
57+
"""``json.loads`` with one retry that doubles any backslash not part of a
58+
valid JSON escape — a math proposer writes LaTeX (``\\(``, ``\\frac``) inside
59+
the contract, which is off-spec JSON but an unambiguous fixup. Valid escapes
60+
are consumed whole so ``\\\\`` stays itself."""
61+
try:
62+
return json.loads(chunk)
63+
except json.JSONDecodeError:
64+
fixed = re.sub(
65+
r'\\(?:[\\"/bfnrtu]|u[0-9a-fA-F]{4})|\\',
66+
lambda m: m.group(0) if len(m.group(0)) > 1 else "\\\\",
67+
chunk,
68+
)
69+
return json.loads(fixed)
70+
71+
5672
class SolveTask(vf.Task[SolveData]):
5773
@classmethod
5874
def from_trace(cls, proposer: vf.Trace) -> "SolveTask":
@@ -69,7 +85,7 @@ def from_trace(cls, proposer: vf.Trace) -> "SolveTask":
6985
if not (chunk.startswith("{") and chunk.endswith("}")):
7086
continue
7187
try:
72-
parsed = json.loads(chunk)
88+
parsed = _loads_contract(chunk)
7389
except json.JSONDecodeError:
7490
continue
7591
if isinstance(parsed, dict) and {"problem", "answer"} <= parsed.keys():

tests/v1/test_bundled_envs.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,3 +351,16 @@ def score(hits: int, n: int = 4) -> Trace:
351351
assert score(2).rewards["learnability"] == 1.0
352352
assert score(4).rewards["learnability"] == 0.0
353353
assert score(1).metrics["solve_rate"] == 0.25
354+
355+
356+
def test_solve_task_contract_tolerates_latex_escapes():
357+
"""Math proposers write LaTeX inside the JSON contract; the parse doubles the
358+
off-spec backslashes instead of failing the episode."""
359+
from proposer_solver_v1.taskset import SolveTask
360+
361+
trace = _reply_trace(
362+
'Here it is:\n{"problem": "Let \\( S \\) be a set with \\\\leq 5 elements.", "answer": 3}'
363+
)
364+
task = SolveTask.from_trace(trace)
365+
assert task.data.answer == "3"
366+
assert "\\( S \\)" in task.data.prompt

0 commit comments

Comments
 (0)