File size: 3,476 Bytes
ac7a77a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
export const CANONICAL_DATASETS = [
  '일반 상식', '멀티 모달', '수치 정보', '팩트 체크', '소버린 벤치마크',
  'K-Prism (Text, Intact)', 'K-Prism (Text, Perturbed)',
  'K-Prism (Image, Intact)', 'K-Prism (Image, Perturbed)',
];

const isText = value => typeof value === 'string' && value.trim().length > 0;

// Accept both the original benchmark.json array and a published {models} snapshot.
// Validate before replacing the visible results so failed refreshes keep good data.
export function normalizeBenchmark(payload) {
  const source = Array.isArray(payload) ? payload : payload?.models;
  if (!Array.isArray(source)) throw new Error('Expected a model array or an object with models.');

  const modelKeys = new Set();
  const models = source.map((model, index) => {
    if (!model || !isText(model.provider) || !isText(model.name)) {
      throw new Error(`Model ${index + 1} is missing its provider or name.`);
    }
    const key = JSON.stringify([model.provider, model.name]);
    if (modelKeys.has(key)) throw new Error(`Duplicate model: ${model.provider} / ${model.name}.`);
    modelKeys.add(key);
    if (!Array.isArray(model.scores)) throw new Error(`Invalid scores for ${model.name}.`);
    if (model.is_multimodal != null && typeof model.is_multimodal !== 'boolean') {
      throw new Error(`Invalid model type for ${model.name}.`);
    }
    const scoreNames = new Set();
    const scores = model.scores.map(score => {
      if (!score || !isText(score.dataset_name) || !Number.isFinite(score.score)) {
        throw new Error(`Invalid score for ${model.name}.`);
      }
      if (scoreNames.has(score.dataset_name)) {
        throw new Error(`Duplicate dataset score for ${model.name}: ${score.dataset_name}.`);
      }
      scoreNames.add(score.dataset_name);
      return {
        dataset_name: score.dataset_name,
        metric_type: score.metric_type === 'llm-as-judge' ? 'llm-as-judge' : 'raw',
        score: score.score,
      };
    });
    return {
      provider: model.provider,
      name: model.name,
      repo: typeof model.repo === 'string' ? model.repo : '',
      is_multimodal: model.is_multimodal ?? false,
      status: typeof model.status === 'string' ? model.status : 'completed',
      updated_at: typeof model.updated_at === 'string' ? model.updated_at : '',
      scores,
    };
  });

  const publishedDatasets = Array.isArray(payload) ? undefined : payload.datasets;
  if (publishedDatasets !== undefined &&
      (!Array.isArray(publishedDatasets) || !publishedDatasets.every(isText))) {
    throw new Error('Invalid dataset list.');
  }
  // A published list preserves the curator's selection and order. Legacy arrays
  // include the canonical datasets plus any additional scored datasets.
  const datasets = [...new Set(publishedDatasets ?? [
    ...CANONICAL_DATASETS,
    ...models.flatMap(model => model.scores.map(score => score.dataset_name)),
  ])];
  return { models, datasets };
}

export async function loadBenchmark(url, timeoutMs = 15000) {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
  try {
    const response = await fetch(url, {
      cache: 'no-store',
      credentials: 'omit',
      signal: controller.signal,
    });
    if (!response.ok) throw new Error(`Score data request failed (HTTP ${response.status}).`);
    return normalizeBenchmark(await response.json());
  } finally {
    clearTimeout(timeout);
  }
}