ELBench-live / src /elbench /loaders /general_capability_loader.py
adelevett's picture
Deploy ELBench-live dashboard
5db30ac verified
Raw History Blame Contribute Delete
14.8 kB
from __future__ import annotations
import json
import re
from typing import Any, Iterator
from elbench.loaders.base import BaseLoader
from elbench.registry import ResolvedRegistryItem
from elbench.schemas.evaluation import RawRecord, Sample
class MMLUProJsonlLoader(BaseLoader):
def iter_raw_records(self, item: ResolvedRegistryItem) -> Iterator[RawRecord]:
with item.path.open("r", encoding="utf-8") as handle:
for row_index, line in enumerate(handle, start=1):
if not line.strip():
continue
yield RawRecord(
source_path=str(item.path),
source_file=item.entry.canonical_name,
row_index=row_index,
record=json.loads(line),
)
def iter_samples(self, item: ResolvedRegistryItem) -> Iterator[Sample]:
for raw_record in self.iter_raw_records(item):
record = raw_record.record
target = self._string_or_none(record.get("target"))
choices = self._choices(record.get("choices"))
metadata = self._metadata(record, raw_record.row_index)
dimension = self._dimension(record, metadata)
yield Sample(
sample_id=self._sample_id(
item.entry.canonical_name,
record,
raw_record.row_index,
namespace=dimension,
),
source_file=item.entry.canonical_name,
source_path=str(item.path),
module=item.entry.module,
subset=item.entry.subset,
task=item.entry.task,
dimension=dimension,
prompt=self._build_zero_shot_prompt(record, choices, item.entry.task),
reference={"target": target},
metadata=metadata,
raw_record=record,
)
def _build_zero_shot_prompt(self, record: dict[str, Any], choices: list[str], task: str | None) -> str:
question = self._extract_question(record)
option_lines = [f"{chr(65 + index)}) {choice}" for index, choice in enumerate(choices)]
letters = ",".join(chr(65 + index) for index in range(len(choices)))
if task == "ceval":
return "\n".join(
[
"请回答以下单项选择题。",
"请先逐步思考,再给出答案。",
f"你回答的最后一行必须严格使用格式:答案:[LETTER],其中 [LETTER] 是 {letters} 中的一个。",
"",
"问题:",
question,
"选项:",
*option_lines,
]
)
return "\n".join(
[
"Answer the following multiple choice question.",
"Think step by step before answering.",
f"The last line of your response must be exactly: ANSWER: [LETTER], where [LETTER] is one of {letters}.",
"",
"Question:",
question,
"Options:",
*option_lines,
]
)
def _extract_question(self, record: dict[str, Any]) -> str:
input_messages = record.get("input")
if isinstance(input_messages, list):
for message in reversed(input_messages):
if not isinstance(message, dict):
continue
content = message.get("content")
if not isinstance(content, str):
continue
matches = re.findall(r"Question:\n(.*?)\nOptions:", content, flags=re.S)
if not matches:
matches = re.findall(r"问题[::]\s*(.*?)\n选项[::]", content, flags=re.S)
if matches:
return matches[-1].strip()
raise ValueError("Cannot extract multiple-choice question from input messages.")
def _metadata(self, record: dict[str, Any], row_index: int) -> dict[str, Any]:
metadata = {}
raw_metadata = record.get("metadata")
if isinstance(raw_metadata, dict):
metadata.update(raw_metadata)
metadata["choices"] = self._choices(record.get("choices"))
metadata["subset_key"] = self._string_or_none(record.get("subset_key"))
metadata["row_index"] = row_index
return metadata
def _dimension(self, record: dict[str, Any], metadata: dict[str, Any]) -> str | None:
return (
self._string_or_none(record.get("subset_key"))
or self._string_or_none(metadata.get("subject"))
or self._string_or_none(metadata.get("id"))
)
def _sample_id(
self,
canonical_name: str,
record: dict[str, Any],
row_index: int,
namespace: str | None = None,
) -> str:
value = record.get("id")
if value in (None, ""):
value = row_index
value_part = self._slug_component(value)
namespace_part = self._slug_component(namespace)
if namespace_part:
return f"{canonical_name.replace('.', '_')}-{namespace_part}-{value_part}"
return f"{canonical_name.replace('.', '_')}-{value_part}"
def _choices(self, value: Any) -> list[str]:
if not isinstance(value, list) or not value:
raise ValueError("Multiple-choice record must contain a non-empty choices list.")
return [str(choice) for choice in value]
def _string_or_none(self, value: Any) -> str | None:
if value in (None, ""):
return None
return str(value)
def _slug_component(self, value: Any) -> str:
if value in (None, ""):
return ""
text = re.sub(r"[^\w]+", "_", str(value)).strip("_")
return text or str(value)
def expected_answer_from_sample(self, sample: Sample) -> str | None:
reference = sample.reference or {}
if isinstance(reference, dict):
value = reference.get("target")
else:
value = reference
if value in (None, ""):
return None
normalized = str(value).strip().upper()
return normalized[0] if normalized else None
def valid_letters_from_sample(self, sample: Sample) -> list[str]:
choices = sample.metadata.get("choices")
if isinstance(choices, list) and choices:
return [chr(65 + index) for index in range(len(choices))]
return list("ABCDEFGHIJ")
class CEvalJsonlLoader(MMLUProJsonlLoader):
pass
class IFEvalJsonlLoader(BaseLoader):
def iter_raw_records(self, item: ResolvedRegistryItem) -> Iterator[RawRecord]:
with item.path.open("r", encoding="utf-8") as handle:
for row_index, line in enumerate(handle, start=1):
if not line.strip():
continue
yield RawRecord(
source_path=str(item.path),
source_file=item.entry.canonical_name,
row_index=row_index,
record=json.loads(line),
)
def iter_samples(self, item: ResolvedRegistryItem) -> Iterator[Sample]:
for raw_record in self.iter_raw_records(item):
record = raw_record.record
metadata = self._metadata(record, raw_record.row_index)
yield Sample(
sample_id=self._sample_id(item.entry.canonical_name, record, raw_record.row_index),
source_file=item.entry.canonical_name,
source_path=str(item.path),
module=item.entry.module,
subset=item.entry.subset,
task=item.entry.task,
dimension="default",
prompt=self._prompt(record, metadata),
reference={"target": self._string_or_empty(record.get("target"))},
metadata=metadata,
raw_record=record,
)
def _metadata(self, record: dict[str, Any], row_index: int) -> dict[str, Any]:
metadata: dict[str, Any] = {}
raw_metadata = record.get("metadata")
if isinstance(raw_metadata, dict):
metadata.update(raw_metadata)
metadata["instruction_id_list"] = self._instruction_ids(metadata.get("instruction_id_list"))
metadata["kwargs"] = self._kwargs(metadata.get("kwargs"))
metadata["key"] = self._string_or_empty(metadata.get("key"))
metadata["prompt"] = self._prompt(record, metadata)
metadata["row_index"] = row_index
return metadata
def _prompt(self, record: dict[str, Any], metadata: dict[str, Any]) -> str:
prompt = metadata.get("prompt")
if isinstance(prompt, str) and prompt:
return prompt
input_messages = record.get("input")
if isinstance(input_messages, list):
for message in input_messages:
if isinstance(message, dict) and isinstance(message.get("content"), str):
return message["content"]
return ""
def _kwargs(self, value: Any) -> list[dict[str, Any]]:
if not isinstance(value, list):
return []
parsed_kwargs = []
for item in value:
if isinstance(item, dict):
parsed_kwargs.append(item)
continue
if isinstance(item, str):
try:
parsed = json.loads(item)
except json.JSONDecodeError:
parsed = {}
parsed_kwargs.append(parsed if isinstance(parsed, dict) else {})
continue
parsed_kwargs.append({})
return parsed_kwargs
def _instruction_ids(self, value: Any) -> list[str]:
if not isinstance(value, list):
return []
return [str(item) for item in value]
def _sample_id(self, canonical_name: str, record: dict[str, Any], row_index: int) -> str:
value = record.get("id")
if value in (None, ""):
value = row_index
return f"{canonical_name.replace('.', '_')}-{value}"
def _string_or_empty(self, value: Any) -> str:
if value in (None, ""):
return ""
return str(value)
def instruction_ids_from_sample(self, sample: Sample) -> list[str]:
return self._instruction_ids(sample.metadata.get("instruction_id_list"))
def kwargs_from_sample(self, sample: Sample) -> list[dict[str, Any]]:
return self._kwargs(sample.metadata.get("kwargs"))
def prompt_key_from_sample(self, sample: Sample) -> str:
return self._string_or_empty(sample.metadata.get("key"))
class MATH500JsonlLoader(BaseLoader):
def iter_raw_records(self, item: ResolvedRegistryItem) -> Iterator[RawRecord]:
with item.path.open("r", encoding="utf-8") as handle:
for row_index, line in enumerate(handle, start=1):
if not line.strip():
continue
yield RawRecord(
source_path=str(item.path),
source_file=item.entry.canonical_name,
row_index=row_index,
record=json.loads(line),
)
def iter_samples(self, item: ResolvedRegistryItem) -> Iterator[Sample]:
for raw_record in self.iter_raw_records(item):
record = raw_record.record
metadata = self._metadata(record, raw_record.row_index)
yield Sample(
sample_id=self._sample_id(item.entry.canonical_name, record, raw_record.row_index),
source_file=item.entry.canonical_name,
source_path=str(item.path),
module=item.entry.module,
subset=item.entry.subset,
task=item.entry.task,
dimension=self._string_or_none(record.get("subset_key")) or "default",
prompt=self._prompt(record),
reference={"target": self._string_or_empty(record.get("target"))},
metadata=metadata,
raw_record=record,
)
def _metadata(self, record: dict[str, Any], row_index: int) -> dict[str, Any]:
metadata: dict[str, Any] = {}
raw_metadata = record.get("metadata")
if isinstance(raw_metadata, dict):
metadata.update(raw_metadata)
metadata["subset_key"] = self._string_or_none(record.get("subset_key"))
metadata["row_index"] = row_index
return metadata
def _prompt(self, record: dict[str, Any]) -> str:
input_messages = record.get("input")
if isinstance(input_messages, list):
for message in input_messages:
if isinstance(message, dict) and isinstance(message.get("content"), str):
return message["content"]
return ""
def _sample_id(self, canonical_name: str, record: dict[str, Any], row_index: int) -> str:
value = record.get("id")
subset_key = self._string_or_none(record.get("subset_key")) or "default"
if value in (None, ""):
value = row_index
subset_slug = re.sub(r"[^A-Za-z0-9]+", "_", subset_key).strip("_").lower()
return f"{canonical_name.replace('.', '_')}-{subset_slug}-{value}"
def _string_or_none(self, value: Any) -> str | None:
if value in (None, ""):
return None
return str(value)
def _string_or_empty(self, value: Any) -> str:
if value in (None, ""):
return ""
return str(value)
def expected_answer_from_sample(self, sample: Sample) -> str:
reference = sample.reference or {}
if isinstance(reference, dict):
value = reference.get("target")
else:
value = reference
return "" if value in (None, "") else str(value)
class AIMEJsonlLoader(MATH500JsonlLoader):
def iter_samples(self, item: ResolvedRegistryItem) -> Iterator[Sample]:
for raw_record in self.iter_raw_records(item):
record = raw_record.record
metadata = self._metadata(record, raw_record.row_index)
yield Sample(
sample_id=self._sample_id(item.entry.canonical_name, record, raw_record.row_index),
source_file=item.entry.canonical_name,
source_path=str(item.path),
module=item.entry.module,
subset=item.entry.subset,
task=item.entry.task,
dimension="default",
prompt=self._prompt(record),
reference={"target": self._string_or_empty(record.get("target"))},
metadata=metadata,
raw_record=record,
)