"""Pydantic v2 data models for Meeting Room.""" from __future__ import annotations from datetime import datetime, timezone from typing import Union from pydantic import BaseModel, ConfigDict, Field, field_validator # --------------------------------------------------------------------------- # Configuration models — must be compatible with config.yaml parsed by # yaml.safe_load() so that DiscussionConfig(**yaml_config) works unchanged. # --------------------------------------------------------------------------- class ProviderConfig(BaseModel): """A single API provider (OpenAI-compatible endpoint).""" model_config = ConfigDict(extra="forbid") base_url: str api_key: str class RoleConfig(BaseModel): """A discussion participant role bound to a provider/model.""" model_config = ConfigDict(extra="forbid") name: str provider: str model: str temperature: float = 0.7 tools: Union[str, list[str]] = "none" system_prompt: str = "" @field_validator("tools", mode="before") @classmethod def _coerce_tools(cls, v: object) -> Union[str, list[str]]: if isinstance(v, str): return v if isinstance(v, list): return [str(item) for item in v] raise ValueError(f"tools must be a string or list of strings, got {type(v).__name__}") class DefaultsConfig(BaseModel): """Default settings inherited by scenarios.""" model_config = ConfigDict(extra="ignore") max_rounds: int = 8 language: str = "ru" framework: str = "custom" workdir: str = "." class DiscussionConfig(BaseModel): """Top-level config — the direct counterpart of config.yaml.""" model_config = ConfigDict(extra="ignore") providers: dict[str, ProviderConfig] = Field(default_factory=dict) roles: dict[str, RoleConfig] = Field(default_factory=dict) defaults: DefaultsConfig = Field(default_factory=DefaultsConfig) # --------------------------------------------------------------------------- # Scenario model # --------------------------------------------------------------------------- class ScenarioConfig(BaseModel): """A discussion scenario (loaded from .md / .yaml files).""" model_config = ConfigDict(extra="ignore") name: str = "Untitled" participants: list[str] = Field(default_factory=list) max_rounds: int = 8 problem: str = "" # --------------------------------------------------------------------------- # Runtime models # --------------------------------------------------------------------------- class Message(BaseModel): """A single message in a discussion session.""" model_config = ConfigDict(extra="forbid") role_id: str name: str content: str class ToolCallRecord(BaseModel): """Record of a single tool call made by an agent.""" model_config = ConfigDict(extra="forbid") id: str name: str arguments: str result: str = "" class Session(BaseModel): """A complete discussion session (scenario + messages + timestamps).""" model_config = ConfigDict(extra="ignore") scenario: ScenarioConfig = Field(default_factory=ScenarioConfig) messages: list[Message] = Field(default_factory=list) tool_calls: list[ToolCallRecord] = Field(default_factory=list) created_at: datetime = Field( default_factory=lambda: datetime.now(timezone.utc), ) updated_at: datetime = Field( default_factory=lambda: datetime.now(timezone.utc), )