@@ -17,6 +17,7 @@ from ml_model.services.logoai import Logoai from ml_model.services.midjourney import Midjourney from ml_model.services.mistral import Mistral from ml_model.services.musicgen import Musicgen +from ml_model.services.perplexity import Perplexity from ml_model.services.pulid import Pulid from ml_model.services.qwen import Qwen from ml_model.services.recraft import Recraft @@ -1,10 +1,21 @@ +import base64 import time -from _decimal import Decimal from datetime import timedelta +from decimal import Decimal +from io import BytesIO +from typing import Iterator, Any + +import filetype +from PIL import Image +from django.db.models.fields.files import FieldFile from messages.models import Message +from ml_model.models import ModelCategory, ModelVersion, ModelInput, ModelParameter from ml_model.services.base import SimpleService -from ml_model.tasks import claude_run +from ml_model.tasks import openrouter_run +from tools.chats.models import Chat +from tools.copywrite.models import Copywrite +from tools.public_api.models import APIStore class Claude(SimpleService): @@ -13,25 +24,27 @@ class Claude(SimpleService): contains abstract method make, which makes a generation """ - TOKEN_PAYMENT_RULES = { - 'claude-instant-1.2': Decimal(0.6), - 'claude-2.1': Decimal(2.572), - } - def __init__(self, store): - super().__init__(store) + TOKENS_COST = { + 'claude-3.7-sonnet:thinking': {'input': Decimal('3000'), 'output': Decimal('3000'), 'input_imgs': Decimal('960')}, # 1M tokens + } - def calculate_price(self, model_name: str, usage: dict[str, int]) -> Decimal: + def calculate_price(self, version: str, input_tokens: int, output_tokens: int, image: FieldFile) -> Decimal: + price_map = self.TOKENS_COST[version.split('/')[1]] price = ( - usage['input_tokens'] * self.TOKEN_PAYMENT_RULES[model_name] - + usage['output_tokens'] * self.TOKEN_PAYMENT_RULES[model_name] + input_tokens * price_map['input'] / 1_000_000 + + output_tokens * price_map['output'] / 1_000_000 ) + if image: + price += price_map['input_imgs'] / 1_000 return price.quantize(Decimal('0.1'), rounding='ROUND_UP') - def save_results(self, r: str, t: timedelta, save: bool = True) -> list[Message]: + def save_results( + self, content: Iterator[Any], t: timedelta, save: bool = True + ) -> list[Message]: msgs = [ Message( - content=r, + content=content, content_object=self.store, elapsed_time=t, ) @@ -40,20 +53,84 @@ class Claude(SimpleService): return Message.objects.bulk_create(msgs) return msgs + def make(self, input_message: Message, save: bool = True) -> list[Message]: - callback_data = dict( - { - 'messages': [{'role': 'user', 'content': input_message.content}], - **input_message.info, - } - ) start_time = time.time() - result = claude_run(callback_data) + version = f'anthropic/{input_message.info.pop('version', 'claude-3.7-sonnet:thinking')}' + callback_data = { + 'provider': { + 'order': ['Anthropic'] + }, + **input_message.info + } + messages = self.get_chat_history() + image = input_message.file + if image: + kind = filetype.guess(image.read(20)) + mime = kind.mime if kind else 'application/octet-stream' + normalized_image = Image.open(image) + format = 'jpeg' if kind.extension == 'jpg' else kind.extension + buf = BytesIO() + normalized_image.save(buf, format=format) + image_url = ( + f'data:{mime};base64,{base64.b64encode(buf.getvalue()).decode("utf-8")}' + ) + buf.close() + messages[-1]['content'] = [ + { + 'type': 'text', + 'text': input_message.content + }, + { + 'type': 'image_url', + 'image_url': { + 'url': image_url + } + } + ] + result = openrouter_run(version, messages, callback_data, self.title) process_time = timedelta(seconds=(time.time() - start_time)) - msgs = self.save_results(result['content']['text'], process_time, save) self.handle_invoice( - self.neuron_model, - model_name=input_message['model'], - usage=result['usage'], + input_message.content_object.model, + version=version, + input_tokens=result[1], + output_tokens=result[2], + image=image ) + msgs = self.save_results(result[0], process_time) return msgs + + def get_chat_history(self, message_limit: int = 10, max_character_limit: int = 1500): + if isinstance(self.store, Chat): + air_messages = list( + reversed( + Message.objects.filter( + chats_chats_messages=self.store, is_deleted=False, is_sent=True + ).order_by('-created_at')[:message_limit] + ) + ) + elif isinstance(self.store, APIStore): + air_messages = [] + elif isinstance(self.store, Copywrite): + air_messages = list( + reversed( + Message.objects.filter( + copywrite_copywrites_messages=self.store, + is_deleted=False, + is_sent=True, + ).order_by('-created_at')[:message_limit] + ) + ) + memory = [] + for msg in air_messages: + content = msg.content or '' + if msg.from_model: + memory.append({"role": "assistant", "content": content}) + else: + memory.append({"role": "user", "content": content}) + character_length = sum(len(content['content']) for content in memory) + while character_length > max_character_limit: + memory.pop(0) + character_length = sum(len(content['content']) for content in memory) + + return memory \ No newline at end of file @@ -3,10 +3,11 @@ import time from datetime import timedelta from decimal import Decimal from io import BytesIO -from typing import Any, Iterator +from typing import Any, Iterator, Dict import filetype from PIL import Image +from django.db.models.fields.files import FieldFile from messages.models import Message from ml_model.services.base import SimpleService @@ -24,19 +25,20 @@ class Grok(SimpleService): TOKENS_COST = { 'grok-2-vision-1212': { - 'input': Decimal('400'), + 'input': Decimal('2000'), 'output': Decimal('2000'), 'input_imgs': Decimal('720'), }, # 1M tokens and 1K imgs } - def calculate_price(self, version: str, input_tokens: int, output_tokens: int) -> Decimal: + def calculate_price(self, version: str, input_tokens: int, output_tokens: int, image: FieldFile) -> Decimal: price_map = self.TOKENS_COST[version.split('/')[1]] price = ( input_tokens * price_map['input'] / 1_000_000 + output_tokens * price_map['output'] / 1_000_000 - + price_map['input_imgs'] / 1_000 ) + if image: + price += price_map['input_imgs'] / 1_000 return price.quantize(Decimal('0.1'), rounding='ROUND_UP') def save_results(self, content: Iterator[Any], t: timedelta, save: bool = True) -> list[Message]: @@ -77,11 +79,12 @@ class Grok(SimpleService): version=version, input_tokens=result[1], output_tokens=result[2], + image=image ) msgs = self.save_results(result[0], process_time) return msgs - def get_chat_history(self, message_limit: int = 10, max_character_limit: int = 1500): + def get_chat_history(self, message_limit: int = 10, max_character_limit: int = 1500) -> list[Dict]: if isinstance(self.store, Chat): air_messages = list( reversed( @@ -0,0 +1,103 @@ +import time +from datetime import timedelta +from decimal import Decimal +from typing import Iterator, Any, Dict + +from messages.models import Message +from ml_model.models import ModelCategory, ModelVersion, ModelInput, ModelParameter +from ml_model.services.base import SimpleService +from ml_model.tasks import openrouter_run +from tools.chats.models import Chat +from tools.copywrite.models import Copywrite +from tools.public_api.models import APIStore + + +class Perplexity(SimpleService): + """ + Perplexity Service + contains abstract method make, which makes a generation + """ + + TOKENS_COST = { + 'sonar': {'input': Decimal('200'), 'output': Decimal('200')}, # 1M tokens + } + + def calculate_price( + self, version: str, input_tokens: int, output_tokens: int + ) -> Decimal: + price_map = self.TOKENS_COST[version.split('/')[1]] + price = ( + input_tokens * price_map['input'] / 1_000_000 + + output_tokens * price_map['output'] / 1_000_000 + ) + return price.quantize(Decimal('0.1'), rounding='ROUND_UP') + + def save_results( + self, content: Iterator[Any], t: timedelta, save: bool = True + ) -> list[Message]: + msgs = [ + Message( + content=content, + content_object=self.store, + elapsed_time=t, + ) + ] + 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() + version = f'perplexity/{input_message.info.pop('version', 'sonar')}' + callback_data = { + 'provider': { + 'order': ['Perplexity'] + }, + **input_message.info + } + messages = self.get_chat_history() + result = openrouter_run(version, messages, callback_data, self.title) + process_time = timedelta(seconds=(time.time() - start_time)) + self.handle_invoice( + input_message.content_object.model, + version=version, + input_tokens=result[1], + output_tokens=result[2] + ) + msgs = self.save_results(result[0], process_time) + return msgs + + def get_chat_history(self, message_limit: int = 10, max_character_limit: int = 1500) -> list[Dict]: + if isinstance(self.store, Chat): + air_messages = list( + reversed( + Message.objects.filter( + chats_chats_messages=self.store, is_deleted=False, is_sent=True + ).order_by('-created_at')[:message_limit] + ) + ) + elif isinstance(self.store, APIStore): + air_messages = [] + elif isinstance(self.store, Copywrite): + air_messages = list( + reversed( + Message.objects.filter( + copywrite_copywrites_messages=self.store, + is_deleted=False, + is_sent=True, + ).order_by('-created_at')[:message_limit] + ) + ) + memory = [] + for msg in air_messages: + content = msg.content or '' + if msg.from_model: + memory.append({"role": "assistant", "content": content}) + else: + memory.append({"role": "user", "content": content}) + character_length = sum(len(content['content']) for content in memory) + while character_length > max_character_limit: + memory.pop(0) + character_length = sum(len(content['content']) for content in memory) + + return memory @@ -1,7 +1,7 @@ import time from datetime import timedelta from decimal import Decimal -from typing import Any, Iterator +from typing import Any, Iterator, Dict from messages.models import Message from ml_model.services.base import SimpleService @@ -18,7 +18,7 @@ class Qwen(SimpleService): """ TOKENS_COST = { - 'qwq-32b': {'input': Decimal('24'), 'output': Decimal('36')}, # 1M tokens + 'qwq-32b': {'input': Decimal('36'), 'output': Decimal('36')}, # 1M tokens 'qwq-32b:free': {'input': Decimal('0'), 'output': Decimal('0')}, # 1M tokens } @@ -57,7 +57,7 @@ class Qwen(SimpleService): msgs = self.save_results(result[0], process_time) return msgs - def get_chat_history(self, message_limit: int = 10, max_character_limit: int = 1500): + def get_chat_history(self, message_limit: int = 10, max_character_limit: int = 1500) -> list[Dict]: if isinstance(self.store, Chat): air_messages = list( reversed( @@ -117,6 +117,7 @@ def openrouter_run(version: str, messages: list, callback_data: dict, model_name base_url='https://openrouter.ai/api/v1', headers={'Authorization': f'Bearer {settings.OPENROUTER_API_KEY}'}, proxy=f'{proxy.protocol}://{proxy.address}', + timeout=600, ) as client: resp = client.post( 'chat/completions',