@@ -0,0 +1,27 @@ +# Generated by Django 5.0.11 on 2025-04-01 17:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ml_model', '0048_alter_modeltag_color_alter_neuronmodel_category'), + ] + + operations = [ + migrations.RemoveField( + model_name='modelconfiguration', + name='version', + ), + migrations.AlterField( + model_name='modelversion', + name='slug', + field=models.CharField(max_length=32, verbose_name='Slug'), + ), + migrations.AlterField( + model_name='neuronmodel', + name='description', + field=models.TextField(blank=True, null=True, verbose_name='Description'), + ), + ] @@ -3,14 +3,13 @@ import time from datetime import timedelta from decimal import Decimal from io import BytesIO -from typing import Iterator, Any +from typing import Any, Iterator import filetype -from PIL import Image from django.db.models.fields.files import FieldFile +from PIL import Image 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 @@ -24,24 +23,30 @@ class Claude(SimpleService): contains abstract method make, which makes a generation """ - TOKENS_COST = { - 'claude-3.7-sonnet:thinking': {'input': Decimal('3000'), 'output': Decimal('3000'), 'input_imgs': Decimal('960')}, # 1M tokens + 'claude-3.7-sonnet:thinking': { + 'input': Decimal('3000'), + 'output': Decimal('3000'), + 'input_imgs': Decimal('960'), + }, + 'claude-3.5-haiku': { + 'input': Decimal('800'), + 'output': Decimal('800'), + }, # 1M tokens } - def calculate_price(self, version: str, input_tokens: int, output_tokens: int, image: FieldFile) -> 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 + 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, content: Iterator[Any], t: timedelta, save: bool = True - ) -> list[Message]: + def save_results(self, content: Iterator[Any], t: timedelta, save: bool = True) -> list[Message]: msgs = [ Message( content=content, @@ -53,16 +58,10 @@ class Claude(SimpleService): 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'anthropic/{input_message.info.pop('version', 'claude-3.7-sonnet:thinking')}' - callback_data = { - 'provider': { - 'order': ['Anthropic'] - }, - **input_message.info - } + 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: @@ -72,30 +71,20 @@ class Claude(SimpleService): 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")}' - ) + 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 - } - } + {'type': 'text', 'text': input_message.content}, + {'type': 'image_url', 'image_url': {'url': image_url}}, ] - result = openrouter_run(version, messages, callback_data, self.title) + result = openrouter_run(version, messages, callback_data, 'Claude') 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], - image=image + image=image, ) msgs = self.save_results(result[0], process_time) return msgs @@ -125,12 +114,12 @@ class Claude(SimpleService): for msg in air_messages: content = msg.content or '' if msg.from_model: - memory.append({"role": "assistant", "content": content}) + memory.append({'role': 'assistant', 'content': content}) else: - memory.append({"role": "user", "content": content}) + 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 + return memory @@ -177,7 +177,7 @@ class ModelSettings(models.Model): class ModelVersion(ModelDepends, OrderedModel): name = models.CharField(max_length=16, verbose_name=_('Name')) description = models.CharField(max_length=128, null=True, blank=True, verbose_name=_('Description')) - slug = models.CharField(max_length=32, unique=True, verbose_name=_('Slug')) + slug = models.CharField(max_length=32, verbose_name=_('Slug')) order_with_respect_to = 'model' @@ -358,15 +358,6 @@ class ModelConfiguration(models.Model): related_name='model_configurations', verbose_name='Модель', ) - version = models.ForeignKey( - ModelVersion, - on_delete=models.PROTECT, - to_field='slug', - null=True, - blank=True, - related_name='version_configurations', - verbose_name='Версия модели', - ) ct = models.ForeignKey(ContentType, on_delete=models.CASCADE) oid = models.UUIDField() @@ -22,7 +22,7 @@ class ModelConfigurationSchema(ModelSchema): class Meta: model = ModelConfiguration - exclude = ('ct', 'oid', 'obj', 'model', 'version') + exclude = ('ct', 'oid', 'obj', 'model') class Config: protected_namespaces = () @@ -1,5 +1,6 @@ import base64 import json +import logging # import uuid from io import BytesIO @@ -16,6 +17,8 @@ from requests import Response from backend import settings from poller.models import Proxy +logger = logging.getLogger(__name__) + @shared_task def create_d_image(payload: dict): @@ -114,33 +117,22 @@ def replicate_run(callback_url: str, payload: dict[str, Any]): def openrouter_run(version: str, messages: list, callback_data: dict, model_name: str): for proxy in Proxy.objects.all(): with httpx.Client( - base_url='https://openrouter.ai/api/v1', - headers={'Authorization': f'Bearer {settings.OPENROUTER_API_KEY}'}, - proxy=f'{proxy.protocol}://{proxy.address}', - timeout=600, + 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', - json={ - 'model': version, - 'messages': messages, - **callback_data - }, + json={'model': version, 'messages': messages, **callback_data}, ) if ( - (data := resp.json()) - and data.get('choices') - and ( - content := ','.join( - [choice['message']['content'] for choice in data.get('choices')] - ) - ) + (data := resp.json()) + and data.get('choices') + and (content := ','.join([choice['message']['content'] for choice in data.get('choices')])) ): - return ( - content, - data['usage']['prompt_tokens'], - data['usage']['completion_tokens'] - ) + return (content, data['usage']['prompt_tokens'], data['usage']['completion_tokens']) + logger.error(f'Error occured via model {model_name}. Data: {resp.content}') raise Exception(f'No answer from {model_name}, please retry later')