import jsonimport torchimport torch.nn as nnimport torch.nn.functional as Ffrom PIL import Imagefrom transformers import AutoProcessor, AutoModelForImageTextToTextfrom peft import PeftModelfrom huggingface_hub import hf_hub_downloadfrom safetensors.torch import load_file REPO_ID = "kinasr/imajev-2b-web-visual-testing"BASE_MODEL_ID = "Qwen/Qwen3.5-2B"DEVICE = "cuda" if torch.cuda.is_available() else "cpu" # 1. Load Processor & Base Modelprocessor = AutoProcessor.from_pretrained(REPO_ID)base_model = AutoModelForImageTextToText.from_pretrained( BASE_MODEL_ID, torch_dtype=torch.float16 if DEVICE != "cpu" else torch.float32, device_map="auto")model = PeftModel.from_pretrained(base_model, REPO_ID).eval() # 2. Load Decision Readout Headreadout_path = hf_hub_download(repo_id=REPO_ID, filename="decision_readout.safetensors")readout_state = load_file(readout_path)num_codes, hidden_dim = readout_state["weight"].shapehas_bias = "bias" in readout_state decision_head = nn.Linear(hidden_dim, num_codes, bias=has_bias, dtype=torch.float32).to(DEVICE)decision_head.weight.data.copy_(readout_state["weight"].to(torch.float32))if has_bias: decision_head.bias.data.copy_(readout_state["bias"].to(torch.float32))decision_head.eval() # 3. Load Calibration Temperaturecal_path = hf_hub_download(repo_id=REPO_ID, filename="calibration.json")with open(cal_path, "r") as f: temp = json.load(f).get("temperature", 1.2334) # 4. Run Visual Regression Checkdef classify_visual_diff(ref_image_path: str, curr_image_path: str, app_state: dict): options = [ "Pass (No visual regression)", "Layout shift, overlap, or misalignment", "Missing interactive element or CTA", "Broken CSS styling or unreadable contrast", "Broken image, icon, or media asset" ] img_ref = Image.open(ref_image_path).convert("RGB") img_curr = Image.open(curr_image_path).convert("RGB") img_ref.thumbnail((640, 640)) img_curr.thumbnail((640, 640)) opts_str = "\n".join([f"[{i}] {opt}" for i, opt in enumerate(options)]) messages = [ {"role": "system", "content": [{"type": "text", "text": "You are a typed decision model for web visual regression testing. Output a calibrated decision over the provided options or unknown."}]}, {"role": "user", "content": [ {"type": "image"}, {"type": "image"}, {"type": "text", "text": f"App State: {json.dumps(app_state)}\nQuestion: Compare the current screenshot against baseline reference and classify the regression.\nType: choice\nOptions:\n{opts_str}\nDecision:"} ]} ] prompt = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) inputs = processor(text=[prompt], images=[img_ref, img_curr], return_tensors="pt").to(DEVICE) with torch.no_grad(): outputs = model(**inputs, output_hidden_states=True) last_hidden = outputs.hidden_states[-1][:, -1, :].to(torch.float32) active_logits = decision_head(last_hidden)[:, :len(options) + 1] probs = F.softmax(active_logits / temp, dim=-1).squeeze(0).cpu().tolist() pred_idx = int(torch.argmax(active_logits, dim=-1).item()) verdict = options[pred_idx] if pred_idx < len(options) else "Unknown (Needs Human Review)" return { "verdict": verdict, "confidence": round(probs[pred_idx], 4), "unknown_prob": round(probs[-1], 4), "needs_review": (pred_idx == len(options) or probs[pred_idx] < 0.75) }