File size: 9,771 Bytes
61b6fb9 | 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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | """Public decision datasets -> common case format (see decisions.py), plus a Spanish version.
python scripts/decisions/prepare_public.py --out $DATA_ROOT/decisions/public \
--translator URL/v1/chat/completions --translator-model NAME [--jev-sample 10000]
typed-decisions (LocalLLaMA, Apache-2.0), pinned to c76749ec (the repo has moved since):
train 1,200 cases / 6,000 questions, test 400 / 2,000; choice, score and noul. Gold carries a
distribution (a ~4B teacher sampled 3 times) -> used as the soft target.
Validation = 10 % of train by id hash (same ids on the EN and ES sides).
jev-decisions-v1 (samatv256, CC-BY-4.0: keep attribution), config general-clean-50k:
agent tool selection, choice only, hard target. English only (translating would break tool
names and schemas). Up to 26 options; the long system policy is dropped, the user goal and
the last turns are kept. Random sample of --jev-sample, 5 % of it held out for validation.
Spanish: typed-decisions train and test translated by an LLM behind an OpenAI-compatible endpoint
(--translator, --translator-model; thinking disabled, temperature 0.3, so a rerun does not reproduce the
released translation byte for byte): only text values; keys, option ids and structure unchanged, checked,
else retried/dropped. The translator actually used is recorded in training/TRANSLATION_PROVENANCE.md.
Outputs (JSONL): typed_{train,val,test}_{en,es}.jsonl, jev_{train,val}.jsonl, SOURCES.md
"""
from __future__ import annotations
import argparse
import hashlib
import json
import random
import re
import sys
import threading
import urllib.request
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
TYPED = ("LocalLLaMA/typed-decisions", "c76749ec58bd8c3d2ea706b31c333a9059c38f90")
JEV = ("samatv256/jev-decisions-v1", None)
def val_side(key, frac=0.1):
return int(hashlib.sha256(str(key).encode()).hexdigest(), 16) % 1000 < frac * 1000
def js(x):
return json.loads(x) if isinstance(x, str) else x
def typed_cases(parquet, split):
import pyarrow.parquet as pq
out = []
for r in pq.read_table(parquet).to_pylist():
qs, gold = js(r["questions"]), js(r["gold"])
g, soft = {}, {}
for qid, q in qs.items():
gq = gold[qid]
lab = gq["label"]
if q["type"] == "noul":
lab = str(lab).lower()
g[qid] = [str(lab)]
probs = gq.get("probabilities")
if probs:
soft[qid] = {str(k).lower() if q["type"] == "noul" else str(k): float(v) for k, v in probs.items()}
out.append({"id": r["id"], "grupo": "typed/" + r["workflow"], "lang": "en", "state": js(r["state"]),
"questions": qs, "gold": g, "suave": soft, "split": split})
return out
def jev_cases(files, sample, rng):
import pyarrow.parquet as pq
rows = []
for f in files:
rows += pq.read_table(f, columns=["id", "source", "state", "question", "answer_options", "target_index"]).to_pylist()
rng.shuffle(rows)
out = []
for r in rows:
opts = r["answer_options"] or []
if not 2 <= len(opts) <= 26 or r["target_index"] is None or r["target_index"] >= len(opts):
continue
st = r["state"] or {}
hist = st.get("history") or []
turns = []
for h in hist[-6:]:
txt = h.get("content") or h.get("text") or json.dumps({k: v for k, v in h.items() if k != "role"}, ensure_ascii=False)
turns.append("%s: %s" % (h.get("role", "?"), str(txt)[:600]))
state = {"user_goal": st.get("user_goal") or "", "recent_history": "\n".join(turns)}
crit, keys = {}, []
for i, o in enumerate(opts):
k = "%s_%d" % (re.sub(r"[^a-z0-9]+", "_", str(o.get("label") or "opt").lower()).strip("_")[:40], i)
crit[k] = "%s: %s" % (o.get("label"), (o.get("description") or "")[:300])
keys.append(k)
out.append({"id": "jev-" + str(r["id"]), "grupo": "jev/" + str(r["source"]).split("/")[-1][:40], "lang": "en",
"state": state, "questions": {"q": {"type": "choice", "instructions": r["question"], "criteria": crit}},
"gold": {"q": [keys[r["target_index"]]]}})
if len(out) >= sample:
break
return out
# ------------------------------------------------------------------ translation
def same_shape(a, b):
"""Same keys everywhere, same non-string leaves (numbers/bools/None), strings may change."""
if isinstance(a, dict):
return isinstance(b, dict) and set(a) == set(b) and all(same_shape(a[k], b[k]) for k in a)
if isinstance(a, list):
return isinstance(b, list) and len(a) == len(b) and all(same_shape(x, y) for x, y in zip(a, b))
if isinstance(a, str):
return isinstance(b, str)
return a == b
def translate_case(url, model, case, tries=3):
# noul questions may come without criteria (literal true/false): only what exists is sent
payload = {"state": case["state"],
"questions": {qid: {k: q[k] for k in ("instructions", "criteria") if q.get(k) is not None}
for qid, q in case["questions"].items()}}
prompt = ("Traduce al español TODOS los textos de este JSON (valores de tipo texto), con redacción natural "
"de España y parafraseando si suena mejor. NO cambies ninguna clave, ni los números, ni los "
"booleanos, ni la estructura, ni los identificadores técnicos (códigos, ids, emails, rutas). "
"Devuelve SOLO el JSON traducido.\n\n" + json.dumps(payload, ensure_ascii=False))
body = {"model": model, "messages": [{"role": "user", "content": prompt}], "max_tokens": 4000,
"temperature": 0.3, "chat_template_kwargs": {"enable_thinking": False}}
for _ in range(tries):
try:
req = urllib.request.Request(url, json.dumps(body).encode(), {"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=300) as r:
txt = json.loads(r.read())["choices"][0]["message"]["content"] or ""
m = re.search(r"\{.*\}", txt, re.S)
tr = json.loads(m.group(0)) if m else None
if tr is not None and same_shape(payload, tr):
es = dict(case, lang="es", id=case["id"] + "-es", state=tr["state"], questions={
qid: dict(q, **tr["questions"][qid]) for qid, q in case["questions"].items()})
return es
except Exception:
pass
return None
def write(path, cases):
with open(path, "w", encoding="utf-8") as f:
for c in cases:
f.write(json.dumps(c, ensure_ascii=False) + "\n")
print(" %-26s %6d cases, %6d questions" % (Path(path).name, len(cases), sum(len(c["questions"]) for c in cases)))
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--out", type=Path, required=True)
ap.add_argument("--translator", required=True, help="OpenAI-compatible chat completions URL")
ap.add_argument("--translator-model", required=True,
help="model name sent to --translator (historical value: see TRANSLATION_PROVENANCE.md)")
ap.add_argument("--threads", type=int, default=16)
ap.add_argument("--jev-sample", type=int, default=10000)
ap.add_argument("--seed", type=int, default=13)
a = ap.parse_args()
a.out.mkdir(parents=True, exist_ok=True)
rng = random.Random(a.seed)
from huggingface_hub import hf_hub_download, list_repo_files
tr_p = hf_hub_download(TYPED[0], "all/train-00000-of-00001.parquet", repo_type="dataset", revision=TYPED[1])
te_p = hf_hub_download(TYPED[0], "all/test-00000-of-00001.parquet", repo_type="dataset", revision=TYPED[1])
train_all, test = typed_cases(tr_p, "train"), typed_cases(te_p, "test")
train = [c for c in train_all if not val_side(c["id"])]
val = [c for c in train_all if val_side(c["id"])]
print("typed-decisions @%s:" % TYPED[1][:8])
write(a.out / "typed_train_en.jsonl", train)
write(a.out / "typed_val_en.jsonl", val)
write(a.out / "typed_test_en.jsonl", test)
jev_files = [f for f in list_repo_files(JEV[0], repo_type="dataset") if f.startswith("clean50k/data/") and f.endswith(".parquet")]
local = [hf_hub_download(JEV[0], f, repo_type="dataset") for f in sorted(jev_files)]
jev = jev_cases(local, a.jev_sample, rng)
print("jev-decisions-v1 (general-clean-50k):")
write(a.out / "jev_train.jsonl", [c for c in jev if not val_side(c["id"], 0.05)])
write(a.out / "jev_val.jsonl", [c for c in jev if val_side(c["id"], 0.05)])
print("translating typed-decisions to Spanish with %s ..." % a.translator, flush=True)
for name, cases in (("train", train), ("val", val), ("test", test)):
done, lock = [], threading.Lock()
with ThreadPoolExecutor(a.threads) as ex:
for es in ex.map(lambda c: translate_case(a.translator, a.translator_model, c), cases):
if es is not None:
with lock:
done.append(es)
print(" %s: %d of %d translated with identical structure" % (name, len(done), len(cases)), flush=True)
write(a.out / ("typed_%s_es.jsonl" % name), done)
(a.out / "SOURCES.md").write_text(
"# Sources\n\n"
"- LocalLLaMA/typed-decisions @%s (Apache-2.0). Spanish files: machine translation by %s.\n"
"- samatv256/jev-decisions-v1, config general-clean-50k (CC-BY-4.0, see its SOURCE_LICENSES.md;\n"
" upstream NVIDIA Nemotron agentic datasets). Sample of %d, reformatted.\n" % (TYPED[1], a.translator_model, a.jev_sample))
if __name__ == "__main__":
sys.exit(main())
|