1478 lines
53 KiB
Python
1478 lines
53 KiB
Python
from typing import List, Dict, Optional, Any, Union, Literal, TypeVar, Annotated
|
|
from pydantic import BaseModel, Field, EmailStr, HttpUrl, model_validator, field_validator, ConfigDict
|
|
from datetime import datetime, UTC
|
|
from enum import Enum
|
|
import os
|
|
import uuid
|
|
from utils.auth_utils import (
|
|
validate_password_strength,
|
|
sanitize_login_input,
|
|
)
|
|
import defines
|
|
|
|
# Generic type variable
|
|
T = TypeVar("T")
|
|
|
|
# ============================
|
|
# Enums
|
|
# ============================
|
|
|
|
|
|
class UserType(str, Enum):
|
|
CANDIDATE = "candidate"
|
|
EMPLOYER = "employer"
|
|
GUEST = "guest"
|
|
|
|
|
|
class UserGender(str, Enum):
|
|
FEMALE = "female"
|
|
MALE = "male"
|
|
|
|
|
|
class UserStatus(str, Enum):
|
|
ACTIVE = "active"
|
|
INACTIVE = "inactive"
|
|
PENDING = "pending"
|
|
BANNED = "banned"
|
|
|
|
|
|
class SkillLevel(str, Enum):
|
|
BEGINNER = "beginner"
|
|
INTERMEDIATE = "intermediate"
|
|
ADVANCED = "advanced"
|
|
EXPERT = "expert"
|
|
|
|
|
|
class EmploymentType(str, Enum):
|
|
FULL_TIME = "full-time"
|
|
PART_TIME = "part-time"
|
|
CONTRACT = "contract"
|
|
INTERNSHIP = "internship"
|
|
FREELANCE = "freelance"
|
|
|
|
|
|
class InterviewType(str, Enum):
|
|
PHONE = "phone"
|
|
VIDEO = "video"
|
|
ONSITE = "onsite"
|
|
TECHNICAL = "technical"
|
|
BEHAVIORAL = "behavioral"
|
|
|
|
|
|
class ApplicationStatus(str, Enum):
|
|
APPLIED = "applied"
|
|
REVIEWING = "reviewing"
|
|
INTERVIEW = "interview"
|
|
OFFER = "offer"
|
|
REJECTED = "rejected"
|
|
ACCEPTED = "accepted"
|
|
WITHDRAWN = "withdrawn"
|
|
|
|
|
|
class InterviewRecommendation(str, Enum):
|
|
STRONG_HIRE = "strong_hire"
|
|
HIRE = "hire"
|
|
NO_HIRE = "no_hire"
|
|
STRONG_NO_HIRE = "strong_no_hire"
|
|
|
|
|
|
class ChatSenderType(str, Enum):
|
|
USER = "user"
|
|
ASSISTANT = "assistant"
|
|
# Frontend can use this to set mock responses
|
|
SYSTEM = "system"
|
|
INFORMATION = "information"
|
|
WARNING = "warning"
|
|
ERROR = "error"
|
|
|
|
|
|
class SkillStatus(str, Enum):
|
|
PENDING = "pending"
|
|
COMPLETE = "complete"
|
|
WAITING = "waiting"
|
|
ERROR = "error"
|
|
|
|
|
|
class SkillStrength(str, Enum):
|
|
STRONG = "strong"
|
|
MODERATE = "moderate"
|
|
WEAK = "weak"
|
|
NONE = "none"
|
|
UNKNOWN = "unknown"
|
|
|
|
|
|
class EvidenceDetail(BaseModel):
|
|
source: str = Field(
|
|
..., alias=str("source"), description="The source of the evidence (e.g., resume section, position, project)"
|
|
)
|
|
quote: str = Field(
|
|
..., alias=str("quote"), description="Exact text from the resume or other source showing evidence"
|
|
)
|
|
context: str = Field(..., alias=str("context"), description="Brief explanation of how this demonstrates the skill")
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class ChromaDBGetResponse(BaseModel):
|
|
# Chroma fields
|
|
ids: List[str] = []
|
|
embeddings: List[List[float]] = []
|
|
documents: List[str] = []
|
|
metadatas: List[Dict[str, Any]] = []
|
|
distances: List[float] = []
|
|
# Additional fields
|
|
name: str = ""
|
|
size: int = 0
|
|
dimensions: int = 2 | 3
|
|
query: str = ""
|
|
query_embedding: Optional[List[float]] = Field(default=None, alias=str("queryEmbedding"))
|
|
umap_embedding_2d: Optional[List[float]] = Field(default=None, alias=str("umapEmbedding2D"))
|
|
umap_embedding_3d: Optional[List[float]] = Field(default=None, alias=str("umapEmbedding3D"))
|
|
|
|
class SkillAssessment(BaseModel):
|
|
candidate_id: str = Field(..., alias=str("candidateId"))
|
|
skill: str = Field(..., alias=str("skill"), description="The skill being assessed")
|
|
skill_modified: Optional[str] = Field(
|
|
default="", alias=str("skillModified"), description="The skill rephrased by LLM during skill match"
|
|
)
|
|
evidence_found: bool = Field(
|
|
..., alias=str("evidenceFound"), description="Whether evidence was found for the skill"
|
|
)
|
|
evidence_strength: SkillStrength = Field(
|
|
..., alias=str("evidenceStrength"), description="Strength of evidence found for the skill"
|
|
)
|
|
assessment: str = Field(
|
|
...,
|
|
alias=str("assessment"),
|
|
description="Short (one to two sentence) assessment of the candidate's proficiency with the skill",
|
|
)
|
|
description: str = Field(
|
|
...,
|
|
alias=str("description"),
|
|
description="Short (two to three sentence) description of what the skill is, independent of whether the candidate has that skill or not",
|
|
)
|
|
evidence_details: List[EvidenceDetail] = Field(
|
|
default_factory=list,
|
|
alias=str("evidenceDetails"),
|
|
description="List of evidence details supporting the skill assessment",
|
|
)
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("createdAt"))
|
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("updatedAt"))
|
|
rag_results: List[ChromaDBGetResponse] = Field(default_factory=list, alias=str("ragResults"))
|
|
match_score: float = Field(default=0.0, alias=str("matchScore"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
class JobAnalysis(BaseModel):
|
|
job_id: str = Field(..., alias=str("jobId"))
|
|
candidate_id: str = Field(..., alias=str("candidateId"))
|
|
skills: List[SkillAssessment] = Field(...)
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
class ApiMessageType(str, Enum):
|
|
BINARY = "binary"
|
|
TEXT = "text"
|
|
JSON = "json"
|
|
|
|
|
|
class ApiStatusType(str, Enum):
|
|
STREAMING = "streaming"
|
|
STATUS = "status"
|
|
DONE = "done"
|
|
ERROR = "error"
|
|
|
|
|
|
class ChatContextType(str, Enum):
|
|
JOB_SEARCH = "job_search"
|
|
JOB_REQUIREMENTS = "job_requirements"
|
|
CANDIDATE_CHAT = "candidate_chat"
|
|
INTERVIEW_PREP = "interview_prep"
|
|
RESUME_CHAT = "resume_chat"
|
|
RESUME_REVIEW = "resume_review"
|
|
EDIT_RESUME = "edit_resume"
|
|
GENERAL = "general"
|
|
GENERATE_PERSONA = "generate_persona"
|
|
GENERATE_PROFILE = "generate_profile"
|
|
GENERATE_RESUME = "generate_resume"
|
|
GENERATE_IMAGE = "generate_image"
|
|
RAG_SEARCH = "rag_search"
|
|
SKILL_MATCH = "skill_match"
|
|
|
|
|
|
class AIModelType(str, Enum):
|
|
QWEN2_5 = "qwen2.5"
|
|
FLUX_SCHNELL = "flux-schnell"
|
|
|
|
|
|
class MFAMethod(str, Enum):
|
|
APP = "app"
|
|
SMS = "sms"
|
|
EMAIL = "email"
|
|
|
|
|
|
class VectorStoreType(str, Enum):
|
|
CHROMA = ("chroma",)
|
|
# FAISS = "faiss",
|
|
# PINECONE = "pinecone"
|
|
# QDRANT = "qdrant"
|
|
# FAISS = "faiss"
|
|
# MILVUS = "milvus"
|
|
# WEAVIATE = "weaviate"
|
|
|
|
|
|
class DataSourceType(str, Enum):
|
|
DOCUMENT = "document"
|
|
WEBSITE = "website"
|
|
API = "api"
|
|
DATABASE = "database"
|
|
INTERNAL = "internal"
|
|
|
|
|
|
class ProcessingStepType(str, Enum):
|
|
EXTRACT = "extract"
|
|
TRANSFORM = "transform"
|
|
CHUNK = "chunk"
|
|
EMBED = "embed"
|
|
FILTER = "filter"
|
|
SUMMARIZE = "summarize"
|
|
|
|
|
|
class SearchType(str, Enum):
|
|
SIMILARITY = "similarity"
|
|
MMR = "mmr"
|
|
HYBRID = "hybrid"
|
|
KEYWORD = "keyword"
|
|
|
|
|
|
class ActivityType(str, Enum):
|
|
LOGIN = "login"
|
|
SEARCH = "search"
|
|
VIEW_JOB = "view_job"
|
|
APPLY_JOB = "apply_job"
|
|
MESSAGE = "message"
|
|
UPDATE_PROFILE = "update_profile"
|
|
CHAT = "chat"
|
|
|
|
|
|
class ThemePreference(str, Enum):
|
|
LIGHT = "light"
|
|
DARK = "dark"
|
|
SYSTEM = "system"
|
|
|
|
|
|
class NotificationType(str, Enum):
|
|
EMAIL = "email"
|
|
PUSH = "push"
|
|
IN_APP = "in_app"
|
|
|
|
|
|
class FontSize(str, Enum):
|
|
SMALL = "small"
|
|
MEDIUM = "medium"
|
|
LARGE = "large"
|
|
|
|
|
|
class SalaryPeriod(str, Enum):
|
|
HOUR = "hour"
|
|
DAY = "day"
|
|
MONTH = "month"
|
|
YEAR = "year"
|
|
|
|
|
|
class LanguageProficiency(str, Enum):
|
|
BASIC = "basic"
|
|
CONVERSATIONAL = "conversational"
|
|
FLUENT = "fluent"
|
|
NATIVE = "native"
|
|
|
|
|
|
class SocialPlatform(str, Enum):
|
|
LINKEDIN = "linkedin"
|
|
TWITTER = "twitter"
|
|
GITHUB = "github"
|
|
DRIBBBLE = "dribbble"
|
|
BEHANCE = "behance"
|
|
WEBSITE = "website"
|
|
OTHER = "other"
|
|
|
|
|
|
class ColorBlindMode(str, Enum):
|
|
PROTANOPIA = "protanopia"
|
|
DEUTERANOPIA = "deuteranopia"
|
|
TRITANOPIA = "tritanopia"
|
|
NONE = "none"
|
|
|
|
|
|
class SortOrder(str, Enum):
|
|
ASC = "asc"
|
|
DESC = "desc"
|
|
|
|
|
|
class LoginRequest(BaseModel):
|
|
login: str # Can be email or username
|
|
password: str
|
|
|
|
@field_validator("login")
|
|
def sanitize_login(cls, v):
|
|
return sanitize_login_input(v)
|
|
|
|
@field_validator("password")
|
|
def validate_password_not_empty(cls, v):
|
|
if not v or not v.strip():
|
|
raise ValueError("Password cannot be empty")
|
|
return v
|
|
|
|
|
|
# ============================
|
|
# MFA Models
|
|
# ============================
|
|
|
|
|
|
class EmailVerificationRequest(BaseModel):
|
|
token: str
|
|
|
|
|
|
class MFARequest(BaseModel):
|
|
username: str
|
|
password: str
|
|
device_id: str = Field(..., alias=str("deviceId"))
|
|
device_name: str = Field(..., alias=str("deviceName"))
|
|
email: str = Field(..., alias=str("email"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class MFAVerifyRequest(BaseModel):
|
|
email: EmailStr
|
|
code: str
|
|
device_id: str = Field(..., alias=str("deviceId"))
|
|
remember_device: bool = Field(False, alias=str("rememberDevice"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class MFAData(BaseModel):
|
|
message: str
|
|
device_id: str = Field(..., alias=str("deviceId"))
|
|
device_name: str = Field(..., alias=str("deviceName"))
|
|
code_sent: str = Field(..., alias=str("codeSent"))
|
|
email: str
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class MFARequestResponse(BaseModel):
|
|
mfa_required: bool = Field(..., alias=str("mfaRequired"))
|
|
mfa_data: Optional[MFAData] = Field(default=None, alias=str("mfaData"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class ResendVerificationRequest(BaseModel):
|
|
email: EmailStr
|
|
|
|
|
|
# ============================
|
|
# Supporting Models
|
|
# ============================
|
|
|
|
|
|
class Tunables(BaseModel):
|
|
enable_rag: bool = Field(default=True, alias=str("enableRAG"))
|
|
enable_tools: bool = Field(default=True, alias=str("enableTools"))
|
|
enable_context: bool = Field(default=True, alias=str("enableContext"))
|
|
temperature: float = Field(default=0.7, alias=str("temperature"))
|
|
top_k: int = Field(default=5, alias=str("topK"))
|
|
threshold: Optional[float] = Field(default=None, alias=str("threshold"))
|
|
|
|
|
|
class CandidateQuestion(BaseModel):
|
|
question: str
|
|
tunables: Optional[Tunables] = None
|
|
|
|
|
|
class Location(BaseModel):
|
|
text: str
|
|
city: Optional[str] = None
|
|
state: Optional[str] = None
|
|
country: Optional[str] = None
|
|
postal_code: Optional[str] = Field(default=None, alias=str("postalCode"))
|
|
latitude: Optional[float] = None
|
|
longitude: Optional[float] = None
|
|
remote: Optional[bool] = None
|
|
hybrid_options: Optional[List[str]] = Field(default=None, alias=str("hybridOptions"))
|
|
address: Optional[str] = None
|
|
model_config = ConfigDict(populate_by_name=True, use_enum_values=True)
|
|
|
|
@model_validator(mode="before")
|
|
@classmethod
|
|
def set_text_from_location(cls, data: Any) -> Any:
|
|
if isinstance(data, dict):
|
|
# Check if text is missing, None, or empty string
|
|
if not data.get("text"):
|
|
city = data.get("city", "")
|
|
country = data.get("country", "")
|
|
|
|
# Build text from available location components
|
|
location_parts = []
|
|
if city:
|
|
location_parts.append(city)
|
|
if country:
|
|
location_parts.append(country)
|
|
|
|
# Set text to combined location or a default if both are empty
|
|
data["text"] = ", ".join(location_parts) if location_parts else "Unknown Location"
|
|
|
|
return data
|
|
|
|
|
|
class Skill(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
name: str
|
|
category: str
|
|
level: SkillLevel
|
|
years_of_experience: Optional[int] = Field(default=None, alias=str("yearsOfExperience"))
|
|
|
|
|
|
class WorkExperience(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
company_name: str = Field(..., alias=str("companyName"))
|
|
position: str
|
|
start_date: datetime = Field(..., alias=str("startDate"))
|
|
end_date: Optional[datetime] = Field(default=None, alias=str("endDate"))
|
|
is_current: bool = Field(..., alias=str("isCurrent"))
|
|
description: str
|
|
skills: List[str]
|
|
location: Location
|
|
achievements: Optional[List[str]] = None
|
|
|
|
|
|
class Education(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
institution: str
|
|
degree: str
|
|
field_of_study: str = Field(..., alias=str("fieldOfStudy"))
|
|
start_date: datetime = Field(..., alias=str("startDate"))
|
|
end_date: Optional[datetime] = Field(default=None, alias=str("endDate"))
|
|
is_current: bool = Field(..., alias=str("isCurrent"))
|
|
gpa: Optional[float] = None
|
|
achievements: Optional[List[str]] = None
|
|
location: Optional[Location] = None
|
|
|
|
|
|
class Language(BaseModel):
|
|
language: str
|
|
proficiency: LanguageProficiency
|
|
|
|
|
|
class Certification(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
name: str
|
|
issuing_organization: str = Field(..., alias=str("issuingOrganization"))
|
|
issue_date: datetime = Field(..., alias=str("issueDate"))
|
|
expiration_date: Optional[datetime] = Field(default=None, alias=str("expirationDate"))
|
|
credential_id: Optional[str] = Field(default=None, alias=str("credentialId"))
|
|
credential_url: Optional[HttpUrl] = Field(default=None, alias=str("credentialUrl"))
|
|
|
|
|
|
class SocialLink(BaseModel):
|
|
platform: SocialPlatform
|
|
url: HttpUrl
|
|
|
|
|
|
class DesiredSalary(BaseModel):
|
|
amount: float
|
|
currency: str
|
|
period: SalaryPeriod
|
|
|
|
|
|
class SalaryRange(BaseModel):
|
|
min: float
|
|
max: float
|
|
currency: str
|
|
period: SalaryPeriod
|
|
is_visible: bool = Field(..., alias=str("isVisible"))
|
|
|
|
|
|
class PointOfContact(BaseModel):
|
|
name: str
|
|
position: str
|
|
email: EmailStr
|
|
phone: Optional[str] = None
|
|
|
|
|
|
class RefreshToken(BaseModel):
|
|
token: str
|
|
expires_at: datetime = Field(..., alias=str("expiresAt"))
|
|
device: str
|
|
ip_address: str = Field(..., alias=str("ipAddress"))
|
|
is_revoked: bool = Field(..., alias=str("isRevoked"))
|
|
revoked_reason: Optional[str] = Field(default=None, alias=str("revokedReason"))
|
|
|
|
|
|
class Attachment(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
file_name: str = Field(..., alias=str("fileName"))
|
|
file_type: str = Field(..., alias=str("fileType"))
|
|
file_size: int = Field(..., alias=str("fileSize"))
|
|
file_url: str = Field(..., alias=str("fileUrl"))
|
|
uploaded_at: datetime = Field(..., alias=str("uploadedAt"))
|
|
is_processed: bool = Field(..., alias=str("isProcessed"))
|
|
processing_result: Optional[Any] = Field(default=None, alias=str("processingResult"))
|
|
thumbnail_url: Optional[str] = Field(default=None, alias=str("thumbnailUrl"))
|
|
|
|
|
|
class MessageReaction(BaseModel):
|
|
user_id: str = Field(..., alias=str("userId"))
|
|
reaction: str
|
|
timestamp: datetime
|
|
|
|
|
|
class EditHistory(BaseModel):
|
|
content: str
|
|
edited_at: datetime = Field(..., alias=str("editedAt"))
|
|
edited_by: str = Field(..., alias=str("editedBy"))
|
|
|
|
|
|
class CustomQuestion(BaseModel):
|
|
question: str
|
|
answer: str
|
|
|
|
|
|
class CandidateContact(BaseModel):
|
|
email: EmailStr
|
|
phone: Optional[str] = None
|
|
|
|
|
|
class ApplicationDecision(BaseModel):
|
|
status: Literal["accepted", "rejected"]
|
|
reason: Optional[str] = None
|
|
date: datetime
|
|
by: str
|
|
|
|
|
|
class NotificationPreference(BaseModel):
|
|
type: NotificationType
|
|
events: List[str]
|
|
is_enabled: bool = Field(..., alias=str("isEnabled"))
|
|
|
|
|
|
class AccessibilitySettings(BaseModel):
|
|
font_size: FontSize = Field(..., alias=str("fontSize"))
|
|
high_contrast: bool = Field(..., alias=str("highContrast"))
|
|
reduce_motion: bool = Field(..., alias=str("reduceMotion"))
|
|
screen_reader: bool = Field(..., alias=str("screenReader"))
|
|
color_blind_mode: Optional[ColorBlindMode] = Field(default=None, alias=str("colorBlindMode"))
|
|
|
|
|
|
class ProcessingStep(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
type: ProcessingStepType
|
|
parameters: Dict[str, Any]
|
|
order: int
|
|
depends_on: Optional[List[str]] = Field(default=None, alias=str("dependsOn"))
|
|
|
|
|
|
class RetrievalParameters(BaseModel):
|
|
search_type: SearchType = Field(..., alias=str("searchType"))
|
|
top_k: int = Field(..., alias=str("topK"))
|
|
similarity_threshold: Optional[float] = Field(default=None, alias=str("similarityThreshold"))
|
|
reranker_model: Optional[str] = Field(default=None, alias=str("rerankerModel"))
|
|
use_keyword_boost: bool = Field(..., alias=str("useKeywordBoost"))
|
|
filter_options: Optional[Dict[str, Any]] = Field(default=None, alias=str("filterOptions"))
|
|
context_window: int = Field(..., alias=str("contextWindow"))
|
|
|
|
|
|
class ErrorDetail(BaseModel):
|
|
code: str
|
|
message: str
|
|
details: Optional[Any] = None
|
|
|
|
|
|
# ============================
|
|
# Main Models
|
|
# ============================
|
|
|
|
|
|
# Generic base user with user_type for API responses
|
|
class BaseUserWithType(BaseModel):
|
|
user_type: UserType = Field(..., alias=str("userType"))
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
last_activity: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("lastActivity"))
|
|
model_config = ConfigDict(populate_by_name=True, use_enum_values=True)
|
|
|
|
|
|
# Base user model without user_type field
|
|
class BaseUser(BaseUserWithType):
|
|
email: EmailStr
|
|
first_name: str = Field(..., alias=str("firstName"))
|
|
last_name: str = Field(..., alias=str("lastName"))
|
|
full_name: str = Field(..., alias=str("fullName"))
|
|
phone: Optional[str] = None
|
|
location: Optional[Location] = None
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("createdAt"))
|
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("updatedAt"))
|
|
last_login: Optional[datetime] = Field(default=None, alias=str("lastLogin"))
|
|
profile_image: Optional[str] = Field(default=None, alias=str("profileImage"))
|
|
status: UserStatus
|
|
is_admin: bool = Field(default=False, alias=str("isAdmin"))
|
|
|
|
model_config = ConfigDict(populate_by_name=True, use_enum_values=True)
|
|
|
|
|
|
class RagEntry(BaseModel):
|
|
name: str
|
|
description: str = ""
|
|
enabled: bool = True
|
|
|
|
|
|
class RagContentMetadata(BaseModel):
|
|
source_file: str = Field(..., alias=str("sourceFile"))
|
|
line_begin: int = Field(..., alias=str("lineBegin"))
|
|
line_end: int = Field(..., alias=str("lineEnd"))
|
|
lines: int
|
|
chunk_begin: Optional[int] = Field(default=None, alias=str("chunkBegin"))
|
|
chunk_end: Optional[int] = Field(default=None, alias=str("chunkEnd"))
|
|
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class RagContentResponse(BaseModel):
|
|
id: str
|
|
content: str
|
|
metadata: RagContentMetadata
|
|
|
|
|
|
class DocumentType(str, Enum):
|
|
PDF = "pdf"
|
|
DOCX = "docx"
|
|
TXT = "txt"
|
|
MARKDOWN = "markdown"
|
|
IMAGE = "image"
|
|
|
|
|
|
class DocumentOptions(BaseModel):
|
|
include_in_rag: bool = Field(default=True, alias=str("includeInRag"))
|
|
is_job_document: Optional[bool] = Field(default=False, alias=str("isJobDocument"))
|
|
overwrite: Optional[bool] = Field(default=False, alias=str("overwrite"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class RAGDocumentRequest(BaseModel):
|
|
"""Request model for RAG document content"""
|
|
|
|
id: str
|
|
|
|
|
|
class Document(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
owner_id: str = Field(..., alias=str("ownerId"))
|
|
filename: str
|
|
original_name: str = Field(..., alias=str("originalName"))
|
|
type: DocumentType
|
|
size: int
|
|
upload_date: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("uploadDate"))
|
|
options: DocumentOptions = Field(default_factory=lambda: DocumentOptions(), alias=str("options"))
|
|
rag_chunks: Optional[int] = Field(default=0, alias=str("ragChunks"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class DocumentContentResponse(BaseModel):
|
|
document_id: str = Field(..., alias=str("documentId"))
|
|
filename: str
|
|
type: DocumentType
|
|
content: str
|
|
size: int
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class DocumentListResponse(BaseModel):
|
|
documents: List[Document]
|
|
total: int
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class DocumentUpdateRequest(BaseModel):
|
|
filename: Optional[str] = None
|
|
options: Optional[DocumentOptions] = None
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class Candidate(BaseUser):
|
|
user_type: UserType = Field(UserType.CANDIDATE, alias=str("userType"))
|
|
username: str
|
|
description: Optional[str] = None
|
|
resume: Optional[str] = None
|
|
skills: Optional[List[Skill]] = None
|
|
experience: Optional[List[WorkExperience]] = None
|
|
questions: Optional[List[CandidateQuestion]] = None
|
|
education: Optional[List[Education]] = None
|
|
preferred_job_types: Optional[List[EmploymentType]] = Field(default=None, alias=str("preferredJobTypes"))
|
|
desired_salary: Optional[DesiredSalary] = Field(default=None, alias=str("desiredSalary"))
|
|
availability_date: Optional[datetime] = Field(default=None, alias=str("availabilityDate"))
|
|
summary: Optional[str] = None
|
|
languages: Optional[List[Language]] = None
|
|
certifications: Optional[List[Certification]] = None
|
|
job_applications: Optional[List["JobApplication"]] = Field(default=None, alias=str("jobApplications"))
|
|
rags: List[RagEntry] = Field(default_factory=list)
|
|
rag_content_size: int = 0
|
|
is_public: bool = Field(default=True, alias=str("isPublic"))
|
|
|
|
|
|
class CandidateAI(Candidate):
|
|
user_type: UserType = Field(UserType.CANDIDATE, alias=str("userType"))
|
|
is_AI: bool = Field(True, alias=str("isAI"))
|
|
age: Optional[int] = None
|
|
gender: Optional[UserGender] = None
|
|
ethnicity: Optional[str] = None
|
|
|
|
|
|
class Employer(BaseUser):
|
|
user_type: UserType = Field(UserType.EMPLOYER, alias=str("userType"))
|
|
company_name: str = Field(..., alias=str("companyName"))
|
|
industry: str
|
|
description: Optional[str] = None
|
|
company_size: str = Field(..., alias=str("companySize"))
|
|
company_description: str = Field(..., alias=str("companyDescription"))
|
|
website_url: Optional[HttpUrl] = Field(default=None, alias=str("websiteUrl"))
|
|
jobs: Optional[List["Job"]] = None
|
|
company_logo: Optional[str] = Field(default=None, alias=str("companyLogo"))
|
|
social_links: Optional[List[SocialLink]] = Field(default=None, alias=str("socialLinks"))
|
|
poc: Optional[PointOfContact] = None
|
|
|
|
|
|
class Guest(BaseUser):
|
|
user_type: UserType = Field(UserType.GUEST, alias=str("userType"))
|
|
session_id: str = Field(..., alias=str("sessionId"))
|
|
username: str # Add username for consistency with other user types
|
|
converted_to_user_id: Optional[str] = Field(default=None, alias=str("convertedToUserId"))
|
|
ip_address: Optional[str] = Field(default=None, alias=str("ipAddress"))
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("createdAt"))
|
|
user_agent: Optional[str] = Field(default=None, alias=str("userAgent"))
|
|
rag_content_size: int = 0
|
|
is_public: bool = Field(default=False, alias=str("isPublic"))
|
|
model_config = ConfigDict(populate_by_name=True, use_enum_values=True)
|
|
|
|
|
|
class Authentication(BaseModel):
|
|
user_id: str = Field(..., alias=str("userId"))
|
|
password_hash: str = Field(..., alias=str("passwordHash"))
|
|
salt: str
|
|
refresh_tokens: List[RefreshToken] = Field(..., alias=str("refreshTokens"))
|
|
reset_password_token: Optional[str] = Field(default=None, alias=str("resetPasswordToken"))
|
|
reset_password_expiry: Optional[datetime] = Field(default=None, alias=str("resetPasswordExpiry"))
|
|
last_password_change: datetime = Field(..., alias=str("lastPasswordChange"))
|
|
mfa_enabled: bool = Field(..., alias=str("mfaEnabled"))
|
|
mfa_method: Optional[MFAMethod] = Field(default=None, alias=str("mfaMethod"))
|
|
mfa_secret: Optional[str] = Field(default=None, alias=str("mfaSecret"))
|
|
login_attempts: int = Field(..., alias=str("loginAttempts"))
|
|
locked_until: Optional[datetime] = Field(default=None, alias=str("lockedUntil"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class AuthResponse(BaseModel):
|
|
access_token: str = Field(..., alias=str("accessToken"))
|
|
refresh_token: str = Field(..., alias=str("refreshToken"))
|
|
user: Union[Candidate, Employer, Guest] # Add Guest support
|
|
expires_at: int = Field(..., alias=str("expiresAt"))
|
|
user_type: Optional[str] = Field(default=UserType.GUEST, alias=str("userType")) # Explicit user type
|
|
is_guest: Optional[bool] = Field(default=True, alias=str("isGuest")) # Guest indicator
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class GuestCleanupRequest(BaseModel):
|
|
"""Request to cleanup inactive guests"""
|
|
|
|
inactive_hours: int = Field(24, alias=str("inactiveHours"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class Requirements(BaseModel):
|
|
required: List[str] = Field(default_factory=list)
|
|
preferred: List[str] = Field(default_factory=list)
|
|
|
|
@model_validator(mode="before")
|
|
def validate_requirements(cls, values):
|
|
if not isinstance(values, dict):
|
|
raise ValueError("Requirements must be a dictionary with 'required' and 'preferred' keys.")
|
|
return values
|
|
|
|
|
|
class JobRequirements(BaseModel):
|
|
technical_skills: Requirements = Field(..., alias=str("technicalSkills"))
|
|
experience_requirements: Requirements = Field(..., alias=str("experienceRequirements"))
|
|
soft_skills: Optional[List[str]] = Field(default_factory=list, alias=str("softSkills"))
|
|
experience: Optional[List[str]] = []
|
|
education: Optional[List[str]] = []
|
|
certifications: Optional[List[str]] = []
|
|
preferred_attributes: Optional[List[str]] = Field(default=None, alias=str("preferredAttributes"))
|
|
company_values: Optional[List[str]] = Field(default=None, alias=str("companyValues"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class JobDetails(BaseModel):
|
|
location: Location
|
|
salary_range: Optional[SalaryRange] = Field(default=None, alias=str("salaryRange"))
|
|
employment_type: EmploymentType = Field(..., alias=str("employmentType"))
|
|
date_posted: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("datePosted"))
|
|
application_deadline: Optional[datetime] = Field(default=None, alias=str("applicationDeadline"))
|
|
is_active: bool = Field(..., alias=str("isActive"))
|
|
applicants: Optional[List["JobApplication"]] = None
|
|
department: Optional[str] = None
|
|
reports_to: Optional[str] = Field(default=None, alias=str("reportsTo"))
|
|
benefits: Optional[List[str]] = None
|
|
visa_sponsorship: Optional[bool] = Field(default=None, alias=str("visaSponsorship"))
|
|
featured_until: Optional[datetime] = Field(default=None, alias=str("featuredUntil"))
|
|
views: int = 0
|
|
application_count: int = Field(0, alias=str("applicationCount"))
|
|
|
|
|
|
class Job(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
owner_id: str = Field(..., alias=str("ownerId"))
|
|
owner_type: UserType = Field(..., alias=str("ownerType"))
|
|
owner: Optional[BaseUser] = None
|
|
title: Optional[str]
|
|
summary: Optional[str]
|
|
company: Optional[str]
|
|
pay_minimum: Optional[str] = Field(default=None, alias=str("payMinimum"))
|
|
pay_maximum: Optional[str] = Field(default=None, alias=str("payMaximum"))
|
|
description: str
|
|
requirements: Optional[JobRequirements]
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("createdAt"))
|
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("updatedAt"))
|
|
details: Optional[JobDetails] = None
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class InterviewFeedback(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
interview_id: str = Field(..., alias=str("interviewId"))
|
|
reviewer_id: str = Field(..., alias=str("reviewerId"))
|
|
technical_score: Annotated[float, Field(ge=0, le=10)] = Field(..., alias=str("technicalScore"))
|
|
cultural_score: Annotated[float, Field(ge=0, le=10)] = Field(..., alias=str("culturalScore"))
|
|
overall_score: Annotated[float, Field(ge=0, le=10)] = Field(..., alias=str("overallScore"))
|
|
strengths: List[str]
|
|
weaknesses: List[str]
|
|
recommendation: InterviewRecommendation
|
|
comments: str
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("createdAt"))
|
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("updatedAt"))
|
|
is_visible: bool = Field(..., alias=str("isVisible"))
|
|
skill_assessments: Optional[List[SkillAssessment]] = Field(default=None, alias=str("skillAssessments"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class InterviewSchedule(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
application_id: str = Field(..., alias=str("applicationId"))
|
|
scheduled_date: datetime = Field(..., alias=str("scheduledDate"))
|
|
end_date: datetime = Field(..., alias=str("endDate"))
|
|
interview_type: InterviewType = Field(..., alias=str("interviewType"))
|
|
interviewers: List[str]
|
|
location: Optional[Union[str, Location]] = None
|
|
notes: Optional[str] = None
|
|
feedback: Optional[InterviewFeedback] = None
|
|
status: Literal["scheduled", "completed", "cancelled", "rescheduled"]
|
|
meeting_link: Optional[HttpUrl] = Field(default=None, alias=str("meetingLink"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class JobApplication(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
job_id: str = Field(..., alias=str("jobId"))
|
|
candidate_id: str = Field(..., alias=str("candidateId"))
|
|
status: ApplicationStatus
|
|
applied_date: datetime = Field(..., alias=str("appliedDate"))
|
|
updated_date: datetime = Field(..., alias=str("updatedDate"))
|
|
resume_version: str = Field(..., alias=str("resumeVersion"))
|
|
cover_letter: Optional[str] = Field(default=None, alias=str("coverLetter"))
|
|
notes: Optional[str] = None
|
|
interview_schedules: Optional[List[InterviewSchedule]] = Field(default=None, alias=str("interviewSchedules"))
|
|
custom_questions: Optional[List[CustomQuestion]] = Field(default=None, alias=str("customQuestions"))
|
|
candidate_contact: Optional[CandidateContact] = Field(default=None, alias=str("candidateContact"))
|
|
decision: Optional[ApplicationDecision] = None
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class GuestSessionResponse(BaseModel):
|
|
"""Response for guest session creation"""
|
|
|
|
access_token: str = Field(..., alias=str("accessToken"))
|
|
refresh_token: str = Field(..., alias=str("refreshToken"))
|
|
user: Guest
|
|
expires_at: int = Field(..., alias=str("expiresAt"))
|
|
user_type: Literal["guest"] = Field("guest", alias=str("userType"))
|
|
is_guest: bool = Field(True, alias=str("isGuest"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class ChatContext(BaseModel):
|
|
type: ChatContextType
|
|
related_entity_id: Optional[str] = Field(default=None, alias=str("relatedEntityId"))
|
|
related_entity_type: Optional[Literal["job", "candidate", "employer"]] = Field(
|
|
default=None, alias=str("relatedEntityType")
|
|
)
|
|
additional_context: Optional[Dict[str, Any]] = Field({}, alias=str("additionalContext"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class ChatOptions(BaseModel):
|
|
seed: Optional[int] = 8911
|
|
num_ctx: Optional[int] = Field(default=None, alias=str("numCtx")) # Number of context tokens
|
|
temperature: Optional[float] = Field(default=0.7) # Higher temperature to encourage tool usage
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
# Enhanced usage statistics model
|
|
class UsageStats(BaseModel):
|
|
"""Comprehensive usage statistics across all providers"""
|
|
|
|
# Token counts (standardized across providers)
|
|
prompt_tokens: Optional[int] = Field(default=None, description="Number of tokens in the prompt")
|
|
completion_tokens: Optional[int] = Field(default=None, description="Number of tokens in the completion")
|
|
total_tokens: Optional[int] = Field(default=None, description="Total number of tokens used")
|
|
|
|
# Ollama-specific detailed stats
|
|
prompt_eval_count: Optional[int] = Field(default=None, description="Number of tokens evaluated in prompt")
|
|
prompt_eval_duration: Optional[int] = Field(default=None, description="Time spent evaluating prompt (nanoseconds)")
|
|
eval_count: Optional[int] = Field(default=None, description="Number of tokens generated")
|
|
eval_duration: Optional[int] = Field(default=None, description="Time spent generating tokens (nanoseconds)")
|
|
total_duration: Optional[int] = Field(default=None, description="Total request duration (nanoseconds)")
|
|
|
|
# Performance metrics
|
|
tokens_per_second: Optional[float] = Field(default=None, description="Generation speed in tokens/second")
|
|
prompt_tokens_per_second: Optional[float] = Field(default=None, description="Prompt processing speed")
|
|
|
|
# Additional provider-specific stats
|
|
extra_stats: Optional[Dict[str, Any]] = Field(
|
|
default_factory=dict, description="Provider-specific additional statistics"
|
|
)
|
|
|
|
def calculate_derived_stats(self) -> None:
|
|
"""Calculate derived statistics where possible"""
|
|
# Calculate tokens per second for Ollama
|
|
if self.eval_count and self.eval_duration and self.eval_duration > 0:
|
|
# Convert nanoseconds to seconds and calculate tokens/sec
|
|
duration_seconds = self.eval_duration / 1_000_000_000
|
|
self.tokens_per_second = self.eval_count / duration_seconds
|
|
|
|
# Calculate prompt processing speed for Ollama
|
|
if self.prompt_eval_count and self.prompt_eval_duration and self.prompt_eval_duration > 0:
|
|
duration_seconds = self.prompt_eval_duration / 1_000_000_000
|
|
self.prompt_tokens_per_second = self.prompt_eval_count / duration_seconds
|
|
|
|
# Standardize token counts across providers
|
|
if not self.total_tokens and self.prompt_tokens and self.completion_tokens:
|
|
self.total_tokens = self.prompt_tokens + self.completion_tokens
|
|
|
|
# Map Ollama counts to standard format if not already set
|
|
if not self.prompt_tokens and self.prompt_eval_count:
|
|
self.prompt_tokens = self.prompt_eval_count
|
|
if not self.completion_tokens and self.eval_count:
|
|
self.completion_tokens = self.eval_count
|
|
|
|
|
|
class ChatResponse(BaseModel):
|
|
content: str
|
|
model: str
|
|
finish_reason: Optional[str] = Field(default="")
|
|
usage: Optional[UsageStats] = Field(default=None)
|
|
# Keep legacy usage field for backward compatibility
|
|
usage_legacy: Optional[Dict[str, int]] = Field(default=None, alias="usage_dict")
|
|
|
|
def get_usage_dict(self) -> Dict[str, Any]:
|
|
"""Get usage statistics as dictionary for backward compatibility"""
|
|
if self.usage:
|
|
return self.usage.model_dump(exclude_none=True)
|
|
return self.usage_legacy or {}
|
|
|
|
|
|
# Add rate limiting configuration models
|
|
class RateLimitConfig(BaseModel):
|
|
"""Rate limit configuration"""
|
|
|
|
requests_per_minute: int = Field(..., alias=str("requestsPerMinute"))
|
|
requests_per_hour: int = Field(..., alias=str("requestsPerHour"))
|
|
requests_per_day: int = Field(..., alias=str("requestsPerDay"))
|
|
burst_limit: int = Field(..., alias=str("burstLimit"))
|
|
burst_window_seconds: int = Field(60, alias=str("burstWindowSeconds"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class RateLimitResult(BaseModel):
|
|
"""Result of rate limit check"""
|
|
|
|
allowed: bool
|
|
reason: Optional[str] = None
|
|
retry_after_seconds: Optional[int] = Field(default=None, alias=str("retryAfterSeconds"))
|
|
remaining_requests: Dict[str, int] = Field(default_factory=dict, alias=str("remainingRequests"))
|
|
reset_times: Dict[str, datetime] = Field(default_factory=dict, alias=str("resetTimes"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class RateLimitStatus(BaseModel):
|
|
"""Rate limit status for a user"""
|
|
|
|
user_id: str = Field(..., alias=str("userId"))
|
|
user_type: str = Field(..., alias=str("userType"))
|
|
is_admin: bool = Field(..., alias=str("isAdmin"))
|
|
current_usage: Dict[str, int] = Field(..., alias=str("currentUsage"))
|
|
limits: Dict[str, int] = Field(..., alias=str("limits"))
|
|
remaining: Dict[str, int] = Field(..., alias=str("remaining"))
|
|
reset_times: Dict[str, datetime] = Field(..., alias=str("resetTimes"))
|
|
config: RateLimitConfig
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
# Add guest conversion request models
|
|
class GuestConversionRequest(BaseModel):
|
|
"""Request to convert guest to permanent user"""
|
|
|
|
account_type: Literal["candidate", "employer"] = Field(..., alias=str("accountType"))
|
|
email: EmailStr
|
|
username: str
|
|
password: str
|
|
first_name: str = Field(..., alias=str("firstName"))
|
|
last_name: str = Field(..., alias=str("lastName"))
|
|
phone: Optional[str] = None
|
|
|
|
# Employer-specific fields (optional)
|
|
company_name: Optional[str] = Field(default=None, alias=str("companyName"))
|
|
industry: Optional[str] = None
|
|
company_size: Optional[str] = Field(default=None, alias=str("companySize"))
|
|
company_description: Optional[str] = Field(default=None, alias=str("companyDescription"))
|
|
website_url: Optional[HttpUrl] = Field(default=None, alias=str("websiteUrl"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
@field_validator("username")
|
|
def validate_username(cls, v):
|
|
if not v or len(v.strip()) < 3:
|
|
raise ValueError("Username must be at least 3 characters long")
|
|
return v.strip().lower()
|
|
|
|
@field_validator("password")
|
|
def validate_password_strength(cls, v):
|
|
# Import here to avoid circular imports
|
|
is_valid, issues = validate_password_strength(v)
|
|
if not is_valid:
|
|
raise ValueError("; ".join(issues))
|
|
return v
|
|
|
|
|
|
# Add guest statistics response model
|
|
class GuestStatistics(BaseModel):
|
|
"""Guest usage statistics"""
|
|
|
|
total_guests: int = Field(..., alias=str("totalGuests"))
|
|
active_last_hour: int = Field(..., alias=str("activeLastHour"))
|
|
active_last_day: int = Field(..., alias=str("activeLastDay"))
|
|
converted_guests: int = Field(..., alias=str("convertedGuests"))
|
|
by_ip: Dict[str, int] = Field(..., alias=str("byIp"))
|
|
creation_timeline: Dict[str, int] = Field(..., alias=str("creationTimeline"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
from utils.llm_proxy import LLMMessage
|
|
|
|
|
|
class ApiMessage(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
session_id: str = Field(..., alias=str("sessionId"))
|
|
sender_id: Optional[str] = Field(default=None, alias=str("senderId"))
|
|
status: ApiStatusType
|
|
type: ApiMessageType
|
|
timestamp: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("timestamp"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
MOCK_UUID = str(uuid.uuid4())
|
|
|
|
|
|
class ChatMessageStreaming(ApiMessage):
|
|
status: ApiStatusType = ApiStatusType.STREAMING
|
|
type: ApiMessageType = ApiMessageType.TEXT
|
|
content: str
|
|
|
|
|
|
class ApiActivityType(str, Enum):
|
|
SYSTEM = "system" # Used solely on frontend
|
|
INFO = "info" # Used solely on frontend
|
|
SEARCHING = "searching" # Used when generating RAG information
|
|
THINKING = "thinking" # Used when determing if AI will use tools
|
|
GENERATING = "generating" # Used when AI is generating a response
|
|
CONVERTING = "converting" # Used when AI is generating a response
|
|
GENERATING_IMAGE = "generating_image" # Used when AI is generating an image
|
|
TOOLING = "tooling" # Used when AI is using tools
|
|
HEARTBEAT = "heartbeat" # Used for periodic updates
|
|
|
|
|
|
class ChatMessageStatus(ApiMessage):
|
|
sender_id: Optional[str] = Field(default=MOCK_UUID, alias=str("senderId"))
|
|
status: ApiStatusType = ApiStatusType.STATUS
|
|
type: ApiMessageType = ApiMessageType.TEXT
|
|
activity: ApiActivityType
|
|
content: Any
|
|
|
|
|
|
class ChatMessageError(ApiMessage):
|
|
sender_id: Optional[str] = Field(default=MOCK_UUID, alias=str("senderId"))
|
|
status: ApiStatusType = ApiStatusType.ERROR
|
|
type: ApiMessageType = ApiMessageType.TEXT
|
|
content: str
|
|
|
|
|
|
class ChatMessageRagSearch(ApiMessage):
|
|
type: ApiMessageType = Field(default=ApiMessageType.JSON)
|
|
dimensions: int = 2 | 3
|
|
content: List[ChromaDBGetResponse] = []
|
|
|
|
|
|
class JobRequirementsMessage(ApiMessage):
|
|
type: ApiMessageType = ApiMessageType.JSON
|
|
job: Job = Field(..., alias=str("job"))
|
|
|
|
|
|
class DocumentMessage(ApiMessage):
|
|
type: ApiMessageType = ApiMessageType.JSON
|
|
sender_id: Optional[str] = Field(default=MOCK_UUID, alias=str("senderId"))
|
|
document: Document = Field(..., alias=str("document"))
|
|
content: Optional[str] = ""
|
|
converted: bool = Field(False, alias=str("converted"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
class ToolCall(BaseModel):
|
|
name: str
|
|
content: str
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
class Tool(BaseModel):
|
|
# generate-types doesn't support nested-nested
|
|
tool_calls: List[dict[str, str]] = Field(default=[], alias=str("toolCalls"))
|
|
messages: List[LLMMessage] = Field(default_factory=list)
|
|
available: List[str] = Field(default_factory=list)
|
|
tool_name: str = Field(..., alias=str("toolName"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
class ChatMessageMetaData(BaseModel):
|
|
model: AIModelType = AIModelType.QWEN2_5
|
|
temperature: float = 0.7
|
|
max_tokens: int = Field(default=8092, alias=str("maxTokens"))
|
|
top_p: float = Field(default=1, alias=str("topP"))
|
|
frequency_penalty: float = Field(default=0, alias=str("frequencyPenalty"))
|
|
presence_penalty: float = Field(default=0, alias=str("presencePenalty"))
|
|
stop_sequences: List[str] = Field(default=[], alias=str("stopSequences"))
|
|
rag_results: List[ChromaDBGetResponse] = Field(default_factory=list, alias=str("ragResults"))
|
|
llm_history: List[LLMMessage] = Field(default_factory=list, alias=str("llmHistory"))
|
|
usage: UsageStats = Field(default_factory=UsageStats)
|
|
options: Optional[ChatOptions] = None
|
|
tools: Optional[Tool] = None
|
|
timers: Dict[str, float] = Field(default_factory=dict)
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
class SkillMatchRequest(BaseModel):
|
|
"""Request model for skill match"""
|
|
|
|
skill: str
|
|
regenerate: bool = Field(default=False, description="Whether to regenerate the skill match even if cached")
|
|
|
|
class ExtraChatContext(BaseModel):
|
|
"""Extra context for chat messages"""
|
|
|
|
job_id: Optional[str] = Field(default=None, alias=str("jobId"))
|
|
candidate_id: Optional[str] = Field(default=None, alias=str("candidateId"))
|
|
resume_id: Optional[str] = Field(default=None, alias=str("resumeId"))
|
|
resume: Optional[str] = Field(default=None)
|
|
is_answer: Optional[bool] = Field(default=None, alias=str("isAnswer"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
class ChatMessageUser(ApiMessage):
|
|
type: ApiMessageType = ApiMessageType.TEXT
|
|
status: ApiStatusType = ApiStatusType.DONE
|
|
role: ChatSenderType = ChatSenderType.USER
|
|
content: str = ""
|
|
extra_context: Optional[ExtraChatContext] = Field(default=None, alias=str("extraContext"))
|
|
tunables: Optional[Tunables] = None
|
|
|
|
|
|
class ChatMessage(ChatMessageUser):
|
|
role: ChatSenderType = ChatSenderType.ASSISTANT
|
|
metadata: ChatMessageMetaData = Field(default=ChatMessageMetaData())
|
|
|
|
class ChatMessageSkillAssessment(ChatMessageUser):
|
|
role: ChatSenderType = ChatSenderType.ASSISTANT
|
|
metadata: ChatMessageMetaData = Field(default=ChatMessageMetaData())
|
|
skill_assessment: SkillAssessment = Field(..., alias=str("skillAssessment"))
|
|
|
|
|
|
class Resume(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
job_id: str = Field(..., alias=str("jobId"))
|
|
candidate_id: str = Field(..., alias=str("candidateId"))
|
|
resume: str
|
|
system_prompt: Optional[str] = Field(default=None)
|
|
prompt: Optional[str] = Field(default=None)
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("createdAt"))
|
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("updatedAt"))
|
|
job: Optional[Job] = None
|
|
candidate: Optional[Candidate] = None
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
class ChatMessageResume(ChatMessageUser):
|
|
role: ChatSenderType = ChatSenderType.ASSISTANT
|
|
metadata: ChatMessageMetaData = Field(default=ChatMessageMetaData())
|
|
resume: Resume
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
class ResumeMessage(ChatMessageUser):
|
|
role: ChatSenderType = ChatSenderType.ASSISTANT
|
|
resume: Resume
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class GPUInfo(BaseModel):
|
|
name: str
|
|
memory: int
|
|
discrete: bool
|
|
|
|
|
|
class SystemInfo(BaseModel):
|
|
installed_RAM: str = Field(..., alias=str("installedRAM"))
|
|
graphics_cards: List[GPUInfo] = Field(..., alias=str("graphicsCards"))
|
|
CPU: str
|
|
llm_backend: str = Field(default=os.getenv("DEFAULT_LLM_PROVIDER", "openai"), alias=str("llmBackend"))
|
|
llm_model: str = Field(default=defines.model, alias=str("llmModel"))
|
|
embedding_model: str = Field(default=defines.embedding_model, alias=str("embeddingModel"))
|
|
max_context_length: int = Field(default=defines.max_context, alias=str("maxContextLength"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class ChatSession(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
user_id: Optional[str] = Field(default=None, alias=str("userId"))
|
|
guest_id: Optional[str] = Field(default=None, alias=str("guestId"))
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("createdAt"))
|
|
last_activity: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("lastActivity"))
|
|
title: Optional[str] = None
|
|
context: ChatContext
|
|
is_archived: bool = Field(False, alias=str("isArchived"))
|
|
system_prompt: Optional[str] = Field(default=None, alias=str("systemPrompt"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
@model_validator(mode="after")
|
|
def check_user_or_guest(self) -> "ChatSession":
|
|
if not self.user_id and not self.guest_id:
|
|
raise ValueError("Either user_id or guest_id must be provided")
|
|
return self
|
|
|
|
|
|
class DataSourceConfiguration(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
rag_config_id: str = Field(..., alias=str("ragConfigId"))
|
|
name: str
|
|
source_type: DataSourceType = Field(..., alias=str("sourceType"))
|
|
connection_details: Dict[str, Any] = Field(..., alias=str("connectionDetails"))
|
|
processing_pipeline: List[ProcessingStep] = Field(..., alias=str("processingPipeline"))
|
|
refresh_schedule: Optional[str] = Field(default=None, alias=str("refreshSchedule"))
|
|
last_refreshed: Optional[datetime] = Field(default=None, alias=str("lastRefreshed"))
|
|
status: Literal["active", "pending", "error", "processing"]
|
|
error_details: Optional[str] = Field(default=None, alias=str("errorDetails"))
|
|
metadata: Optional[Dict[str, Any]] = None
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class RAGConfiguration(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
user_id: str = Field(..., alias=str("userId"))
|
|
name: str
|
|
description: Optional[str] = None
|
|
data_source_configurations: List[DataSourceConfiguration] = Field(..., alias=str("dataSourceConfigurations"))
|
|
embedding_model: str = Field(..., alias=str("embeddingModel"))
|
|
vector_store_type: VectorStoreType = Field(..., alias=str("vectorStoreType"))
|
|
retrieval_parameters: RetrievalParameters = Field(..., alias=str("retrievalParameters"))
|
|
created_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("createdAt"))
|
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC), alias=str("updatedAt"))
|
|
version: int
|
|
is_active: bool = Field(..., alias=str("isActive"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class UserActivity(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
user_id: Optional[str] = Field(default=None, alias=str("userId"))
|
|
guest_id: Optional[str] = Field(default=None, alias=str("guestId"))
|
|
activity_type: ActivityType = Field(..., alias=str("activityType"))
|
|
timestamp: datetime
|
|
metadata: Dict[str, Any]
|
|
ip_address: Optional[str] = Field(default=None, alias=str("ipAddress"))
|
|
user_agent: Optional[str] = Field(default=None, alias=str("userAgent"))
|
|
session_id: Optional[str] = Field(default=None, alias=str("sessionId"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
@model_validator(mode="after")
|
|
def check_user_or_guest(self):
|
|
if not self.user_id and not self.guest_id:
|
|
raise ValueError("Either user_id or guest_id must be provided")
|
|
return self
|
|
|
|
|
|
class Analytics(BaseModel):
|
|
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
entity_type: Literal["job", "candidate", "chat", "system", "employer"] = Field(..., alias=str("entityType"))
|
|
entity_id: str = Field(..., alias=str("entityId"))
|
|
metric_type: str = Field(..., alias=str("metricType"))
|
|
value: float
|
|
timestamp: datetime
|
|
dimensions: Optional[Dict[str, Any]] = None
|
|
segment: Optional[str] = None
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class UserPreference(BaseModel):
|
|
user_id: str = Field(..., alias=str("userId"))
|
|
theme: ThemePreference
|
|
notifications: List[NotificationPreference]
|
|
accessibility: AccessibilitySettings
|
|
dashboard_layout: Optional[Dict[str, Any]] = Field(default=None, alias=str("dashboardLayout"))
|
|
language: str
|
|
timezone: str
|
|
email_frequency: Literal["immediate", "daily", "weekly", "never"] = Field(..., alias=str("emailFrequency"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
# ============================
|
|
# API Request/Response Models
|
|
# ============================
|
|
class CreateCandidateRequest(BaseModel):
|
|
email: EmailStr
|
|
username: str
|
|
password: str
|
|
first_name: str = Field(..., alias=str("firstName"))
|
|
last_name: str = Field(..., alias=str("lastName"))
|
|
# Add other required candidate fields as needed
|
|
phone: Optional[str] = None
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
@field_validator("username")
|
|
def validate_username(cls, v):
|
|
if not v or len(v.strip()) < 3:
|
|
raise ValueError("Username must be at least 3 characters long")
|
|
return v.strip().lower()
|
|
|
|
@field_validator("password")
|
|
def validate_password_strength(cls, v):
|
|
is_valid, issues = validate_password_strength(v)
|
|
if not is_valid:
|
|
raise ValueError("; ".join(issues))
|
|
return v
|
|
|
|
|
|
# Create Employer Endpoint (similar pattern)
|
|
class CreateEmployerRequest(BaseModel):
|
|
email: EmailStr
|
|
username: str
|
|
password: str
|
|
company_name: str = Field(..., alias=str("companyName"))
|
|
industry: str
|
|
company_size: str = Field(..., alias=str("companySize"))
|
|
company_description: str = Field(..., alias=str("companyDescription"))
|
|
# Add other required employer fields
|
|
website_url: Optional[str] = Field(default=None, alias=str("websiteUrl"))
|
|
phone: Optional[str] = None
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
@field_validator("username")
|
|
def validate_username(cls, v):
|
|
if not v or len(v.strip()) < 3:
|
|
raise ValueError("Username must be at least 3 characters long")
|
|
return v.strip().lower()
|
|
|
|
@field_validator("password")
|
|
def validate_password_strength(cls, v):
|
|
is_valid, issues = validate_password_strength(v)
|
|
if not is_valid:
|
|
raise ValueError("; ".join(issues))
|
|
return v
|
|
|
|
|
|
class ChatQuery(BaseModel):
|
|
prompt: str
|
|
tunables: Optional[Tunables] = None
|
|
agent_options: Optional[Dict[str, Any]] = Field(default=None, alias=str("agentOptions"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class PaginatedRequest(BaseModel):
|
|
page: Annotated[int, Field(ge=1)] = 1
|
|
limit: Annotated[int, Field(ge=1, le=100)] = 20
|
|
sort_by: Optional[str] = Field(default=None, alias=str("sortBy"))
|
|
sort_order: Optional[SortOrder] = Field(default=None, alias=str("sortOrder"))
|
|
filters: Optional[Dict[str, Any]] = None
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class SearchQuery(BaseModel):
|
|
query: str
|
|
filters: Optional[Dict[str, Any]] = None
|
|
page: Annotated[int, Field(ge=1)] = 1
|
|
limit: Annotated[int, Field(ge=1, le=100)] = 20
|
|
sort_by: Optional[str] = Field(default=None, alias=str("sortBy"))
|
|
sort_order: Optional[SortOrder] = Field(default=None, alias=str("sortOrder"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class PaginatedResponse(BaseModel):
|
|
data: List[Any] # Will be typed specifically when used
|
|
total: int
|
|
page: int
|
|
limit: int
|
|
total_pages: int = Field(..., alias=str("totalPages"))
|
|
has_more: bool = Field(..., alias=str("hasMore"))
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
|
class ApiResponse(BaseModel):
|
|
success: bool
|
|
data: Optional[Any] = None # Will be typed specifically when used
|
|
error: Optional[ErrorDetail] = None
|
|
meta: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
# Specific typed response models for common use cases
|
|
class CandidateResponse(BaseModel):
|
|
success: bool
|
|
data: Optional[Candidate] = None
|
|
error: Optional[ErrorDetail] = None
|
|
meta: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
class EmployerResponse(BaseModel):
|
|
success: bool
|
|
data: Optional[Employer] = None
|
|
error: Optional[ErrorDetail] = None
|
|
meta: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
class JobResponse(BaseModel):
|
|
success: bool
|
|
data: Optional[Job] = None
|
|
error: Optional[ErrorDetail] = None
|
|
meta: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
class CandidateListResponse(BaseModel):
|
|
success: bool
|
|
data: Optional[List[Candidate]] = None
|
|
error: Optional[ErrorDetail] = None
|
|
meta: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
class JobListResponse(BaseModel):
|
|
success: bool
|
|
data: Optional[List["Job"]] = None
|
|
error: Optional[ErrorDetail] = None
|
|
meta: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
User = Union[Candidate, CandidateAI, Employer, Guest]
|
|
|
|
# Forward references resolution
|
|
Candidate.update_forward_refs()
|
|
Employer.update_forward_refs()
|
|
ChatSession.update_forward_refs()
|
|
JobApplication.update_forward_refs()
|
|
Job.update_forward_refs()
|