"""Pydantic models for request/response schemas.""" from pydantic import BaseModel, Field from typing import Optional, List, Dict, Any, Literal from datetime import datetime, timezone from .enums import ModelName # ============ Auth Models ============ class TokenRequest(BaseModel): """Request for JWT token (can be extended with username/password).""" # Pour l'instant, on pourrait juste retourner un token # Plus tard, on peut ajouter username/password pass class TokenResponse(BaseModel): """JWT token response.""" access_token: str token_type: str = "bearer" expires_in: int # ============ Completion Models ============ class CompletionRequest(BaseModel): """Request for text completion.""" message: str = Field(..., description="User message to complete") model: ModelName = Field(default=ModelName.MISTRAL_LARGE, description="LLM model to use") agent: Optional[str] = Field( default=None, description="Agent identifier to use (ex: 'V1', 'V2', or 'AGENT'). If omitted, defaults to 'AGENT'." ) stream: bool = Field(default=False, description="Enable streaming response") temperature: float = Field(default=0.7, ge=0.0, le=2.0, description="Sampling temperature") max_tokens: Optional[int] = Field(default=None, description="Maximum tokens to generate") conversation_history: Optional[List[Dict[str, str]]] = Field( default=None, description="Optional conversation history" ) # Project-scoped retrieval project_id: Optional[str] = Field(default=None, description="Optional project id to scope retrieval") sources: Optional[List[str]] = Field( default=None, description="Optional list of document UUIDs to restrict project retrieval. Empty list or null means no restriction.", ) # Server-side memory (LangGraph thread_id). When set, conversation history is managed by the server; conversation_history is ignored. conversation_id: Optional[str] = Field( default=None, description="Conversation id for server-side memory (thread_id). When set, conversation_history is ignored." ) class CompletionResponse(BaseModel): """Response for text completion (non-streaming).""" response: str model: str agent: Optional[str] = None usage: Optional[Dict[str, Any]] = None metadata: Optional[Dict[str, Any]] = None conversation_id: Optional[str] = Field( default=None, description="Conversation id used for this request. Reuse it in subsequent requests to maintain server-side memory." ) class StreamChunk(BaseModel): """Single chunk in streaming response.""" content: str done: bool = False conversation_id: Optional[str] = Field( default=None, description="Conversation id present in every chunk. Reuse it in subsequent requests to maintain server-side memory." ) metadata: Optional[Dict[str, Any]] = None # ============ Transcription Models ============ class TranscriptionResponse(BaseModel): """Response for audio transcription. Usage, emissions, latency, etc. are under metadata.usage and metadata.emissions_*. """ text: str language: Optional[str] = None duration_s: Optional[float] = None model: str = "whisper-1" metadata: Optional[Dict[str, Any]] = None class TranscriptMessage(BaseModel): """Single message in a voice conversation transcript.""" role: Literal["user", "assistant"] text: str timestamp: datetime class TranscriptListResponse(BaseModel): """Transcript for a given conversation.""" conversation_id: str messages: List[TranscriptMessage] # ============ Model Info Models ============ class ModelInfo(BaseModel): """Information about an available model.""" name: str provider: str description: Optional[str] = None supports_streaming: bool = True context_window: Optional[int] = None class ModelsListResponse(BaseModel): """List of available models.""" models: List[ModelInfo] total: int class AgentInfo(BaseModel): """Information about an available agent.""" type: str name: str description: str available: bool = True class AgentsListResponse(BaseModel): """List of available agents.""" agents: List[AgentInfo] total: int # ============ Error Models ============ class ErrorResponse(BaseModel): """Error response.""" error: str detail: Optional[str] = None timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) # ============ Health Check ============ class HealthResponse(BaseModel): """Health check response.""" status: str version: str timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) # ============ Voice / VAD Models ============ class VadConfigDTO(BaseModel): """Voice Activity Detection configuration from client or settings. Domain DTO with validation. Mapping to Pipecat VADParams is done in services/voice/vad_config_service to keep domain independent of Pipecat. """ vad_stop_secs: float = Field( default=0.2, ge=0.2, le=2.0, description="Seconds of silence before confirming speech has stopped", ) vad_start_secs: float = Field( default=0.2, ge=0.1, le=0.5, description="Seconds of speech before confirming voice start", ) vad_confidence: float = Field( default=0.7, ge=0.5, le=0.95, description="Minimum confidence threshold for voice detection (0-1)", ) vad_min_volume: float = Field( default=0.6, ge=0.3, le=0.9, description="Minimum audio volume threshold for speech detection (0-1)", ) # ============ Upload / Ingestion Jobs ============ class UploadJobResponse(BaseModel): """Response returned when a background upload/ingestion job is created.""" job_id: str status: str = "queued" created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) # Optional: server-measured audio duration for transcription jobs (seconds) duration_s: Optional[float] = None class JobStatusResponse(BaseModel): """Snapshot of ingestion job status for polling.""" job_id: str status: str # Logical job type: document_ingestion | transcription_audio | transcription_meeting | ... job_type: Optional[str] = None # Progress and stage progress: Optional[float] = None progress_percent: Optional[int] = None stage: Optional[str] = None # upload | ocr | chunk | embed | index # Counters pages_total: Optional[int] = None pages_done: Optional[int] = None chunks_total: Optional[int] = None chunks_done: Optional[int] = None inserted_count: Optional[int] = None # Transcription-specific fields (for audio transcription jobs) transcript_text: Optional[str] = None transcript_language: Optional[str] = None transcript_duration: Optional[float] = None transcript_model: Optional[str] = None transcript_metadata: Optional[Dict[str, Any]] = None # usage, emissions_kgCO2eq, latency_s, etc. # Error and timestamps error: Optional[str] = None created_at: Optional[datetime] = None started_at: Optional[datetime] = None updated_at: Optional[datetime] = None finished_at: Optional[datetime] = None class DeleteDocumentResponse(BaseModel): """Response returned when deleting a document's vector chunks.""" project_id: str document_id: str deleted_count: int