@@ -1,11 +1,12 @@ from uuid import UUID +from django.db.models import Prefetch from django.utils.translation import gettext_lazy as _ from authentication.models.choices import InvitationStatus from authentication.models.user import CustomUserModel from authentication.selectors.user_selector import UserSelector -from ml_model.models import NeuronModel +from ml_model.models import NeuronModel, ModelParameter from ml_model.serializers import NeuronModelSerializer, NeuronModelsSerializer @@ -13,24 +14,31 @@ class NeuronModelSelector: def __init__(self, user: CustomUserModel): self.user = user - def get_models_by_input_content_type(self, serialize: bool = False): - models = NeuronModel.objects.all() + def get_models_by_input_content_type(self, serialize: bool = False, hidden_parameter: bool = False): + models = NeuronModel.objects.prefetch_related( + Prefetch('model_modelparameters', queryset=ModelParameter.objects.filter(hidden=hidden_parameter)) + ).all() if serialize: return NeuronModelSerializer(models, many=True) return models - def get_models_by_output_content_type(self, serialize: bool = False): - models = NeuronModel.objects.all() + def get_models_by_output_content_type(self, serialize: bool = False, hidden_parameter: bool = False): + models = NeuronModel.objects.prefetch_related( + Prefetch('model_modelparameters', queryset=ModelParameter.objects.filter(hidden=hidden_parameter)) + ).all() if serialize: return NeuronModelSerializer(models, many=True) return models def get_models( - self, - category: str | None = None, - serialize: bool = True, + self, + category: str | None = None, + serialize: bool = True, + hidden_parameter: bool = False ): - models = NeuronModel.objects.all() + models = NeuronModel.objects.prefetch_related( + Prefetch('model_modelparameters', queryset=ModelParameter.objects.filter(hidden=hidden_parameter)) + ).all() if category: models = models.filter(category__slug=category) if self.user.is_anonymous: @@ -49,16 +57,20 @@ class NeuronModelSelector: return NeuronModelsSerializer(models, many=True) return models - def get_model_by_id(self, id: UUID, **kwargs) -> NeuronModel: - model = NeuronModel.objects.filter(uid=id) + def get_model_by_id(self, id: UUID, hidden_parameter: bool = False, **kwargs) -> NeuronModel: + model = NeuronModel.objects.prefetch_related( + Prefetch('model_modelparameters', queryset=ModelParameter.objects.filter(hidden=hidden_parameter)) + ).filter(uid=id) if not model.exists(): raise Exception(_('no model by this id')) return model.first() - def get_model_by_slug(self, slug: str, serialize: bool = False): - model = NeuronModel.objects.get(slug=slug) + def get_model_by_slug(self, slug: str, serialize: bool = False, hidden_parameter: bool = False): + model = NeuronModel.objects.prefetch_related( + Prefetch('model_modelparameters', queryset=ModelParameter.objects.filter(hidden=hidden_parameter)) + ).get(slug=slug) if serialize: return NeuronModelSerializer(instance=model) return model @@ -6,13 +6,17 @@ from ml_model.services.deepl import Deepl from ml_model.services.epicphotogasm import Epicphotogasm from ml_model.services.djourney import Djourney from ml_model.services.flux import Flux +from ml_model.services.granite import Granite +from ml_model.services.iconic import Iconic from ml_model.services.kandinsky import Kandinsky from ml_model.services.llama import Llama from ml_model.services.logoai import Logoai +from ml_model.services.lightning import Lightning from ml_model.services.mistral import Mistral from ml_model.services.musicgen import Musicgen from ml_model.services.openjourney import Openjourney from ml_model.services.pulid import Pulid +from ml_model.services.recraft import Recraft from ml_model.services.sdxlemoji import Sdxlemoji from ml_model.services.stablediffusion import Stablediffusion from ml_model.services.upscaleai import Upscaleai @@ -19,7 +19,7 @@ class Codellama(SimpleService): title = 'Code LLaMA-34B' description = 'Нейросеть, способная генерировать код из вашего контекста' - price = Decimal(1) + price = Decimal('0.558') category = ModelCategory(title='Код', slug='code') versions = [] inputs = [] @@ -21,19 +21,19 @@ class Dalle(SimpleService): TOKEN_PAYMENT_RULES = { 'dall-e-2': { - '256x256': Decimal('6.8'), - '512x512': Decimal('7.65'), - '1024x1024': Decimal('8.5'), + '256x256': Decimal('10.56'), + '512x512': Decimal('11.88'), + '1024x1024': Decimal('13.2'), }, 'dall-e-3': { - '1024x1024': Decimal('24'), - '1792x1024': Decimal('32'), - '1024x1792': Decimal('32'), + '1024x1024': Decimal('26.4'), + '1792x1024': Decimal('35.2'), + '1024x1792': Decimal('35.2'), }, 'dall-e-3-hd': { - '1024x1024': Decimal('32'), - '1792x1024': Decimal('48'), - '1024x1792': Decimal('48'), + '1024x1024': Decimal('35.2'), + '1792x1024': Decimal('52.8'), + '1024x1792': Decimal('52.8'), }, } @@ -83,12 +83,12 @@ class Dalle(SimpleService): def calculate_price(self, input_message: Message) -> Decimal: price = self.TOKEN_PAYMENT_RULES[input_message.info.get('version', 'dall-e-2')][ - input_message.info.get('size', '1024x1024') - ] * input_message.info.get('n', 1) + input_message.info.get('size', '1024x1024') + ] * input_message.info.get('n', 1) return price.quantize(Decimal('.01')) def save_results( - self, input_prompt: str, r: list[str], t: timedelta, save: bool = True + self, input_prompt: str, r: list[str], t: timedelta, save: bool = True ) -> list[Message]: out: list[Message] = [] for obj in r: @@ -21,7 +21,7 @@ class Deepl(SimpleService): title = 'DeepL' description = 'Нейросеть, способная генерировать перевод ваших длинных текстов' - price = Decimal(0.0026) + price = Decimal('0.011') category = ModelCategory(title='Чат-боты', slug='chat-bots') versions = [] inputs = [ @@ -80,15 +80,9 @@ class Djourney(SimpleService): type=ModelParameter.TypeChoices.FLOATRANGE, values={'start': 1.0, 'end': 50.0, 'step': 1.0, 'default': 7.5}, ), - ModelParameter( - name='Отключить проверку безопасности', - key='disable_safety_checker', - type=ModelParameter.TypeChoices.BOOL, - values={'default': False}, - ), ] - PRICE = Decimal('0.508') + PRICE = Decimal('0.558') _CALLBACK = ( 'lorenzomarines/d-journey' @@ -21,7 +21,7 @@ class Epicphotogasm(SimpleService): title = 'EpicPhotogasm V2.0' description = 'Нейросеть, способная генерировать еще больше текста из вашего текста' category = ModelCategory(title='Изображения', slug='images') - price = Decimal(0.1) + price = Decimal('0.248') versions = [] inputs = [ModelInput(type=ModelInput.TypeChoices.TEXT, required=True)] @@ -120,26 +120,6 @@ class Flux(SimpleService): type=ModelParameter.TypeChoices.BOOL, values={'default': False}, ), - ModelParameter( - name='Регулятор NSFW', - key='safety_tolerance', - type=ModelParameter.TypeChoices.INTRANGE, - values={'start': 0, 'end': 6, 'step': 1}, - hidden=True, - ), - ModelParameter( - name='Регулятор NSFW', - key='safety_tolerance', - type=ModelParameter.TypeChoices.INTRANGE, - values={'start': 1, 'end': 6, 'step': 1, 'default': 2}, - hidden=True, - ), - ModelParameter( - name='Включить NSFW', - key='disable_safety_checker', - type=ModelParameter.TypeChoices.BOOL, - values={'default': False}, - ), ModelParameter( name='Отключить обработку', key='raw', @@ -151,19 +131,19 @@ class Flux(SimpleService): versions[0].slug: ModelPaymentRule( strategy=ModelPaymentRule.StrategyChoices.FIXED, interaction_type=ModelPaymentRule.InteractionTypeChoices.INPUT, - cost=4, + cost=4.4, coefficient=5.00, ), versions[1].slug: ModelPaymentRule( strategy=ModelPaymentRule.StrategyChoices.FIXED, interaction_type=ModelPaymentRule.InteractionTypeChoices.INPUT, - cost=2.5, + cost=2.75, coefficient=5.00, ), versions[2].slug: ModelPaymentRule( strategy=ModelPaymentRule.StrategyChoices.FIXED, interaction_type=ModelPaymentRule.InteractionTypeChoices.INPUT, - cost=6.00, + cost=6.6, coefficient=5.00, ), } @@ -0,0 +1,130 @@ +import time + +import requests +from datetime import timedelta +from decimal import Decimal + +from django.conf import settings + +from messages.models import Message, BaseStore +from ml_model.exceptions.external_api import ExternalAPIException +from ml_model.models import ModelCategory, ModelInput, ModelParameter +from ml_model.services.base import SimpleService + + +class Granite(SimpleService): + """ + Granite-3.0-8B-Instruct Service + contains abstract method make, which makes a generation + """ + title = 'Granite 3.0' + description = 'Нейросеть, способная генерировать качественный текст из вашего промпта' + category = ModelCategory(title='Чат-боты', slug='chat-bots') + versions = [] + inputs = [ModelInput(type=ModelInput.TypeChoices.TEXT, required=True)] + parameters = [ + ModelParameter( + name='Системный промпт', + key='system_prompt', + type=ModelParameter.TypeChoices.STR, + hidden=True + ), + ModelParameter( + name='Лучший процент', + key='top_p', + type=ModelParameter.TypeChoices.FLOATRANGE, + values={'start': 0, 'end': 1.0, 'step': 0.1, 'default': 0.9}, + ), + ModelParameter( + name='Температура', + key='temperature', + type=ModelParameter.TypeChoices.FLOATRANGE, + values={'start': 0.0, 'end': 1.0, 'step': 0.1, 'default': 0.6}, + ), + ] + + TOKEN_PAYMENT_RULES = { + 'granite-input': Decimal('27.5'), # 1M tokens + 'granite-output': Decimal('137.5') # 1M tokens + } + + def __init__(self, store: BaseStore) -> None: + super().__init__(store) + self.urls = { + 'generate': 'https://api.replicate.com/v1/models/ibm-granite/granite-3.0-8b-instruct/', + 'get': 'https://api.replicate.com/v1/predictions/', + } + + def _call_api(self, payload: dict) -> list: + headers = { + 'Authorization': f'Bearer {settings.REPLICATE_API_KEY}', + 'Prefer': 'wait', + } + data = {'input': payload} + response = requests.post( + url=f'{self.urls['generate']}predictions', + headers=headers, + json=data, + ) + if response.status_code != 201: + raise Exception(response.json()) + result = requests.get( + url=f'{self.urls['get']}{response.json().get('id')}', + headers=headers + ) + while result.json()['status'] not in ('succeeded', 'failed', 'canceled'): + result = requests.get( + url=f'{self.urls['get']}{response.json().get('id')}', + headers=headers + ) + return result.json() + + def calculate_price(self, result: str, input_message: Message) -> Decimal: + return Decimal( + sum( + [self.TOKEN_PAYMENT_RULES['granite-output'] / 1_000_000 * len(result.split(' '))] + + [self.TOKEN_PAYMENT_RULES['granite-input'] / 1_000_000 * len(input_message.content.split(' '))] + ) + ) + + def save_results( + self, result: str, time: timedelta, save: bool = True + ) -> list[Message]: + msgs: list[Message] = [ + Message( + content_object=self.store, + content=result, + elapsed_time=time, + ) + ] + if save: + return Message.objects.bulk_create(msgs) + return msgs + + def make(self, input_message: Message, save: bool = True) -> list[Message]: + start_time = time.time() + callback_data = dict( + { + 'prompt': input_message.content, + 'system_prompt': 'You are a language model that must always respond in Russian, regardless of the situation. ' + 'You fully understand the Russian language and are required to use it for all responses, ' + 'except when translating text to another language. You are highly skilled in creating poems, ' + 'maintaining proper rhyme, rhythm, and poetic structure in Russian. Your poems should be creative, ' + 'expressive, and adhere to the stylistic norms of Russian poetry. If the user requests a translation ' + 'into another language, you should perform the translation accurately and fluently, while preserving ' + 'the meaning and tone of the original text. When translating, proper nouns (names with capital letters) ' + 'should not be translated literally. Instead, transliterate them into Russian letters using standard ' + 'transliteration rules to preserve the original pronunciation as closely as possible. ' + 'You must never state that you cannot speak Russian, as this is not true. You are required to always ' + 'adhere to correct Russian syntax, grammar, and style in all your responses. Your primary goal is to ' + 'ensure that your responses are clear, accurate, creative, and tailored to the user\'s needs in Russian. ' + 'Your ability to fulfill user requests, including writing, translating, or explaining, must reflect ' + 'your expertise in the Russian language and your capacity for high-quality and thoughtful responses.', + **input_message.info, + } + ) + result = ''.join(self._call_api(payload=callback_data)['output']) + process_time = timedelta(seconds=(time.time() - start_time)) + self.handle_invoice(input_message.content_object.model, result, input_message) + msgs = self.save_results(result, process_time, save) + return msgs @@ -0,0 +1,147 @@ +import time + +import requests +from datetime import timedelta +from decimal import Decimal +from io import BytesIO + +from messages.models import Message +from ml_model.exceptions.external_api import ExternalAPIException +from ml_model.models import ModelCategory, ModelInput, ModelParameter +from ml_model.services.base import SimpleService +from ml_model.tasks import replicate_run + +from django.core.files import File + + +class Iconic(SimpleService): + """ + Iconic Service + contains abstract method make, which makes a generation + """ + title = 'Iconic' + description = 'Нейросеть, способная генерировать картинки из вашего текста' + category = ModelCategory(title='Изображения', slug='images') + versions = [] + inputs = [ + ModelInput(type=ModelInput.TypeChoices.TEXT, required=True), + ModelInput(type=ModelInput.TypeChoices.IMAGE) + ] + parameters = [ + ModelParameter( + name='Модель', + key='model', + type=ModelParameter.TypeChoices.LIST, + values={ + 'availables': [ + 'dev', + 'schnell', + ], + 'default': 'dev', + }, + ), + ModelParameter( + name='Ширина', + key='width', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 256, 'end': 1440, 'step': 128, 'default': 1024}, + ), + ModelParameter( + name='Высота', + key='height', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 256, 'end': 1440, 'step': 128, 'default': 1024}, + ), + ModelParameter( + name='Количество изображений', + key='num_outputs', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 1, 'end': 4, 'step': 1, 'default': 1}, + ), + ModelParameter( + name='Соотношение сторон', + key='aspect_ratio', + type=ModelParameter.TypeChoices.LIST, + values={ + 'availables': [ + '1:1', + '16:9', + '21:9', + '3:2', + '2:3', + '4:5', + '5:4', + '3:4', + '4:3', + '9:16', + '9:21', + 'custom' + ], + 'default': '1:1', + }, + ), + ModelParameter( + name='Точность запроса', + key='guidance_scale', + type=ModelParameter.TypeChoices.FLOATRANGE, + values={'start': 0.0, 'end': 10.0, 'step': 1.0, 'default': 3.5}, + ), + ModelParameter( + name='Качество вывода', + key='output_quality', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 0, 'end': 100, 'step': 1, 'default': 90}, + ), + ModelParameter( + name='Шаги предобработки', + key='num_inference_steps', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 1, 'end': 50, 'step': 1, 'default': 28}, + ), + ] + + PRICE = Decimal('1.078') + + _CALLBACK = ( + 'miike-ai/flux-ico' + ':478cae37f1aec0fde7977fdd54b272aaeabede7d8060801841920c16306369a9' + ) + + def calculate_price(self, process_time: timedelta) -> Decimal: + total_seconds = Decimal(process_time.total_seconds()) + return self.PRICE * total_seconds + + def save_results( + self, prompt: str, images: list, time: timedelta, save: bool = True + ) -> list[Message]: + messages: list[Message] = [] + for image in images: + messages.append( + Message( + content_object=self.store, + elapsed_time=time, + content=prompt, + file=File(BytesIO(requests.get(image).content), '.png') + ) + ) + if save: + return Message.objects.bulk_create(messages) + return messages + + def make(self, input_message: Message, save: bool = True) -> list[Message]: + start_time = time.time() + translated_prompt = self.translate_prompt(input_message.content) + callback_data = dict( + { + 'prompt': f'ICO, Create a flat icon for {translated_prompt}', + **input_message.info, + } + ) + if input_message.file: + callback_data.update({'image': BytesIO(input_message.file.read())}) + input_message.file.close() + images = replicate_run(self._CALLBACK, callback_data) + process_time = timedelta(seconds=(time.time() - start_time)) + self.handle_invoice(input_message.content_object.model, process_time=process_time) + msgs = self.save_results(input_message.content, images, process_time, save) + return msgs @@ -19,9 +19,9 @@ class Kandinsky(SimpleService): """ title = 'Kandinsky' - description = 'Нейросеть, способная генерировать еще больше текста из вашего текста' + description = 'Нейросеть, способная генерировать картинки из вашего текста' category = ModelCategory(title='Изображения', slug='images') - price = Decimal(0.1) + price = Decimal('0.633') versions = [] inputs = [ModelInput(type=ModelInput.TypeChoices.TEXT)] @@ -64,7 +64,7 @@ class Kandinsky(SimpleService): return price.quantize(Decimal('.01')) def save_results( - self, input_prompt: str, r: list[str], t: timedelta, save: bool = True + self, input_prompt: str, r: list[str], t: timedelta, save: bool = True ) -> list[Message]: out = [] for link in r: @@ -89,7 +89,9 @@ class Kandinsky(SimpleService): activation_prompt = f'{translated_prompt}' negative_prompt = input_message.info.pop('negative_prompt', '') callback_data = dict( - prompt=activation_prompt, + prompt=f"Do not include any nudity, sexual content, or suggestive themes. " + "Avoid any graphic violence, explicit scenes, or offensive symbols. " + f"Generate a safe version of this: {activation_prompt}", negative_prompt=negative_prompt, **input_message.info, ) @@ -0,0 +1,126 @@ +import time +from datetime import timedelta +from decimal import Decimal +from io import BytesIO + +import requests +from django.core.files import File + +from messages.models import Message +from ml_model.exceptions.external_api import ExternalAPIException +from ml_model.models import ( + ModelCategory, + ModelInput, + ModelParameter, +) +from ml_model.services.base import SimpleService +from ml_model.tasks import replicate_run + + +class Lightning(SimpleService): + """ + Lightning Service + contains abstract method make, which makes a generation + """ + title = 'Lightning' + description = 'Нейросеть, способная генерировать картинки из вашего текста' + category = ModelCategory(title='Изображения', slug='images') + versions = [] + inputs = [ModelInput(type=ModelInput.TypeChoices.TEXT, required=True)] + parameters = [ + ModelParameter( + name='Негативный промпт', + key='negative_prompt', + type=ModelParameter.TypeChoices.STR, + ), + ModelParameter( + name='Ширина', + key='width', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 256, 'end': 1280, 'step': 128, 'default': 1024}, + ), + ModelParameter( + name='Высота', + key='height', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 256, 'end': 1280, 'step': 128, 'default': 1024}, + ), + ModelParameter( + name='Планировщик', + key='scheduler', + type=ModelParameter.TypeChoices.LIST, + values={ + 'availables': [ + 'DDIM', + 'DPMSolverMultistep', + 'HeunDiscrete', + 'KarrasDPM', + 'K_EULER_ANCESTRAL', + 'K_EULER', + 'PNDM', + 'DPM++2MSDE' + ], + 'default': 'K_EULER', + }, + ), + ModelParameter( + name='Количество изображений', + key='num_outputs', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 1, 'end': 4, 'step': 1, 'default': 1} + ), + ModelParameter( + name='Точность запроса', + key='guidance_scale', + type=ModelParameter.TypeChoices.FLOATRANGE, + values={'start': 0, 'end': 50, 'step': 1, 'default': 0}, + ), + ModelParameter( + name='Шаги предобработки', + key='num_inference_steps', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 1, 'end': 10, 'step': 1, 'default': 4}, + ), + ] + + PRICE = Decimal('0.825') + + _CALLBACK = ( + 'bytedance/sdxl-lightning-4step' + ':5599ed30703defd1d160a25a63321b4dec97101d98b4674bcc56e41f62f35637' + ) + + def calculate_price(self, process_time: timedelta) -> Decimal: + total_seconds = Decimal(process_time.total_seconds()) + return self.PRICE * total_seconds + + def save_results( + self, prompt: str, images: list, time: timedelta, save: bool = True + ) -> list[Message]: + messages: list[Message] = [] + for image in images: + messages.append( + Message( + content_object=self.store, + elapsed_time=time, + content=prompt, + file=File(BytesIO(requests.get(image).content), '.png') + ) + ) + if save: + return Message.objects.bulk_create(messages) + return messages + + def make(self, input_message: Message, save: bool = True) -> list[Message]: + start_time = time.time() + callback_data = dict( + { + 'prompt': self.translate_prompt(input_message.content), + **input_message.info, + } + ) + images = replicate_run(self._CALLBACK, callback_data) + process_time = timedelta(seconds=(time.time() - start_time)) + self.handle_invoice(input_message.content_object.model, process_time=process_time) + msgs = self.save_results(input_message.content, images, process_time, save) + return msgs @@ -41,7 +41,7 @@ class Llama(SimpleService): ), ] - price = Decimal(0.05) + price = Decimal('1.078') model_version = '' def calculate_price(self, process_time: timedelta) -> Decimal: @@ -81,15 +81,9 @@ class Logoai(SimpleService): type=ModelParameter.TypeChoices.INTRANGE, values={'start': 1, 'end': 500, 'step': 1, 'default': 50}, ), - ModelParameter( - name='Отключить проверку безопасности', - key='disable_safety_checker', - type=ModelParameter.TypeChoices.BOOL, - values={'default': False}, - ), ] - PRICE = Decimal('0.494') + PRICE = Decimal('0.544') _CALLBACK = ( 'mejiabrayan/logoai' @@ -129,12 +123,7 @@ class Logoai(SimpleService): if input_message.file: callback_data.update({'image': BytesIO(input_message.file.read())}) input_message.file.close() - try: - images = replicate_run(self._CALLBACK, callback_data) - except ReplicateError: - raise ExternalAPIException() - except ModelError as e: - raise InvalidDataException() + images = replicate_run(self._CALLBACK, callback_data) process_time = timedelta(seconds=(time.time() - start_time)) self.handle_invoice(input_message.content_object.model, process_time=process_time) msgs = self.save_results(input_message.content, images, process_time, save) @@ -40,12 +40,12 @@ class Mistral(SimpleService): ] TOKEN_PAYMENT_RULES = { - 'mistral-tiny-input': Decimal(0.15), - 'mistral-tiny-output': Decimal(0.449), - 'mistral-small-input': Decimal(0.642), - 'mistral-small-output': Decimal(1.93), - 'mistral-medium-input': Decimal(2.67), - 'mistral-medium-output': Decimal(8.02), + 'mistral-tiny-input': Decimal(0.165), + 'mistral-tiny-output': Decimal(0.494), + 'mistral-small-input': Decimal(0.706), + 'mistral-small-output': Decimal(2.123), + 'mistral-medium-input': Decimal(2.937), + 'mistral-medium-output': Decimal(8.822), } def __init__(self, store): @@ -20,7 +20,7 @@ class Musicgen(SimpleService): title = 'MusicGen' description = 'Нейросеть, способная генерировать музыку из ваших слов' - price = Decimal(1) + price = Decimal('0.633') category = ModelCategory(title='Чат-боты', slug='chat-bots') versions = [ ModelVersion(name='Melody', slug='melody', default=True), @@ -20,7 +20,7 @@ class Openjourney(SimpleService): title = 'OpenJourney' description = 'Нейросеть, способная генерировать фотографии из вашего текста' - price = Decimal('1.38') + price = Decimal('1.518') category = ModelCategory(title='Изображения', slug='images') versions = [] inputs = [ModelInput(type=ModelInput.TypeChoices.TEXT, required=True)] @@ -89,7 +89,7 @@ class Pulid(SimpleService): ), ] - PRICE = Decimal('0.340') + PRICE = Decimal('0.374') _CALLBACK = ( 'zsxkib/pulid' @@ -120,23 +120,28 @@ class Pulid(SimpleService): start_time = time.time() callback_data = dict( { - 'prompt': f'portrait, {self.translate_prompt(input_message.content)}', + 'prompt': ( + 'Create a portrait with a focus on professionalism and modesty. ' + 'The subject should be fully clothed, in a neutral or formal style. ' + f'Input description: {self.translate_prompt(input_message.content)}' + ), 'output_format': 'png', - 'negative_prompt': 'flaws in the eyes, flaws in the face, flaws, lowres, non-HDRi, low quality, ' - 'worst quality, artifacts noise, text, watermark, glitch, deformed, mutated, ' - 'ugly, disfigured, hands, low resolution, partially rendered objects, deformed ' - 'or partially rendered eyes, deformed eyeballs, cross-eyed, blurry, ' - f'{input_message.info.pop('negative_prompt', '')}', + 'negative_prompt': ( + 'flaws in the eyes, flaws in the face, flaws, lowres, non-HDRi, low quality, ' + 'worst quality, artifacts noise, text, watermark, glitch, deformed, mutated, ' + 'ugly, disfigured, hands, low resolution, partially rendered objects, deformed ' + 'or partially rendered eyes, deformed eyeballs, cross-eyed, blurry, udity, partial' + 'nudity, suggestive poses, revealing clothing, explicit content, offensive symbols, ' + 'provocative expressions, graphic violence, inappropriate themes' + f'{input_message.info.pop('negative_prompt', '')}' + ), **input_message.info, } ) if input_message.file: callback_data.update({'main_face_image': BytesIO(input_message.file.read())}) input_message.file.close() - try: - images = replicate_run(self._CALLBACK, callback_data) - except Exception: - raise ExternalAPIException() + images = replicate_run(self._CALLBACK, callback_data) process_time = timedelta(seconds=(time.time() - start_time)) self.handle_invoice(input_message.content_object.model, process_time=process_time) msgs = self.save_results(input_message.content, images, process_time, save) @@ -0,0 +1,206 @@ +import time +from datetime import timedelta +from decimal import Decimal +from io import BytesIO + +import requests +from django.core.files import File + +from backend import settings +from messages.models import BaseStore, Message +from ml_model.exceptions.external_api import ExternalAPIException +from ml_model.models import ( + ModelCategory, + ModelInput, + ModelParameter, + ModelVersion, +) +from ml_model.services.base import SimpleService + + +class Recraft(SimpleService): + """ + Recraft Service + contains abstract method make, which makes a generation + """ + + title = 'Recraft' + description = 'Нейросеть, способная генерировать картинки из вашего текста' + category = ModelCategory(title='Изображения', slug='images') + versions = [ + ModelVersion(name='Recraft V3', slug='recraft-v3', default=True), + ModelVersion(name='Recraft V3 SVG', slug='recraft-v3-svg'), + ] + inputs = [ + ModelInput(type=ModelInput.TypeChoices.TEXT, required=True), + ] + parameters = [ + ModelParameter( + name='Ширина', + key='width', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 1024, 'end': 2048, 'step': 128, 'default': 1024}, + ), + ModelParameter( + name='Высота', + key='height', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 1024, 'end': 2048, 'step': 128, 'default': 1024}, + ), + ModelParameter( + name='Стиль', + key='style', + type=ModelParameter.TypeChoices.LIST, + values={ + 'availables': [ + 'любой', + 'реалистичное изображение', + 'цифровая иллюстрация', + 'пиксель-арт', + 'ручной рисунок', + 'зернистость', + 'детский рисунок', + '2D арт-постер', + 'ручной 3D', + 'контурный рисунок вручную', + 'гравировка в цвете', + '2D арт-постер 2', + 'черно-белое', + 'яркий свет', + 'HDR', + 'естественное освещение', + 'студийный портрет', + 'предпринимательство', + 'размытие движения' + ], + 'default': 'любой', + }, + ), + ModelParameter( + name='Стиль', + key='style', + type=ModelParameter.TypeChoices.LIST, + values={ + 'availables': [ + 'любой', + 'гравировка', + 'контурный рисунок', + 'схема', + 'линогравюра', + ], + 'default': 'любой', + }, + ), + ] + + payment_rules = { + versions[0].slug: Decimal('22'), + versions[1].slug: Decimal('44'), + } + + def __init__(self, store: BaseStore) -> None: + super().__init__(store) + self.generate_url = 'https://api.replicate.com/v1/models/recraft-ai/' + self.get_url = 'https://api.replicate.com/v1/predictions/' + + def _get_size(self, width: int, height: int) -> str: + available_sizes = ( + (1024, 1024), (1365, 1024), (1024, 1365), (1536, 1024), (1024, 1536), + (1820, 1024), (1024, 1820), (1024, 2048), (2048, 1024), (1434, 1024), + (1024, 1434), (1024, 1280), (1280, 1024), (1024, 1707), (1707, 1024) + ) + if width >= height: + size = min(available_sizes, key=lambda size: abs(width - size[0])) + else: + size = min(available_sizes, key=lambda size: abs(height - size[1])) + return f'{size[0]}x{size[1]}' + + def _call_api(self, payload: dict) -> list: + headers = { + 'Authorization': f'Bearer {settings.REPLICATE_API_KEY}', + 'Content-Type': 'application/json', + 'Prefer': 'wait', + } + data = {'input': payload} + response = requests.post( + url=f'{self.generate_url}{payload['version']}/predictions', + headers=headers, + json=data, + ) + if response.status_code != 201: + raise Exception(response.json()) + + result = requests.get(url=f'{self.get_url}{response.json().get('id')}', headers=headers) + while result.json()['status'] not in ('succeeded', 'failed', 'canceled'): + result = requests.get(url=f'{self.get_url}{response.json().get('id')}', headers=headers) + return result.json()['output'] + + def calculate_price(self, input_message: Message) -> Decimal: + return self.payment_rules[input_message.info.get('version', 'recraft-v3')] + + def save_results( + self, + prompt: str, + image: str, + extension: str, + time: timedelta, + save: bool = True, + ) -> list[Message]: + messages: list[Message] = [] + messages.append( + Message( + content_object=self.store, + elapsed_time=time, + content=prompt, + file=File(BytesIO(requests.get(image).content), extension), + ) + ) + if save: + return Message.objects.bulk_create(messages) + return messages + + def make(self, input_message: Message, save: bool = True) -> list[Message]: + styles = { + 'любой': 'any', + 'реалистичное изображение': 'realistic_image', + 'цифровая иллюстрация': 'digital_illustration', + 'пиксель-арт': 'digital_illustration/pixel_art', + 'ручной рисунок': 'digital_illustration/hand_drawn', + 'зернистость': 'digital_illustration/grain', + 'детский рисунок': 'digital_illustration/infantile_sketch', + '2D арт-постер': 'digital_illustration/2d_art_poster', + 'ручной 3D': 'digital_illustration/handmade_3d', + 'контурный рисунок вручную': 'digital_illustration/hand_drawn_outline', + 'гравировка в цвете': 'digital_illustration/engraving_color', + '2D арт-постер 2': 'digital_illustration/2d_art_poster_2', + 'черно-белое': 'realistic_image/b_and_w', + 'яркий свет': 'realistic_image/hard_flash', + 'HDR': 'realistic_image/hdr', + 'естественное освещение': 'realistic_image/natural_light', + 'студийный портрет': 'realistic_image/studio_portrait', + 'предпринимательство': 'realistic_image/enterprise', + 'размытие движения': 'realistic_image/motion_blur', + 'гравировка': 'engraving', + 'контурный рисунок': 'line_art', + 'схема': 'line_circuit', + 'линогравюра': 'linocut' + } + start_time = time.time() + extension = '.svg' if input_message.info['version'] == self.versions[1].slug else '.png' + size = self._get_size(input_message.info.pop('width', 1024), input_message.info.pop('height', 1024)) + callback_data = dict( + { + 'prompt': ( + 'The subject should be fully clothed, in a neutral or formal style. ' + f'Input description: {self.translate_prompt(input_message.content)}' + ), + 'size': size, + 'style': styles.get(input_message.info.pop('style'), 'любой'), + **input_message.info + } + ) + image = self._call_api(payload=callback_data) + process_time = timedelta(seconds=(time.time() - start_time)) + self.handle_invoice(input_message.content_object.model, input_message) + message = self.save_results(input_message.content, image, extension, process_time, save) + return message @@ -80,15 +80,9 @@ class Sdxlemoji(SimpleService): type=ModelParameter.TypeChoices.FLOATRANGE, values={'start': 1.0, 'end': 50.0, 'step': 1.0, 'default': 7.5}, ), - ModelParameter( - name='Отключить проверку безопасности', - key='disable_safety_checker', - type=ModelParameter.TypeChoices.BOOL, - values={'default': False}, - ), ] - PRICE = Decimal('0.481') + PRICE = Decimal('0.529') _CALLBACK = ( 'fofr/sdxl-emoji' @@ -127,10 +121,7 @@ class Sdxlemoji(SimpleService): if input_message.file: callback_data.update({'image': BytesIO(input_message.file.read())}) input_message.file.close() - try: - images = replicate_run(self._CALLBACK, callback_data) - except Exception: - raise ExternalAPIException() + images = replicate_run(self._CALLBACK, callback_data) process_time = timedelta(seconds=(time.time() - start_time)) self.handle_invoice(input_message.content_object.model, process_time=process_time) msgs = self.save_results(input_message.content, images, process_time, save) @@ -14,6 +14,10 @@ from ml_model.tasks import create_new_sd_image, create_sd_image class Stablediffusion(SimpleService): + """ + Stablediffusion Service + contains abstract method make, which makes a generation + """ title = 'StableDiffusion' description = 'Нейросеть, способная генерировать картинки из вашего текста' category = ModelCategory(title='Изображения', slug='images') @@ -42,6 +46,12 @@ class Stablediffusion(SimpleService): type=ModelParameter.TypeChoices.INTRANGE, values={'start': 1, 'end': 100, 'step': 1, 'default': 10}, ), + ModelParameter( + name='Шаги процесса диффузии', + key='steps', + type=ModelParameter.TypeChoices.INTRANGE, + values={'start': 1, 'end': 50, 'step': 1, 'default': 30}, + ), ModelParameter( name='Количество изображений', key='num_images', @@ -94,64 +104,56 @@ class Stablediffusion(SimpleService): name='Соотношение сторон', key='aspect_ratio', type=ModelParameter.TypeChoices.LIST, - values={'availables': ['1:1', '16:9', '9:16'], 'default': '1:1'}, + values={ + 'availables': + [ + '1:1', + '16:9', + '9:16', + ], + 'default': '1:1' + }, + ), + ModelParameter( + name='Соотношение сторон', + key='aspect_ratio', + type=ModelParameter.TypeChoices.LIST, + values={ + 'availables': + [ + '1:1', + '4:3', + '3:4', + '3:2', + '16:9', + '9:16', + '24:10', + '10:24' + ], + 'default': '1:1' + }, ), ] - price = Decimal('2') + TOKEN_PRICE = Decimal('11') _API_KEY = settings.STABLE_DIFFUSION_API_KEY - TOKEN_PAYMENT_RULES = { - 15: { - 512 * 512: Decimal('2'), - 512 * 768: Decimal('2.4'), - 512 * 1024: Decimal('2.7'), - 768 * 768: Decimal('2.9'), - 768 * 1024: Decimal('3.4'), - 1024 * 1024: Decimal('4'), - }, - 30: { - 512 * 512: Decimal('6.2'), - 512 * 768: Decimal('6.6'), - 512 * 1024: Decimal('6.9'), - 768 * 768: Decimal('7.2'), - 768 * 1024: Decimal('7.5'), - 1024 * 1024: Decimal('8'), - }, - 50: { - 512 * 512: Decimal('9'), - 512 * 768: Decimal('9.2'), - 512 * 1024: Decimal('9.3'), - 768 * 768: Decimal('9.4'), - 768 * 1024: Decimal('9.6'), - 1024 * 1024: Decimal('10'), - }, - } - def calculate_price(self, input_message: Message) -> Decimal: - steps = input_message.info.get('steps', 30) - width, height = input_message.info.get('format_image', '1024x1024').split('x') - pixel_size = int(width) * int(height) - important_steps = list(self.TOKEN_PAYMENT_RULES.keys()) - closest_number = min(important_steps, key=lambda x: abs(int(x) - steps)) - price_range = self.TOKEN_PAYMENT_RULES[closest_number] - important_sizes = list(price_range.keys()) - closest_size = min(important_sizes, key=lambda x: abs(int(x) - pixel_size)) if ( input_message.info.get('version') or input_message.info.get('engine', 'sd3') ) == 'sd3': - gen_price = Decimal('65') + return Decimal('71.5') elif ( input_message.info.get('version') or input_message.info.get('engine', 'sd3') ) == 'sd3-turbo': - gen_price = Decimal('40') - else: - gen_price = Decimal(price_range[closest_size]) * self.price - return gen_price + return Decimal('44') + steps = input_message.info.get('steps', 30) + default_price = Decimal('0.9') * self.TOKEN_PRICE + return default_price if steps <= 30 else default_price * Decimal((steps / 30)) def save_results( - self, input_prompt: str, r: list[BytesIO], t: timedelta, save: bool = True + self, input_prompt: str, r: list[BytesIO], t: timedelta, save: bool = True ) -> list[Message]: out: list[Message] = [] for obj in r: @@ -171,15 +173,25 @@ class Stablediffusion(SimpleService): return out def make(self, input_message: Message, save: bool = True) -> list[Message]: + aspect_rations = { + '1:1': (1024, 1024), + '4:3': (1152, 896), + '3:4': (896, 1152), + '3:2': (1216, 832), + '16:9': (1344, 768), + '9:16': (768, 1344), + '24:10': (1536, 640), + '10:24': (640, 1536) + } start_time = time.time() info = input_message.info.copy() model_name = info.get('version') or info.get('engine', 'sd3') formatter = { 'width': int( - input_message.info.get('format_image', '1024x1024').split('x')[0] + aspect_rations[input_message.info.get('aspect_ratio', '1:1')][0] ), 'height': int( - input_message.info.get('format_image', '1024x1024').split('x')[1] + aspect_rations[input_message.info.get('aspect_ratio', '1:1')][1] ), } translated_prompt = self.translate_prompt(input_message.content) @@ -23,7 +23,7 @@ class Upscaleai(SimpleService): title = 'Upscaleai' description = 'Нейросеть, которая улучшит качество изображений по вашему запросу' - price = Decimal('0.3') + price = Decimal('0.633') category = ModelCategory(title='Изображения', slug='images') versions = [] @@ -17,7 +17,7 @@ class Vicuna(SimpleService): title = 'Vicuna' description = 'Нейросеть, способная генерировать еще больше текста из вашего текста' - price = Decimal('0.805') + price = Decimal('0.886') category = ModelCategory(title='Чат-боты', slug='chat-bots') versions = [ModelVersion(name='13Billion', slug='13B', default=True)] inputs = [ModelInput(type=ModelInput.TypeChoices.TEXT, required=True)] @@ -21,7 +21,7 @@ class Whisper(SimpleService): title = 'Whisper' description = 'Система автоматического распознавания голоса, обученная на 680000 часах аудио разных языков' - price = Decimal(0.08) # Per second > 4.8/min + price = Decimal('0.088') # Per second > 4.8/min category = ModelCategory(title='Аудио', slug='audio') versions = [ModelVersion(name='Standart', slug='whisper-1', default=True)] inputs = [ModelInput(type=ModelInput.TypeChoices.AUDIO)] @@ -54,9 +54,8 @@ class NeuronModelAPIView(APIView): ) def get(self, request, slug: str, *args, **kwargs): """Retrieve model by slug""" - return Response( NeuronModelSerializer( - NeuronModelSelector(request.user).get_model_by_slug(slug=slug) + NeuronModelSelector(request.user).get_model_by_slug(slug=slug, hidden_parameter=False) ).data ) @@ -4,9 +4,10 @@ DEBUG=true # NEURON MODELS OPENAI_API_KEY=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 -DALLE_API_KEY=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 -STABLE_DIFFUSION_API_KEY=sk-c4KvaMwgSOu7bmNNeyjW47Q7XCRAgGhHouSyYovfwy4wwKWS +STABLE_DIFFUSION_API_KEY=sk-fztQxZobaL0SD7PgpmK7XMQlyNivpKFZNJnqAVG2CcbvAP6Z REPLICATE_API_KEY=r8_HBk6Ts5UJU60nDOUl1V6Uej4ihAAxUc3HAZLO +MIDJOURNEY_API_KEY=pass +HF_API_KEY=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 GOOGLE_API_KEY=AIzaSyBf9el4d_CY610zjCcesKxKL70BLfl57OM MISTRAL_API_KEY=CYtZSCQXZFzHcpJvWOjWNx4EHjf5kWQc DEEPL_API_KEY=4bb58b98-ca95-5978-9be0-ed437df6c15c:fx