feat: add submitted collection, /similar and /submitted endpoints (Stage 4)

Made-with: Cursor
This commit is contained in:
2026-02-28 19:00:22 +03:00
parent 955f518429
commit a1d6d2d860
15 changed files with 1308 additions and 400 deletions

View File

@@ -2,36 +2,66 @@
Pydantic схемы для API Embedding сервиса.
"""
from typing import Any, Dict, Optional
from pydantic import BaseModel, Field
# =============================================================================
# Запросы
# =============================================================================
class ScoreRequest(BaseModel):
"""Запрос на расчет скора."""
text: str = Field(..., min_length=1, description="Текст поста для оценки")
model_config = {
"json_schema_extra": {
"example": {
"text": "Это пример текста поста для оценки скоринга"
}
}
"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": "Это пример опубликованного/отклоненного поста"
"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,
}
}
}
@@ -41,8 +71,10 @@ class ExampleRequest(BaseModel):
# Ответы
# =============================================================================
class ScoreMetadata(BaseModel):
"""Метаданные результата скоринга."""
positive_examples: int = Field(..., description="Количество положительных примеров")
negative_examples: int = Field(..., description="Количество отрицательных примеров")
model: str = Field(..., description="Название модели")
@@ -51,11 +83,14 @@ class ScoreMetadata(BaseModel):
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="Скор только по положительным примерам")
rag_score_pos_only: float = Field(
..., ge=0.0, le=1.0, description="Скор только по положительным примерам"
)
meta: ScoreMetadata = Field(..., description="Метаданные")
model_config = {
"json_schema_extra": {
"example": {
@@ -66,8 +101,8 @@ class ScoreResponse(BaseModel):
"positive_examples": 500,
"negative_examples": 350,
"model": "sentence-transformers/all-MiniLM-L12-v2",
"timestamp": 1706270000
}
"timestamp": 1706270000,
},
}
}
}
@@ -75,18 +110,71 @@ class ScoreResponse(BaseModel):
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
"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,
}
}
}
@@ -94,20 +182,24 @@ class ExampleResponse(BaseModel):
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: Optional[str] = Field(None, description="Устройство (cpu/cuda)")
device: str | None = Field(None, description="Устройство (cpu/cuda)")
vector_store: VectorStoreStats = Field(..., description="Статистика хранилища векторов")
model_config = {
"json_schema_extra": {
"example": {
@@ -119,8 +211,8 @@ class StatsResponse(BaseModel):
"negative_count": 350,
"total_count": 850,
"vector_dim": 384,
"max_examples": 10000
}
"max_examples": 10000,
},
}
}
}
@@ -128,16 +220,17 @@ class StatsResponse(BaseModel):
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": "Модель успешно загружена"
"message": "Модель успешно загружена",
}
}
}
@@ -145,14 +238,15 @@ class WarmupResponse(BaseModel):
class ErrorResponse(BaseModel):
"""Ответ с ошибкой."""
detail: str = Field(..., description="Описание ошибки")
error_type: str = Field(..., description="Тип ошибки")
model_config = {
"json_schema_extra": {
"example": {
"detail": "Недостаточно примеров для расчета скора",
"error_type": "InsufficientExamplesError"
"error_type": "InsufficientExamplesError",
}
}
}
@@ -160,23 +254,21 @@ class ErrorResponse(BaseModel):
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"
}
"example": {"status": "healthy", "model_loaded": True, "version": "0.1.0"}
}
}
class ScoringParamsResponse(BaseModel):
"""Ответ с текущими параметрами формулы расчета score."""
score_multiplier: float = Field(
...,
description=(
@@ -185,7 +277,7 @@ class ScoringParamsResponse(BaseModel):
"где diff = avg_pos - avg_neg (разница средних сходств топ-k примеров). "
"Чем больше значение, тем сильнее влияние разницы между положительными и отрицательными примерами на итоговый score. "
"Рекомендуемое значение: 5.0"
)
),
)
k: int = Field(
...,
@@ -195,22 +287,16 @@ class ScoringParamsResponse(BaseModel):
"и вычисляет среднее косинусное сходство. "
"Меньшее значение k делает алгоритм более чувствительным к различиям, но может быть менее стабильным. "
"Рекомендуемое значение: 3"
)
),
)
model_config = {
"json_schema_extra": {
"example": {
"score_multiplier": 5.0,
"k": 3
}
}
}
model_config = {"json_schema_extra": {"example": {"score_multiplier": 5.0, "k": 3}}}
class UpdateScoringParamsRequest(BaseModel):
"""Запрос на обновление параметров формулы расчета score."""
score_multiplier: Optional[float] = Field(
score_multiplier: float | None = Field(
None,
gt=0,
description=(
@@ -219,9 +305,9 @@ class UpdateScoringParamsRequest(BaseModel):
"где diff = avg_pos - avg_neg (разница средних сходств топ-k примеров). "
"Чем больше значение, тем сильнее влияние разницы между положительными и отрицательными примерами на итоговый score. "
"Должен быть > 0. Рекомендуемое значение: 5.0"
)
),
)
k: Optional[int] = Field(
k: int | None = Field(
None,
ge=1,
description=(
@@ -230,14 +316,7 @@ class UpdateScoringParamsRequest(BaseModel):
"и вычисляет среднее косинусное сходство. "
"Меньшее значение k делает алгоритм более чувствительным к различиям, но может быть менее стабильным. "
"Должно быть >= 1. Рекомендуемое значение: 3"
)
),
)
model_config = {
"json_schema_extra": {
"example": {
"score_multiplier": 5.0,
"k": 3
}
}
}
model_config = {"json_schema_extra": {"example": {"score_multiplier": 5.0, "k": 3}}}