sora ad61a2c44e Fix DeepSWE Pier routing and Terminal-Bench context/apt setup.
Keep generation max_tokens from being used as terminus-2 context, rewrite Debian/Ubuntu .sources to the Tsinghua mirror, and skip injecting truncation_tokens into DeepSWE extra_params.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-08 07:32:07 +00:00

429 lines
16 KiB
Python

import json
import os
import sys
import types
from pathlib import Path
from typing import Any, Dict, Optional
from urllib.request import pathname2url
import pytest
from evalscope.api.dataset import Sample
from evalscope.api.evaluator import TaskState
from evalscope.api.registry import get_benchmark
from evalscope.benchmarks.deep_swe.deep_swe_adapter import (
DEFAULT_MODELSCOPE_DATASET_ID,
DeepSWEAdapter,
resolve_pier_api_endpoint,
resolve_pier_model_name,
)
from evalscope.benchmarks.deep_swe.utils import artifact_path, build_score_metadata, parse_timestamp
from evalscope.config import TaskConfig
class MockModel:
name = 'mock-model'
def make_adapter(tmp_path: Path, dataset_args: Optional[Dict[str, Any]] = None, **extra_params: Any) -> DeepSWEAdapter:
dataset_args = dataset_args or {}
cfg = TaskConfig(
datasets=['deep_swe'],
dataset_args={'deep_swe': {
**dataset_args,
'extra_params': extra_params,
}},
work_dir=str(tmp_path / 'outputs'),
)
adapter = get_benchmark('deep_swe', cfg)
assert isinstance(adapter, DeepSWEAdapter)
return adapter
def write_snapshot(tmp_path: Path, tasks: Optional[list] = None) -> Path:
snapshot = tmp_path / 'snapshot'
tasks_dir = snapshot / 'tasks'
tasks_dir.mkdir(parents=True)
tasks = tasks or [
{'task_id': 'task-a', 'language': 'python', 'category': 'bugfix', 'display_description': 'Fix A'},
{'task_id': 'task-b', 'language': 'go', 'category': 'feature', 'display_description': 'Fix B'},
{'task_id': 'task-c', 'language': 'python', 'category': 'bugfix', 'display_description': 'Fix C'},
]
(tasks_dir / 'manifest.json').write_text(json.dumps({'tasks': tasks}), encoding='utf-8')
for task in tasks:
task_path = tasks_dir / task['task_id']
task_path.mkdir()
(task_path / 'task.toml').write_text('id = "task"\n', encoding='utf-8')
(task_path / 'instruction.md').write_text(f'Instruction for {task["task_id"]}', encoding='utf-8')
return snapshot
def test_dataset_args_override_dataset_id_and_local_path(tmp_path: Path) -> None:
assert make_adapter(tmp_path).dataset_id == DEFAULT_MODELSCOPE_DATASET_ID
assert make_adapter(tmp_path, dataset_args={'dataset_id': 'custom/deep-swe'}).dataset_id == 'custom/deep-swe'
assert make_adapter(tmp_path, dataset_args={'local_path': str(tmp_path / 'local')}).dataset_id == str(tmp_path / 'local')
def test_download_snapshot_uses_deep_swe_helper(monkeypatch: Any, tmp_path: Path) -> None:
captured: Dict[str, Any] = {}
snapshot = tmp_path / 'snapshot'
def fake_download_snapshot(**kwargs: Any) -> Path:
captured.update(kwargs)
return snapshot
adapter = make_adapter(tmp_path, dataset_args={'dataset_id': 'custom/deep-swe'})
monkeypatch.setattr(
'evalscope.benchmarks.deep_swe.deep_swe_adapter.download_snapshot',
fake_download_snapshot,
)
assert adapter._download_snapshot() == snapshot
assert captured['data_id_or_path'] == 'custom/deep-swe'
assert captured['data_source'] == adapter.dataset_hub
assert Path(captured['cache_dir']).name == 'snapshots'
def test_load_filters_tasks_and_applies_limit_and_seed(monkeypatch: Any, tmp_path: Path) -> None:
snapshot = write_snapshot(tmp_path)
adapter = make_adapter(tmp_path, languages=['python'], categories=['bugfix'], sample_seed=3)
adapter._task_config.limit = 1
monkeypatch.setattr(adapter, '_download_snapshot', lambda: snapshot)
dataset, _ = adapter.load()
assert len(dataset['test']) == 1
assert dataset['test'][0].metadata['language'] == 'python'
assert dataset['test'][0].metadata['category'] == 'bugfix'
def test_load_dataset_post_processes_sample_prompt(monkeypatch: Any, tmp_path: Path) -> None:
snapshot = write_snapshot(tmp_path)
adapter = make_adapter(tmp_path, task_ids=['task-a'])
monkeypatch.setattr(adapter, '_download_snapshot', lambda: snapshot)
dataset = adapter.load_dataset()
assert len(dataset['test']) == 1
assert dataset['test'][0].input[-1].text == 'Instruction for task-a'
def test_load_validates_snapshot_layout(monkeypatch: Any, tmp_path: Path) -> None:
adapter = make_adapter(tmp_path)
snapshot = tmp_path / 'missing-manifest'
snapshot.mkdir()
monkeypatch.setattr(adapter, '_download_snapshot', lambda: snapshot)
with pytest.raises(FileNotFoundError, match='manifest.json'):
adapter.load()
def test_load_validates_task_toml(monkeypatch: Any, tmp_path: Path) -> None:
snapshot = write_snapshot(tmp_path, tasks=[{'task_id': 'task-a'}])
(snapshot / 'tasks' / 'task-a' / 'task.toml').unlink()
adapter = make_adapter(tmp_path)
monkeypatch.setattr(adapter, '_download_snapshot', lambda: snapshot)
with pytest.raises(FileNotFoundError, match='task.toml'):
adapter.load()
def test_build_score_metadata_collects_reward_and_artifacts(tmp_path: Path) -> None:
trial_dir = tmp_path / 'trial'
(trial_dir / 'verifier').mkdir(parents=True)
(trial_dir / 'agent').mkdir()
(trial_dir / 'artifacts').mkdir()
(trial_dir / 'verifier' / 'reward.json').write_text(
json.dumps({'reward': 1, 'partial': 0.5, 'f2p': 2, 'p2p': 3, 'apply_failed': False}),
encoding='utf-8',
)
(trial_dir / 'verifier' / 'reward.txt').write_text('1\n', encoding='utf-8')
metadata = build_score_metadata({
'job_result_path': str(tmp_path / 'job'),
'trial_results': [{
'trial_uri': f'file://{trial_dir}',
'verifier_result': {
'rewards': {
'reward': 0
}
},
}],
})
assert metadata['reward'] == 1
assert metadata['partial'] == 0.5
assert metadata['f2p'] == 2
assert metadata['p2p'] == 3
assert metadata['apply_failed'] is False
assert metadata['pier_job_result_path'] == str(tmp_path / 'job')
assert metadata['verifier_reward_json_path'] == str(trial_dir / 'verifier' / 'reward.json')
assert metadata['trajectory_path'] == str(trial_dir / 'agent' / 'trajectory.json')
def test_on_inference_uses_mock_pier_result_and_scores_acc(monkeypatch: Any, tmp_path: Path) -> None:
adapter = make_adapter(tmp_path)
sample = Sample(input='', metadata={'task_id': 'task-a', 'task_path': str(tmp_path / 'task-a')})
result = {
'job_result_path': str(tmp_path / 'job'),
'trial_results': [{
'trial_uri': f'file://{tmp_path / "trial"}',
'verifier_result': {
'rewards': {
'reward': 1
}
},
}],
}
monkeypatch.setattr(adapter, '_run_pier_job', lambda model, sample: result)
inference = adapter._on_inference(MockModel(), sample)
task_state = TaskState(model='mock-model', sample=sample, output=inference.output)
score = adapter.match_score('', '', '', task_state)
assert inference.output.completion == f'file://{tmp_path / "trial"}'
assert score.value == {'acc': 1.0}
assert score.metadata['reward'] == 1
def test_pier_exception_result_raises() -> None:
with pytest.raises(RuntimeError, match='RewardFileNotFoundError'):
DeepSWEAdapter._raise_for_pier_failures({
'trial_results': [{
'exception_info': {
'exception_type': 'RewardFileNotFoundError',
'message': 'reward.txt missing',
}
}]
})
def test_missing_pier_reward_without_exception_raises() -> None:
with pytest.raises(RuntimeError, match='did not return a reward or exception info'):
DeepSWEAdapter._raise_for_pier_failures({
'trial_results': [{
'verifier_result': {
'rewards': {}
}
}]
})
def test_pier_exception_with_verifier_reward_is_scored(tmp_path: Path) -> None:
result = {
'trial_results': [{
'exception_info': {
'exception_type': 'NonZeroAgentExitCodeError',
},
'verifier_result': {
'rewards': {
'reward': 0,
'partial': 0.25,
}
},
}]
}
DeepSWEAdapter._raise_for_pier_failures(result)
metadata = build_score_metadata(result)
assert metadata['reward'] == 0
assert metadata['partial'] == 0.25
assert metadata['agent_execution_failed'] is True
def test_artifact_path_decodes_file_uri(tmp_path: Path) -> None:
trial_dir = tmp_path / 'trial dir'
result = {'trial_results': [{'trial_uri': f'file://{pathname2url(str(trial_dir))}'}]}
assert artifact_path(result, 'verifier/reward.json') == trial_dir / 'verifier' / 'reward.json'
def test_parse_timestamp_handles_z_suffix() -> None:
assert parse_timestamp('2026-01-01T00:00:00Z') == parse_timestamp('2026-01-01T00:00:00+00:00')
def install_fake_pier(monkeypatch: Any, captured: Dict[str, Any], result: Dict[str, Any]) -> None:
class FakeConfig:
def __init__(self, **kwargs: Any) -> None:
self.__dict__.update(kwargs)
class FakeJob:
def __init__(self, config: Any) -> None:
self.config = config
@classmethod
async def create(cls, config: Any) -> 'FakeJob':
captured['config'] = config
return cls(config)
async def run(self) -> Any:
class FakeResult:
@staticmethod
def model_dump(mode: str = 'json') -> Dict[str, Any]:
return result
return FakeResult()
pier = types.ModuleType('pier')
pier_job = types.ModuleType('pier.job')
pier_job.Job = FakeJob
pier_models = types.ModuleType('pier.models')
pier_models_job = types.ModuleType('pier.models.job')
pier_models_job_config = types.ModuleType('pier.models.job.config')
pier_models_job_config.JobConfig = FakeConfig
pier_models_trial = types.ModuleType('pier.models.trial')
pier_models_trial_config = types.ModuleType('pier.models.trial.config')
pier_models_trial_config.AgentConfig = FakeConfig
pier_models_trial_config.EnvironmentConfig = FakeConfig
pier_models_trial_config.TaskConfig = FakeConfig
pier_models_trial_config.VerifierConfig = FakeConfig
for name, module in {
'pier': pier,
'pier.job': pier_job,
'pier.models': pier_models,
'pier.models.job': pier_models_job,
'pier.models.job.config': pier_models_job_config,
'pier.models.trial': pier_models_trial,
'pier.models.trial.config': pier_models_trial_config,
}.items():
monkeypatch.setitem(sys.modules, name, module)
monkeypatch.setattr('evalscope.benchmarks.deep_swe.deep_swe_adapter.check_import', lambda *args, **kwargs: True)
def test_resolve_pier_api_endpoint_from_local_intranet_and_gateway_urls() -> None:
local = resolve_pier_api_endpoint('http://127.0.0.1:8004/v1')
assert local is not None
assert local.base_url == 'http://host.docker.internal:8004/v1'
assert local.allowlist_host == 'host.docker.internal'
assert local.uses_host_gateway is True
intranet = resolve_pier_api_endpoint('http://10.1.2.3:8000/v1')
assert intranet is not None
assert intranet.base_url == 'http://10.1.2.3:8000/v1'
assert intranet.allowlist_host == '10.1.2.3'
assert intranet.uses_host_gateway is False
gateway = resolve_pier_api_endpoint('https://api.vectron.meta-stone.com/v1/chat/completions')
assert gateway is not None
assert gateway.base_url == 'https://api.vectron.meta-stone.com/v1'
assert gateway.allowlist_host == 'api.vectron.meta-stone.com'
assert gateway.uses_host_gateway is False
def test_resolve_pier_model_name_openai_compat_and_native() -> None:
assert resolve_pier_model_name('DeepSeek/DeepSeek-V4-Flash-0731') == (
'openai/DeepSeek/DeepSeek-V4-Flash-0731'
)
assert resolve_pier_model_name('Qwen3-VL-8B-Instruct') == 'openai/Qwen3-VL-8B-Instruct'
assert resolve_pier_model_name('openai/gpt-5.5') == 'openai/gpt-5.5'
assert resolve_pier_model_name(
'anthropic/claude-opus-4-8',
route='native',
) == 'anthropic/claude-opus-4-8'
def test_pier_model_name_uses_openai_compat_by_default(tmp_path: Path) -> None:
class SlashModel:
name = 'DeepSeek/DeepSeek-V4-Flash-0731'
adapter = make_adapter(tmp_path)
assert adapter._pier_model_name(SlashModel()) == 'openai/DeepSeek/DeepSeek-V4-Flash-0731'
native = make_adapter(tmp_path, pier_route='native')
assert native._pier_model_name(SlashModel()) == 'DeepSeek/DeepSeek-V4-Flash-0731'
def test_pier_allowed_hosts_come_from_api_url(tmp_path: Path) -> None:
cfg = TaskConfig(
datasets=['deep_swe'],
api_url='https://api.vectron.meta-stone.com/v1/chat/completions',
api_key='sk-test',
work_dir=str(tmp_path / 'outputs'),
dataset_args={'deep_swe': {'extra_params': {'extra_allowed_hosts': ['10.9.9.9']}}},
)
adapter = get_benchmark('deep_swe', cfg)
assert isinstance(adapter, DeepSWEAdapter)
assert adapter._pier_allowed_hosts() == ['api.vectron.meta-stone.com', '10.9.9.9']
def test_run_pier_job_uses_adhoc_task_source(monkeypatch: Any, tmp_path: Path) -> None:
captured: Dict[str, Any] = {}
result = {
'trial_results': [{
'trial_uri': f'file://{tmp_path / "trial"}',
'verifier_result': {
'rewards': {
'reward': 1
}
},
}]
}
install_fake_pier(monkeypatch, captured, result)
task_path = tmp_path / 'tasks' / 'task-a'
task_path.mkdir(parents=True)
adapter = make_adapter(tmp_path, pier_agent_kwargs={'model_class': 'litellm'})
adapter._task_config.api_url = 'http://10.1.2.3:8000/v1'
sample = Sample(input='', metadata={'task_id': 'task-a', 'task_path': str(task_path)})
result_dict = adapter._run_pier_job(MockModel(), sample)
assert result_dict['job_result_path'].startswith(str(Path(adapter.output_dir) / 'deep_swe_jobs'))
assert captured['config'].tasks[0].path == task_path
assert getattr(captured['config'].tasks[0], 'source', None) is None
assert captured['config'].agents[0].kwargs == {'model_class': 'litellm'}
assert captured['config'].agents[0].model_name == 'openai/mock-model'
assert captured['config'].agents[0].extra_allowed_hosts == ['10.1.2.3']
assert captured['config'].environment.type == 'docker'
assert getattr(captured['config'].environment, 'extra_docker_compose', None) in (None, [])
@pytest.mark.skipif(
os.getenv('EVALSCOPE_DEEP_SWE_E2E') != '1' or not os.getenv('OPENAI_API_KEY'),
reason='Set EVALSCOPE_DEEP_SWE_E2E=1 and OPENAI_API_KEY to run the real Pier DeepSWE smoke test.',
)
def test_deep_swe_real_e2e(tmp_path: Path) -> None:
from evalscope import run_task
pier_agent_kwargs = {
'cost_limit': float(os.getenv('EVALSCOPE_DEEP_SWE_COST_LIMIT', '0.05')),
'model_class': os.getenv('EVALSCOPE_DEEP_SWE_MODEL_CLASS', 'litellm'),
}
step_limit = os.getenv('EVALSCOPE_DEEP_SWE_AGENT_STEP_LIMIT')
if step_limit:
pier_agent_kwargs['config_yaml'] = f'agent:\n step_limit: {int(step_limit)}\n'
result = run_task(
TaskConfig(
model=os.getenv('EVALSCOPE_DEEP_SWE_MODEL', 'qwen-plus'),
eval_type='mock_llm',
datasets=['deep_swe'],
limit=1,
eval_batch_size=1,
work_dir=str(tmp_path / 'outputs'),
no_timestamp=True,
dataset_args={
'deep_swe': {
'extra_params': {
'task_ids': ['abs-module-cache-flags'],
'pier_agent_kwargs': pier_agent_kwargs,
}
}
},
)
)
assert isinstance(result, dict)
assert 'deep_swe' in result