"""Validation script for the devops-openenv inference pipeline.""" import argparse import os import re import subprocess import sys import yaml SCHEMA_PATH = os.path.join(os.path.dirname(__file__), "openenv.yaml") EXPECTED_BASELINE = { "easy": 0.94, "medium": 0.73, "hard": 0.58, } START_RE = re.compile(r"^\[START\] task=(?P\w+) env=(?P\w+) model=(?P.+)$") STEP_RE = re.compile( r"^\[STEP\] step=(?P\d+) action=(?P[\w_]+) reward=(?P-?\d+\.\d{2}) " r"done=(?Ptrue|false) error=(?P.+)$" ) END_RE = re.compile( r"^\[END\] success=(?Ptrue|false) steps=(?P\d+) score=(?P\d+\.\d{2}) " r"rewards=(?P[\d\.,-]+)$" ) def load_spec(): with open(SCHEMA_PATH, "r", encoding="utf-8") as handle: return yaml.safe_load(handle) def run_inference(env_vars): result = subprocess.run( [sys.executable, "inference.py"], env={**os.environ, **env_vars}, capture_output=True, text=True, encoding="utf-8", ) if result.returncode != 0: raise RuntimeError(f"inference.py failed:\n{result.stderr}") return [line.strip() for line in result.stdout.splitlines() if line.strip()] def parse_run(lines, spec): tasks = {task["name"] for task in spec["tasks"]} actions = {action["name"] for action in spec["actions"]} parsed = [] index = 0 while index < len(lines): start_match = START_RE.match(lines[index]) if not start_match: raise ValueError(f"Expected [START] line, got: {lines[index]}") task_name = start_match.group("task") if task_name not in tasks: raise ValueError(f"Unknown task in output: {task_name}") rewards = [] step_count = 0 index += 1 while index < len(lines) and lines[index].startswith("[STEP]"): step_match = STEP_RE.match(lines[index]) if not step_match: raise ValueError(f"Malformed [STEP] line: {lines[index]}") step_num = int(step_match.group("step")) if step_num != step_count + 1: raise ValueError(f"Task {task_name} has non-sequential step numbering") action = step_match.group("action") if action not in actions: raise ValueError(f"Task {task_name} emitted invalid action: {action}") reward = float(step_match.group("reward")) if not -0.25 <= reward <= 1.0: raise ValueError(f"Task {task_name} emitted out-of-range reward: {reward}") rewards.append(step_match.group("reward")) step_count += 1 index += 1 if index >= len(lines): raise ValueError(f"Task {task_name} is missing its [END] line") end_match = END_RE.match(lines[index]) if not end_match: raise ValueError(f"Malformed [END] line: {lines[index]}") steps_reported = int(end_match.group("steps")) if steps_reported != step_count: raise ValueError( f"Task {task_name} reported {steps_reported} steps but emitted {step_count}" ) end_rewards = end_match.group("rewards").split(",") if end_rewards != rewards: raise ValueError(f"Task {task_name} rewards list does not match step rewards") score = float(end_match.group("score")) if not 0.0 <= score <= 1.0: raise ValueError(f"Task {task_name} emitted out-of-range score: {score}") parsed.append( { "task": task_name, "model": start_match.group("model"), "success": end_match.group("success") == "true", "steps": steps_reported, "score": score, } ) index += 1 seen_tasks = {entry["task"] for entry in parsed} if seen_tasks != tasks: raise ValueError(f"Expected tasks {sorted(tasks)}, got {sorted(seen_tasks)}") return parsed def print_summary(label, parsed): print(f"{label}:") for entry in parsed: outcome = "RESOLVED" if entry["success"] else "PARTIAL" print( f" - {entry['task']}: {outcome}, steps={entry['steps']}, " f"score={entry['score']:.2f}, model={entry['model']}" ) def require_expected_scores(label, parsed, expected_scores): observed = {entry["task"]: entry["score"] for entry in parsed} mismatches = [] for task_name, expected_score in expected_scores.items(): observed_score = observed.get(task_name) if observed_score is None or abs(observed_score - expected_score) > 0.01: mismatches.append(f"{task_name}: expected {expected_score:.2f}, got {observed_score!r}") if mismatches: raise RuntimeError(f"{label} score mismatch: {'; '.join(mismatches)}") def build_model_env(model_name): api_base = os.getenv("API_BASE_URL") api_key = os.getenv("OPENAI_API_KEY") or os.getenv("API_KEY") or os.getenv("HF_TOKEN") if not api_base or not api_key: missing = [] if not api_base: missing.append("API_BASE_URL") if not api_key: missing.append("OPENAI_API_KEY/API_KEY/HF_TOKEN") raise ValueError(f"Missing configuration for external model test: {', '.join(missing)}") return { "API_BASE_URL": api_base, "MODEL_NAME": model_name, "OPENAI_API_KEY": os.getenv("OPENAI_API_KEY", ""), "API_KEY": os.getenv("API_KEY", ""), "HF_TOKEN": os.getenv("HF_TOKEN", ""), } def main(): parser = argparse.ArgumentParser() parser.add_argument( "--models", default="", help="Comma-separated model names to test against the configured API_BASE_URL.", ) args = parser.parse_args() spec = load_spec() baseline_lines = run_inference( { "API_BASE_URL": "", "MODEL_NAME": "", "OPENAI_API_KEY": "", "API_KEY": "", "HF_TOKEN": "", } ) baseline = parse_run(baseline_lines, spec) print_summary("Baseline validation", baseline) require_expected_scores("Baseline validation", baseline, EXPECTED_BASELINE) model_names = [model.strip() for model in args.models.split(",") if model.strip()] for model_name in model_names: parsed = parse_run(run_inference(build_model_env(model_name)), spec) print_summary(f"Model validation [{model_name}]", parsed) if __name__ == "__main__": main()