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>
125 lines
5.2 KiB
Python
125 lines
5.2 KiB
Python
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
from evalscope.api.metric.semantics import MetricIdentity, MetricSelector
|
|
from evalscope.metrics.semantics.catalog import LEGACY_METRIC_MIGRATIONS
|
|
from evalscope.metrics.semantics.identity import migrate_legacy_identity
|
|
from evalscope.metrics.semantics.legacy import LEGACY_METRIC_ALIASES
|
|
|
|
|
|
def test_identity_sorts_dimensions_and_builds_stable_key() -> None:
|
|
identity = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'target': 'answer', 'level': 'overall'})
|
|
assert list(identity.dimensions) == ['level', 'target']
|
|
assert identity.key == 'accuracy:mean[level="overall",target="answer"]'
|
|
|
|
|
|
def test_identity_key_preserves_dimension_types_and_boundaries() -> None:
|
|
numeric = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'k': 1})
|
|
text = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'k': '1'})
|
|
embedded_delimiters = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'a': 'x,b=y'})
|
|
separate_dimensions = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'a': 'x', 'b': 'y'})
|
|
|
|
assert numeric.key != text.key
|
|
assert embedded_delimiters.key != separate_dimensions.key
|
|
|
|
|
|
def test_identity_comparison_keeps_booleans_distinct_and_normalizes_json_numbers() -> None:
|
|
boolean = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'value': True})
|
|
integer = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'value': 1})
|
|
integral_float = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'value': 1.0})
|
|
negative_zero = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'value': -0.0})
|
|
|
|
assert boolean != integer
|
|
assert len({boolean, integer}) == 2
|
|
assert integer == integral_float
|
|
assert integer.key == integral_float.key == 'accuracy:mean[value=1]'
|
|
assert negative_zero.key == 'accuracy:mean[value=0]'
|
|
|
|
|
|
def test_selector_does_not_match_boolean_to_numeric_dimension() -> None:
|
|
selector = MetricSelector(name='accuracy', dimensions={'value': True})
|
|
identity = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'value': 1})
|
|
|
|
assert not selector.matches(identity)
|
|
|
|
|
|
def test_frozen_identity_rejects_field_assignment() -> None:
|
|
identity = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'target': 'answer'})
|
|
|
|
with pytest.raises(ValidationError):
|
|
identity.dimensions = {'target': 'figure'}
|
|
|
|
|
|
def test_identity_equality_and_hash_ignore_dimension_order() -> None:
|
|
# `dimensions` is a plain dict; what makes an identity stable is that equality and hash are
|
|
# derived from the normalized `sort_key`, not from the mapping's insertion order.
|
|
left = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'target': 'answer', 'level': 'overall'})
|
|
right = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'level': 'overall', 'target': 'answer'})
|
|
|
|
assert left == right
|
|
assert hash(left) == hash(right)
|
|
assert len({left, right}) == 1
|
|
|
|
|
|
@pytest.mark.parametrize('field,value', [('name', 'F1'), ('name', 'pass@1'), ('aggregation', 'Macro Mean')])
|
|
def test_identity_rejects_non_canonical_names(field: str, value: str) -> None:
|
|
values = {'name': 'accuracy', 'aggregation': 'mean'}
|
|
values[field] = value
|
|
with pytest.raises(ValidationError):
|
|
MetricIdentity(**values)
|
|
|
|
|
|
def test_selector_dimensions_are_partial_constraints() -> None:
|
|
selector = MetricSelector(name='accuracy', aggregation='mean', dimensions={'target': 'answer'})
|
|
identity = MetricIdentity(name='accuracy', aggregation='mean', dimensions={'level': 'overall', 'target': 'answer'})
|
|
assert selector.matches(identity)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
('legacy_name', 'aggregation', 'expected'),
|
|
[
|
|
('mean_acc', 'identity', ('accuracy', 'mean', {})),
|
|
('Bleu_4', 'mean', ('bleu', 'mean', {
|
|
'ngram': 4
|
|
})),
|
|
('bleu-4', 'mean', ('bleu', 'mean', {
|
|
'ngram': 4
|
|
})),
|
|
('Rouge-L-R', 'mean', ('rouge', 'mean', {
|
|
'statistic': 'recall',
|
|
'variant': 'l'
|
|
})),
|
|
('Rouge-2-F', 'mean', ('rouge', 'mean', {
|
|
'ngram': 2,
|
|
'statistic': 'f1'
|
|
})),
|
|
('ACC@0.5', 'mean', ('accuracy', 'mean', {
|
|
'threshold': 0.5
|
|
})),
|
|
('all/success_rate', 'avg@8', ('success_rate', 'mean', {
|
|
'k': 8,
|
|
'scope': 'all'
|
|
})),
|
|
('acc_pass@16', 'mean', ('accuracy', 'pass_at_k', {
|
|
'k': 16
|
|
})),
|
|
('Act.EM', 'mean', ('exact_match', 'mean', {
|
|
'target': 'action'
|
|
})),
|
|
('mean_total_wall_time_s', 'identity', ('total_wall_time', 'mean', {})),
|
|
],
|
|
)
|
|
def test_legacy_names_migrate_to_structured_identity(
|
|
legacy_name: str,
|
|
aggregation: str,
|
|
expected: tuple,
|
|
) -> None:
|
|
identity = migrate_legacy_identity(legacy_name, aggregation)
|
|
assert (identity.name, identity.aggregation, identity.dimensions) == expected
|
|
|
|
|
|
def test_exact_alias_manifest_drives_identity_and_read_old_semantics() -> None:
|
|
for name, alias in LEGACY_METRIC_ALIASES.items():
|
|
assert migrate_legacy_identity(name, 'identity').name == alias.canonical_name
|
|
assert (name in LEGACY_METRIC_MIGRATIONS) is (alias.baseline is not None)
|