e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
670 lines
24 KiB
Python
670 lines
24 KiB
Python
"""Run dataset_none_detect.py against synthetic + two HF datasets; log to tests/logs/none_detect_results.log."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import sys
|
||
import traceback
|
||
from datetime import datetime
|
||
from io import StringIO
|
||
from pathlib import Path
|
||
|
||
# Import dataset_none_detect directly, bypassing utils/datasets/__init__.py (heavy deps).
|
||
REPO_ROOT = Path(__file__).resolve().parent.parent.parent
|
||
sys.path.insert(0, str(REPO_ROOT / "studio" / "backend" / "utils" / "datasets"))
|
||
|
||
from dataset_none_detect import (
|
||
find_none_chatml,
|
||
print_report,
|
||
scan_dataset,
|
||
)
|
||
|
||
LOG_DIR = REPO_ROOT / "tests" / "logs"
|
||
LOG_DIR.mkdir(parents = True, exist_ok = True)
|
||
LOG_PATH = LOG_DIR / "none_detect_results.log"
|
||
|
||
# Helpers
|
||
|
||
|
||
class Tee:
|
||
"""Write to both stdout and a file simultaneously."""
|
||
|
||
def __init__(self, file):
|
||
self.file = file
|
||
self.stdout = sys.stdout
|
||
|
||
def write(self, data):
|
||
self.stdout.write(data)
|
||
self.file.write(data)
|
||
|
||
def flush(self):
|
||
self.stdout.flush()
|
||
self.file.flush()
|
||
|
||
|
||
def section(title: str):
|
||
line = "=" * 70
|
||
print(f"\n{line}")
|
||
print(f" {title}")
|
||
print(f"{line}")
|
||
|
||
|
||
def run_scan(
|
||
dataset,
|
||
label: str,
|
||
fmt: str = "auto",
|
||
) -> dict | None:
|
||
print(f"\n--- Scanning: {label} (fmt={fmt}) ---")
|
||
try:
|
||
stats = scan_dataset(dataset, fmt = fmt)
|
||
print_report(stats, stats["format"])
|
||
return stats
|
||
except Exception as exc:
|
||
print(f" [ERROR] {exc}")
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
|
||
def assert_bad_rows(stats: dict, expected_min: int, label: str):
|
||
bad = len(stats.get("bad_row_indices", []))
|
||
status = "PASS" if bad >= expected_min else "FAIL"
|
||
print(f" [{status}] {label}: found {bad} bad rows (expected >= {expected_min})")
|
||
return status == "PASS"
|
||
|
||
|
||
def assert_exact_recall(stats: dict, expected_bad: set, label: str):
|
||
"""Every injected bad row index must appear in bad_row_indices."""
|
||
actual_bad = set(stats.get("bad_row_indices", []))
|
||
missed = expected_bad - actual_bad
|
||
all_caught = len(missed) == 0
|
||
status = "PASS" if all_caught else "FAIL"
|
||
caught_count = len(expected_bad) - len(missed)
|
||
print(
|
||
f" [{status}] {label}: exact recall — "
|
||
f"{caught_count}/{len(expected_bad)} injected bad rows caught",
|
||
end = "",
|
||
)
|
||
if missed:
|
||
print(f" (missed rows: {sorted(missed)})")
|
||
else:
|
||
print()
|
||
return all_caught
|
||
|
||
|
||
# 1. Synthetic datasets
|
||
|
||
# Minimal mock for hand-crafted rows pyarrow can't represent (e.g. messages=None / "not a list").
|
||
|
||
|
||
class _MockDataset:
|
||
"""Behaves like an HF Dataset for iteration, len(), and index access."""
|
||
|
||
def __init__(self, rows: list, columns: list):
|
||
self.column_names = columns
|
||
self._rows = rows
|
||
|
||
def __len__(self):
|
||
return len(self._rows)
|
||
|
||
def __iter__(self):
|
||
return iter(self._rows)
|
||
|
||
def __getitem__(self, idx):
|
||
"""Support dataset[i] and dataset[i][col] patterns used by _probe_conversation."""
|
||
return self._rows[idx]
|
||
|
||
|
||
def test_p1_fix():
|
||
"""find_none_chatml records rows where messages is None or non-list."""
|
||
section("P1 Fix Verification — non-list conversation column values")
|
||
|
||
sys.path.insert(0, str(REPO_ROOT / "tests" / "utils"))
|
||
from generate_dataset_with_none import make_chatml_p1_rows
|
||
|
||
p1_rows = make_chatml_p1_rows()
|
||
mock_ds = _MockDataset(p1_rows, ["messages"])
|
||
|
||
print(f" Rows under test: {p1_rows}")
|
||
stats = find_none_chatml(mock_ds, col = "messages")
|
||
print_report(stats, "chatml")
|
||
|
||
expected_bad = set(range(len(p1_rows)))
|
||
actual_bad = set(stats.get("bad_row_indices", []))
|
||
all_caught = expected_bad.issubset(actual_bad)
|
||
print(
|
||
f" [{'PASS' if all_caught else 'FAIL'}] P1 fix: all {len(expected_bad)} non-list rows caught"
|
||
)
|
||
|
||
for row in stats.get("findings", []):
|
||
vtype = row.get("value_type", "?")
|
||
raw = row.get("raw_value", "?")
|
||
print(f" row {row['row_index']}: value_type={vtype!r} raw={raw}")
|
||
|
||
return stats
|
||
|
||
|
||
def test_probe_p1_fix():
|
||
"""scan_dataset(fmt='auto') on an all-corrupt messages column returns findings, not ValueError."""
|
||
section("P1 Fix Verification — probe skip on all-corrupt column (auto-detect path)")
|
||
|
||
# All rows have messages=None, so the probe finds no dict turn.
|
||
all_corrupt_rows = [{"messages": None}] * 5
|
||
mock_ds = _MockDataset(all_corrupt_rows, ["messages"])
|
||
|
||
print(f" Rows under test: {len(all_corrupt_rows)} rows all with messages=None")
|
||
try:
|
||
stats = scan_dataset(mock_ds, fmt = "auto")
|
||
print_report(stats, stats.get("format", "?"))
|
||
bad = len(stats.get("bad_row_indices", []))
|
||
status = "PASS" if bad == len(all_corrupt_rows) else "FAIL"
|
||
print(
|
||
f" [{status}] Probe P1 fix: {bad}/{len(all_corrupt_rows)} all-corrupt rows caught via auto-detect"
|
||
)
|
||
return stats
|
||
except ValueError as exc:
|
||
print(f" [FAIL] scan_dataset raised ValueError (probe P1 bug NOT fixed): {exc}")
|
||
return None
|
||
|
||
|
||
def test_probe_string_corrupt():
|
||
"""P2 fix: a plain-string 'messages' column must NOT be classified as chatml (raises ValueError)."""
|
||
section("P2 Fix Verification — plain-string messages not classified as chatml")
|
||
|
||
string_rows = [{"messages": "this is a string, not a list"}] * 5
|
||
mock_ds = _MockDataset(string_rows, ["messages"])
|
||
|
||
print(f" Rows under test: {len(string_rows)} rows all with messages='string'")
|
||
try:
|
||
stats = scan_dataset(mock_ds, fmt = "auto")
|
||
fmt = stats.get("format", "?")
|
||
not_chatml = fmt != "chatml"
|
||
status = "PASS" if not_chatml else "FAIL"
|
||
print(
|
||
f" [{status}] String-corrupt probe: detected fmt={fmt!r} "
|
||
f"(expected: anything except 'chatml')"
|
||
)
|
||
return stats
|
||
except ValueError as exc:
|
||
# ValueError (unknown format) is the correct outcome for a non-conversation column.
|
||
print(
|
||
f" [PASS] String-corrupt probe: scan_dataset raised ValueError (not chatml, as expected): {exc}"
|
||
)
|
||
return None
|
||
|
||
|
||
def test_explicit_fmt_corrupt():
|
||
"""scan_dataset(fmt='chatml') on an all-corrupt column returns findings, not ValueError."""
|
||
section("P1 Fix Verification — explicit fmt='chatml' on all-corrupt column")
|
||
|
||
all_corrupt_rows = [{"messages": None}] * 4 + [{"messages": "not a list"}] * 3
|
||
mock_ds = _MockDataset(all_corrupt_rows, ["messages"])
|
||
|
||
print(f" Rows under test: {len(all_corrupt_rows)} rows (4×None, 3×string)")
|
||
try:
|
||
stats = scan_dataset(mock_ds, fmt = "chatml")
|
||
print_report(stats, stats.get("format", "?"))
|
||
bad = len(stats.get("bad_row_indices", []))
|
||
status = "PASS" if bad == len(all_corrupt_rows) else "FAIL"
|
||
print(
|
||
f" [{status}] Explicit-fmt P1 fix: {bad}/{len(all_corrupt_rows)} rows caught with fmt='chatml'"
|
||
)
|
||
return stats
|
||
except ValueError as exc:
|
||
print(f" [FAIL] scan_dataset raised ValueError (explicit-fmt P1 NOT fixed): {exc}")
|
||
return None
|
||
|
||
|
||
def test_p2_probe_skips_corrupt_prefers_valid():
|
||
"""P2 fix: probe continues past a corrupt 'messages' column to a valid 'conversations' column."""
|
||
section("P2 Fix Verification — probe continues past corrupt first column to valid second")
|
||
|
||
# messages column is all-None; conversations is a valid ShareGPT column.
|
||
rows = [
|
||
{
|
||
"messages": None,
|
||
"conversations": [
|
||
{"from": "human", "value": "Hello"},
|
||
{"from": "gpt", "value": "Hi!"},
|
||
],
|
||
}
|
||
] * 5 + [
|
||
{
|
||
"messages": None,
|
||
"conversations": [
|
||
{"from": "human", "value": ""},
|
||
{"from": "gpt", "value": "OK"},
|
||
],
|
||
}
|
||
] * 2
|
||
mock_ds = _MockDataset(rows, ["messages", "conversations"])
|
||
|
||
print(f" Rows: {len(rows)} — messages=None, conversations=valid ShareGPT (2 bad value='')")
|
||
try:
|
||
stats = scan_dataset(mock_ds, fmt = "auto")
|
||
fmt = stats.get("format", "?")
|
||
col = stats.get("column", "?")
|
||
bad = len(stats.get("bad_row_indices", []))
|
||
# Must detect 'conversations' (sharegpt), not 'messages'.
|
||
correct_col = col == "conversations"
|
||
correct_fmt = fmt == "sharegpt"
|
||
correct_bad = bad == 2
|
||
status = "PASS" if (correct_col and correct_fmt and correct_bad) else "FAIL"
|
||
print(
|
||
f" [{status}] Probe P2 fix: fmt={fmt!r} col={col!r} bad_rows={bad} "
|
||
f"(expected fmt='sharegpt' col='conversations' bad=2)"
|
||
)
|
||
print_report(stats, fmt)
|
||
return stats
|
||
except ValueError as exc:
|
||
print(f" [FAIL] scan_dataset raised ValueError: {exc}")
|
||
return None
|
||
|
||
|
||
def test_p2_explicit_fmt_col_priority():
|
||
"""P2 fix: explicit fmt='sharegpt' lets find_none_sharegpt pick its own column (conversations)."""
|
||
section("P2 Fix Verification — explicit fmt='sharegpt' respects per-scanner column priority")
|
||
|
||
# messages has valid role/content turns (chatml-ish); conversations has bad sharegpt turns.
|
||
rows = [
|
||
{
|
||
"messages": [
|
||
{"role": "user", "content": "Hi"},
|
||
{"role": "assistant", "content": "Hello"},
|
||
],
|
||
"conversations": [
|
||
{"from": "human", "value": None},
|
||
{"from": "gpt", "value": "OK"},
|
||
],
|
||
}
|
||
] * 5
|
||
mock_ds = _MockDataset(rows, ["messages", "conversations"])
|
||
|
||
print(f" Rows: {len(rows)} — messages=clean chatml, conversations=bad sharegpt (value=None)")
|
||
stats = scan_dataset(mock_ds, fmt = "sharegpt")
|
||
col = stats.get("column", "?")
|
||
bad = len(stats.get("bad_row_indices", []))
|
||
# fmt='sharegpt' scans 'conversations' -> 5 bad rows.
|
||
correct_col = col == "conversations"
|
||
correct_bad = bad == 5
|
||
status = "PASS" if (correct_col and correct_bad) else "FAIL"
|
||
print(
|
||
f" [{status}] Explicit-fmt P2 fix: col={col!r} bad_rows={bad} "
|
||
f"(expected col='conversations' bad=5)"
|
||
)
|
||
print_report(stats, "sharegpt")
|
||
return stats
|
||
|
||
|
||
def test_p2_gptoss_col_priority():
|
||
"""P2 fix: fmt='gptoss' scans 'messages' only, not a clean 'conversations' fallback."""
|
||
section("P2 Fix Verification — fmt='gptoss' scans messages only, not conversations")
|
||
|
||
# messages is all-None (corrupt); conversations is clean sharegpt.
|
||
rows = [
|
||
{
|
||
"messages": None,
|
||
"conversations": [
|
||
{"from": "human", "value": "Hello"},
|
||
{"from": "gpt", "value": "Hi!"},
|
||
],
|
||
}
|
||
] * 5
|
||
mock_ds = _MockDataset(rows, ["messages", "conversations"])
|
||
|
||
print(f" Rows: {len(rows)} — messages=None (corrupt), conversations=clean sharegpt")
|
||
try:
|
||
stats = scan_dataset(mock_ds, fmt = "gptoss")
|
||
col = stats.get("column", "?")
|
||
bad = len(stats.get("bad_row_indices", []))
|
||
correct_col = col == "messages"
|
||
correct_bad = bad == 5
|
||
status = "PASS" if (correct_col and correct_bad) else "FAIL"
|
||
print(
|
||
f" [{status}] gptoss P2 fix: col={col!r} bad_rows={bad} "
|
||
f"(expected col='messages' bad=5)"
|
||
)
|
||
print_report(stats, "gptoss")
|
||
return stats
|
||
except ValueError as exc:
|
||
print(f" [FAIL] scan_dataset raised ValueError: {exc}")
|
||
return None
|
||
|
||
|
||
def test_new_p1_explicit_sharegpt_both_all_corrupt():
|
||
"""NEW P1 (commit eb7fea3b7e): fmt='sharegpt' with both columns all-corrupt scans 'conversations', not 'messages'."""
|
||
section(
|
||
"NEW P1 — explicit fmt='sharegpt' scans 'conversations' even when both columns all-corrupt"
|
||
)
|
||
|
||
# Both columns are all-corrupt: every row has None.
|
||
rows = [{"messages": None, "conversations": None}] * 5
|
||
mock_ds = _MockDataset(rows, ["messages", "conversations"])
|
||
|
||
print(f" Rows: {len(rows)} — messages=None, conversations=None (both all-corrupt)")
|
||
try:
|
||
stats = scan_dataset(mock_ds, fmt = "sharegpt")
|
||
col = stats.get("column", "?")
|
||
bad = len(stats.get("bad_row_indices", []))
|
||
# Must scan 'conversations', not 'messages'.
|
||
correct_col = col == "conversations"
|
||
correct_bad = bad == 5
|
||
status = "PASS" if (correct_col and correct_bad) else "FAIL"
|
||
print(
|
||
f" [{status}] New-P1 explicit sharegpt: col={col!r} bad_rows={bad} "
|
||
f"(expected col='conversations' bad=5)"
|
||
)
|
||
print_report(stats, "sharegpt")
|
||
return stats
|
||
except ValueError as exc:
|
||
print(f" [FAIL] scan_dataset raised ValueError: {exc}")
|
||
return None
|
||
|
||
|
||
def test_new_p2_plain_string_messages_not_chatml():
|
||
"""NEW P2 (commit eb7fea3b7e): plain-string 'messages' must NOT be auto-classified as chatml."""
|
||
section("NEW P2 — plain-string 'messages' column must NOT be classified as chatml")
|
||
|
||
# messages is a plain text column, not a conversation column.
|
||
rows = [{"messages": "hello world"}] * 5
|
||
mock_ds = _MockDataset(rows, ["messages"])
|
||
|
||
print(f" Rows: {len(rows)} — messages='hello world' (plain strings, not conversation)")
|
||
try:
|
||
stats = scan_dataset(mock_ds, fmt = "auto")
|
||
fmt = stats.get("format", "?")
|
||
not_chatml = fmt != "chatml"
|
||
status = "PASS" if not_chatml else "FAIL"
|
||
print(
|
||
f" [{status}] New-P2 plain-string messages: detected fmt={fmt!r} "
|
||
f"(expected: anything except 'chatml')"
|
||
)
|
||
return stats
|
||
except ValueError as exc:
|
||
# ValueError is also acceptable: not a valid conversation format.
|
||
print(
|
||
f" [PASS] New-P2 plain-string messages: scan_dataset raised ValueError (not chatml): {exc}"
|
||
)
|
||
return {
|
||
"format": "unknown",
|
||
"total_rows": 5,
|
||
"bad_row_indices": [],
|
||
"findings": [],
|
||
}
|
||
|
||
|
||
def test_synthetic():
|
||
section("1. Synthetic Datasets (generated in-memory)")
|
||
|
||
sys.path.insert(0, str(REPO_ROOT / "tests" / "utils"))
|
||
from generate_dataset_with_none import (
|
||
make_alpaca_dataset,
|
||
make_chatml_dataset,
|
||
make_sharegpt_dataset,
|
||
)
|
||
|
||
results = {}
|
||
|
||
# ChatML — 10 clean rows (0-9), 8 bad rows (10-17)
|
||
ds_chatml = make_chatml_dataset()
|
||
stats = run_scan(ds_chatml, "Synthetic ChatML (messages/role/content)")
|
||
assert_bad_rows(stats, 8, "ChatML bad rows")
|
||
assert_exact_recall(stats, set(range(10, 18)), "ChatML exact recall")
|
||
results["chatml"] = stats
|
||
|
||
# ShareGPT — 5 clean rows (0-4), 5 bad rows (5-9)
|
||
ds_sgpt = make_sharegpt_dataset()
|
||
stats = run_scan(ds_sgpt, "Synthetic ShareGPT (conversations/from/value)")
|
||
assert_bad_rows(stats, 3, "ShareGPT bad rows")
|
||
assert_exact_recall(stats, set(range(5, 10)), "ShareGPT exact recall")
|
||
results["sharegpt"] = stats
|
||
|
||
# Alpaca — 5 clean rows (0-4), 5 bad rows (5-9)
|
||
ds_alpaca = make_alpaca_dataset()
|
||
stats = run_scan(ds_alpaca, "Synthetic Alpaca (instruction/output)")
|
||
assert_bad_rows(stats, 4, "Alpaca bad rows")
|
||
assert_exact_recall(stats, set(range(5, 10)), "Alpaca exact recall")
|
||
results["alpaca"] = stats
|
||
|
||
return results
|
||
|
||
|
||
# 2. HuggingFace: peteromallet/dataclaw-peteromallet
|
||
|
||
|
||
def _brute_force_bad_rows(ds, fmt: str) -> set:
|
||
"""Pure-Python ground-truth scanner (no shared code with dataset_none_detect) for independent proof.
|
||
|
||
Flags a row bad if any field/turn is None, empty, or whitespace-only; returns bad row indices.
|
||
"""
|
||
|
||
def _blank(val) -> bool:
|
||
if val is None:
|
||
return True
|
||
if isinstance(val, str) and val.strip() == "":
|
||
return True
|
||
return False
|
||
|
||
bad: set = set()
|
||
for i, row in enumerate(ds):
|
||
if fmt in ("chatml", "gptoss"):
|
||
msgs = row.get("messages")
|
||
if msgs is None or not isinstance(msgs, list):
|
||
bad.add(i)
|
||
continue
|
||
for turn in msgs:
|
||
if turn is None or (isinstance(turn, dict) and _blank(turn.get("content"))):
|
||
bad.add(i)
|
||
break
|
||
elif fmt == "sharegpt":
|
||
convs = row.get("conversations")
|
||
if convs is None or not isinstance(convs, list):
|
||
bad.add(i)
|
||
continue
|
||
for turn in convs:
|
||
if turn is None or (isinstance(turn, dict) and _blank(turn.get("value"))):
|
||
bad.add(i)
|
||
break
|
||
elif fmt == "alpaca":
|
||
if _blank(row.get("instruction")) or _blank(row.get("output")):
|
||
bad.add(i)
|
||
return bad
|
||
|
||
|
||
def _assert_hf_no_misses(ds, stats: dict, label: str) -> bool:
|
||
"""Independent check: scan_dataset() must find every bad row brute-force finds (no misses)."""
|
||
fmt = stats.get("format", "unknown")
|
||
module_bad = set(stats.get("bad_row_indices", []))
|
||
|
||
print(f" Running brute-force independent scan (fmt={fmt!r}, {len(ds)} rows)...")
|
||
brute_bad = _brute_force_bad_rows(ds, fmt)
|
||
|
||
missed = brute_bad - module_bad # brute-force found, module missed
|
||
extra = module_bad - brute_bad # module flagged, brute-force didn't
|
||
|
||
no_misses = len(missed) == 0
|
||
snippet = ""
|
||
if missed:
|
||
sample = sorted(missed)[:10]
|
||
snippet = f" (first missed rows: {sample}{'...' if len(missed) > 10 else ''})"
|
||
status = "PASS" if no_misses else "FAIL"
|
||
print(
|
||
f" [{status}] {label} no-miss check — "
|
||
f"brute-force: {len(brute_bad)} bad rows | "
|
||
f"module: {len(module_bad)} bad rows | "
|
||
f"missed: {len(missed)}{snippet}"
|
||
)
|
||
if extra:
|
||
# Module may legitimately flag more rows (extra structural checks); informational only.
|
||
print(
|
||
f" [INFO] {label} — module flagged {len(extra)} rows not in brute-force "
|
||
f"(may reflect additional structural checks, not false positives)"
|
||
)
|
||
return no_misses
|
||
|
||
|
||
def test_dataclaw():
|
||
section("2. HuggingFace — peteromallet/dataclaw-peteromallet")
|
||
try:
|
||
from datasets import load_dataset
|
||
|
||
print(" Loading dataset (streaming first 500 rows for speed)...")
|
||
ds = load_dataset(
|
||
"peteromallet/dataclaw-peteromallet",
|
||
split = "train",
|
||
streaming = False,
|
||
)
|
||
print(f" Loaded {len(ds)} rows, columns: {ds.column_names}")
|
||
stats = run_scan(ds, "dataclaw-peteromallet")
|
||
if stats:
|
||
_assert_hf_no_misses(ds, stats, "dataclaw-peteromallet")
|
||
return stats
|
||
except Exception as exc:
|
||
print(f" [ERROR] Could not load dataclaw dataset: {exc}")
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
|
||
# 3. HuggingFace: peteromallet/my-personal-codex-data
|
||
|
||
|
||
def test_codex_data():
|
||
section("3. HuggingFace — peteromallet/my-personal-codex-data")
|
||
try:
|
||
# load_dataset fails here (ujson chokes on the large JSONL batch); download + parse raw instead.
|
||
from huggingface_hub import hf_hub_download
|
||
from datasets import Dataset
|
||
|
||
print(" Downloading conversations.jsonl via huggingface_hub...")
|
||
path = hf_hub_download(
|
||
"peteromallet/my-personal-codex-data",
|
||
"conversations.jsonl",
|
||
repo_type = "dataset",
|
||
)
|
||
rows = []
|
||
with open(path, encoding = "utf-8") as f:
|
||
for line in f:
|
||
line = line.strip()
|
||
if line:
|
||
rows.append(json.loads(line))
|
||
ds = Dataset.from_list(rows)
|
||
print(f" Loaded {len(ds)} rows, columns: {ds.column_names}")
|
||
stats = run_scan(ds, "my-personal-codex-data")
|
||
if stats:
|
||
_assert_hf_no_misses(ds, stats, "my-personal-codex-data")
|
||
return stats
|
||
except Exception as exc:
|
||
print(f" [ERROR] Could not load codex dataset: {exc}")
|
||
traceback.print_exc()
|
||
return None
|
||
|
||
|
||
# Main
|
||
|
||
|
||
def main():
|
||
started = datetime.now().isoformat()
|
||
|
||
with open(LOG_PATH, "w", encoding = "utf-8") as log_file:
|
||
sys.stdout = Tee(log_file)
|
||
|
||
print(f"dataset_none_detect.py — Test Run")
|
||
print(f"Started: {started}")
|
||
print(f"Python: {sys.version}")
|
||
print(f"Log: {LOG_PATH}")
|
||
|
||
all_results = {}
|
||
|
||
all_results["p1_fix"] = test_p1_fix()
|
||
all_results["probe_p1_fix"] = test_probe_p1_fix()
|
||
all_results["probe_string_corrupt"] = test_probe_string_corrupt()
|
||
all_results["explicit_fmt_corrupt"] = test_explicit_fmt_corrupt()
|
||
all_results["p2_probe_valid_fallback"] = test_p2_probe_skips_corrupt_prefers_valid()
|
||
all_results["p2_explicit_col_priority"] = test_p2_explicit_fmt_col_priority()
|
||
all_results["p2_gptoss_col_priority"] = test_p2_gptoss_col_priority()
|
||
all_results["new_p1_sharegpt_all_corrupt"] = (
|
||
test_new_p1_explicit_sharegpt_both_all_corrupt()
|
||
)
|
||
all_results["new_p2_plain_string_not_chatml"] = (
|
||
test_new_p2_plain_string_messages_not_chatml()
|
||
)
|
||
all_results["synthetic"] = test_synthetic()
|
||
all_results["dataclaw"] = test_dataclaw()
|
||
all_results["codex_data"] = test_codex_data()
|
||
|
||
# Summary table
|
||
section("SUMMARY")
|
||
rows = [
|
||
("Dataset", "Format", "Total rows", "Bad rows", "Bad turns"),
|
||
]
|
||
|
||
def _row(label, stats):
|
||
if stats is None:
|
||
return (label, "ERROR", "-", "-", "-")
|
||
fmt = stats.get("format", "?")
|
||
total = stats.get("total_rows", "?")
|
||
bad = len(stats.get("bad_row_indices", []))
|
||
turns = stats.get("total_none_turns") or len(stats.get("findings", []))
|
||
return (label, fmt, str(total), str(bad), str(turns))
|
||
|
||
for key, label in [
|
||
("chatml", "Synthetic chatml"),
|
||
("sharegpt", "Synthetic sharegpt"),
|
||
("alpaca", "Synthetic alpaca"),
|
||
]:
|
||
s = all_results.get("synthetic") or {}
|
||
rows.append(_row(label, s.get(key) if isinstance(s, dict) else None))
|
||
|
||
rows.append(_row("dataclaw-peteromallet", all_results.get("dataclaw")))
|
||
rows.append(_row("my-personal-codex-data", all_results.get("codex_data")))
|
||
|
||
col_widths = [max(len(r[i]) for r in rows) for i in range(5)]
|
||
fmt_str = " " + " ".join(f"{{:<{w}}}" for w in col_widths)
|
||
header = rows[0]
|
||
print(fmt_str.format(*header))
|
||
print(" " + "-" * (sum(col_widths) + 10))
|
||
for row in rows[1:]:
|
||
print(fmt_str.format(*row))
|
||
|
||
# Write machine-readable JSON summary alongside the log
|
||
json_path = LOG_DIR / "none_detect_results.json"
|
||
summary = {}
|
||
for key, val in all_results.items():
|
||
if val is None:
|
||
summary[key] = None
|
||
elif isinstance(val, dict):
|
||
# Flatten synthetic sub-keys
|
||
if key == "synthetic":
|
||
for subkey, subval in val.items():
|
||
summary[f"synthetic_{subkey}"] = (
|
||
{
|
||
"format": subval.get("format"),
|
||
"total_rows": subval.get("total_rows"),
|
||
"bad_row_count": len(subval.get("bad_row_indices", [])),
|
||
"bad_turn_count": subval.get(
|
||
"total_none_turns", len(subval.get("findings", []))
|
||
),
|
||
}
|
||
if subval
|
||
else None
|
||
)
|
||
else:
|
||
summary[key] = {
|
||
"format": val.get("format"),
|
||
"total_rows": val.get("total_rows"),
|
||
"bad_row_count": len(val.get("bad_row_indices", [])),
|
||
"bad_turn_count": val.get("total_none_turns", len(val.get("findings", []))),
|
||
}
|
||
|
||
json_path.write_text(json.dumps(summary, indent = 2), encoding = "utf-8")
|
||
|
||
finished = datetime.now().isoformat()
|
||
print(f"\nFinished: {finished}")
|
||
print(f"Log: {LOG_PATH}")
|
||
print(f"JSON: {json_path}")
|
||
|
||
sys.stdout = sys.stdout.stdout # restore
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|