Voltline's picture
Release frozen VimeML tiny-ja-v1 FP32 and Core ML INT8 (GPL-2.0)
2c87024 verified
Raw History Blame Contribute Delete
6.57 kB
"""Convert a local AJIMEE JSON array to anco input without changing its text."""
import argparse
import hashlib
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
FORMAT = "ajimee_anco_input_v1"
DEVELOPMENT_FORMAT = "ime_development_anco_input_v1"
SOURCE_URL = "https://github.com/azooKey/AJIMEE-Bench/tree/main/JWTD_v2/v1"
def sha(data):
return hashlib.sha256(data).hexdigest()
def convert_items(rows):
if not isinstance(rows, list) or not rows:
raise ValueError("Expected a nonempty AJIMEE JSON array.")
converted, mapping, seen = [], [], set()
for position, row in enumerate(rows):
if not isinstance(row, dict):
raise ValueError(f"Row {position}: expected an object.")
index = row.get("index")
if type(index) not in (str, int) or (isinstance(index, str) and not index.strip()):
raise ValueError(f"Row {position}: missing/invalid index.")
case_id = f"ajimee:{index}"
if case_id in seen:
raise ValueError(f"Row {position}: duplicate index {index!r}.")
seen.add(case_id)
query, context, answers = row.get("input"), row.get("context_text"), row.get("expected_output")
if not isinstance(query, str) or not query.strip():
raise ValueError(f"Row {position}: input must be a nonempty string.")
if not isinstance(context, str):
raise ValueError(f"Row {position}: context_text must be a string (empty is allowed).")
if not isinstance(answers, list) or not answers or any(not isinstance(answer, str) or not answer.strip() for answer in answers):
raise ValueError(f"Row {position}: expected_output must be a nonempty string array.")
group = "with-context" if context else "without-context"
# Preserve kana, width, punctuation, whitespace, answer order and the
# full input exactly. Neither original_text nor split gold is supplied.
converted.append({"query": query, "answer": list(answers), "tag": [case_id, group],
"left_context": context, "right_context": None})
mapping.append({"position": position, "id": case_id, "source_index": index,
"query": query, "left_context": context, "answers": list(answers), "group": group})
stats = {"cases": len(rows), "with_context": sum(bool(row["context_text"]) for row in rows),
"without_context": sum(not row["context_text"] for row in rows),
"multiple_acceptable_outputs": sum(len(row["expected_output"]) > 1 for row in rows),
"has_split_inputs": sum(bool(row.get("splitted_input_for_limited_input_length")) for row in rows)}
return converted, mapping, stats
def json_bytes(value):
return (json.dumps(value, ensure_ascii=False, indent=2) + "\n").encode("utf-8")
def prepare(input_path, output):
input_path, output = Path(input_path).resolve(), Path(output).resolve()
raw = input_path.read_bytes()
converted, mapping, stats = convert_items(json.loads(raw.decode("utf-8-sig")))
manifest_path = output / "manifest.json"
if manifest_path.exists():
saved = json.loads(manifest_path.read_text(encoding="utf-8"))
if saved.get("format") != FORMAT or saved.get("source_sha256") != sha(raw):
raise ValueError("Output belongs to different input/version. Choose a new --output directory.")
for name, digest in saved["files_sha256"].items():
if sha((output / name).read_bytes()) != digest:
raise ValueError(f"Prepared file changed: {name}. Choose a new --output directory.")
return saved["stats"]
if output.exists() and any(output.iterdir()):
raise ValueError("Output is not empty. Choose a new --output directory.")
notice = (
"# AJIMEE-Bench provenance\n\n"
f"Source: {SOURCE_URL}\n\n"
"Dataset: JWTD_v2/v1, based on Japanese Wikipedia Typo Dataset v2.\n"
"Upstream dataset license: CC-BY-SA 3.0. Preserve attribution and share-alike terms.\n"
"License: https://creativecommons.org/licenses/by-sa/3.0/\n\n"
"evaluation_items.json is the supplied local source, preserved byte for byte.\n"
"Its exact upstream commit has not been verified; source SHA256 is in manifest.json.\n"
"ajimee-input.json maps input/context_text/expected_output to query/left_context/answer.\n"
"No text normalization, splitting, filtering or answer injection into context.\n"
"original_text remains in the source snapshot and is excluded from anco input.\n"
"For evaluation only; do not add these files to the training corpus.\n"
).encode("utf-8")
files = {"evaluation_items.json": raw, "ajimee-input.json": json_bytes(converted),
"case-map.json": json_bytes(mapping), "NOTICE.md": notice}
output.mkdir(parents=True, exist_ok=True)
for name, content in files.items():
path = output / name
temporary = path.with_suffix(path.suffix + ".tmp")
temporary.write_bytes(content)
temporary.replace(path)
manifest = {"format": FORMAT, "status": "complete", "source_path": str(input_path),
"source_url": SOURCE_URL, "source_sha256": sha(raw), "upstream_commit_verified": False,
"license": "CC-BY-SA-3.0", "stats": stats,
"files_sha256": {name: sha(content) for name, content in files.items()},
"policy": "Unmodified full queries and all references; supplied left context only; right context null. Mapping positions are zero-based."}
temporary = manifest_path.with_suffix(".json.tmp")
temporary.write_bytes(json_bytes(manifest))
temporary.replace(manifest_path)
return stats
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--input", required=True, type=Path, help="Downloaded evaluation_items.json path.")
parser.add_argument("--output", type=Path, default=ROOT / "artifacts/benchmarks/ajimee-jwtd-v2-v1",
help="Directory for anco input, source snapshot, case mapping and manifest.")
args = parser.parse_args(argv)
try:
stats = prepare(args.input, args.output)
except (OSError, ValueError) as error:
parser.exit(2, f"Error: {error}\n")
print(json.dumps(stats, ensure_ascii=False, indent=2))
print(f"Anco input: {args.output.resolve() / 'ajimee-input.json'}")
print(f"Case map: {args.output.resolve() / 'case-map.json'}")
if __name__ == "__main__":
main()