#!/usr/bin/env python3
"""
Post-process dataset: clean teacher explanations by stripping blocks.
Qwen3 instruct outputs reasoning in ... tags.
We want only the text after .
Also generates an improved dataset with augmented prompt templates.
"""
import json, re, random
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[2]
ARTIFACTS_DIR = REPO_ROOT / "artifacts" / "tiny_nla"
SIDECAR_PATH = Path(__file__).resolve().parent / "nla_meta.yaml"
JSONL_PATH = ARTIFACTS_DIR / "dataset.jsonl"
ACT_PATH = ARTIFACTS_DIR / "activations.pt"
def clean_explanation(text: str) -> str:
"""Strip blocks, extract only the answer text."""
if not text:
return ""
# Remove ... blocks (possibly with newlines)
cleaned = re.sub(r'.*?', '', text, flags=re.DOTALL)
cleaned = cleaned.strip()
# Also strip leading/trailing whitespace per line
cleaned = '\n'.join(line.strip() for line in cleaned.split('\n') if line.strip())
# If after stripping think there's still nothing, keep minimal
if not cleaned:
# Try to salvage something from within think
inner = re.search(r'(.*?)', text, re.DOTALL)
if inner:
cleaned = inner.group(1).strip()[:100]
return cleaned
def is_empty_or_useless(text: str) -> bool:
"""Check if explanation is empty or just boilerplate."""
if not text or len(text) < 5:
return True
useless = ["[空输出]", "[生成失败", ""]
if any(u in text for u in useless):
return True
return False
def main():
print("=" * 60)
print("🧹 Post-Processing Dataset")
print("=" * 60)
# Load dataset
records = []
with open(JSONL_PATH, "r", encoding="utf-8") as f:
for line in f:
records.append(json.loads(line))
print(f" Loaded {len(records)} records from {JSONL_PATH}")
# Check stats before
empty_before = sum(1 for r in records if is_empty_or_useless(r.get("teacher_explanation", "")))
has_think = sum(1 for r in records if "" in r.get("teacher_explanation", ""))
print(f" Before cleaning:")
print(f" Empty/useless: {empty_before}")
print(f" Contains : {has_think}")
# Clean explanations
for r in records:
raw = r.get("teacher_explanation", "")
cleaned = clean_explanation(raw)
r["teacher_explanation_raw"] = raw # keep original
r["teacher_explanation"] = cleaned
empty_after = sum(1 for r in records if is_empty_or_useless(r.get("teacher_explanation", "")))
print(f" After cleaning:")
print(f" Empty/useless: {empty_after}")
# Check quality
print("\n Sample cleaned explanations:")
sample = random.Random(42).sample(records, 8)
for r in sample:
print(f" [{r['token_text']!r:10}] {r['teacher_explanation'][:80]}")
# Save cleaned dataset
with open(JSONL_PATH, "w", encoding="utf-8") as f:
for r in records:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f"\n ✅ Saved cleaned dataset to {JSONL_PATH}")
# Also save a separate "av ready" format (for AV training directly)
av_data = []
for r in records:
if not is_empty_or_useless(r.get("teacher_explanation", "")):
av_data.append({
"text": r["text"],
"token_text": r["token_text"],
"pos": r["pos"],
"teacher_explanation": r["teacher_explanation"],
"top_tokens": r["top_tokens"],
})
av_path = ARTIFACTS_DIR / "av_training_data.json"
with open(av_path, "w", encoding="utf-8") as f:
json.dump(av_data, f, ensure_ascii=False, indent=2)
print(f" ✅ Saved AV-ready data ({len(av_data)} samples) to {av_path}")
print("\n✅ Post-processing complete!")
if __name__ == "__main__":
main()