swe scoring: official swebench harness (es-identical path), backend hook

Both swe variants now score through make_test_spec -> container ->
apply-patch -> official eval_script -> get_eval_report -- the exact
pipeline es's eval_instance drives (swebench==4.1.0 installed). Our
in-container protocol stays as fallback when the package is absent.

env_reward gains a recipe-level 'backend' delegation hook (tau2's
reward_info reader and the swe official scorer both ride it); the
generic bfcl call-sequence comparison remains the default.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sora 2026-09-18 02:40:58 +00:00
parent f662006517
commit de847206e5
2 changed files with 127 additions and 4 deletions

View File

@ -247,6 +247,126 @@ exit $FAIL
return {'patch.diff': pred or '', 'run.sh': script}
def _official_swebench_eval(instance_meta: dict, patch: str, timeout: int = 1800):
"""Score ONE instance via the OFFICIAL swebench harness -- the exact
code path es uses (make_test_spec -> build_container -> apply patch ->
eval script -> get_eval_report). Requires pip install swebench."""
from pathlib import Path
import tempfile
from swebench.harness.grading import get_eval_report
from swebench.harness.test_spec.test_spec import make_test_spec
test_spec = make_test_spec(instance_meta, namespace='swebench',
arch='x86_64')
log_dir = Path(tempfile.mkdtemp(prefix='eh-swe-eval-')) / test_spec.instance_id
log_dir.mkdir(parents=True)
from docker.client import DockerClient
client = DockerClient.from_env()
# official image must exist locally (pull handled by our preflight)
image_name = test_spec.instance_image_key
container = client.containers.run(
image_name, command='tail -f /dev/null', detach=True,
name=f'eh-swe-official-{test_spec.instance_id[:40]}')
try:
patch_file = log_dir / 'patch.diff'
patch_file.write_text(patch or '')
container.exec_run(f'cp {patch_file} /tmp/patch.diff' if False
else 'mkdir -p /tmp', workdir='/testbed')
# copy patch in
import tarfile
import io as _io
with tempfile.NamedTemporaryFile(suffix='.tar') as tf:
with tarfile.open(tf.name, 'w') as tar:
info = tarfile.TarInfo('patch.diff')
data = (patch or '').encode()
info.size = len(data)
tar.addfile(info, _io.BytesIO(data))
with open(tf.name, 'rb') as f:
container.put_archive('/tmp', f.read())
applied = False
for cmd in ('git apply -v /tmp/patch.diff',
'git apply --reject /tmp/patch.diff',
'patch -p1 -i /tmp/patch.diff'):
r = container.exec_run(cmd, workdir='/testbed')
if r.exit_code == 0:
applied = True
break
if not applied:
return {'resolved': 0.0, 'error': 'patch_apply_failed'}
eval_file = log_dir / 'eval.sh'
eval_file.write_text(test_spec.eval_script)
with tempfile.NamedTemporaryFile(suffix='.tar') as tf:
with tarfile.open(tf.name, 'w') as tar:
info = tarfile.TarInfo('eval.sh')
data = test_spec.eval_script.encode()
info.size = len(data)
tar.addfile(info, _io.BytesIO(data))
with open(tf.name, 'rb') as f:
container.put_archive('/tmp', f.read())
container.exec_run('chmod +x /tmp/eval.sh')
r = container.exec_run('/bin/bash /tmp/eval.sh', workdir='/testbed')
# exec_run without demux gives combined output in .output
test_output = r.output.decode('utf-8', 'replace')
(log_dir / 'test_output.txt').write_text(test_output)
report = get_eval_report(
test_spec=test_spec,
prediction={'model_patch': patch, 'instance_id': test_spec.instance_id},
test_log_path=log_dir / 'test_output.txt',
include_tests_status=True,
)
return {'resolved': float(report.get('resolved', 0.0)),
'report': {k: v for k, v in report.items()
if isinstance(v, (int, float, str, bool))}}
finally:
try:
container.remove(force=True)
except Exception:
pass
def _swe_official_reward(pred, target, sample, ctx):
"""Agentic + single-turn shared scorer: official harness when the
swebench package is importable, our in-container protocol otherwise."""
es = ctx.params.get('env_state') or {}
patch = es.get('patch') or pred or ''
if not patch or '(no patch produced)' in patch:
return ({'acc': 0.0}, {'acc': {'error': 'no patch'}})
md = dict(sample.metadata or {})
# official make_test_spec needs the raw instance fields
for k in ('FAIL_TO_PASS', 'PASS_TO_PASS'):
v = md.get(k)
if isinstance(v, str):
try:
md[k] = _json.loads(v)
except Exception:
pass
md.setdefault('repo', str(md.get('repo', '')))
md.setdefault('version', str(md.get('version', '')))
try:
from swebench.harness.test_spec.test_spec import make_test_spec # noqa
have = True
except ImportError:
have = False
if have:
try:
res = _official_swebench_eval(md, patch)
return ({'acc': res.get('resolved', 0.0)},
{'acc': {'mode': 'official_swebench',
'resolved': res.get('resolved'),
'error': res.get('error', ''),
'turns_used': es.get('turns_used')}})
except Exception as e:
return ({'acc': 0.0}, {'acc': {'mode': 'official_swebench',
'error': f'{type(e).__name__}: {str(e)[:150]}'}})
# fallback: our in-container protocol (no swebench package)
return _swe_agentic_reward(pred, target, sample, ctx)
@register_eval('swe_bench_verified')
def swe_bench_verified():
return EvalRecipe(
@ -303,10 +423,8 @@ def swe_bench_verified_agentic():
return EvalRecipe(
name='swe_bench_verified_agentic',
extract='identity',
scorers={'resolved': {'name': 'execution',
'harness': _swe_agentic_harness,
'entry': 'run.sh', 'sandbox': 'docker',
'timeout_s': 1800}},
scorers={'resolved': {'name': 'env_reward',
'backend': _swe_official_reward}},
exec_workers=2,
description='SWE-bench Verified AGENTIC (mini-swe-agent protocol): '
'multi-turn bash agent explores /testbed in the '

View File

@ -363,6 +363,11 @@ def env_reward(pred: str, target, sample: Sample, ctx: ScoreContext):
tau2/swe get dedicated envs later; this scorer stays the entry point.
"""
env_state = ctx.params.get('env_state') or {}
# recipe-provided backend (tau2 reward_info / swe official harness):
# delegated BEFORE the generic bfcl call-sequence comparison
backend = ctx.params.get('backend')
if callable(backend):
return backend(pred, target, sample, ctx)
if not env_state:
raise LayerNotReady(
'env_reward needs env_state from an agent trajectory '