Download groot1/schema.py from marcyano/Groot-1: direct link, hf CLI and curl.
- Browser
- Download file 8.98 kB
-
https://huggingface.co/marcyano/Groot-1/resolve/main/groot1/schema.py
- Command line
-
hf download hf://marcyano/Groot-1/groot1/schema.py
-
curl -L -o schema.py https://huggingface.co/marcyano/Groot-1/resolve/main/groot1/schema.py
8.98 kB
| """Request/response schema and confidence formulas. | |
| The schema mirrors TypeSafe's System One contract (OpenAPI 0.2.0, observed | |
| Oct 2026) so Groot 1 is a drop-in replacement for `POST /v1/systemone`: | |
| * `choice` — 1..255 named criteria, returns choice + confidence + probabilities | |
| * `score` — 1..10 ordered levels, returns expected score + confidence + legend | |
| * `noul` — yes/no statement, returns the probability of "true" | |
| Confidence formulas are the ones TypeSafe documents (docs.typesafe.ai/confidence): | |
| * choice: (p_max - 1/n) / (1 - 1/n) | |
| * score : max(0, 1 - sum_i p_i |i - m| / MAD_unif), MAD_unif = mean_i |i - (n-1)/2| | |
| * noul : no confidence field; |2p - 1| is the suggested equivalent. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import math | |
| from typing import Any, Literal, Mapping, Sequence, Union | |
| from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator | |
| Content = Union[str, dict, list, None] | |
| # ----------------------------------------------------------------- rendering | |
| def render_content(value: Content, *, indent: int = 0) -> str: | |
| """Render a state / instruction / criterion value as model-readable text. | |
| JSON objects are rendered as indented `key: value` lines (key paths help | |
| the model with indirection questions); arrays become numbered items. | |
| Strings pass through unchanged. | |
| """ | |
| if value is None: | |
| return "" | |
| if isinstance(value, str): | |
| return value | |
| if isinstance(value, bool): | |
| return "true" if value else "false" | |
| if isinstance(value, (int, float)): | |
| return json.dumps(value) | |
| pad = " " * indent | |
| if isinstance(value, Mapping): | |
| lines = [] | |
| for key, item in value.items(): | |
| if isinstance(item, (Mapping, list)) and item: | |
| lines.append(f"{pad}{key}:") | |
| lines.append(render_content(item, indent=indent + 1)) | |
| else: | |
| lines.append(f"{pad}{key}: {render_content(item)}") | |
| return "\n".join(lines) | |
| if isinstance(value, Sequence): | |
| lines = [] | |
| for i, item in enumerate(value): | |
| if isinstance(item, (Mapping, list)) and item: | |
| lines.append(f"{pad}[{i}]") | |
| lines.append(render_content(item, indent=indent + 1)) | |
| else: | |
| lines.append(f"{pad}[{i}] {render_content(item)}") | |
| return "\n".join(lines) | |
| return json.dumps(value, ensure_ascii=False) | |
| # ----------------------------------------------------------------- questions | |
| class ChoiceQuestion(BaseModel): | |
| model_config = ConfigDict(extra="forbid") | |
| type: Literal["choice"] = "choice" | |
| instructions: Content = None | |
| criteria: dict[str, Content] = Field(..., min_length=1, max_length=255) | |
| def option_names(self) -> list[str]: | |
| return list(self.criteria) | |
| def option_texts(self) -> list[str]: | |
| out = [] | |
| for name, desc in self.criteria.items(): | |
| text = render_content(desc) | |
| out.append(f"{name}: {text}" if text else name) | |
| return out | |
| class ScoreQuestion(BaseModel): | |
| model_config = ConfigDict(extra="forbid") | |
| type: Literal["score"] = "score" | |
| instructions: Content = None | |
| criteria: list[Content] = Field(..., min_length=1, max_length=10) | |
| def option_names(self) -> list[str]: | |
| return [str(i) for i in range(len(self.criteria))] | |
| def option_texts(self) -> list[str]: | |
| return [f"level {i}: {render_content(c)}" for i, c in enumerate(self.criteria)] | |
| class NoulCriteria(BaseModel): | |
| model_config = ConfigDict(extra="forbid") | |
| true: Content = None | |
| false: Content = None | |
| class NoulQuestion(BaseModel): | |
| model_config = ConfigDict(extra="forbid") | |
| type: Literal["noul"] = "noul" | |
| instructions: Content = None | |
| criteria: NoulCriteria | None = None | |
| def _needs_text(self) -> "NoulQuestion": | |
| has_criteria = self.criteria is not None and ( | |
| self.criteria.true is not None or self.criteria.false is not None | |
| ) | |
| if self.instructions is None and not has_criteria: | |
| raise ValueError("noul requires instructions or a described outcome") | |
| return self | |
| def option_names(self) -> list[str]: | |
| return ["false", "true"] | |
| def option_texts(self) -> list[str]: | |
| f = render_content(self.criteria.false) if self.criteria else "" | |
| t = render_content(self.criteria.true) if self.criteria else "" | |
| return [f"false: {f}" if f else "false", f"true: {t}" if t else "true"] | |
| Question = Union[ChoiceQuestion, ScoreQuestion, NoulQuestion] | |
| def parse_question(raw: Mapping[str, Any]) -> Question: | |
| kind = raw.get("type") | |
| if kind == "choice": | |
| return ChoiceQuestion(**raw) | |
| if kind == "score": | |
| return ScoreQuestion(**raw) | |
| if kind == "noul": | |
| return NoulQuestion(**raw) | |
| raise ValueError(f"unsupported question type {kind!r} (box/images are not supported by Groot 1)") | |
| class SystemOneRequest(BaseModel): | |
| model_config = ConfigDict(extra="forbid") | |
| model: str = "groot-latest" | |
| state: Union[str, dict, list] | |
| questions: dict[str, Any] = Field(..., min_length=1) | |
| assets: dict[str, Any] | None = None | |
| def _parse(cls, value: dict[str, Any]) -> dict[str, Question]: | |
| parsed: dict[str, Question] = {} | |
| for name, q in value.items(): | |
| if not isinstance(name, str) or not name: | |
| raise ValueError("question names must be non-empty strings") | |
| parsed[name] = q if isinstance(q, (ChoiceQuestion, ScoreQuestion, NoulQuestion)) else parse_question(q) | |
| return parsed | |
| def _no_assets(self) -> "SystemOneRequest": | |
| if self.assets: | |
| raise ValueError("assets/images are not supported by Groot 1") | |
| return self | |
| # ------------------------------------------------------------------ answers | |
| class ChoiceAnswer(BaseModel): | |
| type: Literal["choice"] = "choice" | |
| choice: str | |
| confidence: float | |
| probabilities: dict[str, float] | |
| class ScoreAnswer(BaseModel): | |
| type: Literal["score"] = "score" | |
| score: float | |
| confidence: float | |
| legend: dict[str, Content] | |
| probabilities: dict[str, float] | |
| class NoulAnswer(BaseModel): | |
| type: Literal["noul"] = "noul" | |
| noul: float | |
| class Usage(BaseModel): | |
| input_tokens: int | |
| output_tokens: int | |
| class SystemOneResponse(BaseModel): | |
| model: str | |
| answers: dict[str, Union[ChoiceAnswer, ScoreAnswer, NoulAnswer]] | |
| usage: Usage | |
| x_groot: dict[str, Any] | None = None # optional extension (correctness estimate etc.) | |
| class ModelMetadata(BaseModel): | |
| name: str | |
| description: str | |
| release_date: str # YYYY-MM-DD | |
| # -------------------------------------------------------------- confidence | |
| def confidence_choice(probabilities: Sequence[float]) -> float: | |
| n = len(probabilities) | |
| if n <= 1: | |
| return 1.0 | |
| p_max = max(probabilities) | |
| return max(0.0, min(1.0, (p_max - 1.0 / n) / (1.0 - 1.0 / n))) | |
| def confidence_score(probabilities: Sequence[float]) -> float: | |
| n = len(probabilities) | |
| if n <= 1: | |
| return 1.0 | |
| m = max(range(n), key=probabilities.__getitem__) | |
| spread = sum(p * abs(i - m) for i, p in enumerate(probabilities)) | |
| even = sum(abs(i - (n - 1) / 2) for i in range(n)) / n | |
| return max(0.0, min(1.0, 1.0 - spread / even)) | |
| def confidence_noul(p_true: float) -> float: | |
| return abs(2.0 * p_true - 1.0) | |
| def expected_score(probabilities: Sequence[float]) -> float: | |
| return float(sum(i * p for i, p in enumerate(probabilities))) | |
| def softmax(logits: Sequence[float], temperature: float = 1.0) -> list[float]: | |
| if not logits: | |
| return [] | |
| scaled = [x / temperature for x in logits] | |
| top = max(scaled) | |
| exps = [math.exp(x - top) for x in scaled] | |
| total = sum(exps) | |
| return [e / total for e in exps] | |
| def build_answer(question: Question, probabilities: Sequence[float]) -> ChoiceAnswer | ScoreAnswer | NoulAnswer: | |
| names = question.option_names | |
| if len(names) != len(probabilities): | |
| raise ValueError("probability count must match option count") | |
| probs = {name: float(p) for name, p in zip(names, probabilities)} | |
| if isinstance(question, ChoiceQuestion): | |
| best = max(range(len(names)), key=lambda i: probabilities[i]) | |
| return ChoiceAnswer(choice=names[best], confidence=confidence_choice(probabilities), probabilities=probs) | |
| if isinstance(question, ScoreQuestion): | |
| legend = {str(i): c for i, c in enumerate(question.criteria)} | |
| return ScoreAnswer( | |
| score=expected_score(probabilities), | |
| confidence=confidence_score(probabilities), | |
| legend=legend, | |
| probabilities=probs, | |
| ) | |
| return NoulAnswer(noul=float(probabilities[1])) | |