Fabella / schema.py
OpenCode
feat: sync final submission code
8e12b6e
Raw
History Blame Contribute Delete
3.27 kB
"""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
@dataclass
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.",
)
@field_validator("issues")
@classmethod
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()]
@field_validator("reasoning")
@classmethod
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