@@ -767,6 +767,17 @@ msgstr "Не найдено лицо на картинке. Попробуйте msgid "The input image may contain real person." msgstr "Загруженное изображение может содержать реального человека." +#: ml_model/exceptions.py:185 +msgid "not specified" +msgstr "не указана" + +#: ml_model/exceptions.py:187 +msgid "" +"Version \"%(version)s\" is not available. Available versions: " +"%(available_versions)s." +msgstr "" +"Версия «%(version)s» недоступна. Доступные версии: %(available_versions)s." + #: ml_model/models.py:18 ml_model/models.py:38 ml_model/models.py:70 #: ml_model/models.py:182 tools/media/models.py:67 msgid "Slug" @@ -33,6 +33,7 @@ from ml_model.exceptions import ( CorruptedFileError, FileExtensionNotSupported, FileUploadUnsupported, + ModelVersionNotAvailable, ) from ml_model.models import ModelConfiguration, NeuronModel from ml_model.services.EmbeddingService import EmbeddingService @@ -104,7 +105,9 @@ class Chatgpt(SimpleService): ) -> list[Message]: start_time = time.time() info = input_message.info.copy() - model_name = info.pop('version', 'gpt-4o') + model_name = info.pop('version', None) + if model_name is None or model_name not in self.TOKENS_COST: + raise ModelVersionNotAvailable(model_name, self.TOKENS_COST) user_system_prompt = info.pop('system_prompt', '') input_content = [{'type': 'text', 'text': input_message.content or ''}] file = input_message.file @@ -145,8 +148,6 @@ class Chatgpt(SimpleService): 'top_p': info.pop('top_p', 0.5), } self.llm.tiktoken_model_name = 'gpt-4' - if model_name not in self.TOKENS_COST.keys(): - raise Exception(_('No matching version found')) chat_history = self.get_chat_history(model_name=model_name) chat_history.add_message(HumanMessage(content=input_message.content)) conversation = RunnableWithMessageHistory( @@ -14,6 +14,7 @@ from ml_model.exceptions import ( ) from ml_model.models import NeuronModel from ml_model.services import Chatgpt +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.EmbeddingService import EmbeddingService from ml_model.services.FileService import FileProcessingService from poller.models import Proxy @@ -99,7 +100,9 @@ class Chatgpt_5(Chatgpt): ) -> list[Message]: start_time = time.time() info = input_message.info.copy() - model_name = info.pop('version', 'gpt-5') + model_name = info.pop('version', None) + if model_name is None or model_name not in self.TOKENS_COST: + raise ModelVersionNotAvailable(model_name, self.TOKENS_COST) user_system_prompt = info.pop('system_prompt', '') input_content = [{'type': 'text', 'text': input_message.content or ''}] file = input_message.file @@ -22,6 +22,7 @@ from ml_model.exceptions import ( ) from ml_model.models import NeuronModel from ml_model.services import Chatgpt +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.EmbeddingService import EmbeddingService from ml_model.services.FileService import FileProcessingService from poller.models import Proxy @@ -113,7 +114,9 @@ class Chatgpt_5_4(Chatgpt): ) -> list[Message]: start_time = time.time() info = input_message.info.copy() - model_name = info.pop('version', 'gpt-5.4') + model_name = info.pop('version', None) + if model_name is None or model_name not in self.TOKENS_COST: + raise ModelVersionNotAvailable(model_name, self.TOKENS_COST) user_system_prompt = info.pop('system_prompt', '') plan_info = self.store.user.payment_plan is_regular_user = self.store.user.account_type == 'regular' @@ -155,7 +155,7 @@ class Chatgpt_5_5(Chatgpt): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() info = input_message.info.copy() - model_name = info.pop('version', 'gpt-5.5') + model_name = 'gpt-5.5' user_system_prompt = info.pop('system_prompt', '') file = input_message.file image = None @@ -9,7 +9,7 @@ import filetype from PIL import Image from messages.models import Message -from ml_model.exceptions import FileExtensionNotSupported +from ml_model.exceptions import FileExtensionNotSupported, ModelVersionNotAvailable from ml_model.services.EmbeddingService import EmbeddingService from ml_model.services.FileService import FileProcessingService from ml_model.services.base import SimpleService @@ -65,7 +65,10 @@ class Claude(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = f'anthropic/{input_message.info.pop("version", "claude-sonnet-4.6")}' + version_slug = input_message.info.pop('version', None) + if version_slug is None or version_slug not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version_slug, self.TOKENS_COST) + version = f'anthropic/{version_slug}' system_prompt = input_message.info.pop('system_prompt', '') callback_data = {'provider': {'order': ['Anthropic']}, **input_message.info} messages = [ @@ -3,6 +3,7 @@ from _decimal import Decimal from datetime import timedelta from messages.models import Message +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import openrouter_run from tools.chats.models import Chat @@ -54,7 +55,9 @@ class Deepseek(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: info = input_message.info.copy() - version = info.pop('version') + version = info.pop('version', None) + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) system_prompt = info.pop('system_prompt', '') callback_data = { @@ -10,6 +10,7 @@ from messages.models import Message from ml_model.adapters.bytedance_model_ark import BytedanceContentType, BytedanceModelArkAdapter from ml_model.exceptions import FileExtensionNotSupported from ml_model.services.FileService import FileProcessingService +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import bytedance_model_ark_run from tools.chats.models import Chat @@ -72,7 +73,9 @@ class Dola_Seed(SimpleService): return msgs def make(self, input_message: Message, save: bool = True) -> list[Message]: - version = input_message.info.pop('version', 'seed-2-0-pro') + version = input_message.info.pop('version', None) + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) callback_data = { "reasoning_effort": "minimal", **input_message.info, @@ -73,7 +73,7 @@ class Flux(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = input_message.info.get('version') + version = 'flux-schnell' user_prompt = self.translate_prompt(input_message.content) callback_data = dict( { @@ -84,7 +84,7 @@ class Flux(SimpleService): } ) runner = replicate_run( - f'{self._CALLBACK_BASE}{callback_data.get("version", "flux-schnell")}', + f'{self._CALLBACK_BASE}{version}', callback_data, ) images = runner if isinstance(runner, list) else [runner] @@ -14,6 +14,7 @@ from django.core.files.images import get_image_dimensions from messages.models import Message from ml_model.exceptions import PredictionInterruptedError, RequestBlocked, GenerationException, \ FileExtensionNotSupported +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -67,7 +68,9 @@ class Flux_2(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = input_message.info.get('version', 'flux-2-pro') + version = input_message.info.get('version') + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) width = input_message.info.pop('width', 1024) height = input_message.info.pop('height', 1024) output_mp = math.ceil((width*height) / 1_000_000) @@ -70,7 +70,7 @@ class Fluxkrea(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = input_message.info.get('version') + version = 'flux-krea-dev' callback_data = dict( { 'prompt': self.translate_prompt(input_message.content), @@ -85,7 +85,7 @@ class Fluxkrea(SimpleService): input_message.file.close() callback_data.update({'image': image}) runner = replicate_run( - f'{self._CALLBACK_BASE}{callback_data.get("version", "flux-krea-dev")}', + f'{self._CALLBACK_BASE}{version}', callback_data, ) images = runner if isinstance(runner, list) else [runner] @@ -1,18 +1,17 @@ import time -import httpx - -from backend import settings -from decimal import Decimal from datetime import timedelta +from decimal import Decimal from io import BytesIO from typing import Any +import httpx from django.core.files import File +from backend import settings from messages.models import Message - -from ml_model.exceptions import ModelTimeoutError, GenerationException +from ml_model.exceptions import GenerationException, ModelTimeoutError from ml_model.services.base import SimpleService +from poller.models import Proxy class Fluxlorafast(SimpleService): @@ -43,12 +42,27 @@ class Fluxlorafast(SimpleService): ) -> list[Message]: messages: list[Message] = [] for image in images: + for proxy in Proxy.objects.all(): + client = httpx.Client( + base_url='https://queue.fal.run', + headers={'Authorization': f'Key {settings.FAL_API_KEY}'}, + timeout=600, + proxy=f'{proxy.protocol}://{proxy.address}', + ) + + try: + file = File(BytesIO(client.get(image).content), '.png') + except Exception: + continue + + break + messages.append( Message( content_object=self.store, elapsed_time=time, content=prompt, - file=File(BytesIO(httpx.get(image).content), '.png'), + file=file, ) ) if save: @@ -64,20 +78,19 @@ class Fluxlorafast(SimpleService): '4:3': 'landscape_4_3', '16:9': 'landscape_16_9', } - requests_number = 0 start_time = time.time() - version = input_message.info.get('version') + version = 'flux-lora' translated_prompt = self.translate_prompt(input_message.content) callback_data = dict( { 'prompt': f'in style of raif3_corporate Isometric illustration, ' - f'contemporary vector art style, 3/4 perspective view: {translated_prompt}', + f'contemporary vector art style, 3/4 perspective view: {translated_prompt}', 'model_version': 'fb90c17a-d410-41e7-9961-dc7c687bc627', 'image_size': sizes.get(input_message.info.get('image_size', '1:1')), 'loras': [ { 'path': 'https://v3.fal.media/files/elephant/JthCZoCdAr7' - 'LqnOiNNVCC_pytorch_lora_weights.safetensors' + 'LqnOiNNVCC_pytorch_lora_weights.safetensors' } ], 'guidance_scale': 5, @@ -85,29 +98,53 @@ class Fluxlorafast(SimpleService): 'num_images': input_message.info.get('num_images', 4), } ) - client = httpx.Client( - base_url="https://queue.fal.run", - headers={"Authorization": f"Key {settings.FAL_API_KEY}"}, - timeout=600, - ) - result = client.post( - f'fal-ai/{version}', - json={'prompt': input_message.content, **callback_data}, - ).json() - try: - while True: - status = client.get(result['status_url']).json() - if status.get('status') == 'COMPLETED': - break - requests_number += 1 - if requests_number == 271: - raise ModelTimeoutError - time.sleep(1/3) - except Exception as exc: - raise GenerationException from exc + + final_result = self._request(version, input_message, callback_data) process_time = timedelta(seconds=(time.time() - start_time)) - final_result = client.get(result['response_url']).json() + images = [img['url'] for img in final_result['images']] self.handle_invoice(input_message.content_object.model, input_message=input_message, version=version) msgs = self.save_results(input_message.content, images, process_time, save) return msgs + + def _request(self, version: str, input_message: Message, callback_data: dict) -> dict: + proxies = Proxy.objects.all() + for proxy in proxies: + requests_number = 0 + client = httpx.Client( + base_url='https://queue.fal.run', + headers={'Authorization': f'Key {settings.FAL_API_KEY}'}, + timeout=600, + proxy=f'{proxy.protocol}://{proxy.address}', + ) + result = client.post( + f'fal-ai/{version}', + json={'prompt': input_message.content, **callback_data}, + ).json() + + is_success = False + + while True: + if requests_number == 271 / len(proxies): + break + + try: + status = client.get(result['status_url']).json() + except Exception: + requests_number += 1 + continue + if status.get('status') == 'COMPLETED': + is_success = True + break + + time.sleep(1 / 3) + + if is_success: + break + + if not is_success: + raise GenerationException from ModelTimeoutError + + final_result = client.get(result['response_url']).json() + + return final_result @@ -14,6 +14,7 @@ from ml_model.models import ( ModelInput, NeuronModel, ) +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -84,6 +85,8 @@ class Fluxproultra(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() version = input_message.info.get('version') + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) callback_data = dict( { 'prompt': self.translate_prompt(input_message.content), @@ -9,7 +9,7 @@ from django.db.models.fields.files import FieldFile from PIL import Image from messages.models import Message -from ml_model.exceptions import FileExtensionNotSupported +from ml_model.exceptions import FileExtensionNotSupported, ModelVersionNotAvailable from ml_model.services.EmbeddingService import EmbeddingService from ml_model.services.FileService import FileProcessingService from ml_model.services.base import SimpleService @@ -94,14 +94,17 @@ class Gemini(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: info = input_message.info.copy() - version = info.pop('version') + version_slug = info.pop('version', None) + if version_slug is None or version_slug not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version_slug, self.TOKENS_COST) + version = f'google/{version_slug}' callback_data = { 'provider': {'order': ['Google AI Studio']}, **input_message.info, } messages = self.get_chat_history() messages.append({'role': 'user', 'content': input_message.content}) - if version == 'google/gemini-3-flash-preview': + if version_slug == 'gemini-3-flash-preview': messages.insert( 0, { @@ -12,6 +12,7 @@ from messages.models import Message from ml_model.exceptions import CorruptedFileError, FileExtensionNotSupported from ml_model.services.EmbeddingService import EmbeddingService from ml_model.services.FileService import FileProcessingService +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import openrouter_run from poller.models import Proxy @@ -66,7 +67,10 @@ class Gemini_3_1(SimpleService): return msgs def make(self, input_message: Message, save: bool = True) -> list[Message]: - version = input_message.info.get('version', 'gemini-3.1-pro-preview:online') + version_slug = input_message.info.get('version') + if version_slug is None or version_slug not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version_slug, self.TOKENS_COST) + model_slug = f'google/{version_slug}:online' callback_data = { 'provider': {'order': ['Google AI Studio']}, **input_message.info, @@ -135,11 +139,11 @@ class Gemini_3_1(SimpleService): else: raise FileExtensionNotSupported(supported_extensions) start_time = time.time() - result = openrouter_run(f'google/{version}:online', messages, callback_data, 'Gemini 3.1') + result = openrouter_run(model_slug, messages, callback_data, 'Gemini 3.1') process_time = timedelta(seconds=(time.time() - start_time)) self.handle_invoice( input_message.content_object.model, - version=version, + version=version_slug, input_tokens=result[1], output_tokens=result[2], embedding_tokens=embedding_tokens, @@ -44,7 +44,7 @@ class Glm_4_7(SimpleService): return msgs def make(self, input_message: Message, save: bool = True) -> list[Message]: - version = input_message.info.pop('version', 'glm-4-7-251222') + version = 'glm-4-7-251222' callback_data = { "reasoning_effort": "minimal", **input_message.info, @@ -9,7 +9,7 @@ from pathlib import Path import filetype from PIL import Image from ml_model.services.FileService import FileProcessingService -from ml_model.exceptions import FileExtensionNotSupported, CorruptedFileError +from ml_model.exceptions import CorruptedFileError, FileExtensionNotSupported from ml_model.services.EmbeddingService import EmbeddingService from payments.exceptions.insufficient_balance import InsufficientBalance from payments.selectors.payment_plan_selector import PaymentPlanSelector @@ -68,7 +68,7 @@ class Grok(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = f'x-ai/{input_message.info.pop("version", "grok-4.3")}' + version = 'x-ai/grok-4.3' callback_data = {**input_message.info} messages = self.get_chat_history() messages.append({'role': 'user', 'content': input_message.content}) @@ -11,6 +11,7 @@ from django.core.files import File from messages.models import Message from ml_model.exceptions import RequestBlocked, GenerationException +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -53,7 +54,9 @@ class Hailuo(SimpleService): return [msg] def make(self, input_message: Message, save: bool = True) -> list[Message]: - version = input_message.info.get('version', 'hailuo-2.3-fast') + version = input_message.info.get('version') + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) resolution = input_message.info.get('resolution', '768p') if ( (balance := PaymentPlanSelector(self.store.user).get_current_balance()) @@ -67,7 +67,7 @@ class Ideogram(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = input_message.info.get('version') + version = 'ideogram-v3-turbo' if ( input_message.info.get('style_preset', 'None') != 'None' and input_message.info.get('style_type', 'None') not in ('None', 'Auto', 'General') @@ -80,7 +80,7 @@ class Ideogram(SimpleService): } ) runner = replicate_run( - f'{self._CALLBACK_BASE}{callback_data.get("version", "ideogram-v3-turbo")}', + f'{self._CALLBACK_BASE}{version}', callback_data, ) images = runner if isinstance(runner, list) else [runner] @@ -63,7 +63,7 @@ class Leonardo(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = input_message.info.get('version', 'lucid-origin') + version = 'lucid-origin' generation_mode = input_message.info.get('generation_mode', 'standard') num_images = input_message.info.get('num_images', 1) callback_data = dict( @@ -73,7 +73,7 @@ class Leonardo(SimpleService): } ) runner = replicate_run( - f'{self._CALLBACK_BASE}{callback_data.get("version", "lucid-origin")}', + f'{self._CALLBACK_BASE}{version}', callback_data, ) images = runner if isinstance(runner, list) else [runner] @@ -10,6 +10,7 @@ from datetime import timedelta from django.db.models.fields.files import FieldFile from messages.models import Message +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import openrouter_run from tools.chats.models import Chat @@ -61,7 +62,10 @@ class Llama(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = f'meta-llama/{input_message.info.pop('version', 'llama-4-maverick')}' + version_slug = input_message.info.pop('version', None) + if version_slug is None or version_slug not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version_slug, self.TOKENS_COST) + version = f'meta-llama/{version_slug}' callback_data = {'provider': {'order': ['DeepInfra']}, **input_message.info} messages = self.get_chat_history() messages.append({'role': 'user', 'content': input_message.content}) @@ -9,6 +9,7 @@ from django.core.files import File from messages.models import Message from ml_model.exceptions import RequestBlocked, GenerationException +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run from payments.exceptions.insufficient_balance import InsufficientBalance @@ -37,7 +38,9 @@ class Lyria(SimpleService): return [msg] def make(self, input_message: Message, save: bool = True) -> list[Message]: - version = input_message.info.pop('version', 'lyria-3') + version = input_message.info.pop('version', None) + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < ( cost := self.TOKENS_COST[version] ): @@ -42,7 +42,7 @@ class Minimaxvideo(SimpleService): return [msg] def make(self, input_message: Message, save: bool = True) -> list[Message]: - version = input_message.info.pop('version', 'video-01') + version = 'video-01' if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < self.TOKENS_COST[version]: raise InsufficientBalance(balance, self.TOKENS_COST[version]) callback_data = dict({'prompt': input_message.content, **input_message.info}) @@ -54,8 +54,7 @@ class Mistral(SimpleService): return msgs def make(self, input_message: Message, save: bool = True) -> list[Message]: - info = input_message.info.copy() - version = f'mistralai/{info.pop("version")}' + version = 'mistralai/mistral-small-3.1-24b-instruct' callback_data = {'provider': {'order': ['Parasail']}, **input_message.info} messages = self.get_chat_history() messages.append({'role': 'user', 'content': input_message.content}) @@ -16,6 +16,7 @@ from ml_model.exceptions import ( ModelCouldNotInterpretPrompt, RequestBlocked, ) +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -62,7 +63,9 @@ class Nanobanana(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = input_message.info.get('version', 'nano-banana') + version = input_message.info.get('version') + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) resolution = input_message.info.get('resolution', '2K') if version == 'nano-banana-pro' else None callback_data = dict( { @@ -8,6 +8,7 @@ import httpx from django.conf import settings from messages.models import Message +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from poller.models import Proxy from tools.chats.models import Chat @@ -64,7 +65,10 @@ class Perplexity(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = f'perplexity/{input_message.info.pop("version", "sonar")}' + version_slug = input_message.info.pop('version', None) + if version_slug is None or version_slug not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version_slug, self.TOKENS_COST) + version = f'perplexity/{version_slug}' callback_data = {'provider': {'order': ['Perplexity']}, **input_message.info} messages = self.get_chat_history() messages.append({'role': 'user', 'content': input_message.content}) @@ -11,6 +11,7 @@ from django.core.files import File from messages.models import Message from ml_model.exceptions import GenerationException, RequestBlocked +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run from payments.exceptions.insufficient_balance import InsufficientBalance @@ -42,7 +43,9 @@ class Prunaai(SimpleService): return [msg] def make(self, input_message: Message, save: bool = True) -> list[Message]: - version = input_message.info.get('version', 'p-image') + version = input_message.info.get('version') + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) aspect_ratio = input_message.info.pop('aspect_ratio', 'custom') if input_message.file: version = 'p-image-edit' @@ -4,6 +4,7 @@ from decimal import Decimal from typing import Any, Dict, Iterator from messages.models import Message +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import openrouter_run from tools.chats.models import Chat @@ -49,7 +50,10 @@ class Qwen(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = f'qwen/{input_message.info.pop("version", "qwq-32b")}' + version_slug = input_message.info.pop('version', None) + if version_slug is None or version_slug not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version_slug, self.TOKENS_COST) + version = f'qwen/{version_slug}' callback_data = {'provider': {'order': ['DeepInfra']}, **input_message.info} messages = self.get_chat_history() messages.insert( @@ -4,6 +4,7 @@ from decimal import Decimal from typing import Any, Dict, Iterator from messages.models import Message +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import openrouter_run from tools.chats.models import Chat @@ -53,7 +54,10 @@ class Qwen_235B(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = f'qwen/{input_message.info.pop("version", "qwen3-235b-a22b")}' + version_slug = input_message.info.pop('version', None) + if version_slug is None or version_slug not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version_slug, self.TOKENS_COST) + version = f'qwen/{version_slug}' callback_data = {'provider': {'order': ['DeepInfra']}, **input_message.info} messages = self.get_chat_history() messages.insert( @@ -11,6 +11,7 @@ from messages.models import Message from ml_model.exceptions import FileExtensionNotSupported from ml_model.services.EmbeddingService import EmbeddingService from ml_model.services.FileService import FileProcessingService +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import openrouter_run from poller.models import Proxy @@ -62,7 +63,10 @@ class Qwen_3_6(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() - version = self.MODELS_LINKS[input_message.info.get('version', 'qwen3.6-flash')] + version_slug = input_message.info.get('version') + if version_slug is None or version_slug not in self.MODELS_LINKS: + raise ModelVersionNotAvailable(version_slug, self.MODELS_LINKS) + api_slug = self.MODELS_LINKS[version_slug] callback_data = {'max_tokens': self.MAX_OUTPUT_TOKENS, **input_message.info} messages = self.get_chat_history() messages.append({'role': 'user', 'content': input_message.content}) @@ -112,12 +116,12 @@ class Qwen_3_6(SimpleService): ] else: raise FileExtensionNotSupported(['PDF', 'DOC', 'DOCX', 'XLSX', 'JPG', 'JPEG', 'PNG', 'WEBP']) - model_slug = f'qwen/{version}:online' + model_slug = f'qwen/{api_slug}:online' result = openrouter_run(model_slug, messages, callback_data, 'Qwen 3.6') process_time = timedelta(seconds=(time.time() - start_time)) self.handle_invoice( input_message.content_object.model, - version=version, + version=api_slug, input_tokens=result[1], output_tokens=result[2], embedding_tokens=embedding_tokens, @@ -11,6 +11,7 @@ from django.core.files import File from messages.models import Message from ml_model.exceptions import RequestBlocked, GenerationException +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -44,7 +45,9 @@ class Ray(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: duration = input_message.info.get('duration', 5) - version = input_message.info.get('version', 'ray-2-720p') + version = input_message.info.get('version') + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < ( cost := self.TOKENS_COST[version] * duration ): @@ -14,6 +14,7 @@ from ml_model.models import ( ModelParameter, ModelVersion, ) +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -178,14 +179,15 @@ class Recraft(SimpleService): 'линогравюра': 'linocut', } start_time = time.time() - extension = ( - '.svg' if input_message.info.get('version', 'recraft-v3') == self.versions[1].slug else '.png' - ) + version = input_message.info.get('version') + if version is None or version not in self.payment_rules: + raise ModelVersionNotAvailable(version, self.payment_rules) + extension = '.svg' if version == self.versions[1].slug else '.png' size = self._get_size( input_message.info.pop('width', 1024), input_message.info.pop('height', 1024), ) - callback_url = f'recraft-ai/{input_message.info.get("version", "recraft-v3")}' + callback_url = f'recraft-ai/{version}' callback_data = dict( { 'prompt': ( @@ -47,7 +47,7 @@ class Runway(SimpleService): return [msg] def make(self, input_message: Message, save: bool = True) -> list[Message]: - version = input_message.info.pop('version', 'gen4-turbo') + version = 'gen4-turbo' duration = input_message.info.get('duration', 5) file = input_message.file if ( @@ -11,6 +11,7 @@ from django.core.files import File from messages.models import Message from ml_model.exceptions import RequestBlocked, GenerationException, FileExtensionNotSupported from ml_model.services.FileService import FileProcessingService +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -63,7 +64,9 @@ class Seedance(SimpleService): return [msg] def make(self, input_message: Message, save: bool = True) -> list[Message]: - version = input_message.info.pop('version', 'seedance-2.0') + version = input_message.info.pop('version', None) + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) resolution = input_message.info.get('resolution', '720p') duration = input_message.info.get('duration', 5) file = input_message.file or None @@ -16,6 +16,7 @@ from messages.models import Message from ml_model.adapters.bytedance_model_ark import BytedanceContentType from ml_model.exceptions import FileExtensionNotSupported, InvalidParameterError from ml_model.services.FileService import FileProcessingService +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import bytedance_model_ark_run @@ -100,7 +101,9 @@ class Seedance_2_Dreamina(SimpleService): return [msg] def make(self, input_message: Message, save: bool = True) -> list[Message]: - version = input_message.info.pop('version', 'dreamina-seedance-2-0') + version = input_message.info.pop('version', None) + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) resolution = input_message.info.get('resolution', '720p') duration = input_message.info.get('duration', 5) ratio = input_message.info.get('ratio', '16:9') @@ -12,6 +12,7 @@ from replicate.exceptions import ModelError from messages.models import Message from ml_model.adapters.bytedance_model_ark import BytedanceContentType from ml_model.exceptions import InvalidParameterError +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import bytedance_model_ark_run from payments.exceptions.insufficient_balance import InsufficientBalance @@ -80,7 +81,9 @@ class Seedream(SimpleService): )): raise InsufficientBalance(balance, predicted) - version = input_message.info.pop('version', 'seedream-boosted') + version = input_message.info.pop('version', None) + if version is None or version not in self.TOKEN_COST: + raise ModelVersionNotAvailable(version, self.TOKEN_COST) size = input_message.info.get('size', '2K') if version == 'seedream-4.5' and size == '3K': @@ -13,6 +13,7 @@ from django.core.files import File from messages.models import Message from ml_model.exceptions import ModelTimeoutError, RequestBlocked, UnsupportedSize +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from poller.models import Proxy @@ -50,7 +51,9 @@ class Sora(SimpleService): def make(self, input_message: "Message", save: bool = True) -> list["Message"]: if input_message.content: for proxy in Proxy.objects.all(): - version = input_message.info.pop('version', 'sora-2') + version = input_message.info.pop('version', None) + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) seconds = int(input_message.info.pop('seconds', 4)) callback_data = { 'prompt': input_message.content, @@ -11,7 +11,7 @@ from django.conf import settings from django.core.files import File from messages.models import Message -from ml_model.exceptions import RequestBlocked +from ml_model.exceptions import ModelVersionNotAvailable, RequestBlocked from ml_model.services.base import SimpleService from poller.models import Proxy @@ -76,7 +76,10 @@ class Stablediffusion(SimpleService): def make(self, input_message: Message, save: bool = True) -> list[Message]: start_time = time.time() info = input_message.info.copy() - model_name = self.MODELS_LINKS[info.get('version', 'sd3')] + version = info.get('version') + if version is None or version not in self.MODELS: + raise ModelVersionNotAvailable(version, self.MODELS) + model_name = self.MODELS_LINKS[version] translated_prompt = self.translate_prompt(input_message.content) callback_data = { 'prompt': translated_prompt, @@ -11,6 +11,7 @@ from django.core.files import File from messages.models import Message from ml_model.exceptions import RequestBlocked, GenerationException +from ml_model.exceptions import ModelVersionNotAvailable from ml_model.services.base import SimpleService from ml_model.tasks import replicate_run @@ -41,7 +42,9 @@ class Veo(SimpleService): return [msg] def make(self, input_message: Message, save: bool = True) -> list[Message]: - version = input_message.info.pop('version', 'veo-3-fast') + version = input_message.info.pop('version', None) + if version is None or version not in self.TOKENS_COST: + raise ModelVersionNotAvailable(version, self.TOKENS_COST) if (balance := PaymentPlanSelector(self.store.user).get_current_balance()) < self.TOKENS_COST[ version ]: @@ -174,3 +174,18 @@ class FaceNotFoundError(Exception): class RealPersonDetectedError(Exception): def __str__(self) -> str: return _('The input image may contain real person.') + + +class ModelVersionNotAvailable(Exception): + def __init__(self, version: str | None, available_versions: Iterable[str]) -> None: + self.version = version + self.available_versions = list(available_versions) + + def __str__(self) -> str: + version_label = self.version if self.version is not None else _('not specified') + return _( + 'Version "%(version)s" is not available. Available versions: %(available_versions)s.' + ) % { + 'version': version_label, + 'available_versions': ', '.join(self.available_versions), + } @@ -27,13 +27,14 @@ from ml_model.exceptions import ( FileTooLargeError, FileUploadUnsupported, ImageAnalysisError, - UnrecognizedFileError, + InvalidParameterError, + ModelVersionNotAvailable, PaidPlanRequiredError, PromptLengthExceeded, RequestBlocked, TemplateNotFound, TemplateUnknownException, - InvalidParameterError + UnrecognizedFileError, ) from ml_model.services.base import SimpleService from payments.exceptions.insufficient_balance import InsufficientBalance @@ -184,6 +185,7 @@ class MessagesAPIView(APIView): FileUploadUnsupported, UnrecognizedFileError, InvalidParameterError, + ModelVersionNotAvailable, ) as exc: return Response({'detail': f'{exc}'}, status=HTTP_400_BAD_REQUEST) except TemplateNotFound as exc: @@ -17,17 +17,18 @@ from ml_model.exceptions import ( FileExtensionNotSupported, FileNotProvided, FileTooLargeError, - UnrecognizedFileError, ImageAnalysisError, ImageContentNotFound, InvalidParameterError, - ModelCouldNotInterpretPrompt, InvalidStyleCombinationError, + ModelCouldNotInterpretPrompt, + ModelVersionNotAvailable, PromptLengthExceeded, + RealPersonDetectedError, RequestBlocked, ServiceHighDemandError, + UnrecognizedFileError, UnsupportedSize, - RealPersonDetectedError ) from ml_model.models import NeuronModel from ml_model.services.base import SimpleService @@ -190,6 +191,7 @@ class MediaAPIView(APIView): ModelCouldNotInterpretPrompt, InvalidStyleCombinationError, InvalidParameterError, + ModelVersionNotAvailable, PromptLengthExceeded, ExceededContextLengthError, FileExtensionNotSupported,