| """DEBATE → ChatML extraction for ARCHON SFT v2 family_multi_agent. |
| |
| Schema source (Multi-Agent-LLMs/DEBATE): |
| - globalMemory: list of turn dicts with {agent_id, persona, message, contribution, turn, agreement} |
| - personas: list of {agentId, persona, personaDescription} |
| - instruction: task prompt |
| - input: problem context |
| - paradigm: debate|relay|report|memory |
| - agreements: vote records |
| |
| Output ChatML format: |
| { |
| "messages": [ |
| {"role": "system", "content": "<peer-equal team description>"}, |
| {"role": "user", "content": "<instruction + input>"}, |
| {"role": "assistant", "content": "<multi-turn transcript with persona prefixes>"} |
| ], |
| "task_type": "family_negotiate|family_consult", |
| "niveau": "N2|N3", |
| "peers_involved": ["Marine Biologist", "Food Safety Specialist", ...], |
| "language": "en", |
| "source_ds": "Multi-Agent-LLMs/DEBATE", |
| "paradigm": "debate|relay|report|memory" |
| } |
| |
| CPU-only. No GPU. Streams configs sequentially. |
| |
| Usage: |
| python debate_extract_v2.py --output debate_chatml.jsonl --rows-per-config 30 |
| """ |
| from __future__ import annotations |
| import argparse |
| import json |
| import sys |
| import time |
| from pathlib import Path |
|
|
| try: |
| from datasets import load_dataset, get_dataset_config_names |
| except ImportError: |
| print("ERR: pip install datasets", file=sys.stderr) |
| sys.exit(1) |
|
|
| REPO = "Multi-Agent-LLMs/DEBATE" |
|
|
| PARADIGM_TO_NIVEAU = { |
| "debate": ("family_negotiate", "N3"), |
| "relay": ("family_consult", "N2"), |
| "report": ("family_consult", "N2"), |
| "memory": ("family_consult", "N2"), |
| } |
|
|
| SYSTEM_TEMPLATE = ( |
| "Vous êtes membre d'une équipe d'experts collaborant sur un problème. " |
| "Aucun maître, voix égales. Chaque expert contribue sa spécialité, " |
| "puis l'équipe décide par consensus ou vote. Persona attribuée : {personas}." |
| ) |
|
|
|
|
| def detect_paradigm(config_name: str) -> str | None: |
| """Extract paradigm from config name (e.g. 'critical_expert_debate_approval_voting' -> 'debate').""" |
| for paradigm in ("debate", "relay", "report", "memory"): |
| if f"_{paradigm}_" in config_name: |
| return paradigm |
| return None |
|
|
|
|
| def extract_sample(sample: dict, paradigm: str, source_ds: str = REPO) -> dict | None: |
| """Convert one DEBATE sample to ChatML multi-agent transcript.""" |
| gm = sample.get("globalMemory") |
| if not isinstance(gm, list) or len(gm) < 3: |
| return None |
|
|
| personas_meta = sample.get("personas") or [] |
| personas_seen = [] |
| for p in personas_meta: |
| if isinstance(p, dict): |
| name = p.get("persona") or "Expert" |
| if name not in personas_seen: |
| personas_seen.append(name) |
| if not personas_seen: |
| |
| for turn in gm: |
| name = (turn.get("persona") or "").strip() |
| if name and name not in personas_seen: |
| personas_seen.append(name) |
|
|
| if len(personas_seen) < 2: |
| return None |
|
|
| |
| raw_instr = sample.get("instruction") |
| raw_input = sample.get("input") |
| instruction = "" |
| if isinstance(raw_instr, str): |
| instruction = raw_instr.strip() |
| elif isinstance(raw_instr, list): |
| instruction = "\n".join(str(x) for x in raw_instr if x).strip() |
| input_str = "" |
| if isinstance(raw_input, str): |
| input_str = raw_input.strip() |
| elif isinstance(raw_input, list): |
| input_str = "\n".join(str(x) for x in raw_input if x).strip() |
| if input_str and input_str not in instruction: |
| user_content = f"{instruction}\n\n{input_str}".strip() |
| else: |
| user_content = instruction or input_str |
| if len(user_content) < 20: |
| return None |
|
|
| |
| lines = [] |
| for turn in gm: |
| if not isinstance(turn, dict): |
| continue |
| persona = (turn.get("persona") or "Expert").strip() |
| msg = (turn.get("message") or "").strip() |
| contrib = (turn.get("contribution") or "").strip() |
| if not msg: |
| continue |
| |
| if len(msg) < 30: |
| continue |
| prefix = f"**{persona}**" |
| if contrib and contrib not in ("draft", ""): |
| prefix += f" *({contrib})*" |
| lines.append(f"{prefix}: {msg}") |
|
|
| if len(lines) < 3: |
| return None |
|
|
| assistant_content = "\n\n".join(lines) |
| if len(assistant_content) > 12000: |
| |
| assistant_content = assistant_content[:11800] + "\n\n[...transcript tronqué pour longueur...]" |
|
|
| task_type, niveau = PARADIGM_TO_NIVEAU.get(paradigm, ("family_negotiate", "N3")) |
|
|
| system_content = SYSTEM_TEMPLATE.format(personas=", ".join(personas_seen)) |
|
|
| return { |
| "messages": [ |
| {"role": "system", "content": system_content}, |
| {"role": "user", "content": user_content}, |
| {"role": "assistant", "content": assistant_content}, |
| ], |
| "task_type": task_type, |
| "niveau": niveau, |
| "peers_involved": personas_seen, |
| "language": "en", |
| "source_ds": source_ds, |
| "paradigm": paradigm, |
| "n_turns": len(lines), |
| } |
|
|
|
|
| def main() -> int: |
| ap = argparse.ArgumentParser() |
| ap.add_argument("--output", required=True, help="Output JSONL path") |
| ap.add_argument("--rows-per-config", type=int, default=30, |
| help="Max samples to extract per config (default 30, 145 configs * 30 = 4350 target)") |
| ap.add_argument("--max-configs", type=int, default=145, |
| help="Max configs to process (default all)") |
| ap.add_argument("--paradigms", nargs="+", |
| default=["debate", "relay", "report", "memory"], |
| help="Paradigms to extract") |
| args = ap.parse_args() |
|
|
| out_path = Path(args.output) |
| out_path.parent.mkdir(parents=True, exist_ok=True) |
|
|
| print(f"Listing configs...") |
| cfgs = get_dataset_config_names(REPO) |
| cfgs = [c for c in cfgs if c != "_preview"] |
| print(f"Total configs available: {len(cfgs)}") |
|
|
| |
| selected = [] |
| for c in cfgs: |
| p = detect_paradigm(c) |
| if p and p in args.paradigms: |
| selected.append((c, p)) |
| print(f"Configs matching paradigms {args.paradigms}: {len(selected)}") |
| selected = selected[:args.max_configs] |
| print(f"Processing {len(selected)} configs, {args.rows_per_config} rows each (target ~{len(selected) * args.rows_per_config})") |
|
|
| total_written = 0 |
| total_failed = 0 |
| paradigm_counts = {} |
| t0 = time.time() |
|
|
| with open(out_path, "w", encoding="utf-8") as f: |
| for ci, (config_name, paradigm) in enumerate(selected, 1): |
| try: |
| ds = load_dataset(REPO, config_name, split="train", streaming=True) |
| except Exception as e: |
| print(f"[{ci}/{len(selected)}] LOAD_FAIL {config_name}: {type(e).__name__}", file=sys.stderr) |
| continue |
|
|
| written_this_cfg = 0 |
| for i, sample in enumerate(ds): |
| if i >= args.rows_per_config: |
| break |
| try: |
| out = extract_sample(sample, paradigm) |
| if out is None: |
| total_failed += 1 |
| continue |
| f.write(json.dumps(out, ensure_ascii=False) + "\n") |
| written_this_cfg += 1 |
| total_written += 1 |
| paradigm_counts[paradigm] = paradigm_counts.get(paradigm, 0) + 1 |
| except Exception as e: |
| total_failed += 1 |
| if total_failed <= 2: |
| import traceback |
| print(f" [extract_fail] {config_name}#{i}: {type(e).__name__}: {e}", file=sys.stderr) |
| traceback.print_exc(file=sys.stderr) |
| print(f" sample keys: {list(sample.keys())}", file=sys.stderr) |
| print(f" personas type: {type(sample.get('personas')).__name__}", file=sys.stderr) |
| print(f" globalMemory type: {type(sample.get('globalMemory')).__name__}", file=sys.stderr) |
| gm = sample.get('globalMemory') |
| if isinstance(gm, list) and gm: |
| print(f" gm[0] type: {type(gm[0]).__name__}", file=sys.stderr) |
| print(f" gm[0] repr (200ch): {repr(gm[0])[:200]}", file=sys.stderr) |
|
|
| elapsed = time.time() - t0 |
| rate = total_written / max(1, elapsed) |
| if ci % 10 == 0 or ci == len(selected): |
| print(f"[{ci}/{len(selected)}] {config_name[:40]:40s} written={written_this_cfg:2d} total={total_written:5d} rate={rate:.1f}/s elapsed={elapsed:.0f}s") |
|
|
| print(f"\n=== DONE ===") |
| print(f"Total written: {total_written}") |
| print(f"Total failed/skipped: {total_failed}") |
| print(f"By paradigm: {paradigm_counts}") |
| print(f"Output: {out_path}") |
| print(f"Elapsed: {time.time() - t0:.0f}s") |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|