@@ -7,6 +7,7 @@ from typing import Any, Generator, TypeAlias, TypedDict import httpx from backend import settings +from messages.services.message_service import MessageService from ml_model.exceptions import ( FileExtensionNotSupported, GenerationException, @@ -132,7 +133,6 @@ class BytedanceModelArkAdapter: return str(error.get('code', '')) == 'InputTextSensitiveContentDetected' - @classmethod def _extract_chat_answer( cls, @@ -145,11 +145,14 @@ class BytedanceModelArkAdapter: for choice in choices if choice.get('finish_reason') in (BytedanceFinishReason.STOP, BytedanceFinishReason.LENGTH) and choice.get('message') - and choice['message'].get('content') is not None + and ( + choice['message'].get('content') is not None + or choice['message'].get('reasoning_content') is not None + ) ] if stop_choices: - content = ','.join(str(choice['message']['content']) for choice in stop_choices) - # TODO: включить после разделения reasoning и content в хранении + content = ','.join(choice['message'].get('content') or '' for choice in stop_choices) + reasoning = '' if include_reasoning: reasoning = ','.join( str(reasoning) @@ -157,11 +160,7 @@ class BytedanceModelArkAdapter: if choice.get('message') and (reasoning := choice['message'].get('reasoning_content')) is not None ) - if reasoning and content: - return f'**Рассуждение:**\n\n{reasoning}\n\n**Основная мысль:**\n\n{content}' - if reasoning: - return reasoning - return content + return MessageService.prepare_output_message(reasoning, content) cls._raise_by_error_payload(data, choices) @@ -10,23 +10,35 @@ import requests from django.core.files import File from messages.models import Message +from ml_model.adapters.bytedance_model_ark import BytedanceContentType from ml_model.services.base import SimpleService -from ml_model.tasks import replicate_run +from ml_model.tasks import bytedance_model_ark_run, replicate_run class Reve(SimpleService): - PRICE = { - 'create': Decimal('12.5'), - 'edit-fast': Decimal('5'), - } + # Reve временно не работает на репликейте. Временно используем сидрим + TEMPORARY_PROVIDER_MODEL = 'seedream-5-0-260128' + + PRICE = Decimal('25') + # PRICE = { + # 'create': Decimal('12.5'), + # 'edit-fast': Decimal('5'), + # } @classmethod def predict_price(cls, content: str, file_exists: bool, info: dict[str, Any]) -> Decimal | None: - type_ = 'edit-fast' if file_exists else 'create' - return cls.PRICE[type_].quantize(Decimal('0.1'), rounding='ROUND_UP') + return cls.PRICE.quantize(Decimal('0.1'), rounding='ROUND_UP') + + def calculate_price(self) -> Decimal: + return self.PRICE.quantize(Decimal('0.1'), rounding='ROUND_UP') - def calculate_price(self, type: str) -> Decimal: - return self.PRICE[type].quantize(Decimal('0.1'), rounding='ROUND_UP') + # @classmethod + # def predict_price(cls, content: str, file_exists: bool, info: dict[str, Any]) -> Decimal | None: + # type_ = 'edit-fast' if file_exists else 'create' + # return cls.PRICE[type_].quantize(Decimal('0.1'), rounding='ROUND_UP') + # + # def calculate_price(self, type: str) -> Decimal: + # return self.PRICE[type].quantize(Decimal('0.1'), rounding='ROUND_UP') def save_results( self, @@ -53,18 +65,38 @@ class Reve(SimpleService): callback_data = { 'prompt': self.translate_prompt(input_message.content), **input_message.info, + 'size': '2K', + 'watermark': False, } - type = 'create' - if input_message.file: - kind = filetype.guess(input_message.file.read(20)) - mime = kind.mime if kind else 'application/octet-stream' - input_message.file.seek(0) - image = f'data:{mime};base64,{base64.b64encode(input_message.file.read()).decode("utf-8")}' - input_message.file.close() - callback_data.update({'image': image}) - type = 'edit-fast' - image = replicate_run(f'reve/{type}', callback_data) + if image := input_message.file: + callback_data.update({'image': image.url}) + image = bytedance_model_ark_run( + self.TEMPORARY_PROVIDER_MODEL, + callback_data, + content_type=BytedanceContentType.IMAGE, + )[0] process_time = timedelta(seconds=(time.time() - start_time)) - self.handle_invoice(input_message.content_object.model, type=type) + self.handle_invoice(input_message.content_object.model) msgs = self.save_results(input_message.content, image, process_time, save) return msgs + + # def make(self, input_message: Message, save: bool = True) -> list[Message]: + # start_time = time.time() + # callback_data = { + # 'prompt': self.translate_prompt(input_message.content), + # **input_message.info, + # } + # type = 'create' + # if input_message.file: + # kind = filetype.guess(input_message.file.read(20)) + # mime = kind.mime if kind else 'application/octet-stream' + # input_message.file.seek(0) + # image = f'data:{mime};base64,{base64.b64encode(input_message.file.read()).decode("utf-8")}' + # input_message.file.close() + # callback_data.update({'image': image}) + # type = 'edit-fast' + # image = replicate_run(f'reve/{type}', callback_data) + # process_time = timedelta(seconds=(time.time() - start_time)) + # self.handle_invoice(input_message.content_object.model, type=type) + # msgs = self.save_results(input_message.content, image, process_time, save) + # return msgs @@ -20,6 +20,7 @@ from replicate.exceptions import ModelError from requests import Response from backend import settings +from messages.services.message_service import MessageService from ml_model.adapters.bytedance_model_ark import BytedanceContentType, BytedanceModelArkAdapter from ml_model.adapters.openrouter import OpenrouterAdapter from ml_model.exceptions import ( @@ -167,7 +168,12 @@ def openrouter_run(version: str, messages: list, callback_data: dict, model_name for c in data.get('choices', []) if c.get('message') and c['message'].get('content') is not None ] - if not raw_content: + raw_reasoning = ','.join( + reasoning + for choice in data.get('choices', []) + if (reasoning := choice['message'].get('reasoning')) is not None + ) + if not raw_content and not raw_reasoning: if any( c.get('native_finish_reason') == 'SAFETY_CHECK_TYPE_CSAM' for c in (data.get('choices') or []) @@ -182,22 +188,8 @@ def openrouter_run(version: str, messages: list, callback_data: dict, model_name ) raise GenerationException content = ','.join(raw_content) - reasoning = ','.join( - reasoning - for choice in data.get('choices', []) - if (reasoning := choice['message'].get('reasoning')) is not None - ) - reasoning = re.sub(r'Вывод:|Основная мысль:|Рассуждение:|\*\*', '', reasoning) - answer = reasoning - if any(m in data['model'] for m in ('google/gemini', 'x-ai/grok-4.3')) or re.match( - r'^qwen/qwen3\.(?:5|6|7)-.*$', data['model'] - ): - answer = content - elif reasoning and content: - # TODO: переделать рендеринг сообщения на Jinja 2 - answer = f'**Рассуждение:**\n\n{reasoning}\n\n**Основная мысль:**\n\n{content}' - elif content: - answer = content + reasoning = re.sub(r'Вывод:|Основная мысль:|Рассуждение:|\*\*', '', raw_reasoning) + answer = MessageService.prepare_output_message(reasoning, content) if int(data.get('choices')[0].get('error', {}).get('code', 0)) == 502: error_type = re.sub(r'["\']', '', str(data['choices'][0]['error']['message'])) if error_type == 'Overloaded':