"""Typed-decision core from STEAV's published System One inference implementation. Apache-2.0. The encoder layout and decision head follow Convai Laya's Apache-2.0 implementation. No remote model code is executed. """ from __future__ import annotations import argparse import hashlib import json import math import os import re import sys import warnings from dataclasses import dataclass from pathlib import Path from typing import Any, Iterable, Sequence import numpy as np import torch from torch import nn __all__ = ["SystemOneDecisionModel", "LayaDecisionHead", "render_state", "offset_logit", "apply_prior_shift", "encode_decision", "load_laya_encoder", "ModelError"] # --- Contract constants (identical to the trainer that produced these models) --- PROMPT_TEMPLATE = "cid/system-one-encoder-prompt/laya-v1" HEAD_SCHEMA = "cid/system-one-head/v1" CALIBRATION_SCHEMA = "cid/system-one-calibration/v1" RELEASE_SCHEMA = "system-one-release/v1" LAYA_QTYPES = {"choice": 0, "score": 1, "noul": 2} LAYA_HEAD_MAX_LEN = 192 LAYA_OPTION_TOKENS = 48 MAX_SLOTS = 128 OFFSET_CLIP = 1e-6 PRIOR_CLIP = 1e-9 # the benchmark scorer's clip before the prior shift DEFAULT_DESCRIPTIONS = {"choice": "Which answer is correct?", "score": "Which level applies?", "noul": "Is the proposition true?"} NOUL_SLOTS = ({"id": "false", "description": "The proposition is false."}, {"id": "true", "description": "The proposition is true."}) NOUL_OPTION_TEXTS = ("false: no, the statement does not hold", "true: yes, the statement holds") _NUMBER = re.compile(r"[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[eE][+-]?[0-9]+)?") # --- Base model pin: Hugging Face convaiinnovations/laya, root files (unchanged since the initial release commit # 00c37c405e3c3ad73ee070227614c89cda06b99e). Byte-identical to the base used in training except config.json's # "architectures" entry, which AutoModel.from_config ignores. LAYA_BASE_REPO = "convaiinnovations/laya" LAYA_BASE_REVISION = "7b928d828b7b0e022f929d9bd2e44165aa270148" LAYA_BASE_SHA256 = "891102d372688fc2a094dac56a384bc537b87c63f21f9f3dac0be2b7cbc8d86c" LAYA_BASE_FILES = ("model.safetensors", "encoder/config.json", "tokenizer/tokenizer.json", "tokenizer/tokenizer_config.json") class ModelError(ValueError): """Raised for invalid model folders, inputs that break the input contract, or failed integrity checks.""" # ----------------------------------------------------------------------------- input contract def question_slots(question: dict) -> list[dict]: """Answer slots in scoring order: choice options, score levels, or [false, true] for noul.""" qtype = str(question.get("type", "")).strip() if qtype == "choice": slots = question.get("options") or [] elif qtype == "score": slots = question.get("levels") or [] elif qtype == "noul": if question.get("options") or question.get("levels"): raise ModelError("noul takes no options or levels") slots = NOUL_SLOTS else: raise ModelError("question type must be choice, score or noul") if not 2 <= len(slots) <= MAX_SLOTS: raise ModelError("questions must carry between 2 and %d answer slots" % MAX_SLOTS) ids = [str(slot.get("id", "")).strip() for slot in slots] if any(not identity for identity in ids) or len(set(ids)) != len(ids): raise ModelError("answer ids must be nonempty and distinct") return [{"id": identity, "description": str(slot.get("description") or "")} for identity, slot in zip(ids, slots)] def option_texts(question: dict, slots: Sequence[dict]) -> list[str]: """Answer texts as rendered after each [MASK] marker.""" qtype = str(question.get("type", "")).strip() if qtype == "score": return ["level %d: %s" % (i, " ".join(slot["description"].split()) or slot["id"]) for i, slot in enumerate(slots)] if qtype == "noul": return list(NOUL_OPTION_TEXTS) return [(slot["id"] + ": " + " ".join(slot["description"].split())) if slot["description"].strip() else slot["id"] for slot in slots] def render_state(state: Any, offset_field: str | None = None, offset_hidden: bool = False) -> str: """Evidence text exactly as trained. A dict is rendered as compact JSON with sorted keys and raw Unicode. Key order therefore decides which fields survive end truncation: digits < uppercase (Context_*, Tree_*) < "_" < lowercase. A str state is used as given (stripped). With offset_hidden, only the literal offset key is removed from the text. """ if offset_hidden and offset_field and isinstance(state, dict): state = {key: value for key, value in state.items() if key != offset_field} if isinstance(state, str): return state.strip() return json.dumps(state, sort_keys=True, separators=(",", ":"), ensure_ascii=False) def offset_logit(state: Any, question: dict, field: str | None) -> float: """Fixed upstream log-odds added to the "true" slot of noul questions. The value must be a JSON number or a numeric string in [0, 1]; booleans are rejected. It is clipped to [1e-6, 1 - 1e-6], so the offset lies within about +/-13.8. """ if not field or str(question.get("type", "")).strip() != "noul": return 0.0 if not isinstance(state, dict) or field not in state: raise ModelError("offset field %r missing from state" % field) value = state[field] if isinstance(value, bool) or not isinstance(value, (int, float, str)): raise ModelError("offset field %r must be a probability in [0, 1]" % field) if isinstance(value, str) and not _NUMBER.fullmatch(value.strip()): raise ModelError("offset field %r must be a probability in [0, 1]" % field) try: p = float(value) except (TypeError, ValueError, OverflowError): raise ModelError("offset field %r must be a probability in [0, 1]" % field) from None if not math.isfinite(p) or p < 0 or p > 1: raise ModelError("offset field %r must be a probability in [0, 1]" % field) p = min(max(p, OFFSET_CLIP), 1 - OFFSET_CLIP) return math.log(p / (1 - p)) def bucket_key(question: dict, count: int) -> str: """Calibration bucket: ':2', ':3-4', ':5-8' or ':9+'.""" width = "2" if count <= 2 else "3-4" if count <= 4 else "5-8" if count <= 8 else "9+" return str(question.get("type", "")).strip() + ":" + width @dataclass class Encoded: ids: list[int] positions: list[int] # index of each answer's [MASK] marker qtype: int offset: float = 0.0 state_tokens: int = 0 # evidence tokens before truncation dropped_state_tokens: int = 0 # evidence tokens cut from the END to fit max_seq_length def encode_decision(tokenizer, state: Any, question: dict, limit: int, offset_field: str | None = None, offset_hidden: bool = False) -> Encoded: """Laya sequence (template cid/system-one-encoder-prompt/laya-v1): [CLS] " question: " [SEP] [MASK] "