| """CPU-side quality gate for validating GPU inference output.""" |
|
|
| from __future__ import annotations |
|
|
|
|
| class QualityGate: |
| """Validates output from GPU-backed LLM calls before accepting results. |
| |
| Each static method returns (bool, str): (passed, error_message). |
| Empty string means pass; non-empty means failure with that reason. |
| """ |
|
|
| @staticmethod |
| def validate_rank(output: list[dict]) -> tuple[bool, str]: |
| """Validate collapsed filter+score output. |
| |
| Each item must have keys: i (int), score (int 0-10), reason (str). |
| output must be a non-empty list. |
| """ |
| if not isinstance(output, list) or len(output) == 0: |
| return False, "rank output must be a non-empty list" |
|
|
| for idx, item in enumerate(output): |
| if not isinstance(item, dict): |
| return False, f"rank output[{idx}] is not a dict" |
|
|
| for key in ("i", "score", "reason"): |
| if key not in item: |
| return False, f"rank output[{idx}] missing key '{key}'" |
|
|
| if not isinstance(item["i"], int): |
| return False, f"rank output[{idx}].i must be int" |
|
|
| if not isinstance(item["score"], int): |
| return False, f"rank output[{idx}].score must be int" |
|
|
| if not (0 <= item["score"] <= 10): |
| return False, f"rank output[{idx}].score {item['score']} out of range 0-10" |
|
|
| if not isinstance(item["reason"], str): |
| return False, f"rank output[{idx}].reason must be str" |
|
|
| return True, "" |
|
|
| @staticmethod |
| def validate_script_segment(output: list[dict]) -> tuple[bool, str]: |
| """Validate script segment output. |
| |
| Each line must have: speaker (A or B), text (non-empty str), emotion (str). |
| Must have 2-20 lines (within 2-sigma of expected ranges). |
| """ |
| if not isinstance(output, list): |
| return False, "script output must be a list" |
|
|
| if len(output) < 2: |
| return False, f"script output has {len(output)} lines, minimum is 2" |
|
|
| if len(output) > 20: |
| return False, f"script output has {len(output)} lines, maximum is 20" |
|
|
| for idx, line in enumerate(output): |
| if not isinstance(line, dict): |
| return False, f"script output[{idx}] is not a dict" |
|
|
| for key in ("speaker", "text", "emotion"): |
| if key not in line: |
| return False, f"script output[{idx}] missing key '{key}'" |
|
|
| speaker = line["speaker"] |
| if speaker not in ("A", "B"): |
| return False, f"script output[{idx}].speaker must be A or B, got '{speaker}'" |
|
|
| if not isinstance(line["text"], str) or not line["text"].strip(): |
| return False, f"script output[{idx}].text must be non-empty str" |
|
|
| if not isinstance(line["emotion"], str): |
| return False, f"script output[{idx}].emotion must be str" |
|
|
| return True, "" |
|
|