@@ -325,6 +325,8 @@ UPSCALE_MULTIPLIER_HOST = env.str('UPSCALE_MULTIPLIER_HOST', 'packet:8080') # FILES ImageFile.LOAD_TRUNCATED_IMAGES = True +DATA_UPLOAD_MAX_MEMORY_SIZE = env.int('DATA_UPLOAD_MAX_MEMORY_SIZE', 5) << 20 + # Payments YOOKASSA_ACCOUNT_ID = env.str('YOOKASSA_ACCOUNT_ID', default='defaultapikey') YOOKASSA_SECRET_KEY = env.str('YOOKASSA_SECRET_KEY', default='defaultapikey') @@ -4,7 +4,7 @@ from typing import Literal from django.conf import settings from django.conf.urls.static import static from django.contrib import admin -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ObjectDoesNotExist, RequestDataTooBig from django.urls import include, path from django.utils.translation import gettext_lazy as _ from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView @@ -79,6 +79,14 @@ def object_does_not_exists_error_handler(request, exc: ObjectDoesNotExist): return api.create_response(request, {'message': _('Requested object does not exists')}, status=404) +@compatibility_api.exception_handler(RequestDataTooBig) +@api.exception_handler(RequestDataTooBig) +def request_data_too_big_error_handler(request, exc: RequestDataTooBig): + return api.create_response( + request, {'detail': _('Data size limit exceeded. Please reduce the size')}, status=413 + ) + + @api.exception_handler(InvalidToken) def invalid_token_error_handler(request, exc: InvalidToken): return api.create_response(request, {'message': _('Token is invalid')}, status=401) @@ -1178,6 +1178,10 @@ msgstr "Списания" msgid "Amount" msgstr "Количество" +#: lib/middleware.py:40 +msgid "Data size limit exceeded. Please reduce the size" +msgstr "Превышен лимит размера данных. Уменьшите размер" + #: payments/models/payment.py:41 msgid "Plan" msgstr "План" @@ -85,7 +85,7 @@ class OpenrouterAdapter: ) continue - if not input_tokens or not output_tokens: + if not input_tokens and not output_tokens: logger.error(f'Opernrouter failed get data about tokens for model {model_name}') input_tokens, output_tokens = cls._fallback_tokenize( model_name, messages, content + reasoning @@ -2,6 +2,7 @@ from ml_model.services.chatgpt import Chatgpt from ml_model.services.chatgpt_5 import Chatgpt_5 from ml_model.services.chatgpt_5_4 import Chatgpt_5_4 from ml_model.services.chatgpt_5_5 import Chatgpt_5_5 +from ml_model.services.chatgpt_5_6 import Chatgpt_5_6 from ml_model.services.claude import Claude from ml_model.services.codellama import Codellama from ml_model.services.dalle import Dalle @@ -22,6 +22,7 @@ from ml_model.exceptions import ( CorruptedFileError, FileExtensionNotSupported, InvalidParameterError, + ModelVersionNotAvailable, PaidPlanRequiredError, ) from ml_model.services import Chatgpt @@ -53,6 +54,8 @@ class Chatgpt_5_5(Chatgpt, StreamSimpleService, OpenAIStreamMixin): }, } + BASE_VERSION = 'gpt-5.5' + TOKEN_LIMITS = {'gpt-5.5': 1_050_000 // 2} FORMATION_INSTRUCTIONS = ( @@ -211,7 +214,7 @@ class Chatgpt_5_5(Chatgpt, StreamSimpleService, OpenAIStreamMixin): try: for proxy in Proxy.objects.all(): - json_data, predicted_input_tokens = self._build_payload(proxy, input_message, ctx) + json_data, predicted_input_tokens = self._build_payload(proxy, input_message, ctx, include_image_tool=False) model_name = ctx['model_name'] self.logger.info( f'Predicted input tokens (responses/input_tokens) для {model_name} - {predicted_input_tokens}' @@ -261,10 +264,14 @@ class Chatgpt_5_5(Chatgpt, StreamSimpleService, OpenAIStreamMixin): proxy: Proxy, input_message: Message, ctx: dict[str, Any], + *, + include_image_tool: bool = True ) -> tuple[dict[str, Any], int]: if not ctx: - model_name = 'gpt-5.5' info = input_message.info.copy() + model_name = info.get('version', self.BASE_VERSION) + 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', '') file = input_message.file image = None @@ -361,7 +368,7 @@ class Chatgpt_5_5(Chatgpt, StreamSimpleService, OpenAIStreamMixin): 'instructions': self.FORMATION_INSTRUCTIONS, 'tools': [], } - if ctx['has_full_access']: + if ctx['has_full_access'] and include_image_tool: json_data['tools'].append( { 'type': 'image_generation', @@ -435,7 +442,7 @@ class Chatgpt_5_5(Chatgpt, StreamSimpleService, OpenAIStreamMixin): + ctx['predict_embedding_tokens'] * self.TOOLS_TOKEN_COSTS[self.EMBEDDING_MODEL_FOR_BILLING]['output'] ) - if ctx['has_full_access']: + if ctx['has_full_access'] and include_image_tool: predicted_input_price += self.TOKENS_COST[model_name]['generated_image'] max_output_tokens = max( int( @@ -0,0 +1,62 @@ +from decimal import Decimal + +from ml_model.services import Chatgpt_5_5 + + +class Chatgpt_5_6(Chatgpt_5_5): + TOKENS_COST = { + 'gpt-5.6-sol': { + 'input': Decimal('0.0025'), # $5 / 1M tokens + 'output': Decimal('0.015'), # $30 / 1M tokens + 'web_search': { + 'low': Decimal('5'), # 1 call + 'medium': Decimal('5'), # 1 call + 'high': Decimal('5'), # 1 call + }, + 'code_interpreter': Decimal('15'), # 1 call + 'generated_image': Decimal('10.2'), + }, + 'gpt-5.6-luna': { + 'input': Decimal('0.0005'), # $1 / 1M tokens + 'output': Decimal('0.003'), # $6 / 1M tokens + 'web_search': { + 'low': Decimal('5'), # 1 call + 'medium': Decimal('5'), # 1 call + 'high': Decimal('5'), # 1 call + }, + 'code_interpreter': Decimal('15'), # 1 call + 'generated_image': Decimal('10.2'), + }, + 'gpt-5.6-terra': { + 'input': Decimal('0.00125'), # $2.5 / 1M tokens + 'output': Decimal('0.0075'), # $15 / 1M tokens + 'web_search': { + 'low': Decimal('5'), # 1 call + 'medium': Decimal('5'), # 1 call + 'high': Decimal('5'), # 1 call + }, + 'code_interpreter': Decimal('15'), # 1 call + 'generated_image': Decimal('10.2'), + }, + } + + BASE_VERSION = 'gpt-5.6-luna' + + BASE_SYSTEM = ( + 'You are an analytical assistant optimized for GPT-5.6.\n' + 'Provide accurate, evidence-based, and practical answers.\n\n' + 'Guidelines:\n' + '- Never fabricate facts or technical behavior\n' + '- Distinguish facts from assumptions and uncertainty\n' + '- Use available retrieved evidence when relevant\n' + '- Prefer official documentation and primary sources\n' + '- Lead with the conclusion, then include supporting evidence and material caveats\n' + '- Mention limitations only when they affect the answer\n' + '- Avoid unnecessary repetition or speculative discussion' + ) + + TOKEN_LIMITS = { + 'gpt-5.6-sol': 1_050_000 // 2, + 'gpt-5.6-luna': 1_050_000 // 2, + 'gpt-5.6-terra': 1_050_000 // 2, + } @@ -114,4 +114,6 @@ DJANGO_RUNSERVER_HIDE_WARNING=true PYTHONWARNINGS=ignore::UserWarning:polymorphic # temporarily # SSE STREAMING -FF__STREAMING_ENABLED=True \ No newline at end of file +FF__STREAMING_ENABLED=True + +DATA_UPLOAD_MAX_MEMORY_SIZE=5 # MB \ No newline at end of file