lesson-agent-dev / research /evals /language_lesson_smoke.py
msg encrypted ai
Feat/sprint last 2hours (#22)
aac5f23
Raw
History Blame Contribute Delete
3.02 kB
#!/usr/bin/env python3
"""Smoke-check language-lesson eval JSONL for TeacherVoice format."""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
_REPO = Path(__file__).resolve().parents[2]
_DATA = _REPO / "research" / "data"
_JSON_LEAK = re.compile(r"^\s*[\{\[]|```")
_ARABIC = re.compile(r"[\u0600-\u06FF]")
_FRENCH_MARKERS = re.compile(
r"[\u00C0-\u024F]|"
r"\b(le|la|les|un|une|des|est|sont|pour|dans|avec|que|qui|comment|pourquoi|c'est|ce)\b",
re.IGNORECASE,
)
_VOICE_SUFFIX = "Reply now in 2-4 complete spoken sentences only"
def _load_rows(path: Path) -> list[dict]:
rows: list[dict] = []
with path.open(encoding="utf-8") as fh:
for line in fh:
line = line.strip()
if line:
rows.append(json.loads(line))
return rows
def _score_row(row: dict, *, language: str) -> list[str]:
issues: list[str] = []
messages = row.get("messages") or []
if len(messages) < 3:
issues.append("missing messages")
return issues
system = messages[0].get("content", "")
user = messages[-2].get("content", "")
assistant = messages[-1].get("content", "")
if "TeacherVoice" not in system:
issues.append("system missing TeacherVoice")
label = "French" if language == "fr" else "Arabic"
if f"Target language: {label}" not in system:
issues.append(f"system missing target language {label}")
if _VOICE_SUFFIX not in user:
issues.append("user missing voice suffix")
if not (40 <= len(assistant) <= 600):
issues.append(f"assistant length {len(assistant)} out of range")
if _JSON_LEAK.search(assistant):
issues.append("assistant looks like JSON/code")
if language == "ar" and not _ARABIC.search(assistant):
issues.append("assistant missing Arabic script")
if language == "fr" and not _FRENCH_MARKERS.search(assistant):
issues.append("assistant missing French markers")
return issues
def run_eval(*, language: str) -> int:
path = _DATA / f"language-lesson-eval-{language}.jsonl"
if not path.is_file():
print(f"skip {path.name} (not found)")
return 0
rows = _load_rows(path)
if not rows:
print(f"skip {path.name} (empty)")
return 0
bad = 0
for index, row in enumerate(rows):
issues = _score_row(row, language=language)
if issues:
bad += 1
print(f" row {index}: {', '.join(issues)}")
ok = len(rows) - bad
print(f"{language.upper()} eval: {ok}/{len(rows)} passed")
return 0 if bad == 0 else 1
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--language", choices=("fr", "ar", "both"), default="both")
args = parser.parse_args()
codes = ("fr", "ar") if args.language == "both" else (args.language,)
return max(run_eval(language=code) for code in codes)
if __name__ == "__main__":
sys.exit(main())