diff --git a/solution/services/backend/integrations/__init__.py b/solution/services/backend/integrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solution/services/backend/integrations/yandexai/__init__.py b/solution/services/backend/integrations/yandexai/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solution/services/backend/integrations/yandexai/generators/__init__.py b/solution/services/backend/integrations/yandexai/generators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/solution/services/backend/integrations/yandexai/generators/ad_text.py b/solution/services/backend/integrations/yandexai/generators/ad_text.py new file mode 100644 index 0000000..57f5df9 --- /dev/null +++ b/solution/services/backend/integrations/yandexai/generators/ad_text.py @@ -0,0 +1,64 @@ +# ruff: noqa: E501, W291 +import logging + +from django.conf import settings +from yandex_cloud_ml_sdk import YCloudML +from yandex_cloud_ml_sdk.exceptions import YCloudMLError + +logger = logging.getLogger(__name__) + +AD_PROMPT_TEMPLATE = """ +Сгенерируй креативный рекламный текст для рекламодателя с именем: "{advertiser_name}", +название рекламной кампании: "{ad_title}". + +Требования: +1. Текст должен быть максимально привлекательным и продающим +2. Использовать современные маркетинговые приемы +3. Включить призыв к действию +4. Соблюдать структуру: заголовок - основной текст - заключение +5. Длина: 3-5 коротких предложений +6. Ответ должен содержать только текст рекламы без дополнительных комментариев +7. Весь текст должен быть на одной строчке + +Пример хорошего текста: +"Запустите свой бизнес в космос с {{advertiser_name}}! Кампания "{{ad_title}}" предлагает +уникальные решения для цифрового продвижения. Присоединяйтесь к лидерам рынка - получите +персональную консультацию сегодня!" +""".strip() + + +class YandexAIAdTextGenerator: + def __init__(self) -> None: + self.sdk = YCloudML( + folder_id=settings.YANDEX_CLOUD_FOLDER_ID, + auth=settings.YANDEX_CLOUD_API_KEY, + ) + + def generate_ad_text( + self, advertiser_name: str, ad_title: str + ) -> str | None: + try: + prompt = AD_PROMPT_TEMPLATE.format( + advertiser_name=advertiser_name, ad_title=ad_title + ) + + promise = ( + self.sdk.models.completions( + "yandexgpt-lite", model_version="latest" + ) + .configure(max_tokens=400, temperature=0.9) + .run_deferred([{"role": "system", "text": prompt}]) + ) + + result = promise.wait() + logger.debug("Generated ad text: %s", result) + + return self._clean_response(result.alternatives[0].text) + + except YCloudMLError: + return None + + def _clean_response(self, text: str) -> str: + cleaned = text.strip() + cleaned = cleaned.replace('"', "") + return " ".join(cleaned.splitlines()) diff --git a/solution/services/backend/integrations/yandexai/healthcheck.py b/solution/services/backend/integrations/yandexai/healthcheck.py new file mode 100644 index 0000000..3f4659b --- /dev/null +++ b/solution/services/backend/integrations/yandexai/healthcheck.py @@ -0,0 +1,25 @@ +from django.conf import settings +from health_check.backends import BaseHealthCheckBackend +from yandex_cloud_ml_sdk import YCloudML +from yandex_cloud_ml_sdk.exceptions import YCloudMLError + + +class YandexAIHealthCheck(BaseHealthCheckBackend): + critical_service = False + + def check_status(self) -> None: + try: + sdk = YCloudML( + folder_id=settings.YANDEX_CLOUD_FOLDER_ID, + auth=settings.YANDEX_CLOUD_API_KEY, + ) + result = sdk.models.completions( + "yandexgpt-lite", model_version="latest" + ).tokenize("ping") + if not result: + self.add_error("YandexAI API is unaccessible") + except YCloudMLError: + self.add_error("YandexAI API is unaccessible") + + def identifier(self) -> str: + return self.__class__.__name__ diff --git a/solution/services/backend/integrations/yandexai/moderation.py b/solution/services/backend/integrations/yandexai/moderation.py new file mode 100644 index 0000000..1ccd3fc --- /dev/null +++ b/solution/services/backend/integrations/yandexai/moderation.py @@ -0,0 +1,57 @@ +# ruff: noqa: E501, W291 +import logging + +from django.conf import settings +from yandex_cloud_ml_sdk import YCloudML +from yandex_cloud_ml_sdk.exceptions import YCloudMLError + +logger = logging.getLogger(__name__) + +DEFAULT_INVALID_SIGNAL = ( + "В интернете есть много сайтов с информацией на эту тему. " + "[Посмотрите, что нашлось в поиске](https://ya.ru)" +).lower() + +MODERATION_PROMPT = """ +Ты — строгий AI-модератор контента. Анализируй текст ПО ВСЕМ указанным критериям. +Если ЛЮБОЙ из критериев нарушен — верни true. Только если ВСЕ критерии соблюдены — верни false. + +Критерии нарушений (true): +1. Нецензурная лексика: мат, эвфемизмы, оскорбительные выражения +2. Угрозы: прямые/косвенные угрозы жизни, шантаж, буллинг +3. Дискриминация: расизм, сексизм, ксенофобия, гомофобия +""".strip() + + +class YandexAIModerator: + def __init__(self) -> None: + self.sdk = YCloudML( + folder_id=settings.YANDEX_CLOUD_FOLDER_ID, + auth=settings.YANDEX_CLOUD_API_KEY, + ) + + def get_moderation_verdict(self, text: str) -> bool: + try: + promise = ( + self.sdk.models.completions( + "yandexgpt-lite", model_version="latest" + ) + .configure(max_tokens=200, temperature=0.1) + .run_deferred( + [ + {"role": "system", "text": MODERATION_PROMPT}, + {"role": "user", "text": text}, + ] + ) + ) + result = promise.wait() + logger.debug("Moderation API response: %s", result) + + return self._normalize_response(result.alternatives[0].text) + + except YCloudMLError: + return False + + def _normalize_response(self, text: str) -> bool: + clean_verdict = text.strip().lower().split("\n")[0] + return clean_verdict in ("true", DEFAULT_INVALID_SIGNAL)