Release Notes: dev-12 #14
50
.github/workflows/deploy.yml
vendored
50
.github/workflows/deploy.yml
vendored
@@ -172,6 +172,56 @@ jobs:
|
|||||||
|
|
||||||
🔗 View details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
🔗 View details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Get PR body from merged PR
|
||||||
|
if: job.status == 'success' && github.event_name == 'push'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
echo "🔍 Searching for merged PR associated with commit ${{ github.sha }}..."
|
||||||
|
|
||||||
|
# Находим последний мерженный PR для main ветки по merge commit SHA
|
||||||
|
COMMIT_SHA="${{ github.sha }}"
|
||||||
|
PR_NUMBER=$(gh pr list --state merged --base main --limit 10 --json number,mergeCommit --jq ".[] | select(.mergeCommit.oid == \"$COMMIT_SHA\") | .number" | head -1)
|
||||||
|
|
||||||
|
# Если не нашли по merge commit, ищем последний мерженный PR
|
||||||
|
if [ -z "$PR_NUMBER" ]; then
|
||||||
|
echo "⚠️ PR not found by merge commit, trying to get latest merged PR..."
|
||||||
|
PR_NUMBER=$(gh pr list --state merged --base main --limit 1 --json number --jq '.[0].number')
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$PR_NUMBER" ] && [ "$PR_NUMBER" != "null" ]; then
|
||||||
|
echo "✅ Found PR #$PR_NUMBER"
|
||||||
|
PR_BODY=$(gh pr view $PR_NUMBER --json body --jq '.body // ""')
|
||||||
|
|
||||||
|
if [ -n "$PR_BODY" ] && [ "$PR_BODY" != "null" ]; then
|
||||||
|
echo "PR_BODY<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$PR_BODY" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
|
||||||
|
echo "✅ PR body extracted successfully"
|
||||||
|
else
|
||||||
|
echo "⚠️ PR body is empty"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ No merged PR found for this commit"
|
||||||
|
fi
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Send PR body to important logs
|
||||||
|
if: job.status == 'success' && github.event_name == 'push' && env.PR_BODY != ''
|
||||||
|
uses: appleboy/telegram-action@v1.0.0
|
||||||
|
with:
|
||||||
|
to: ${{ secrets.IMPORTANT_LOGS_CHAT }}
|
||||||
|
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||||
|
message: |
|
||||||
|
📋 Pull Request Description (PR #${{ env.PR_NUMBER }}):
|
||||||
|
|
||||||
|
${{ env.PR_BODY }}
|
||||||
|
|
||||||
|
🔗 PR: ${{ github.server_url }}/${{ github.repository }}/pull/${{ env.PR_NUMBER }}
|
||||||
|
📝 Commit: ${{ github.sha }}
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
rollback:
|
rollback:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
"""Репозиторий для работы с миграциями базы данных."""
|
"""Репозиторий для работы с миграциями базы данных."""
|
||||||
import aiosqlite
|
import aiosqlite
|
||||||
|
|
||||||
from database.base import DatabaseConnection
|
from database.base import DatabaseConnection
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,12 @@
|
|||||||
- ScoringManager - объединение всех сервисов скоринга
|
- ScoringManager - объединение всех сервисов скоринга
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .base import ScoringResult, ScoringServiceProtocol, CombinedScore
|
from .base import CombinedScore, ScoringResult, ScoringServiceProtocol
|
||||||
from .exceptions import (
|
|
||||||
ScoringError,
|
|
||||||
ModelNotLoadedError,
|
|
||||||
VectorStoreError,
|
|
||||||
DeepSeekAPIError,
|
|
||||||
InsufficientExamplesError,
|
|
||||||
TextTooShortError,
|
|
||||||
)
|
|
||||||
from .rag_client import RagApiClient
|
|
||||||
from .deepseek_service import DeepSeekService
|
from .deepseek_service import DeepSeekService
|
||||||
|
from .exceptions import (DeepSeekAPIError, InsufficientExamplesError,
|
||||||
|
ModelNotLoadedError, ScoringError, TextTooShortError,
|
||||||
|
VectorStoreError)
|
||||||
|
from .rag_client import RagApiClient
|
||||||
from .scoring_manager import ScoringManager
|
from .scoring_manager import ScoringManager
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Optional, Protocol, Dict, Any
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from typing import Any, Dict, Optional, Protocol
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ DeepSeek API сервис для скоринга постов.
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from typing import Optional, List
|
from typing import List, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from helper_bot.utils.metrics import track_errors, track_time
|
||||||
from logs.custom_logger import logger
|
from logs.custom_logger import logger
|
||||||
from helper_bot.utils.metrics import track_time, track_errors
|
|
||||||
|
|
||||||
from .base import ScoringResult
|
from .base import ScoringResult
|
||||||
from .exceptions import DeepSeekAPIError, ScoringError, TextTooShortError
|
from .exceptions import DeepSeekAPIError, ScoringError, TextTooShortError
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ HTTP клиент для взаимодействия с внешним RAG се
|
|||||||
Использует REST API для получения скоров и отправки примеров.
|
Использует REST API для получения скоров и отправки примеров.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Optional, Dict, Any
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from helper_bot.utils.metrics import track_errors, track_time
|
||||||
from logs.custom_logger import logger
|
from logs.custom_logger import logger
|
||||||
from helper_bot.utils.metrics import track_time, track_errors
|
|
||||||
|
|
||||||
from .base import ScoringResult
|
from .base import ScoringResult
|
||||||
from .exceptions import ScoringError, InsufficientExamplesError, TextTooShortError
|
from .exceptions import (InsufficientExamplesError, ScoringError,
|
||||||
|
TextTooShortError)
|
||||||
|
|
||||||
|
|
||||||
class RagApiClient:
|
class RagApiClient:
|
||||||
|
|||||||
@@ -8,13 +8,14 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from helper_bot.utils.metrics import track_errors, track_time
|
||||||
from logs.custom_logger import logger
|
from logs.custom_logger import logger
|
||||||
from helper_bot.utils.metrics import track_time, track_errors
|
|
||||||
|
|
||||||
from .base import CombinedScore, ScoringResult
|
from .base import CombinedScore, ScoringResult
|
||||||
from .rag_client import RagApiClient
|
|
||||||
from .deepseek_service import DeepSeekService
|
from .deepseek_service import DeepSeekService
|
||||||
from .exceptions import ScoringError, InsufficientExamplesError, TextTooShortError
|
from .exceptions import (InsufficientExamplesError, ScoringError,
|
||||||
|
TextTooShortError)
|
||||||
|
from .rag_client import RagApiClient
|
||||||
|
|
||||||
|
|
||||||
class ScoringManager:
|
class ScoringManager:
|
||||||
|
|||||||
@@ -130,11 +130,8 @@ class BaseDependencyFactory:
|
|||||||
|
|
||||||
Вызывается лениво при первом обращении к get_scoring_manager().
|
Вызывается лениво при первом обращении к get_scoring_manager().
|
||||||
"""
|
"""
|
||||||
from helper_bot.services.scoring import (
|
from helper_bot.services.scoring import (DeepSeekService, RagApiClient,
|
||||||
ScoringManager,
|
ScoringManager)
|
||||||
RagApiClient,
|
|
||||||
DeepSeekService,
|
|
||||||
)
|
|
||||||
|
|
||||||
scoring_config = self.settings['Scoring']
|
scoring_config = self.settings['Scoring']
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ class TestKeyboards:
|
|||||||
|
|
||||||
assert isinstance(keyboard, ReplyKeyboardMarkup)
|
assert isinstance(keyboard, ReplyKeyboardMarkup)
|
||||||
assert keyboard.keyboard is not None
|
assert keyboard.keyboard is not None
|
||||||
assert len(keyboard.keyboard) == 2 # Две строки
|
assert len(keyboard.keyboard) == 3 # Три строки
|
||||||
|
|
||||||
# Проверяем первую строку (3 кнопки)
|
# Проверяем первую строку (3 кнопки)
|
||||||
first_row = keyboard.keyboard[0]
|
first_row = keyboard.keyboard[0]
|
||||||
@@ -124,7 +124,12 @@ class TestKeyboards:
|
|||||||
second_row = keyboard.keyboard[1]
|
second_row = keyboard.keyboard[1]
|
||||||
assert len(second_row) == 2
|
assert len(second_row) == 2
|
||||||
assert second_row[0].text == "Разбан (список)"
|
assert second_row[0].text == "Разбан (список)"
|
||||||
assert second_row[1].text == "Вернуться в бота"
|
assert second_row[1].text == "📊 ML Статистика"
|
||||||
|
|
||||||
|
# Проверяем третью строку (1 кнопка)
|
||||||
|
third_row = keyboard.keyboard[2]
|
||||||
|
assert len(third_row) == 1
|
||||||
|
assert third_row[0].text == "Вернуться в бота"
|
||||||
|
|
||||||
def test_get_reply_keyboard_for_post(self):
|
def test_get_reply_keyboard_for_post(self):
|
||||||
"""Тест клавиатуры для постов"""
|
"""Тест клавиатуры для постов"""
|
||||||
|
|||||||
@@ -3,16 +3,14 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import pytest
|
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
# Импорты для тестирования базовых классов
|
# Импорты для тестирования базовых классов
|
||||||
from helper_bot.services.scoring.base import ScoringResult, CombinedScore
|
from helper_bot.services.scoring.base import CombinedScore, ScoringResult
|
||||||
from helper_bot.services.scoring.exceptions import (
|
from helper_bot.services.scoring.exceptions import (InsufficientExamplesError,
|
||||||
ScoringError,
|
ScoringError,
|
||||||
InsufficientExamplesError,
|
TextTooShortError)
|
||||||
TextTooShortError,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestScoringResult:
|
class TestScoringResult:
|
||||||
@@ -159,7 +157,7 @@ class TestVectorStore:
|
|||||||
def test_max_examples_limit(self, vector_store):
|
def test_max_examples_limit(self, vector_store):
|
||||||
"""Тест ограничения максимального количества примеров."""
|
"""Тест ограничения максимального количества примеров."""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
# Добавляем больше чем max_examples
|
# Добавляем больше чем max_examples
|
||||||
for i in range(150):
|
for i in range(150):
|
||||||
vector = np.random.randn(768).astype(np.float32)
|
vector = np.random.randn(768).astype(np.float32)
|
||||||
@@ -179,7 +177,7 @@ class TestVectorStore:
|
|||||||
def test_calculate_similarity_with_examples(self, vector_store):
|
def test_calculate_similarity_with_examples(self, vector_store):
|
||||||
"""Тест расчета скора с примерами."""
|
"""Тест расчета скора с примерами."""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
# Добавляем положительные примеры
|
# Добавляем положительные примеры
|
||||||
for i in range(10):
|
for i in range(10):
|
||||||
vector = np.random.randn(768).astype(np.float32)
|
vector = np.random.randn(768).astype(np.float32)
|
||||||
@@ -215,7 +213,8 @@ class TestDeepSeekService:
|
|||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def deepseek_service(self):
|
def deepseek_service(self):
|
||||||
"""Создает DeepSeekService для тестов."""
|
"""Создает DeepSeekService для тестов."""
|
||||||
from helper_bot.services.scoring.deepseek_service import DeepSeekService
|
from helper_bot.services.scoring.deepseek_service import \
|
||||||
|
DeepSeekService
|
||||||
return DeepSeekService(
|
return DeepSeekService(
|
||||||
api_key="test_key",
|
api_key="test_key",
|
||||||
enabled=True,
|
enabled=True,
|
||||||
@@ -224,7 +223,8 @@ class TestDeepSeekService:
|
|||||||
|
|
||||||
def test_service_disabled_without_key(self):
|
def test_service_disabled_without_key(self):
|
||||||
"""Тест отключения сервиса без API ключа."""
|
"""Тест отключения сервиса без API ключа."""
|
||||||
from helper_bot.services.scoring.deepseek_service import DeepSeekService
|
from helper_bot.services.scoring.deepseek_service import \
|
||||||
|
DeepSeekService
|
||||||
service = DeepSeekService(api_key=None, enabled=True)
|
service = DeepSeekService(api_key=None, enabled=True)
|
||||||
|
|
||||||
assert service.is_enabled is False
|
assert service.is_enabled is False
|
||||||
@@ -255,7 +255,8 @@ class TestDeepSeekService:
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_calculate_score_disabled(self):
|
async def test_calculate_score_disabled(self):
|
||||||
"""Тест расчета скора при отключенном сервисе."""
|
"""Тест расчета скора при отключенном сервисе."""
|
||||||
from helper_bot.services.scoring.deepseek_service import DeepSeekService
|
from helper_bot.services.scoring.deepseek_service import \
|
||||||
|
DeepSeekService
|
||||||
service = DeepSeekService(api_key=None, enabled=False)
|
service = DeepSeekService(api_key=None, enabled=False)
|
||||||
|
|
||||||
with pytest.raises(ScoringError):
|
with pytest.raises(ScoringError):
|
||||||
@@ -281,6 +282,8 @@ class TestScoringManager:
|
|||||||
source="rag",
|
source="rag",
|
||||||
model="rubert",
|
model="rubert",
|
||||||
))
|
))
|
||||||
|
mock.add_positive_example = AsyncMock()
|
||||||
|
mock.add_negative_example = AsyncMock()
|
||||||
return mock
|
return mock
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -293,6 +296,8 @@ class TestScoringManager:
|
|||||||
source="deepseek",
|
source="deepseek",
|
||||||
model="deepseek-chat",
|
model="deepseek-chat",
|
||||||
))
|
))
|
||||||
|
mock.add_positive_example = AsyncMock()
|
||||||
|
mock.add_negative_example = AsyncMock()
|
||||||
return mock
|
return mock
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -301,7 +306,7 @@ class TestScoringManager:
|
|||||||
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
||||||
|
|
||||||
manager = ScoringManager(
|
manager = ScoringManager(
|
||||||
rag_service=mock_rag_service,
|
rag_client=mock_rag_service,
|
||||||
deepseek_service=mock_deepseek_service,
|
deepseek_service=mock_deepseek_service,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -317,7 +322,7 @@ class TestScoringManager:
|
|||||||
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
||||||
|
|
||||||
manager = ScoringManager(
|
manager = ScoringManager(
|
||||||
rag_service=mock_rag_service,
|
rag_client=mock_rag_service,
|
||||||
deepseek_service=None,
|
deepseek_service=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -331,7 +336,7 @@ class TestScoringManager:
|
|||||||
"""Тест скоринга пустого текста."""
|
"""Тест скоринга пустого текста."""
|
||||||
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
||||||
|
|
||||||
manager = ScoringManager(rag_service=mock_rag_service)
|
manager = ScoringManager(rag_client=mock_rag_service)
|
||||||
|
|
||||||
result = await manager.score_post("")
|
result = await manager.score_post("")
|
||||||
|
|
||||||
@@ -342,12 +347,12 @@ class TestScoringManager:
|
|||||||
async def test_score_post_service_error(self, mock_rag_service, mock_deepseek_service):
|
async def test_score_post_service_error(self, mock_rag_service, mock_deepseek_service):
|
||||||
"""Тест обработки ошибки сервиса."""
|
"""Тест обработки ошибки сервиса."""
|
||||||
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
||||||
|
|
||||||
# RAG выбрасывает ошибку
|
# RAG выбрасывает ошибку
|
||||||
mock_rag_service.calculate_score = AsyncMock(side_effect=Exception("Test error"))
|
mock_rag_service.calculate_score = AsyncMock(side_effect=Exception("Test error"))
|
||||||
|
|
||||||
manager = ScoringManager(
|
manager = ScoringManager(
|
||||||
rag_service=mock_rag_service,
|
rag_client=mock_rag_service,
|
||||||
deepseek_service=mock_deepseek_service,
|
deepseek_service=mock_deepseek_service,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -365,7 +370,7 @@ class TestScoringManager:
|
|||||||
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
||||||
|
|
||||||
manager = ScoringManager(
|
manager = ScoringManager(
|
||||||
rag_service=mock_rag_service,
|
rag_client=mock_rag_service,
|
||||||
deepseek_service=mock_deepseek_service,
|
deepseek_service=mock_deepseek_service,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -380,7 +385,7 @@ class TestScoringManager:
|
|||||||
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
from helper_bot.services.scoring.scoring_manager import ScoringManager
|
||||||
|
|
||||||
manager = ScoringManager(
|
manager = ScoringManager(
|
||||||
rag_service=mock_rag_service,
|
rag_client=mock_rag_service,
|
||||||
deepseek_service=mock_deepseek_service,
|
deepseek_service=mock_deepseek_service,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user