mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-30 19:02:16 +00:00
* 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>
160 lines
5.1 KiB
Python
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
|