Spaces:
Running
Running
| """Typed inputs and outputs for Fabella — the small-words-for-big-questions tool.""" | |
| from dataclasses import dataclass | |
| from typing import Literal | |
| from pydantic import BaseModel, Field, field_validator | |
| class ExplainRequest: | |
| """What a parent tells Fabella to explain to their child. | |
| Attributes: | |
| situation: A 1-3 sentence freeform description of the situation the | |
| parent needs help explaining ("We're moving to a new house in | |
| 3 weeks", "Why is grandma in the hospital?"). | |
| age: The child's age in years (5-12 range supported). | |
| child_name: Optional name. If set, the explanation addresses the | |
| child directly. If empty, the parent is addressed ("your child"). | |
| tone: "gentle" | "matter-of-fact" | "playful". Controls the | |
| register of the explanation. | |
| seed: Determinism for the drafter (the judge is temperature=0). | |
| history: Recent parent/Fabella turns in this conversation. The | |
| drafter uses this as context so follow-up questions | |
| ("What if she asks if grandma will die?") get a coherent | |
| answer that builds on the previous explanation. Each item is | |
| a dict with keys "role" ("parent"|"fabella") and "content". | |
| """ | |
| situation: str | |
| age: int | |
| child_name: str = "" | |
| tone: str = "gentle" | |
| seed: int = 0 | |
| history: list = () # type: ignore[type-arg] | |
| share_trace: bool = True | |
| # --- Judge output --------------------------------------------------------- | |
| Verdict = Literal["approve", "revise"] | |
| class JudgeVerdict(BaseModel): | |
| """Structured output of the small Nemotron judge. | |
| The judge receives a draft explanation and a 6-criterion rubric, and | |
| returns one of these. Validated by Pydantic so that any deviation | |
| from the schema is caught immediately. | |
| """ | |
| ok: bool = Field( | |
| description="True iff the draft is good enough to ship as-is." | |
| ) | |
| issues: list[str] = Field( | |
| default_factory=list, | |
| description="Concrete, actionable problems with the draft. Empty if ok=true.", | |
| ) | |
| score: float = Field( | |
| ge=0.0, | |
| le=1.0, | |
| description="0..1 quality score. >=0.8 is generally approve-worthy.", | |
| ) | |
| verdict: Verdict = Field( | |
| description='"approve" if the draft is ready, "revise" if the drafter should rewrite.' | |
| ) | |
| reasoning: str = Field( | |
| default="", | |
| description="One short sentence explaining the verdict.", | |
| ) | |
| def _issues_short(cls, v: list[str]) -> list[str]: | |
| # Strip and bound each issue to 200 chars so a runaway model can't | |
| # blow up the drafter's context window. | |
| return [str(i).strip()[:200] for i in v if str(i).strip()] | |
| def _reasoning_short(cls, v: str) -> str: | |
| return (v or "").strip()[:300] | |
| class JudgeFailed(Exception): | |
| """Raised when the judge output cannot be parsed after a retry. | |
| The caller (the validate_explanation tool) should fall back to the | |
| rule-based check. | |
| """ | |
| def __init__(self, message: str, last_text: str = ""): | |
| super().__init__(message) | |
| self.last_text = last_text | |