426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
305 lines
11 KiB
Python
305 lines
11 KiB
Python
"""Normalize every downloaded dataset into the DEPRECATED flat ElizaRecord
|
|
intermediate.
|
|
|
|
This emits the legacy flat `ElizaRecord` shape (see
|
|
`scripts/lib/eliza_record.py`), NOT the canonical Eliza-1 corpus record. The
|
|
canonical corpus record is `eliza_native_v1`; see
|
|
`packages/training/docs/dataset/CANONICAL_RECORD.md`. This path is kept only so
|
|
the existing bulk corpus keeps loading — new corpus data should be authored as
|
|
`eliza_native_v1` rows.
|
|
|
|
Reads `datasets.yaml`, walks `data/raw/<slug>/`, dispatches to the named
|
|
adapter in `lib/adapters.REGISTRY`, and writes
|
|
`data/normalized/<slug>.jsonl` (+ `<slug>.errors.jsonl` for dropped rows).
|
|
Outputs use JSON expectedResponse payloads for native tool calling.
|
|
|
|
Source files are auto-discovered:
|
|
- `*.parquet` (loaded via pyarrow)
|
|
- `*.jsonl`, `*.json` (one record per line, or one JSON list per file)
|
|
|
|
Filtering rules:
|
|
- For scambench, prefer `formats/eliza-*.jsonl` — that's the canonical
|
|
config. Skip the parquet `data/*.parquet` because it's a flat shape.
|
|
- For other datasets we use parquet+jsonl indiscriminately.
|
|
|
|
Usage:
|
|
uv run python scripts/normalize.py
|
|
uv run python scripts/normalize.py --only scambench,claude-distills
|
|
uv run python scripts/normalize.py --max-records 1000 # smoke test
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any, Iterator
|
|
|
|
import yaml
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(ROOT / "scripts"))
|
|
|
|
from lib.adapters import REGISTRY # noqa: E402
|
|
from lib.expected_response import ExpectedResponseEncoder, make_expected_response_encoder # noqa: E402
|
|
|
|
RAW_DIR = ROOT / "data" / "raw"
|
|
OUT_DIR = ROOT / "data" / "normalized"
|
|
REGISTRY_FILE = ROOT / "datasets.yaml"
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
)
|
|
log = logging.getLogger("normalize")
|
|
|
|
|
|
def split_from_filename(path: Path) -> str:
|
|
haystack = "/".join(p.lower() for p in path.parts)
|
|
if "held-out" in haystack or "held_out" in haystack or "heldout" in haystack:
|
|
return "test"
|
|
name = path.name.lower()
|
|
for s in ("train", "test", "validation", "val", "dev"):
|
|
if s in name:
|
|
return "train" if s in ("train",) else ("validation" if s in ("val", "validation", "dev") else "test")
|
|
return "train"
|
|
|
|
|
|
def iter_jsonl(path: Path) -> Iterator[dict[str, Any]]:
|
|
with path.open("r", encoding="utf-8", errors="replace") as f:
|
|
first = f.readline()
|
|
if not first:
|
|
return
|
|
first_strip = first.lstrip()
|
|
# Whole-file JSON list (e.g. dataset.json shipped as one array)
|
|
if first_strip.startswith("["):
|
|
f.seek(0)
|
|
try:
|
|
data = json.load(f)
|
|
except json.JSONDecodeError as e:
|
|
log.warning("could not parse %s as JSON list: %s", path, e)
|
|
return
|
|
if isinstance(data, list):
|
|
yield from (r for r in data if isinstance(r, dict))
|
|
return
|
|
# Whole-file JSON object — common for MCP-Flow per-tool specs.
|
|
# We yield it as a single record.
|
|
if first_strip.startswith("{") and path.suffix == ".json":
|
|
f.seek(0)
|
|
try:
|
|
data = json.load(f)
|
|
except json.JSONDecodeError:
|
|
# fall through to JSONL handling
|
|
pass
|
|
else:
|
|
if isinstance(data, dict):
|
|
yield data
|
|
return
|
|
if isinstance(data, list):
|
|
yield from (r for r in data if isinstance(r, dict))
|
|
return
|
|
# JSONL
|
|
try:
|
|
yield json.loads(first)
|
|
except json.JSONDecodeError:
|
|
pass
|
|
for line in f:
|
|
line = line.strip()
|
|
if not line:
|
|
continue
|
|
try:
|
|
yield json.loads(line)
|
|
except json.JSONDecodeError:
|
|
continue
|
|
|
|
|
|
def iter_parquet(path: Path) -> Iterator[dict[str, Any]]:
|
|
"""Stream a parquet file row-batch by row-batch — never load the whole
|
|
table. Some sources ship multi-GB shards (toucan, glm-51) and the
|
|
`pq.read_table` path OOMs."""
|
|
import pyarrow.parquet as pq
|
|
pf = pq.ParquetFile(path)
|
|
for batch in pf.iter_batches(batch_size=2048):
|
|
for row in batch.to_pylist():
|
|
yield row
|
|
|
|
|
|
def discover_files(slug: str, raw_dir: Path) -> list[Path]:
|
|
if slug == "scambench":
|
|
files = sorted((raw_dir / "formats").glob("eliza-*.jsonl"))
|
|
if files:
|
|
return files
|
|
if slug == "playwright-mcp-toolcalling":
|
|
# The playwright corpus ships the same trajectories under several
|
|
# filenames (`dataset.parquet` ≡ `data_with_llm_grades.parquet`,
|
|
# `train_v3.parquet` ≡ `train_v3.jsonl`, plus older versioned
|
|
# train files). Pin to the canonical splits and the latest train
|
|
# to avoid emitting near-identical training records.
|
|
canonical = ["train_v4.jsonl", "train.parquet", "test.parquet",
|
|
"eval.parquet", "val.parquet"]
|
|
picks = [raw_dir / "data" / n for n in canonical]
|
|
return [p for p in picks if p.exists()]
|
|
files = []
|
|
files.extend(sorted(raw_dir.rglob("*.jsonl")))
|
|
files.extend(sorted(raw_dir.rglob("*.parquet")))
|
|
files.extend(sorted(raw_dir.rglob("*.json")))
|
|
return [
|
|
p for p in files
|
|
if not any(part in {"node_modules"} for part in p.parts)
|
|
and p.suffix in {".jsonl", ".parquet", ".json"}
|
|
and p.name not in {"dataset_info.json", "dataset_infos.json"}
|
|
]
|
|
|
|
|
|
def load_records(path: Path) -> Iterator[dict[str, Any]]:
|
|
if path.suffix == ".parquet":
|
|
yield from iter_parquet(path)
|
|
else:
|
|
yield from iter_jsonl(path)
|
|
|
|
|
|
def _tag_source(records: Iterator[dict[str, Any]], filename: str) -> Iterator[dict[str, Any]]:
|
|
"""Inject the source filename so file-aware adapters can pick task_type."""
|
|
for r in records:
|
|
if isinstance(r, dict):
|
|
r.setdefault("_source_filename", filename)
|
|
yield r
|
|
|
|
|
|
def normalize_dataset(
|
|
entry: dict, *, max_records: int | None, encoder: ExpectedResponseEncoder,
|
|
) -> tuple[int, int, int]:
|
|
slug = entry["slug"]
|
|
license = entry.get("license", "unknown")
|
|
adapter_name = entry["normalizer"]
|
|
adapter = REGISTRY.get(adapter_name)
|
|
if not adapter:
|
|
log.error("no adapter registered for %s (slug=%s)", adapter_name, slug)
|
|
return (0, 0, 1)
|
|
|
|
raw_dir = RAW_DIR / slug
|
|
if not raw_dir.exists() or not (raw_dir / ".done").exists():
|
|
log.warning("skip %s — not downloaded yet", slug)
|
|
return (0, 0, 0)
|
|
|
|
files = discover_files(slug, raw_dir)
|
|
if not files:
|
|
log.warning("no source files found in %s", raw_dir)
|
|
return (0, 0, 0)
|
|
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
out_path = OUT_DIR / f"{slug}.jsonl"
|
|
err_path = OUT_DIR / f"{slug}.errors.jsonl"
|
|
|
|
n_in = n_out = n_err = 0
|
|
with out_path.open("w", encoding="utf-8") as out, \
|
|
err_path.open("w", encoding="utf-8") as err:
|
|
for f in files:
|
|
split = split_from_filename(f)
|
|
log.info(" %s [%s] %s", slug, split, f.name)
|
|
records = _tag_source(load_records(f), f.name)
|
|
try:
|
|
for ezr in adapter(
|
|
records, slug=slug, license=license, split=split, encoder=encoder
|
|
):
|
|
n_in += 1
|
|
ok, why = ezr.is_valid()
|
|
if not ok:
|
|
n_err += 1
|
|
err.write(json.dumps({"reason": why, "record": ezr.to_dict()}) + "\n")
|
|
continue
|
|
out.write(ezr.to_jsonl() + "\n")
|
|
n_out += 1
|
|
if max_records and n_out >= max_records:
|
|
break
|
|
except Exception as e: # noqa: BLE001
|
|
log.exception("adapter %s crashed on %s: %s", adapter_name, f, e)
|
|
n_err += 1
|
|
if max_records and n_out >= max_records:
|
|
break
|
|
|
|
log.info(" %s: %d in, %d out, %d errors → %s", slug, n_in, n_out, n_err, out_path.name)
|
|
return (n_in, n_out, n_err)
|
|
|
|
|
|
def main() -> int:
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument("--registry", type=Path, default=REGISTRY_FILE)
|
|
ap.add_argument("--only", type=str, default="")
|
|
ap.add_argument("--skip", type=str, default="")
|
|
ap.add_argument("--max-records", type=int, default=None,
|
|
help="cap output records per dataset (smoke testing)")
|
|
ap.add_argument("--sample-per-source", type=int, default=0,
|
|
help="when >0, limit each source to ~N output records "
|
|
"(head sample). Alias of --max-records used by "
|
|
"run_pipeline.py --from-scratch; the smaller of the "
|
|
"two wins when both are given.")
|
|
ap.add_argument(
|
|
"--expected-response-format",
|
|
choices=("json",),
|
|
default="json",
|
|
help="supervised target encoding for generated ElizaRecord rows",
|
|
)
|
|
args = ap.parse_args()
|
|
|
|
with args.registry.open() as f:
|
|
registry = yaml.safe_load(f)
|
|
|
|
only = {s.strip() for s in args.only.split(",") if s.strip()}
|
|
skip = {s.strip() for s in args.skip.split(",") if s.strip()}
|
|
|
|
entries = []
|
|
for e in registry.get("datasets") or []:
|
|
if only and e["slug"] not in only:
|
|
continue
|
|
if e["slug"] in skip:
|
|
continue
|
|
entries.append(e)
|
|
|
|
if not entries:
|
|
log.warning("nothing to normalize")
|
|
return 0
|
|
|
|
caps = [c for c in (args.max_records, args.sample_per_source) if c and c > 0]
|
|
effective_cap = min(caps) if caps else None
|
|
if args.sample_per_source:
|
|
log.info("sampling ≤%d records per source (smoke mode)", effective_cap)
|
|
|
|
encoder = make_expected_response_encoder(args.expected_response_format)
|
|
try:
|
|
manifest = []
|
|
total_in = total_out = total_err = 0
|
|
for entry in entries:
|
|
log.info("normalizing %s (%s)", entry["slug"], entry["normalizer"])
|
|
n_in, n_out, n_err = normalize_dataset(
|
|
entry, max_records=effective_cap, encoder=encoder,
|
|
)
|
|
manifest.append({
|
|
"slug": entry["slug"],
|
|
"in": n_in, "out": n_out, "errors": n_err,
|
|
"license": entry.get("license", "unknown"),
|
|
"weight": float(entry.get("weight", 1.0)),
|
|
})
|
|
total_in += n_in
|
|
total_out += n_out
|
|
total_err += n_err
|
|
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
(OUT_DIR / "manifest.json").write_text(
|
|
json.dumps({
|
|
"totals": {"in": total_in, "out": total_out, "errors": total_err},
|
|
"datasets": manifest,
|
|
}, indent=2),
|
|
encoding="utf-8",
|
|
)
|
|
log.info("normalize summary: %d in, %d out, %d errors", total_in, total_out, total_err)
|
|
finally:
|
|
encoder.close()
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|