""" schemas.py — Pydantic models for all API inputs and outputs. Design choice: Every external boundary (request in, response out) is typed and validated via Pydantic. This gives automatic HTTP 422 errors on malformed input without writing any validation logic by hand, and makes the contract explicit and machine-readable. Interview Q: "Why Pydantic over dataclasses?" A: Pydantic models give JSON serialization, validation, and OpenAPI schema generation for free. Dataclasses require manual validators and don't integrate with FastAPI's schema generation. Trade-off: Pydantic v2 is stricter and faster than v1, but we use v1-compatible syntax (model_validator, Field) for broader compatibility with HF Spaces base images. """ from typing import List, Optional from pydantic import BaseModel, Field, field_validator class Message(BaseModel): """ A single turn in the conversation. role must be 'user' or 'assistant' — anything else is rejected at the boundary. content must be a non-empty string. """ role: str = Field(..., description="'user' or 'assistant'") content: str = Field(..., min_length=1, description="Turn content, non-empty") @field_validator("role") @classmethod def role_must_be_valid(cls, v: str) -> str: # Explicit allowlist keeps prompt-injection via role spoofing out. if v not in ("user", "assistant"): raise ValueError("role must be 'user' or 'assistant'") return v class ChatRequest(BaseModel): """ Stateless chat request: the caller sends the FULL conversation history on every call. No session IDs, no server-side memory. Design rationale: statelessness makes the service trivially horizontally scalable and removes any sticky-session / cache-invalidation complexity. """ messages: List[Message] = Field( ..., min_length=1, description="Full conversation history; must contain at least one message." ) class Recommendation(BaseModel): """ A single catalog item returned to the caller. Only fields that exist in the catalog are returned — no hallucinated metadata. """ name: str url: str test_type: str class ChatResponse(BaseModel): """ Exact schema the SHL evaluator expects. - reply: the agent's natural-language response. - recommendations: [] when clarifying or refusing; 1–10 items when shortlisting. - end_of_conversation: True when the agent detects the conversation is complete. """ reply: str recommendations: List[Recommendation] = Field(default_factory=list) end_of_conversation: bool = False