| |
| |
| """Convert benchmark samples into generic thinker+talker input JSONL.""" |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| from pathlib import Path |
| from typing import Any |
|
|
|
|
| def read_json(path: Path) -> Any: |
| with path.open("r", encoding="utf-8") as f: |
| return json.load(f) |
|
|
|
|
| def read_jsonl(path: Path) -> list[dict[str, Any]]: |
| rows = [] |
| with path.open("r", encoding="utf-8") as f: |
| for line in f: |
| line = line.strip() |
| if line: |
| rows.append(json.loads(line)) |
| return rows |
|
|
|
|
| def write_jsonl(path: Path, rows: list[dict[str, Any]]) -> None: |
| path.parent.mkdir(parents=True, exist_ok=True) |
| with path.open("w", encoding="utf-8") as f: |
| for row in rows: |
| f.write(json.dumps(row, ensure_ascii=False) + "\n") |
|
|
|
|
| def abs_or_join(root: Path, maybe_rel: str) -> str: |
| p = Path(maybe_rel) |
| if p.is_absolute(): |
| return str(p) |
| return str((root / p).resolve()) |
|
|
|
|
| def parse_echomind_demo_name(path: Path) -> dict[str, str]: |
| stem = path.stem |
| if stem.endswith("_response"): |
| stem = stem[: -len("_response")] |
| parts = stem.split("_") |
| sample_id = "_".join(parts[:2]) if len(parts) >= 2 else stem |
| variant = parts[-1] if parts and parts[-1] in {"target", "neutral", "alternative"} else "" |
| middle = parts[2:-1] if variant else parts[2:] |
| cue = middle[0] if middle else "" |
| topic = "_".join(middle[1:]) if len(middle) > 1 else "" |
| return { |
| "sample_id": sample_id, |
| "cue": cue.replace("_", " "), |
| "topic": topic.replace("_", " "), |
| "variant": variant, |
| } |
|
|
|
|
| def echomind_demo_response(args: argparse.Namespace) -> None: |
| root = Path(args.root_dir).resolve() |
| audio_dir = root / "Audio" |
| wavs = sorted( |
| p |
| for p in audio_dir.glob("*.wav") |
| if not p.stem.endswith("_response") |
| and (not args.voice_type or any(p.stem.endswith(f"_{v}") for v in args.voice_type)) |
| ) |
| out = [] |
| for idx, wav in enumerate(wavs): |
| meta = parse_echomind_demo_name(wav) |
| system = ( |
| "This is an EchoMind demo response-generation sample. " |
| "Listen to the user's speech and generate a concise empathetic reply. " |
| "Use both spoken content and paralinguistic cues. " |
| "The filename metadata is only a weak hint, not a substitute for listening." |
| ) |
| if meta["topic"] or meta["cue"] or meta["variant"]: |
| system += ( |
| f" Metadata hint: topic={meta['topic'] or 'unknown'}, " |
| f"vocal_cue={meta['cue'] or 'unknown'}, style_variant={meta['variant'] or 'unknown'}." |
| ) |
| out.append( |
| { |
| "id": f"echomind_demo_resp_{idx:04d}", |
| "orig_id": meta["sample_id"], |
| "benchmark": "EchoMind", |
| "task": "demo_response", |
| "language": args.language, |
| "audio_path": str(wav), |
| "cue_hint": meta["cue"], |
| "topic_hint": meta["topic"], |
| "voice_type": meta["variant"], |
| "system_prompt": system, |
| "user_prompt": ( |
| "Listen to the audio and provide one emotionally appropriate spoken response. " |
| "Answer only with the response text." |
| ), |
| } |
| ) |
| if args.limit: |
| out = out[: args.limit] |
| write_jsonl(Path(args.out), out) |
| print(f"[write] {args.out} rows={len(out)}") |
|
|
|
|
| def echomind_demo_mcq_lite(args: argparse.Namespace) -> None: |
| root = Path(args.root_dir).resolve() |
| audio_dir = root / "MCQ" |
| wavs = sorted(p for p in audio_dir.glob("*.wav") if not p.stem.endswith("_response")) |
| out = [] |
| for idx, wav in enumerate(wavs): |
| meta = parse_echomind_demo_name(wav) |
| out.append( |
| { |
| "id": f"echomind_demo_mcq_lite_{idx:04d}", |
| "orig_id": meta["sample_id"], |
| "benchmark": "EchoMind", |
| "task": "demo_mcq_lite", |
| "language": args.language, |
| "audio_path": str(wav), |
| "cue_hint": meta["cue"], |
| "topic_hint": meta["topic"], |
| "voice_type": meta["variant"], |
| "system_prompt": ( |
| "This is an EchoMind demo vocal-cue perception sample. " |
| "No official multiple-choice options are available in the demo folder, " |
| "so describe the most salient vocal cue or emotional state in a short phrase." |
| ), |
| "user_prompt": ( |
| "Listen to the audio. Identify the most salient vocal cue or emotional state " |
| "in a concise phrase." |
| ), |
| } |
| ) |
| if args.limit: |
| out = out[: args.limit] |
| write_jsonl(Path(args.out), out) |
| print(f"[write] {args.out} rows={len(out)}") |
|
|
|
|
| def uro_single(args: argparse.Namespace) -> None: |
| dataset = Path(args.dataset).resolve() |
| rows = read_jsonl(dataset) |
| data_root = dataset.parent |
| out = [] |
| for idx, item in enumerate(rows): |
| sample_id = str(item.get("id") or item.get("sample_id") or f"{idx:04d}") |
| target_text = item.get("target_text", item.get("source_text", "")) |
| if isinstance(target_text, list): |
| target_joined = " / ".join(str(x) for x in target_text) |
| else: |
| target_joined = str(target_text) |
| out.append( |
| { |
| "id": f"{idx:04d}", |
| "orig_id": sample_id, |
| "benchmark": "URO-Bench", |
| "task": args.dataset_name or dataset.parent.name, |
| "language": args.language, |
| "audio_path": abs_or_join(data_root, item["source_wav"]), |
| "source_text": item.get("source_text", ""), |
| "target_text": target_joined, |
| "system_prompt": args.system_prompt or "", |
| "user_prompt": args.user_prompt |
| or "Listen to the input audio and respond directly to the user's spoken request. Do not explain your reasoning.", |
| } |
| ) |
| if args.limit: |
| out = out[: args.limit] |
| write_jsonl(Path(args.out), out) |
| print(f"[write] {args.out} rows={len(out)}") |
|
|
|
|
| def echomind_asr(args: argparse.Namespace) -> None: |
| root = Path(args.root_dir).resolve() |
| input_dir = root / "dataset" / f"data_{args.data_type}" |
| data = read_json(input_dir / f"script_info_{args.data_type}.json") |
| out = [] |
| for d in data: |
| info = d["target_audio_info"] |
| out.append( |
| { |
| "id": f"{d['case_id']}__asr", |
| "case_id": d["case_id"], |
| "benchmark": "EchoMind", |
| "task": "asr", |
| "language": args.language, |
| "audio_path": str((input_dir / "audio" / info["target_input_audio_file"]).resolve()), |
| "source_text": d.get("script", ""), |
| "target_text": d.get("script", ""), |
| "system_prompt": "You are an ASR engine for this task.", |
| "user_prompt": "Please transcribe the speech in the input audio into text. Text must contain only the transcript.", |
| } |
| ) |
| if args.limit: |
| out = out[: args.limit] |
| write_jsonl(Path(args.out), out) |
| print(f"[write] {args.out} rows={len(out)}") |
|
|
|
|
| def echomind_mcq(args: argparse.Namespace) -> None: |
| root = Path(args.root_dir).resolve() |
| input_dir = root / "dataset" / f"data_{args.data_type}" |
| rows = read_json(input_dir / "MCQ" / args.mcq_file) |
| out = [] |
| for d in rows: |
| out.append( |
| { |
| "id": f"{d['question_id']}__{Path(args.mcq_file).stem}", |
| "question_id": d["question_id"], |
| "benchmark": "EchoMind", |
| "task": f"mcq_{Path(args.mcq_file).stem}", |
| "language": args.language, |
| "audio_path": str((input_dir / "audio" / d["audio_dir"] / d["audio_name"]).resolve()), |
| "question": d.get("question", ""), |
| "options_text": d.get("options_text", ""), |
| "answer": d.get("answer", ""), |
| "system_prompt": ( |
| "Determine the correct answer to a multiple-choice question based on the audio clip. " |
| "Analyze the audio and select the most accurate answer without extra explanation." |
| ), |
| "user_prompt": ( |
| f"Question: {d.get('question', '')}\nOptions:\n{d.get('options_text', '')}\n" |
| "Please provide your answer exactly in the format: 'The answer is: [A/B/C/D]'." |
| ), |
| } |
| ) |
| if args.limit: |
| out = out[: args.limit] |
| write_jsonl(Path(args.out), out) |
| print(f"[write] {args.out} rows={len(out)}") |
|
|
|
|
| def echomind_response(args: argparse.Namespace) -> None: |
| root = Path(args.root_dir).resolve() |
| input_dir = root / "dataset" / f"data_{args.data_type}" |
| data = read_json(input_dir / f"script_info_{args.data_type}.json") |
| voice_types = [v.strip() for v in args.voice_type.split(",") if v.strip()] |
| out = [] |
| for d in data: |
| for voice_type in voice_types: |
| key = f"{voice_type}_audio_info" |
| if key not in d: |
| continue |
| info = d[key] |
| out.append( |
| { |
| "id": f"{d['case_id']}__response__{voice_type}", |
| "case_id": d["case_id"], |
| "voice_type": voice_type, |
| "benchmark": "EchoMind", |
| "task": f"response_{args.system_prompt}", |
| "language": args.language, |
| "audio_path": str((input_dir / "audio" / info[f"{voice_type}_input_audio_file"]).resolve()), |
| "topic": d.get("topic", ""), |
| "spoken_info": d.get("spoken_info", ""), |
| "system_prompt": args.system_prompt_text |
| or ( |
| f"The current topic/scenario is: {d.get('topic', '')}. " |
| "Provide a one-turn empathetic response based on the input audio. " |
| "Use both spoken content and voice cues when they are useful." |
| ), |
| "user_prompt": "Listen to the audio and provide a concise, emotionally appropriate one-turn response.", |
| } |
| ) |
| if args.limit: |
| out = out[: args.limit] |
| write_jsonl(Path(args.out), out) |
| print(f"[write] {args.out} rows={len(out)}") |
|
|
|
|
| def generic_jsonl(args: argparse.Namespace) -> None: |
| rows = read_jsonl(Path(args.input)) |
| out = [] |
| for idx, item in enumerate(rows): |
| audio = item.get(args.audio_field) |
| if not audio: |
| continue |
| out.append( |
| { |
| "id": str(item.get(args.id_field) or f"{idx:04d}"), |
| "benchmark": args.benchmark, |
| "task": item.get("task", args.task), |
| "language": item.get("language", args.language), |
| "audio_path": abs_or_join(Path(args.audio_root).resolve(), audio), |
| "source_text": item.get("source_text", item.get("question", "")), |
| "target_text": item.get("target_text", item.get("answer", "")), |
| "system_prompt": item.get("system_prompt", args.system_prompt or ""), |
| "user_prompt": item.get(args.prompt_field, args.user_prompt), |
| } |
| ) |
| if args.limit: |
| out = out[: args.limit] |
| write_jsonl(Path(args.out), out) |
| print(f"[write] {args.out} rows={len(out)}") |
|
|
|
|
| def main() -> None: |
| ap = argparse.ArgumentParser() |
| sub = ap.add_subparsers(dest="cmd", required=True) |
|
|
| p = sub.add_parser("uro-single") |
| p.add_argument("--dataset", required=True) |
| p.add_argument("--out", required=True) |
| p.add_argument("--language", default="en") |
| p.add_argument("--dataset-name", default="") |
| p.add_argument("--system-prompt", default="") |
| p.add_argument("--user-prompt", default="") |
| p.add_argument("--limit", type=int, default=0) |
| p.set_defaults(func=uro_single) |
|
|
| p = sub.add_parser("echomind-demo-response") |
| p.add_argument("--root-dir", required=True) |
| p.add_argument("--out", required=True) |
| p.add_argument("--language", default="en") |
| p.add_argument("--voice-type", nargs="*", default=["target", "neutral", "alternative"]) |
| p.add_argument("--limit", type=int, default=0) |
| p.set_defaults(func=echomind_demo_response) |
|
|
| p = sub.add_parser("echomind-demo-mcq-lite") |
| p.add_argument("--root-dir", required=True) |
| p.add_argument("--out", required=True) |
| p.add_argument("--language", default="en") |
| p.add_argument("--limit", type=int, default=0) |
| p.set_defaults(func=echomind_demo_mcq_lite) |
|
|
| p = sub.add_parser("echomind-asr") |
| p.add_argument("--root-dir", required=True) |
| p.add_argument("--data-type", default="synthesis") |
| p.add_argument("--out", required=True) |
| p.add_argument("--language", default="en") |
| p.add_argument("--limit", type=int, default=0) |
| p.set_defaults(func=echomind_asr) |
|
|
| p = sub.add_parser("echomind-mcq") |
| p.add_argument("--root-dir", required=True) |
| p.add_argument("--data-type", default="synthesis") |
| p.add_argument("--mcq-file", required=True) |
| p.add_argument("--out", required=True) |
| p.add_argument("--language", default="en") |
| p.add_argument("--limit", type=int, default=0) |
| p.set_defaults(func=echomind_mcq) |
|
|
| p = sub.add_parser("echomind-response") |
| p.add_argument("--root-dir", required=True) |
| p.add_argument("--data-type", default="synthesis") |
| p.add_argument("--voice-type", default="target,neutral,alternative") |
| p.add_argument("--system-prompt", default="enhance") |
| p.add_argument("--system-prompt-text", default="") |
| p.add_argument("--out", required=True) |
| p.add_argument("--language", default="en") |
| p.add_argument("--limit", type=int, default=0) |
| p.set_defaults(func=echomind_response) |
|
|
| p = sub.add_parser("generic-jsonl") |
| p.add_argument("--input", required=True) |
| p.add_argument("--out", required=True) |
| p.add_argument("--benchmark", required=True) |
| p.add_argument("--task", default="") |
| p.add_argument("--language", default="en") |
| p.add_argument("--audio-root", default=".") |
| p.add_argument("--audio-field", default="audio_path") |
| p.add_argument("--id-field", default="id") |
| p.add_argument("--prompt-field", default="question") |
| p.add_argument("--system-prompt", default="") |
| p.add_argument("--user-prompt", default="Listen to the audio and answer directly.") |
| p.add_argument("--limit", type=int, default=0) |
| p.set_defaults(func=generic_jsonl) |
|
|
| args = ap.parse_args() |
| args.func(args) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|