Keep K3 suite selection and report-schema scoring in bash, merge K3/vision dataset_args into dpv4 yamls, and pin EvalScope at 735d920ee911 with local patches. Co-authored-by: Cursor <cursoragent@cursor.com>
156 lines
4.9 KiB
Python
156 lines
4.9 KiB
Python
"""Judge configuration is typed before the Native judge subsystem sees it."""
|
|
import argparse
|
|
|
|
import pytest
|
|
|
|
from evalscope.arguments import add_argument
|
|
from evalscope.config import JudgeConfig, TaskConfig
|
|
from evalscope.metrics.judge.llm_judge import LLMJudge
|
|
|
|
|
|
def test_typed_config_assigns_unique_model_id_as_judge_id():
|
|
config = JudgeConfig(models=[{'model_id': 'judge-a'}])
|
|
|
|
assert config.models[0].judge_id == 'judge-a'
|
|
|
|
|
|
def test_typed_config_accepts_a_single_model_mapping():
|
|
config = JudgeConfig(models={'model_id': 'judge-a'})
|
|
|
|
assert [model.model_id for model in config.models] == ['judge-a']
|
|
|
|
|
|
def test_duplicate_model_id_requires_explicit_judge_ids():
|
|
with pytest.raises(ValueError, match='needs judge_id'):
|
|
JudgeConfig(models=[{'model_id': 'same'}, {'model_id': 'same'}])
|
|
|
|
|
|
def test_duplicate_model_id_is_allowed_with_distinct_judge_ids():
|
|
config = JudgeConfig(models=[{'model_id': 'same', 'judge_id': 'a'}, {'model_id': 'same', 'judge_id': 'b'}])
|
|
|
|
assert [model.judge_id for model in config.models] == ['a', 'b']
|
|
|
|
|
|
def test_new_config_exposes_repeats_swap_aggregation_and_quorum():
|
|
config = TaskConfig(
|
|
model='m',
|
|
datasets=['simple_qa'],
|
|
judge={
|
|
'strategy': 'llm',
|
|
'models': [{'model_id': 'judge-a'}],
|
|
'repeats': 2,
|
|
'position_swap': 'on',
|
|
'aggregation': 'median',
|
|
'min_valid_judges': 1,
|
|
},
|
|
)
|
|
|
|
assert config.judge.repeats == 2
|
|
assert config.judge.position_swap == 'on'
|
|
assert config.judge.aggregation == 'median'
|
|
|
|
|
|
def test_task_config_accepts_a_single_judge_model_mapping():
|
|
config = TaskConfig(
|
|
model='m',
|
|
datasets=['simple_qa'],
|
|
judge={'strategy': 'llm', 'models': {'model_id': 'judge-a'}},
|
|
)
|
|
|
|
assert [model.model_id for model in config.judge.models] == ['judge-a']
|
|
|
|
|
|
def test_update_revalidates_judge_config():
|
|
config = TaskConfig(model='m', datasets=['simple_qa'])
|
|
|
|
config.update({'judge': {'strategy': 'llm', 'models': {'model_id': 'judge-a'}}})
|
|
|
|
assert isinstance(config.judge, JudgeConfig)
|
|
assert [model.model_id for model in config.judge.models] == ['judge-a']
|
|
|
|
|
|
def test_cli_parses_typed_judge_config_without_removed_legacy_defaults():
|
|
parser = argparse.ArgumentParser()
|
|
add_argument(parser)
|
|
|
|
args = parser.parse_args([
|
|
'--model', 'm', '--datasets', 'simple_qa', '--judge', '{"strategy": "llm", "models": {"model_id": "judge-a"}}'
|
|
])
|
|
config = TaskConfig.from_args(args)
|
|
|
|
assert config.judge.strategy == 'llm'
|
|
assert [model.model_id for model in config.judge.models] == ['judge-a']
|
|
|
|
|
|
def test_legacy_single_mapping_is_converted_at_the_boundary():
|
|
config = TaskConfig(
|
|
model='m',
|
|
datasets=['simple_qa'],
|
|
judge_strategy='llm',
|
|
judge_model_args={'model_id': 'judge-a'},
|
|
)
|
|
|
|
assert config.judge.strategy == 'llm'
|
|
assert config.judge.models[0].model_id == 'judge-a'
|
|
assert 'judge_model_args' not in config.model_dump()
|
|
|
|
|
|
def test_legacy_semantics_move_to_the_shared_contract():
|
|
config = TaskConfig(
|
|
model='m',
|
|
datasets=['simple_qa'],
|
|
judge_strategy='llm',
|
|
judge_model_args={
|
|
'model_id': 'judge-a',
|
|
'prompt_template': 'Question: {question}',
|
|
'score_mapping': {'yes': 1.0, 'no': 0.0},
|
|
},
|
|
)
|
|
|
|
assert config.judge.contract.prompt_template == 'Question: {question}'
|
|
assert config.judge.contract.score_mapping == {'yes': 1.0, 'no': 0.0}
|
|
assert 'prompt_template' not in config.judge.models[0].model_dump()
|
|
|
|
|
|
def test_model_specific_semantics_are_rejected():
|
|
with pytest.raises(ValueError, match='prompt_template'):
|
|
JudgeConfig(models=[{'model_id': 'judge-a', 'prompt_template': 'not allowed'}])
|
|
|
|
|
|
def test_removed_judge_worker_num_is_rejected():
|
|
with pytest.raises(ValueError, match='judge_worker_num'):
|
|
TaskConfig(model='m', datasets=['simple_qa'], judge_worker_num=1)
|
|
|
|
|
|
@pytest.mark.parametrize('value', [
|
|
{'strategy': 'not-a-strategy'},
|
|
{'contract': {'score_type': 'not-a-contract'}},
|
|
{'contract': {'score_typo': 'pattern'}},
|
|
{'min_valid_judge': 2},
|
|
])
|
|
def test_typed_judge_config_rejects_unknown_or_invalid_values(value):
|
|
with pytest.raises(ValueError):
|
|
JudgeConfig(**value)
|
|
|
|
|
|
def test_legacy_score_pattern_has_an_actionable_error():
|
|
with pytest.raises(ValueError, match='score_pattern'):
|
|
TaskConfig(
|
|
model='m',
|
|
datasets=['simple_qa'],
|
|
judge_model_args={'model_id': 'judge-a', 'score_pattern': '[[A]]'},
|
|
)
|
|
|
|
|
|
def test_llm_judge_is_concrete_after_removing_the_legacy_judge_api(monkeypatch):
|
|
monkeypatch.setattr(LLMJudge, '_init_server_adapter', lambda self: None)
|
|
|
|
judge = LLMJudge(model_id='judge-a')
|
|
|
|
assert judge.model_id == 'judge-a'
|
|
|
|
|
|
def test_llm_judge_rejects_the_removed_score_pattern_parameter():
|
|
with pytest.raises(TypeError, match='score_pattern'):
|
|
LLMJudge(model_id='judge-a', score_pattern='[[A]]')
|