thangvip's picture
fix: synchronize the complete application runtime (#5)
513ea7b
Raw
History Blame Contribute Delete
4.38 kB
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field, field_validator
ForestStyle = Literal[
"surprise",
"watercolor",
"paper_cut",
"moonlit_gouache",
"botanical_ink",
]
ArcRole = Literal["arrive", "steady", "widen", "step", "carry"]
class StrictModel(BaseModel):
model_config = ConfigDict(extra="forbid", str_strip_whitespace=True)
class FactAnchor(StrictModel):
source_phrase: str = Field(min_length=1, max_length=240)
meaning: str = Field(min_length=3, max_length=300)
class SituationPlan(StrictModel):
faithful_summary: str = Field(min_length=12, max_length=500)
fact_anchors: list[FactAnchor] = Field(min_length=1, max_length=4)
central_uncertainty: str = Field(min_length=3, max_length=300)
desired_direction: str = Field(min_length=3, max_length=300)
class Clearing(StrictModel):
arc_role: ArcRole
source_phrase: str = Field(min_length=1, max_length=240)
scene_title: str = Field(min_length=3, max_length=80)
scene_intro: str = Field(min_length=12, max_length=240)
narration: str = Field(min_length=80, max_length=720)
strength: str = Field(min_length=3, max_length=100)
reflection: str = Field(min_length=12, max_length=260)
spell: str = Field(min_length=3, max_length=80)
image_prompt: str = Field(min_length=8, max_length=300)
@field_validator("spell")
@classmethod
def validate_spell(cls, value: str) -> str:
if not value.lower().startswith(("i ", "i'm ", "i am ")):
raise ValueError("spell must be a first-person present-tense mantra")
if len(value.split()) > 12:
raise ValueError("spell must contain at most 12 words")
return value
class IntakeTurn(StrictModel):
question: str = Field(min_length=4, max_length=240)
answer: str = Field(min_length=1, max_length=240)
class IntakeQuestion(StrictModel):
question: str = Field(min_length=4, max_length=240)
options: list[str] = Field(min_length=3, max_length=4)
rationale: str = Field(default="", max_length=2000)
@field_validator("options")
@classmethod
def validate_unique_options(cls, values: list[str]) -> list[str]:
normalized = {value.casefold() for value in values}
if len(normalized) != len(values):
raise ValueError("options must be unique")
return values
class ForestDraft(StrictModel):
forest_title: str = Field(min_length=3, max_length=120)
proposed_strengths: list[str] = Field(min_length=3, max_length=6)
clearings: list[Clearing] = Field(min_length=1, max_length=6)
@field_validator("proposed_strengths")
@classmethod
def validate_unique_strengths(cls, values: list[str]) -> list[str]:
normalized = {value.casefold() for value in values}
if len(normalized) != len(values):
raise ValueError("proposed strengths must be unique")
return values
class CriticScore(StrictModel):
index: int = Field(ge=0, le=5)
specificity: int = Field(ge=1, le=5)
warmth: int = Field(ge=1, le=5)
non_genericness: int = Field(ge=1, le=5)
non_toxic_positivity: int = Field(ge=1, le=5)
reason: str = Field(min_length=3, max_length=240)
class CriticDecision(StrictModel):
keep_indices: list[int] = Field(min_length=1, max_length=6)
revise_indices: list[int] = Field(default_factory=list, max_length=6)
reasons: dict[str, str] = Field(default_factory=dict)
scores: list[CriticScore] = Field(default_factory=list, max_length=6)
@field_validator("keep_indices", "revise_indices")
@classmethod
def validate_unique_indices(cls, values: list[int]) -> list[int]:
if len(set(values)) != len(values):
raise ValueError("indices must be unique")
if any(index < 0 or index > 5 for index in values):
raise ValueError("indices must be between zero and five")
return values
class GuardResult(StrictModel):
allowed: bool
category: Literal["ok", "invalid", "crisis", "abuse", "medical"]
message: str = ""
class StreamEvent(StrictModel):
type: Literal[
"status",
"support",
"forest",
"clearing",
"soundscape",
"complete",
"error",
]
message: str = ""
data: dict[str, object] = Field(default_factory=dict)