Skip to content

Commit d9fc8a8

Browse files
authored
Merge pull request #19 from spacedock-dev/fix/dab-verify-batch-per-query-isolation
fix(dab): isolate per-query validator failures in verify_batch
2 parents c2dd2c3 + e50dab8 commit d9fc8a8

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

packages/razorback-plugin-dab/src/razorback_plugin_dab/verify/verify_batch.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,15 @@ def emit_reward(
2424
answer = answers.get(key, "") if isinstance(answers, dict) else ""
2525
validate_fn = _load_validate(validators[query_id])
2626
if answer:
27-
is_valid, reason = validate_fn(answer)
27+
# Isolate per-query validator failures: a single validator raising
28+
# (e.g. a validator calling .lower() on a non-string answer) must NOT
29+
# abort grading for the whole dataset — that would write no reward.json
30+
# and silently drop the entire dataset from the run (RewardFileNotFoundError).
31+
# Score the offending query 0 with the error as the reason and continue.
32+
try:
33+
is_valid, reason = validate_fn(answer)
34+
except Exception as exc: # noqa: BLE001 — robustness boundary, any validator error
35+
is_valid, reason = False, f"validator error: {type(exc).__name__}: {exc}"
2836
else:
2937
is_valid, reason = False, "empty answer"
3038
reward = 1.0 if is_valid else 0.0

packages/razorback-plugin-dab/tests/unit/test_verify_batch_reward_shape.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,39 @@ def test_batch_verify_does_not_mask_validator_import_errors(tmp_path: Path) -> N
114114
assert "missing_verifier_dependency" in result.stderr
115115
assert not reward_out.exists()
116116
assert not per_query_out.exists()
117+
118+
119+
def test_batch_verify_isolates_per_query_runtime_validator_error(tmp_path: Path) -> None:
120+
"""A single query's validator raising at call time (e.g. a non-string answer)
121+
must score that query 0 and continue grading the rest — not abort the whole
122+
dataset (which would drop it from the run as a RewardFileNotFoundError)."""
123+
tests_dir = tmp_path / "tests"
124+
tests_dir.mkdir()
125+
shutil.copy2(Path(verify_batch_module.__file__), tests_dir / "verify_batch.py")
126+
# q1 validator crashes on a non-string answer; q2 validator is well-behaved.
127+
(tests_dir / "validate_q1.py").write_text(
128+
"def validate(answer):\n"
129+
" return (answer.lower() == 'x', 'checked')\n"
130+
)
131+
(tests_dir / "validate_q2.py").write_text(
132+
"def validate(answer):\n"
133+
" return (answer == 'ok', 'checked')\n"
134+
)
135+
answers = tmp_path / "answers.json"
136+
answers.write_text(json.dumps({"q1": ["a", "b"], "q2": "ok"})) # q1 is a LIST
137+
reward_out = tmp_path / "reward.json"
138+
per_query_out = tmp_path / "reward_per_query.json"
139+
140+
result = _run_generated_verify_batch(
141+
tests_dir=tests_dir,
142+
answers_path=answers,
143+
reward_out=reward_out,
144+
per_query_out=per_query_out,
145+
)
146+
147+
assert result.returncode == 0, result.stderr
148+
per_query = json.loads(per_query_out.read_text())
149+
assert per_query["q1"]["reward"] == 0.0
150+
assert "validator error" in per_query["q1"]["reason"]
151+
assert per_query["q2"]["reward"] == 1.0 # the good query still graded
152+
assert json.loads(reward_out.read_text()) == {"reward": 0.5}

0 commit comments

Comments
 (0)