""" Pydantic схемы для API Embedding сервиса. """ from pydantic import BaseModel, Field # ============================================================================= # Запросы # ============================================================================= class ScoreRequest(BaseModel): """Запрос на расчет скора.""" text: str = Field(..., min_length=1, description="Текст поста для оценки") model_config = { "json_schema_extra": {"example": {"text": "Это пример текста поста для оценки скоринга"}} } class ExampleRequest(BaseModel): """Запрос на добавление примера.""" text: str = Field(..., min_length=1, description="Текст примера") model_config = { "json_schema_extra": {"example": {"text": "Это пример опубликованного/отклоненного поста"}} } class SimilarRequest(BaseModel): """Запрос на поиск похожих постов.""" text: str = Field(..., min_length=1, description="Текст для поиска похожих") threshold: float = Field( default=0.9, ge=0.0, le=1.0, description="Минимальный порог similarity" ) hours: int = Field(default=24, ge=1, le=168, description="Количество часов для фильтрации") model_config = { "json_schema_extra": { "example": { "text": "Текст поста для поиска похожих", "threshold": 0.9, "hours": 24, } } } class SubmittedRequest(BaseModel): """Запрос на добавление submitted-поста.""" text: str = Field(..., min_length=1, description="Текст поста") post_id: int | None = None rag_score: float | None = None model_config = { "json_schema_extra": { "example": { "text": "Текст submitted-поста", "post_id": 12345, "rag_score": 0.85, } } } # ============================================================================= # Ответы # ============================================================================= class ScoreMetadata(BaseModel): """Метаданные результата скоринга.""" positive_examples: int = Field(..., description="Количество положительных примеров") negative_examples: int = Field(..., description="Количество отрицательных примеров") model: str = Field(..., description="Название модели") timestamp: int = Field(..., description="Время расчета (unix timestamp)") class ScoreResponse(BaseModel): """Ответ с результатом скоринга.""" rag_score: float = Field(..., ge=0.0, le=1.0, description="Основной скор (neg/pos формула)") rag_confidence: float = Field(..., ge=0.0, le=1.0, description="Уверенность в оценке") rag_score_pos_only: float = Field( ..., ge=0.0, le=1.0, description="Скор только по положительным примерам" ) meta: ScoreMetadata = Field(..., description="Метаданные") model_config = { "json_schema_extra": { "example": { "rag_score": 0.7523, "rag_confidence": 0.85, "rag_score_pos_only": 0.6891, "meta": { "positive_examples": 500, "negative_examples": 350, "model": "sentence-transformers/all-MiniLM-L12-v2", "timestamp": 1706270000, }, } } } class ExampleResponse(BaseModel): """Ответ на добавление примера.""" success: bool = Field(..., description="Успешность добавления") message: str = Field(..., description="Сообщение о результате") positive_count: int = Field(..., description="Текущее количество положительных примеров") negative_count: int = Field(..., description="Текущее количество отрицательных примеров") model_config = { "json_schema_extra": { "example": { "success": True, "message": "Положительный пример добавлен", "positive_count": 501, "negative_count": 350, } } } class SimilarPostItem(BaseModel): """Элемент похожего поста.""" similarity: float = Field(..., description="Косинусное сходство") created_at: int = Field(..., description="Unix timestamp создания") post_id: int | None = None text: str = Field(..., description="Текст поста") rag_score: float | None = None class SimilarResponse(BaseModel): """Ответ с похожими постами.""" similar_count: int = Field(..., description="Количество найденных похожих постов") similar_posts: list[SimilarPostItem] = Field(..., description="Список похожих постов") model_config = { "json_schema_extra": { "example": { "similar_count": 2, "similar_posts": [ { "similarity": 0.95, "created_at": 1706270000, "post_id": 123, "text": "Похожий пост", "rag_score": 0.85, } ], } } } class SubmittedResponse(BaseModel): """Ответ на добавление submitted-поста.""" success: bool = Field(..., description="Успешность добавления") message: str = Field(..., description="Сообщение о результате") submitted_count: int = Field(..., description="Текущее количество submitted-постов") model_config = { "json_schema_extra": { "example": { "success": True, "message": "Submitted-пост добавлен", "submitted_count": 42, } } } class VectorStoreStats(BaseModel): """Статистика хранилища векторов.""" positive_count: int = Field(..., description="Количество положительных примеров") negative_count: int = Field(..., description="Количество отрицательных примеров") total_count: int = Field(..., description="Общее количество примеров") submitted_count: int = Field(default=0, description="Количество submitted-постов") vector_dim: int = Field(..., description="Размерность векторов") max_examples: int = Field(..., description="Максимальное количество примеров") max_submitted: int = Field(default=5000, description="Максимальное количество submitted-постов") class StatsResponse(BaseModel): """Ответ со статистикой сервиса.""" model_name: str = Field(..., description="Название модели") model_loaded: bool = Field(..., description="Загружена ли модель") device: str | None = Field(None, description="Устройство (cpu/cuda)") vector_store: VectorStoreStats = Field(..., description="Статистика хранилища векторов") model_config = { "json_schema_extra": { "example": { "model_name": "sentence-transformers/all-MiniLM-L12-v2", "model_loaded": True, "device": "cpu", "vector_store": { "positive_count": 500, "negative_count": 350, "total_count": 850, "vector_dim": 384, "max_examples": 10000, }, } } } class WarmupResponse(BaseModel): """Ответ на прогрев модели.""" success: bool = Field(..., description="Успешность загрузки") model_loaded: bool = Field(..., description="Загружена ли модель") message: str = Field(..., description="Сообщение о результате") model_config = { "json_schema_extra": { "example": { "success": True, "model_loaded": True, "message": "Модель успешно загружена", } } } class ErrorResponse(BaseModel): """Ответ с ошибкой.""" detail: str = Field(..., description="Описание ошибки") error_type: str = Field(..., description="Тип ошибки") model_config = { "json_schema_extra": { "example": { "detail": "Недостаточно примеров для расчета скора", "error_type": "InsufficientExamplesError", } } } class HealthResponse(BaseModel): """Ответ проверки здоровья сервиса.""" status: str = Field(..., description="Статус сервиса") model_loaded: bool = Field(..., description="Загружена ли модель") version: str = Field(..., description="Версия сервиса") model_config = { "json_schema_extra": { "example": {"status": "healthy", "model_loaded": True, "version": "0.1.0"} } } class ScoringParamsResponse(BaseModel): """Ответ с текущими параметрами формулы расчета score.""" score_multiplier: float = Field( ..., description=( "Множитель для масштабирования разницы в скорах. " "Используется в формуле: score = (diff * score_multiplier + 1) / 2, " "где diff = avg_pos - avg_neg (разница средних сходств топ-k примеров). " "Чем больше значение, тем сильнее влияние разницы между положительными и отрицательными примерами на итоговый score. " "Рекомендуемое значение: 5.0" ), ) k: int = Field( ..., description=( "Количество ближайших примеров для расчета среднего сходства. " "Алгоритм берет топ-k самых похожих примеров из каждого типа (положительные/отрицательные) " "и вычисляет среднее косинусное сходство. " "Меньшее значение k делает алгоритм более чувствительным к различиям, но может быть менее стабильным. " "Рекомендуемое значение: 3" ), ) model_config = {"json_schema_extra": {"example": {"score_multiplier": 5.0, "k": 3}}} class UpdateScoringParamsRequest(BaseModel): """Запрос на обновление параметров формулы расчета score.""" score_multiplier: float | None = Field( None, gt=0, description=( "Множитель для масштабирования разницы в скорах. " "Используется в формуле: score = (diff * score_multiplier + 1) / 2, " "где diff = avg_pos - avg_neg (разница средних сходств топ-k примеров). " "Чем больше значение, тем сильнее влияние разницы между положительными и отрицательными примерами на итоговый score. " "Должен быть > 0. Рекомендуемое значение: 5.0" ), ) k: int | None = Field( None, ge=1, description=( "Количество ближайших примеров для расчета среднего сходства. " "Алгоритм берет топ-k самых похожих примеров из каждого типа (положительные/отрицательные) " "и вычисляет среднее косинусное сходство. " "Меньшее значение k делает алгоритм более чувствительным к различиям, но может быть менее стабильным. " "Должно быть >= 1. Рекомендуемое значение: 3" ), ) model_config = {"json_schema_extra": {"example": {"score_multiplier": 5.0, "k": 3}}}