@@ -22,6 +22,7 @@ from ml_model.exceptions import ( CorruptedFileError, FileExtensionNotSupported, InvalidParameterError, + ModelVersionNotAvailable, PaidPlanRequiredError, ) from ml_model.services import Chatgpt @@ -51,9 +52,49 @@ class Chatgpt_5_5(Chatgpt, StreamSimpleService, OpenAIStreamMixin): 'code_interpreter': Decimal('15'), # 1 call 'generated_image': Decimal('10.2'), }, + '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'), + }, } - TOKEN_LIMITS = {'gpt-5.5': 1_050_000 // 2} + BASE_VERSION = 'gpt-5.6-luna' + + TOKEN_LIMITS = { + 'gpt-5.5': 1_050_000 // 2, + 'gpt-5.6-sol': 1_050_000 // 2, + 'gpt-5.6-luna': 1_050_000 // 2, + 'gpt-5.6-terra': 1_050_000 // 2, + } FORMATION_INSTRUCTIONS = ( 'Форматирование — обязательное требование. Выполняй строго по правилам:\n\n' @@ -74,7 +115,7 @@ class Chatgpt_5_5(Chatgpt, StreamSimpleService, OpenAIStreamMixin): 'добавляй две пустые строки между абзацами и блоками для улучшения читаемости.' ) - BASE_SYSTEM = ( + BASE_5_SYSTEM = ( 'You are an advanced analytical assistant optimized for GPT-5.5 with strong reasoning, ' 'factual accuracy, and adaptive web retrieval.\n' 'Your primary goal is to provide correct, evidence-based, and practical answers with minimal hallucinations.\n\n' @@ -116,6 +157,19 @@ class Chatgpt_5_5(Chatgpt, StreamSimpleService, OpenAIStreamMixin): 'If reliable evidence is unavailable, clearly state the uncertainty instead of hallucinating details.' ) + BASE_6_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' + ) + def save_results( self, results: list[BaseMessage], @@ -211,7 +265,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 +315,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 @@ -337,7 +395,11 @@ class Chatgpt_5_5(Chatgpt, StreamSimpleService, OpenAIStreamMixin): 'is_free_plan': is_free_plan, 'is_low_balance': is_low_balance, 'has_full_access': not is_free_plan and not is_low_balance, - 'gpt_5_5_system': SystemMessage(content=self.BASE_SYSTEM), + 'base_system': SystemMessage( + content=( + self.BASE_6_SYSTEM if model_name.startswith('gpt-5.6') else self.BASE_5_SYSTEM + ) + ), } ) @@ -353,7 +415,7 @@ class Chatgpt_5_5(Chatgpt, StreamSimpleService, OpenAIStreamMixin): for msg in history_messages[1:] ] messages.insert(0, {'role': 'system', 'content': system.content}) - messages.insert(0, {'role': 'system', 'content': ctx['gpt_5_5_system'].content}) + messages.insert(0, {'role': 'system', 'content': ctx['base_system'].content}) messages.insert(0, {'role': 'system', 'content': ctx['user_system_prompt']}) json_data = { 'model': model_name, @@ -361,7 +423,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 +497,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(