Files
OpenJarvis/tests/evals/test_pinchbench_grading.py
T
1d24c64f11 fix(evals): PinchBench harness fixes — scores from 26% to 84% (#124)
* fix(evals): PinchBench harness fixes — scores from 26% to 84%

Multiple infrastructure bugs prevented PinchBench from producing
accurate scores. This commit fixes the eval harness so model scores
match the official leaderboard (Qwen3.5-397B: 26% → 84%, GPT-5.4:
5% → 58%).

Key fixes:

- EvalRunner: wrap generation in PinchBenchTaskEnv context so workspace
  files persist through grading (was deleting before scorer ran)
- EvalRunner: detect task_env datasets and force sequential processing
  (CWD changes aren't thread-safe)
- EvalRunner: fix episode_mode auto-detection to check for real
  iter_episodes() override instead of hasattr() (always True)
- Scorer: use "params" field in transcripts to match PinchBench grade()
  functions (was "arguments")
- Scorer: add None guard in _trace_to_transcript for tool_calls
- Scorer: capture final assistant text response in transcript so
  text-only tasks (like sanity check) can be graded
- Scorer: add _tool_results_to_transcript() helper for EvalRunner path
- native_openhands: add native function-calling support (tools=
  parameter) with text-based fallback, matching monitor_operative
- Tools: add Python fallbacks for file_read, file_write, shell_exec,
  calculator, think, http_request when openjarvis_rust unavailable
- Security: add Python fallback for is_sensitive_file()
- Config: add "pinchbench" to KNOWN_BENCHMARKS
- Add PinchBench eval configs for Qwen3.5-397B and GPT-5.4

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(evals): fix hybrid grading crash when grading_weights is None

The 4 tasks with grading_type=hybrid (task_10, task_13, task_16_market,
task_22) crashed because grading_weights was explicitly None in task
metadata. dict.get("key", default) returns None (not the default) when
the key exists with value None. Use `or` to coalesce None to defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(evals): handle MagicMock datasets in runner type checks

The iter_episodes and create_task_env type identity checks fail with
AttributeError when the dataset is a MagicMock (used in tracker tests).
Wrap in try/except to default to False for non-DatasetProvider objects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 21:16:57 -07:00

160 lines
5.1 KiB
Python

"""Tests for PinchBench grading functions."""
from openjarvis.evals.core.types import EvalRecord
from openjarvis.evals.scorers.pinchbench import (
_grade_automated,
_parse_judge_response,
_summarize_transcript,
grade_pinchbench_task,
)
def _make_record(**meta_overrides) -> EvalRecord:
meta = {
"grading_type": "automated",
"automated_checks": None,
"llm_judge_rubric": None,
"grading_weights": None,
}
meta.update(meta_overrides)
return EvalRecord(
record_id="test_task",
problem="Do the thing",
reference="Expected behavior",
category="test",
subject="Test Task",
metadata=meta,
)
class TestGradeAutomated:
def test_simple_pass(self, tmp_path):
code = '''
def grade(transcript, workspace_path):
from pathlib import Path
f = Path(workspace_path) / "output.txt"
return {"file_exists": 1.0 if f.exists() else 0.0}
'''
(tmp_path / "output.txt").write_text("hello")
record = _make_record(automated_checks=code)
result = _grade_automated(record, [], str(tmp_path))
assert result["score"] == 1.0
assert result["breakdown"]["file_exists"] == 1.0
def test_simple_fail(self, tmp_path):
code = '''
def grade(transcript, workspace_path):
from pathlib import Path
f = Path(workspace_path) / "output.txt"
return {"file_exists": 1.0 if f.exists() else 0.0}
'''
record = _make_record(automated_checks=code)
result = _grade_automated(record, [], str(tmp_path))
assert result["score"] == 0.0
def test_transcript_inspection(self, tmp_path):
code = '''
def grade(transcript, workspace_path):
used_read = False
for entry in transcript:
if entry.get("type") == "message":
msg = entry.get("message", {})
if msg.get("role") == "assistant":
for item in msg.get("content", []):
name = item.get("name")
if item.get("type") == "toolCall" and name == "read_file":
used_read = True
return {"used_read_file": 1.0 if used_read else 0.0}
'''
transcript = [{
"type": "message",
"message": {
"role": "assistant",
"content": [
{
"type": "toolCall",
"name": "read_file",
"params": {"path": "a.txt"},
}
],
},
}]
record = _make_record(automated_checks=code)
result = _grade_automated(record, transcript, str(tmp_path))
assert result["score"] == 1.0
def test_no_checks_returns_zero(self, tmp_path):
record = _make_record(automated_checks=None)
result = _grade_automated(record, [], str(tmp_path))
assert result["score"] == 0.0
class TestParseJudgeResponse:
def test_json_code_block(self):
raw = (
'```json\n{"scores": {"quality": 0.8}, "total": 0.8, "notes": "good"}\n```'
)
parsed = _parse_judge_response(raw)
assert parsed["total"] == 0.8
assert parsed["scores"]["quality"] == 0.8
def test_bare_json(self):
raw = 'The agent did well. {"scores": {"a": 0.9}, "total": 0.9, "notes": ""}'
parsed = _parse_judge_response(raw)
assert parsed["total"] == 0.9
def test_regex_fallback(self):
raw = "The agent performed reasonably. Overall score: 0.65"
parsed = _parse_judge_response(raw)
assert parsed["total"] == 0.65
def test_empty_response(self):
parsed = _parse_judge_response("")
assert parsed["total"] == 0.0
class TestSummarizeTranscript:
def test_tool_call_and_result(self):
transcript = [
{
"type": "message",
"message": {
"role": "assistant",
"content": [
{
"type": "toolCall",
"name": "read_file",
"params": {"path": "a.txt"},
}
],
},
},
{
"type": "message",
"message": {
"role": "toolResult",
"content": [{"text": "file contents"}],
},
},
]
summary = _summarize_transcript(transcript)
assert 'Tool: read_file({"path": "a.txt"})' in summary
assert "Result:" in summary
class TestGradeRouter:
def test_routes_automated(self, tmp_path):
code = 'def grade(t, w): return {"ok": 1.0}'
record = _make_record(grading_type="automated", automated_checks=code)
result = grade_pinchbench_task(
record=record, transcript=[], workspace_path=str(tmp_path)
)
assert result["score"] == 1.0
def test_unknown_type(self, tmp_path):
record = _make_record(grading_type="unknown")
result = grade_pinchbench_task(
record=record, transcript=[], workspace_path=str(tmp_path)
)
assert result["score"] == 0.0