feat: интеграция ML-скоринга с использованием RAG и DeepSeek
- Обновлен Dockerfile для установки необходимых зависимостей. - Добавлены новые переменные окружения для настройки ML-скоринга в env.example. - Реализованы методы для получения и обновления ML-скоров в AsyncBotDB и PostRepository. - Обновлены обработчики публикации постов для интеграции ML-скоринга. - Добавлен новый обработчик для получения статистики ML-скоринга в админ-панели. - Обновлены функции для форматирования сообщений с учетом ML-скоров.
This commit is contained in:
155
helper_bot/services/scoring/base.py
Normal file
155
helper_bot/services/scoring/base.py
Normal file
@@ -0,0 +1,155 @@
|
||||
"""
|
||||
Базовые классы и протоколы для сервисов скоринга.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Optional, Protocol, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScoringResult:
|
||||
"""
|
||||
Результат оценки поста от одного сервиса.
|
||||
|
||||
Attributes:
|
||||
score: Оценка от 0.0 до 1.0 (вероятность публикации)
|
||||
source: Источник оценки ("deepseek", "rag", etc.)
|
||||
model: Название используемой модели
|
||||
confidence: Уверенность в оценке (опционально)
|
||||
timestamp: Время получения оценки
|
||||
metadata: Дополнительные данные
|
||||
"""
|
||||
score: float
|
||||
source: str
|
||||
model: str
|
||||
confidence: Optional[float] = None
|
||||
timestamp: int = field(default_factory=lambda: int(datetime.now().timestamp()))
|
||||
metadata: Dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def __post_init__(self):
|
||||
"""Валидация score в диапазоне [0.0, 1.0]."""
|
||||
if not 0.0 <= self.score <= 1.0:
|
||||
raise ValueError(f"Score должен быть в диапазоне [0.0, 1.0], получено: {self.score}")
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Преобразует результат в словарь для сохранения в JSON."""
|
||||
result = {
|
||||
"score": round(self.score, 4),
|
||||
"model": self.model,
|
||||
"ts": self.timestamp,
|
||||
}
|
||||
if self.confidence is not None:
|
||||
result["confidence"] = round(self.confidence, 4)
|
||||
if self.metadata:
|
||||
result["metadata"] = self.metadata
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, source: str, data: Dict[str, Any]) -> "ScoringResult":
|
||||
"""Создает ScoringResult из словаря."""
|
||||
return cls(
|
||||
score=data["score"],
|
||||
source=source,
|
||||
model=data.get("model", "unknown"),
|
||||
confidence=data.get("confidence"),
|
||||
timestamp=data.get("ts", int(datetime.now().timestamp())),
|
||||
metadata=data.get("metadata", {}),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CombinedScore:
|
||||
"""
|
||||
Объединенный результат от всех сервисов скоринга.
|
||||
|
||||
Attributes:
|
||||
deepseek: Результат от DeepSeek API (None если отключен/ошибка)
|
||||
rag: Результат от RAG сервиса (None если отключен/ошибка)
|
||||
errors: Словарь с ошибками по источникам
|
||||
"""
|
||||
deepseek: Optional[ScoringResult] = None
|
||||
rag: Optional[ScoringResult] = None
|
||||
errors: Dict[str, str] = field(default_factory=dict)
|
||||
|
||||
@property
|
||||
def deepseek_score(self) -> Optional[float]:
|
||||
"""Возвращает только числовой скор от DeepSeek."""
|
||||
return self.deepseek.score if self.deepseek else None
|
||||
|
||||
@property
|
||||
def rag_score(self) -> Optional[float]:
|
||||
"""Возвращает только числовой скор от RAG."""
|
||||
return self.rag.score if self.rag else None
|
||||
|
||||
def to_json_dict(self) -> Dict[str, Any]:
|
||||
"""
|
||||
Преобразует в словарь для сохранения в ml_scores колонку.
|
||||
|
||||
Формат:
|
||||
{
|
||||
"deepseek": {"score": 0.75, "model": "...", "ts": ...},
|
||||
"rag": {"score": 0.90, "model": "...", "ts": ...}
|
||||
}
|
||||
"""
|
||||
result = {}
|
||||
if self.deepseek:
|
||||
result["deepseek"] = self.deepseek.to_dict()
|
||||
if self.rag:
|
||||
result["rag"] = self.rag.to_dict()
|
||||
return result
|
||||
|
||||
def has_any_score(self) -> bool:
|
||||
"""Проверяет, есть ли хотя бы один успешный скор."""
|
||||
return self.deepseek is not None or self.rag is not None
|
||||
|
||||
|
||||
class ScoringServiceProtocol(Protocol):
|
||||
"""
|
||||
Протокол для сервисов скоринга.
|
||||
|
||||
Любой сервис скоринга должен реализовывать эти методы.
|
||||
"""
|
||||
|
||||
@property
|
||||
def source_name(self) -> str:
|
||||
"""Возвращает имя источника ("deepseek", "rag", etc.)."""
|
||||
...
|
||||
|
||||
@property
|
||||
def is_enabled(self) -> bool:
|
||||
"""Проверяет, включен ли сервис."""
|
||||
...
|
||||
|
||||
async def calculate_score(self, text: str) -> ScoringResult:
|
||||
"""
|
||||
Рассчитывает скор для текста поста.
|
||||
|
||||
Args:
|
||||
text: Текст поста для оценки
|
||||
|
||||
Returns:
|
||||
ScoringResult с оценкой
|
||||
|
||||
Raises:
|
||||
ScoringError: При ошибке расчета
|
||||
"""
|
||||
...
|
||||
|
||||
async def add_positive_example(self, text: str) -> None:
|
||||
"""
|
||||
Добавляет текст как положительный пример (опубликованный пост).
|
||||
|
||||
Args:
|
||||
text: Текст опубликованного поста
|
||||
"""
|
||||
...
|
||||
|
||||
async def add_negative_example(self, text: str) -> None:
|
||||
"""
|
||||
Добавляет текст как отрицательный пример (отклоненный пост).
|
||||
|
||||
Args:
|
||||
text: Текст отклоненного поста
|
||||
"""
|
||||
...
|
||||
Reference in New Issue
Block a user