APUS-Reflex / deployment /contracts.py
gump2049's picture
Add TypeSafe SDK serving with verified low/high vLLM routes (#1)
5c6d744
Raw History Blame Contribute Delete
11.2 kB
"""Wire validation and deterministic semantic bridge to existing decision prompts."""
import json
import math
from dataclasses import dataclass
PROMPT_VERSION = "jev.dynamic.prompt.v2"
LABELS = tuple("ABCDEFGHIJKLMNOP")
BINARY_CRITERIA = [
{"id": "yes", "description": "The stated proposition is true."},
{"id": "no", "description": "The stated proposition is false."},
]
def validate_request(record):
for key in ("id", "group_id", "state", "instructions"):
if not isinstance(record.get(key), str) or not record[key].strip():
raise ValueError(f"{key} must be a nonempty string")
if record.get("primitive") not in ("choice", "noul", "score_level"):
raise ValueError("unsupported primitive")
criteria = record.get("criteria")
if not isinstance(criteria, list) or not 2 <= len(criteria) <= len(LABELS):
raise ValueError("criteria must contain 2..16 candidates")
ids = []
for candidate in criteria:
if not isinstance(candidate, dict):
raise TypeError("candidate must be an object")
for key in ("id", "description"):
if not isinstance(candidate.get(key), str) or not candidate[key].strip():
raise ValueError(f"candidate {key} must be nonempty")
ids.append(candidate["id"])
if len(set(ids)) != len(ids):
raise ValueError("duplicate candidate ids")
if record["primitive"] != "choice" and criteria != BINARY_CRITERIA:
raise ValueError("noul and score_level require canonical yes/no criteria")
def validate_record(record):
validate_request(record)
if record.get("gold") not in [c["id"] for c in record["criteria"]]:
raise ValueError("gold must be a candidate id")
if not isinstance(record.get("provenance"), dict):
raise TypeError("provenance must be an object")
def label_mapping(record):
validate_request(record)
return dict(zip(LABELS, (c["id"] for c in record["criteria"])))
def render_prompt_parts(record):
"""Text prefix/suffix; callers MUST check tokenizer boundary equivalence."""
validate_request(record)
prefix = "Shared state:\n" + record["state"] + "\n\n"
task = {
"primitive": record["primitive"],
"instructions": record["instructions"],
"criteria": [
{"label": label, "description": candidate["description"]}
for label, candidate in zip(LABELS, record["criteria"])
],
}
suffix = json.dumps(task, ensure_ascii=False, sort_keys=True)
suffix += (
"\nReturn only the selected letter: "
+ ", ".join(LABELS[: len(record["criteria"])])
+ ".\nAnswer:"
)
return prefix, suffix
def render_prompt(record):
return "".join(render_prompt_parts(record))
COMPILER_VERSION = "jev.typesafe.compiler.v1"
CONFIDENCE_POLICY = "normalized-entropy.v1"
class ServingError(Exception):
def __init__(self, status, code, message):
super().__init__(message)
self.status, self.code, self.message = status, code, message
def invalid(message):
raise ServingError(422, "invalid_request", message)
def text(value):
if isinstance(value, str) and value.strip():
return value
return json.dumps(value, ensure_ascii=False, sort_keys=True, allow_nan=False)
def content(value):
return isinstance(value, (str, dict, list))
@dataclass(frozen=True)
class Question:
key: str
kind: str
record: dict
legend: dict | None = None
def prepare_request(body, *, model_name, max_questions=16, allow_score=True):
if not isinstance(body, dict) or set(body) - {
"state",
"questions",
"model",
"effort",
}:
invalid("Expected state, questions, model and optional effort only")
if not isinstance(body.get("model", "jev-latest"), str) or body.get(
"model", "jev-latest"
) not in {model_name, "jev-latest"}:
raise ServingError(404, "unknown_model", "Model is not served")
if not content(body.get("state")):
invalid("state must be a string, object or array")
questions = body.get("questions")
if not isinstance(questions, dict) or not 1 <= len(questions) <= max_questions:
invalid("questions must be a nonempty map within the configured limit")
result = []
for key, question in questions.items():
if not isinstance(key, str) or not key.strip():
invalid("Question IDs must be nonempty strings")
if not isinstance(question, dict) or set(question) - {
"type",
"instructions",
"criteria",
}:
invalid("Invalid question fields")
kind = question.get("type")
instruction = question.get("instructions")
if instruction is not None and not content(instruction):
invalid("instructions must be text, object, array or null")
instruction = (
text(instruction)
if instruction is not None
else "Evaluate the state against the supplied criteria."
)
criteria = question.get("criteria")
legend = None
internal_kind = kind
if kind == "choice":
if not isinstance(criteria, dict) or not 2 <= len(criteria) <= len(LABELS):
invalid(
"This model supports 2..16 Choice candidates; none are truncated"
)
candidates = []
for name, description in criteria.items():
if not isinstance(name, str) or not name.strip():
invalid("Choice names must be nonempty strings")
if description is not None and not content(description):
invalid("Invalid Choice description")
candidates.append(
{
"id": name,
"description": name
if description is None
else text(description),
}
)
elif kind == "noul":
if criteria is not None:
if not isinstance(criteria, dict) or set(criteria) - {"true", "false"}:
invalid("Noul criteria accepts true and false descriptions")
if any(v is not None and not content(v) for v in criteria.values()):
invalid("Invalid Noul criterion")
if any(v is not None for v in criteria.values()) and criteria != {
"true": BINARY_CRITERIA[0]["description"],
"false": BINARY_CRITERIA[1]["description"],
}:
instruction = text(
{"question": instruction, "meaning_of_answers": criteria}
)
candidates = [dict(c) for c in BINARY_CRITERIA]
elif kind == "score":
if not allow_score:
raise ServingError(
422,
"unsupported_score",
"Ordinal Score is experimental and disabled; it is not score_level",
)
if not isinstance(criteria, list) or not 1 <= len(criteria) <= 10:
invalid("Score requires 1..10 ordered levels")
if any(not content(v) for v in criteria):
invalid("Invalid Score level")
legend = {str(i): v for i, v in enumerate(criteria)}
candidates = [{"id": k, "description": text(v)} for k, v in legend.items()]
internal_kind = "choice"
instruction = text(
{
"question": instruction,
"task": "Select the best matching ordered rubric level. Levels run from lowest to highest.",
}
)
else:
invalid("Unsupported question type")
record = {
"id": key,
"group_id": "serving",
"state": text(body["state"]),
"primitive": internal_kind,
"instructions": instruction,
"criteria": candidates,
}
result.append(Question(key, kind, record, legend))
return result
@dataclass(frozen=True)
class Compiled:
question: Question
prompt_ids: list[int]
candidate_token_ids: list[int]
def compile_question(question, tokenizer, max_tokens=8192):
if question.kind == "score" and len(question.record["criteria"]) == 1:
return Compiled(question, [], [])
prompt = tokenizer.apply_chat_template(
[{"role": "user", "content": render_prompt(question.record)}],
tokenize=False,
add_generation_prompt=True,
enable_thinking=False,
)
ids = tokenizer.encode(prompt, add_special_tokens=False)
if not ids or len(ids) > max_tokens:
invalid("Compiled prompt exceeds token budget; no truncation is allowed")
special = set(tokenizer.all_special_ids)
if special.intersection(
tokenizer.encode(render_prompt(question.record), add_special_tokens=False)
):
invalid("Input contains reserved model control tokens")
tokens = []
for label in LABELS[: len(question.record["criteria"])]:
encoded = tokenizer.encode(label, add_special_tokens=False)
if (
len(encoded) != 1
or tokenizer.encode(prompt + label, add_special_tokens=False)
!= ids + encoded
):
invalid("Candidate is not one token at the answer boundary")
if encoded[0] in special or encoded[0] in tokens:
invalid("Invalid candidate token mapping")
tokens.append(encoded[0])
return Compiled(question, ids, tokens)
def probabilities(scores):
if len(scores) < 2 or any(
type(v) not in (int, float) or not math.isfinite(v) for v in scores
):
raise ServingError(
502, "invalid_distribution", "Backend returned invalid candidate scores"
)
shift = max(scores)
values = [math.exp(v - shift) for v in scores]
total = sum(values)
return [v / total for v in values]
def confidence(probs):
if len(probs) == 1:
return 1.0
entropy = -sum(p * math.log(p) for p in probs if p > 0)
return min(1.0, max(0.0, 1.0 - entropy / math.log(len(probs))))
def answer(question, probs):
keys = [c["id"] for c in question.record["criteria"]]
if (
len(keys) != len(probs)
or any(
type(p) not in (int, float) or not math.isfinite(p) or not 0 <= p <= 1
for p in probs
)
or not math.isclose(sum(probs), 1, abs_tol=1e-6)
):
raise ServingError(
502,
"invalid_distribution",
"Backend distribution does not match candidates",
)
if question.kind == "noul":
return {"type": "noul", "noul": probs[keys.index("yes")]}
value = {
"type": question.kind,
"probabilities": dict(zip(keys, probs)),
"confidence": confidence(probs),
}
if question.kind == "choice":
value["choice"] = keys[max(range(len(probs)), key=probs.__getitem__)]
else:
value.update(
score=sum(i * p for i, p in enumerate(probs)), legend=question.legend
)
return value