Files
meeting-room/meeting_room/models.py
vitya d292d32afa feat: add Pydantic models and EventEmitter for v2 core
- models.py: ProviderConfig, RoleConfig, DiscussionConfig, ScenarioConfig,
  Message, ToolCallRecord, Session — compatible with config.yaml format
- events.py: Event constants, Event dataclass, EventEmitter with on/off/emit
- 67 tests passing (37 models + 30 events)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 00:00:21 +03:00

124 lines
3.4 KiB
Python

"""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),
)