| from __future__ import annotations
|
|
|
| from datetime import datetime, timezone
|
| from typing import Any, Literal
|
| from uuid import uuid4
|
|
|
| from pydantic import BaseModel, ConfigDict, Field, computed_field
|
|
|
|
|
| def utc_now() -> datetime:
|
| return datetime.now(timezone.utc)
|
|
|
|
|
| class StrictModel(BaseModel):
|
| model_config = ConfigDict(extra="forbid", validate_assignment=True)
|
|
|
|
|
| class VisualMotif(StrictModel):
|
| key: str
|
| label: str
|
| palette: list[str] = Field(default_factory=list)
|
| motion: str | None = None
|
|
|
|
|
| class Destination(StrictModel):
|
| year: str
|
| place: str
|
| mode: Literal["past", "future", "strange"]
|
| atmosphere: str
|
| visual_preset_key: str
|
| visual_motifs: list[VisualMotif] = Field(default_factory=list)
|
| temporal_oddity: str | None = None
|
|
|
|
|
| class Persona(StrictModel):
|
| name: str
|
| local_identifier: str | None = None
|
| role: str
|
| immediate_situation: str
|
| daily_concern: str
|
| secret_or_desire: str
|
| worldview_constraints: list[str] = Field(default_factory=list)
|
| voice_theory: str
|
| speaking_style: str
|
| safety_notes: list[str] = Field(default_factory=list)
|
|
|
|
|
| class VoiceProfile(StrictModel):
|
| voice_id: str
|
| description: str
|
| pace: Literal["slow", "medium", "fast"] = "medium"
|
| emotion: str = "curious"
|
| accent_hint: str | None = None
|
|
|
|
|
| class ConversationTurn(StrictModel):
|
| role: Literal["user", "character", "machine"]
|
| text: str
|
| audio_path: str | None = None
|
| metadata: dict[str, Any] = Field(default_factory=dict)
|
| created_at: datetime = Field(default_factory=utc_now)
|
|
|
|
|
| class ConversationHistory(StrictModel):
|
| turns: list[ConversationTurn] = Field(default_factory=list)
|
|
|
| def append(self, turn: ConversationTurn) -> None:
|
| self.turns.append(turn)
|
|
|
| def character_turns(self) -> list[ConversationTurn]:
|
| return [turn for turn in self.turns if turn.role == "character"]
|
|
|
|
|
| class ConversationResponse(StrictModel):
|
| text: str
|
| prosody_hint: str | None = None
|
| ui_signal_event: str | None = None
|
|
|
|
|
| class Transcript(StrictModel):
|
| text: str
|
| confidence: float | None = Field(default=None, ge=0, le=1)
|
| language: str | None = None
|
| is_final: bool = True
|
|
|
|
|
| class AudioResult(StrictModel):
|
| path: str | None = None
|
| mime_type: str = "audio/wav"
|
| duration_seconds: float | None = Field(default=None, ge=0)
|
| description: str | None = None
|
|
|
|
|
| class ImmersiveScene(StrictModel):
|
| prompt: str
|
| image_b64: str | None = None
|
| mime_type: str = "image/png"
|
| ambient_key: str = "temporal_storm"
|
| visual_style: str = "surreal cinematic time portal"
|
| description: str | None = None
|
|
|
|
|
| class CharacterPortrait(StrictModel):
|
| prompt: str
|
| image_b64: str | None = None
|
| mime_type: str = "image/png"
|
| description: str | None = None
|
|
|
|
|
| class Narration(StrictModel):
|
| text: str
|
| audio: AudioResult | None = None
|
| voice_profile: VoiceProfile | None = None
|
|
|
|
|
| class VisualArtifact(StrictModel):
|
| title: str
|
| kind: str
|
| description: str
|
| prompt: str
|
| image_b64: str | None = None
|
| mime_type: str = "image/png"
|
|
|
|
|
| class Souvenir(StrictModel):
|
| destination: str
|
| contact: str
|
| quote: str
|
| artifact: str
|
| stamp_name: str
|
| summary: str
|
|
|
|
|
| class EncounterSession(StrictModel):
|
| session_id: str
|
| encounter_id: str = Field(default_factory=lambda: str(uuid4()))
|
| destination: Destination | None = None
|
| persona: Persona | None = None
|
| voice_profile: VoiceProfile | None = None
|
| scene: ImmersiveScene | None = None
|
| portrait: CharacterPortrait | None = None
|
| narration: Narration | None = None
|
| artifact: VisualArtifact | None = None
|
| history: ConversationHistory = Field(default_factory=ConversationHistory)
|
| souvenir: Souvenir | None = None
|
| created_at: datetime = Field(default_factory=utc_now)
|
| updated_at: datetime = Field(default_factory=utc_now)
|
|
|
| def touch(self) -> None:
|
| self.updated_at = utc_now()
|
|
|
|
|
| class SavedEncounter(StrictModel):
|
| encounter_id: str
|
| session_id: str
|
| path: str
|
| saved_at: datetime = Field(default_factory=utc_now)
|
|
|
|
|
| class ModelSpec(StrictModel):
|
| role: str
|
| provider: str
|
| model_id: str
|
| parameters_billion: float = Field(ge=0)
|
| enabled: bool = True
|
| runtime: str
|
| license: str | None = None
|
| notes: str | None = None
|
|
|
|
|
| class ModelBudget(StrictModel):
|
| models: list[ModelSpec] = Field(default_factory=list)
|
| parameter_limit_billion: float = 32.0
|
|
|
| @computed_field
|
| @property
|
| def enabled_parameters_billion(self) -> float:
|
| return round(
|
| sum(model.parameters_billion for model in self.models if model.enabled),
|
| 3,
|
| )
|
|
|
| @computed_field
|
| @property
|
| def largest_enabled_parameters_billion(self) -> float:
|
| enabled = [model.parameters_billion for model in self.models if model.enabled]
|
| return round(max(enabled, default=0.0), 3)
|
|
|
| @computed_field
|
| @property
|
| def is_within_limit(self) -> bool:
|
| return all(
|
| model.parameters_billion <= self.parameter_limit_billion
|
| for model in self.models
|
| if model.enabled
|
| )
|
|
|