# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project from __future__ import annotations import hashlib import json import sys from types import SimpleNamespace import pytest from vllm_omni.benchmarks.duplex import omni_duplex_eval_eval as eval_module from vllm_omni.benchmarks.duplex.omni_duplex_eval_clock import ( extract_timed_sentences, normalize_response_items, split_text, validate_clock, ) from vllm_omni.benchmarks.duplex.omni_duplex_eval_dataset import ( DuplexSample, canonical_task_type, family_for_split, load_samples, task_type_for_split, ) from vllm_omni.benchmarks.duplex.omni_duplex_eval_metrics import ( PROTOCOL_PIN, build_content_prompt, build_reminder_prompt, build_temporal_prompt, parse_judge_json, reminder_window, summarize_pr_results, temporal_window, ) pytestmark = [pytest.mark.core_model, pytest.mark.cpu] def test_dataset_mapping_and_manifest(tmp_path): manifest = tmp_path / "samples.json" manifest.write_text(json.dumps([{"id": "a", "split": "RTD_OCR", "video": "clip.mp4"}]), encoding="utf-8") sample = load_samples(manifest, media_root=tmp_path)[0] assert sample.family == "rtd" assert sample.video == str(tmp_path / "clip.mp4") assert family_for_split("PR_event_reminder") == "pr" assert canonical_task_type("post-event-reminder") == "post_event_reminder" assert task_type_for_split("PR_correction") == "correction" assert load_samples([{"id": "pr", "split": "PR_correction"}])[0].task_type == "correction" def test_hugging_face_subset_names_are_splits(monkeypatch): calls = [] def fake_load_dataset(name, **kwargs): calls.append((name, kwargs)) return [{"id": "pr", "question_text": "Correct this."}] monkeypatch.setitem(sys.modules, "datasets", SimpleNamespace(load_dataset=fake_load_dataset)) sample = load_samples("Hothan/Omni-DuplexEval", split="PR_correction")[0] assert calls == [("Hothan/Omni-DuplexEval", {"split": "PR_correction"})] assert sample.split == "PR_correction" assert sample.task_type == "correction" def test_local_hf_dataset_directory_is_routed_to_hf_loader(tmp_path, monkeypatch): # An existing local Hugging Face dataset layout (directory with a # ``data/*.parquet`` file) must be loaded through ``datasets.load_dataset`` # instead of being parsed as a JSON/JSONL manifest. hf_dir = tmp_path / "Omni-DuplexEval" data_dir = hf_dir / "data" data_dir.mkdir(parents=True) (data_dir / "train-00000-of-00001.parquet").write_bytes(b"PAR1") calls = [] def fake_load_dataset(name, **kwargs): calls.append((name, kwargs)) return [{"id": "pr", "split": "PR_correction", "question_text": "Correct this."}] monkeypatch.setitem(sys.modules, "datasets", SimpleNamespace(load_dataset=fake_load_dataset)) samples = load_samples(hf_dir) assert calls == [(str(hf_dir), {})] assert len(samples) == 1 assert samples[0].task_type == "correction" def test_local_parquet_file_is_routed_to_hf_loader(tmp_path, monkeypatch): parquet = tmp_path / "samples.parquet" parquet.write_bytes(b"PAR1") calls = [] def fake_load_dataset(name, **kwargs): calls.append((name, kwargs)) return [{"id": "pr", "split": "PR_correction", "question_text": "Correct this."}] monkeypatch.setitem(sys.modules, "datasets", SimpleNamespace(load_dataset=fake_load_dataset)) samples = load_samples(str(parquet)) assert calls == [("parquet", {"data_files": str(parquet)})] assert len(samples) == 1 assert samples[0].family == "pr" def test_hf_collapse_to_train_keeps_row_split_identity(tmp_path, monkeypatch): # datasets.load_dataset collapses a config-less local mirror (a bare data/ # directory) into a single generic ``train`` split. Rows that already carry # their own split/subset identity must keep it so family inference is not # poisoned by the generic name (regression for the direct-read path). hf_dir = tmp_path / "Omni-DuplexEval" data_dir = hf_dir / "data" data_dir.mkdir(parents=True) (data_dir / "train-00000-of-00021.parquet").write_bytes(b"PAR1") def fake_load_dataset(name, **kwargs): return { "train": [ {"id": "pr", "split": "PR_correction", "question_text": "Correct this."}, {"id": "pr-subset", "subset": "PR_event_reminder", "question_text": "Remind me."}, {"id": "rtd", "split": "RTD_OCR", "question_text": "Read this."}, ] } monkeypatch.setitem(sys.modules, "datasets", SimpleNamespace(load_dataset=fake_load_dataset)) samples = load_samples(hf_dir) by_id = {sample.id: sample for sample in samples} assert by_id["pr"].split == "PR_correction" assert by_id["pr"].family == "pr" assert by_id["pr"].task_type == "correction" assert by_id["pr-subset"].split == "PR_event_reminder" assert by_id["pr-subset"].family == "pr" assert by_id["pr-subset"].task_type == "proactive_reminder" assert by_id["rtd"].split == "RTD_OCR" assert by_id["rtd"].family == "rtd" def test_hf_config_layout_stamps_split_name_when_row_has_none(tmp_path, monkeypatch): # With a proper config-per-split mirror the loader still stamps the Hugging # Face split name onto rows that do not identify themselves (unchanged). hf_dir = tmp_path / "Omni-DuplexEval" data_dir = hf_dir / "data" data_dir.mkdir(parents=True) (data_dir / "RTD_OCR-00000-of-00001.parquet").write_bytes(b"PAR1") (data_dir / "PR_correction-00000-of-00001.parquet").write_bytes(b"PAR1") def fake_load_dataset(name, **kwargs): return { "RTD_OCR": [{"id": "rtd", "question_text": "Read this."}], "PR_correction": [{"id": "pr", "question_text": "Correct this."}], } monkeypatch.setitem(sys.modules, "datasets", SimpleNamespace(load_dataset=fake_load_dataset)) samples = load_samples(hf_dir) by_id = {sample.id: sample for sample in samples} assert by_id["rtd"].split == "RTD_OCR" assert by_id["rtd"].family == "rtd" assert by_id["pr"].split == "PR_correction" assert by_id["pr"].family == "pr" assert by_id["pr"].task_type == "correction" def test_hf_collapse_to_train_without_row_identity_raises_actionable_error(tmp_path, monkeypatch): # A row that carries no split/family/task_type and lands in a generic # collapsed ``train`` split cannot be routed; the loader must raise a clear, # actionable ValueError instead of the bare family_for_split message. hf_dir = tmp_path / "Omni-DuplexEval" data_dir = hf_dir / "data" data_dir.mkdir(parents=True) (data_dir / "train-00000-of-00021.parquet").write_bytes(b"PAR1") def fake_load_dataset(name, **kwargs): return {"train": [{"id": "x", "question_text": "What is this?"}]} monkeypatch.setitem(sys.modules, "datasets", SimpleNamespace(load_dataset=fake_load_dataset)) with pytest.raises(ValueError, match="Hugging Face splits"): load_samples(hf_dir) def test_non_manifest_file_raises_clear_error(tmp_path): # A plain non-JSON file (e.g. a README) must surface a clear ValueError # listing the accepted inputs instead of a raw JSONDecodeError. bad = tmp_path / "README.md" bad.write_text("# readme\nnot a manifest", encoding="utf-8") with pytest.raises(ValueError, match="JSON/JSONL manifest"): load_samples(str(bad)) def _write_parquet(path, rows): pyarrow = pytest.importorskip("pyarrow") pyarrow.parquet.write_table(pyarrow.Table.from_pylist(rows), str(path)) def test_local_parquet_split_filters_rows_by_identity(tmp_path): # Regression: a single ``.parquet`` file is exposed by ``load_dataset`` as a # generic ``train`` physical split, so ``--split RTD_OCR`` used to raise # ``Unknown split "RTD_OCR"``. The requested split must instead filter the # rows by their preserved identity. pytest.importorskip("datasets") parquet = tmp_path / "samples.parquet" _write_parquet( parquet, [ {"id": "rtd", "split": "RTD_OCR", "question_text": "Read this."}, {"id": "pr", "split": "PR_correction", "question_text": "Correct this."}, ], ) assert {sample.id for sample in load_samples(str(parquet))} == {"rtd", "pr"} rtd = load_samples(str(parquet), split="RTD_OCR") assert [sample.id for sample in rtd] == ["rtd"] assert rtd[0].family == "rtd" pr = load_samples(str(parquet), split="PR_correction") assert [sample.id for sample in pr] == ["pr"] assert pr[0].family == "pr" assert pr[0].task_type == "correction" def test_local_directory_named_splits_resolve_without_row_identity(tmp_path): # Regression: a single-configuration mirror whose data files are named after # the benchmark splits must resolve each split for real (no fake loader) and # without any per-row identity. pytest.importorskip("datasets") hf_dir = tmp_path / "Omni-DuplexEval" data_dir = hf_dir / "data" data_dir.mkdir(parents=True) _write_parquet(data_dir / "RTD_OCR-00000-of-00001.parquet", [{"id": "rtd", "question_text": "Read this."}]) _write_parquet( data_dir / "PR_correction-00000-of-00001.parquet", [{"id": "pr", "question_text": "Correct this."}], ) by_id = {sample.id: sample for sample in load_samples(hf_dir)} assert by_id["rtd"].split == "RTD_OCR" assert by_id["rtd"].family == "rtd" assert by_id["pr"].split == "PR_correction" assert by_id["pr"].family == "pr" assert by_id["pr"].task_type == "correction" assert [sample.id for sample in load_samples(hf_dir, split="RTD_OCR")] == ["rtd"] def test_response_aliases_and_clock_guard(): assert split_text("One. Two!") == ["One.", "Two!"] assert normalize_response_items({"chunks": [{"text": "x", "current_time": 800}]}) == [ {"sentence": "x", "start": 800.0, "end": 800.0} ] with pytest.raises(ValueError, match="clock=invalid"): validate_clock({"clock": "invalid"}) validate_clock({"clock": "invalid"}, allow_invalid=True) timed = extract_timed_sentences([{"type": "response.output_text.delta", "delta": "Done.", "_media_clock_ms": 800}]) assert [item.as_dict() for item in timed] == [{"sentence": "Done.", "start": 0.8, "end": 0.8}] with pytest.raises(ValueError, match="only clock=media"): extract_timed_sentences([], clock="wall") def test_protocol_windows_and_parsing(): assert PROTOCOL_PIN == "ca3c122b4d4bf67afd6b18ea5e724b4561bdde48" assert temporal_window(4, 5, 10) == (2.0, 3.0) assert temporal_window(1, 1.2, 10) == (0.0, 0.5) assert reminder_window(5) == (5.0, 15.0) assert parse_judge_json('noise {"content_score": 2.345, "is_relevant": 1}') == { "content_score": 2.35, "is_relevant": 1, } assert parse_judge_json('noise "success_score": 1') == {"success_score": 1} assert summarize_pr_results([{"task_type": "correction", "all_success": 1}])["mean_all_success"] == 1.0 def test_protocol_prompt_wording_is_pinned(): prompts = [ build_temporal_prompt(1, 2, "response", "question"), build_content_prompt("response", "question", ["one", "two"]), build_reminder_prompt("instruction", "response", "proactive_reminder"), build_reminder_prompt("instruction", "response", "post_event_reminder"), build_reminder_prompt("instruction", "response", "correction", "answer"), ] assert [hashlib.sha256(prompt.encode()).hexdigest() for prompt in prompts] == [ "5f7f5fcb4a37e4d2eea8ba23beabf74630e6fc2fb5b2661ceb94403254bb04d5", "1fccbed6c1ed8ea1c76d5564d7289ed6adca3678f6a0da2cc735942123d64457", "d0a0e3a9d873d2f368c5c99b9fe46681f683cc36a9fe0e09b99d6cbca1d6dd0c", "08f9951243c859d271f82ee53da22cd0bdab1fe93c6ca04f4870d3b959c0803a", "3de131a7e36cb4b8ac369bb05e87ede518be20221e214df3644211c179131a73", ] def test_frame_sample_content_passes_frames(tmp_path, monkeypatch): response = tmp_path / "response.json" response.write_text(json.dumps([{"sentence": "A person moves.", "start": 1, "end": 2}]), encoding="utf-8") response.with_name("response.meta.json").write_text(json.dumps({"clock": "media"}), encoding="utf-8") video = tmp_path / "video.mp4" video.write_bytes(b"video") monkeypatch.setattr(eval_module, "extract_jpeg", lambda *args, **kwargs: b"\xff\xd8jpeg") class Judge: def temporal(self, *args, **kwargs): return '{"temporal_score": 3, "is_relevant": 1}' def content(self, prompt, video, frames=None, *, mode="video_url"): assert mode == "frame-sample" assert frames == [b"\xff\xd8jpeg", b"\xff\xd8jpeg"] return '{"content_score": 3}' sample = DuplexSample( id="rtd", split="RTD_OCR", family="rtd", task_type=None, video=video, video_duration=4, ) score = eval_module.evaluate_sample( sample, response, tmp_path / "score.json", Judge(), judge_video_mode="frame-sample", ) assert score["content"]["frame_count"] == 2 def test_local_directory_collapsed_train_with_row_identity_and_split_filter(tmp_path): # Reviewer-pointed gap: a collapsed local mirror (data/train-*.parquet) # whose rows carry their own split identity must correctly filter by # --split RTD_OCR. pytest.importorskip("datasets") hf_dir = tmp_path / "Omni-DuplexEval" data_dir = hf_dir / "data" data_dir.mkdir(parents=True) _write_parquet( data_dir / "train-00000-of-00001.parquet", [ {"id": "rtd", "split": "RTD_OCR", "question_text": "Read this."}, {"id": "pr", "split": "PR_correction", "question_text": "Correct this."}, ], ) all_samples = load_samples(hf_dir) assert {s.id for s in all_samples} == {"rtd", "pr"} rtd = load_samples(hf_dir, split="RTD_OCR") assert [s.id for s in rtd] == ["rtd"] assert rtd[0].family == "rtd" def test_local_directory_mistyped_split_raises_clear_error(tmp_path): # A named-shard mirror with --split RTD_TYPO must raise ValueError # listing the observed splits instead of silently returning nothing. pytest.importorskip("datasets") hf_dir = tmp_path / "Omni-DuplexEval" data_dir = hf_dir / "data" data_dir.mkdir(parents=True) _write_parquet( data_dir / "RTD_OCR-00000-of-00001.parquet", [{"id": "rtd", "question_text": "Read this."}], ) _write_parquet( data_dir / "PR_correction-00000-of-00001.parquet", [{"id": "pr", "question_text": "Correct this."}], ) with pytest.raises(ValueError) as exc_info: load_samples(hf_dir, split="RTD_TYPO") message = str(exc_info.value) assert "matched zero" in message assert "RTD_OCR" in message or "PR_correction" in message def test_manifest_without_split_identity_split_override_still_works(tmp_path): # Manifest rows without split/subset/config identity must keep the # base override behaviour: --split RTD_OCR stamps the split so rows # are kept instead of rejected (regression test for P2). manifest = tmp_path / "samples.json" manifest.write_text( json.dumps([{"id": "a", "video": "clip.mp4"}, {"id": "b", "video": "clip2.mp4"}]), encoding="utf-8", ) samples = load_samples(manifest, split="RTD_OCR") assert len(samples) == 2 assert all(s.split == "RTD_OCR" for s in samples) assert all(s.family == "rtd" for s in samples) def test_iterable_without_split_identity_split_override_still_works(): # Iterable rows without split/subset/config identity must keep the # base override behaviour (regression test for F5/P2). data = [{"id": "a", "video": "clip.mp4"}, {"id": "b", "video": "clip2.mp4"}] samples = load_samples(data, split="RTD_OCR") assert len(samples) == 2 assert all(s.split == "RTD_OCR" for s in samples) assert all(s.family == "rtd" for s in samples) def test_audio_feature_decode_false_preserves_bytes_key(tmp_path): # When a parquet file carries an Audio feature column, the loader # must recast it with decode=False so rows contain the ``bytes`` key # instead of ``array``/``sampling_rate``. pytest.importorskip("datasets") pytest.importorskip("pyarrow") import datasets audio_bytes = b"\x00\x01\x02" ds = datasets.Dataset.from_dict( { "id": ["a"], "question_text": ["Hello"], "task_type": ["pr_correction"], "audio": [{"bytes": audio_bytes, "path": None}], }, features=datasets.Features( { "id": datasets.Value("string"), "question_text": datasets.Value("string"), "task_type": datasets.Value("string"), "audio": datasets.Audio(sampling_rate=16000), } ), ) parquet = tmp_path / "audio_sample.parquet" ds.to_parquet(str(parquet)) samples = load_samples(str(parquet)) assert len(samples) == 1 raw = samples[0].raw assert raw is not None audio_val = raw.get("audio") assert audio_val is not None # The Audio feature was decoded with decode=False, so the struct # keeps the ``bytes`` key that materialize_media needs. assert isinstance(audio_val, dict) and "bytes" in audio_val assert audio_val["bytes"] == audio_bytes assert "path" in audio_val