@@ -144,13 +144,13 @@ AUTH_PASSWORD_VALIDATORS = [ 'password_validation.UserAttributeSimilarityValidator', }, { - 'NAME': 'django.contrib.auth.' 'password_validation.MinimumLengthValidator', + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { - 'NAME': 'django.contrib.auth.' 'password_validation.CommonPasswordValidator', + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { - 'NAME': 'django.contrib.auth.' 'password_validation.NumericPasswordValidator', + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] @@ -325,6 +325,7 @@ CLAUDE_API_KEY = env.str('CLAUDE_API_KEY', default='defaultapikey') GOOGLE_API_KEY = env.str('GOOGLE_API_KEY', default='defaultapikey') SERPER_API_KEY = env.str('SERPER_API_KEY', 'defaultapikey') FLUX_API_KEY = env.str('FLUX_API_KEY', 'defaultapikey') +OPENROUTER_API_KEY = env.str('OPENROUTER_API_KEY', 'defaultapikey') OPENAI_PROXY_HOST = env.str('OPENAI_PROXY_HOST', 'neuron-proxy:8080') UPSCALE_MULTIPLIER_HOST = env.str('UPSCALE_MULTIPLIER_HOST', 'packet:8080') @@ -3,6 +3,7 @@ from ml_model.services.claude import Claude from ml_model.services.codellama import Codellama from ml_model.services.dalle import Dalle from ml_model.services.deepl import Deepl +from ml_model.services.deepseek import Deepseek from ml_model.services.djourney import Djourney from ml_model.services.epicphotogasm import Epicphotogasm from ml_model.services.flux import Flux @@ -25,30 +25,23 @@ class SimpleService(ABC): self.translator = Translator() @property - @abstractmethod def title(self) -> str: ... @property - @abstractmethod - def description(self) -> str: - return '' + def description(self) -> str: ... @property - @abstractmethod def category(self) -> ModelCategory: ... @property - @abstractmethod def inputs(self) -> list[ModelInput] | list[Never]: return [] @property - @abstractmethod def versions(self) -> list[ModelVersion] | list[Never]: return [] @property - @abstractmethod def parameters(self) -> list[ModelParameter] | list[Never]: return [] @@ -0,0 +1,86 @@ +import time +from _decimal import Decimal +from datetime import timedelta +from typing import Any, Iterator + +import httpx +from django.conf import settings + +from messages.models import Message +from ml_model.services.base import SimpleService + + +class Deepseek(SimpleService): + TOKENS_COST = { + 'deepseek/deepseek-chat': { + 'input': Decimal('107.800') / 1_000_000, + 'output': Decimal('195.800') / 1_000_000, + }, + 'deepseek/deepseek-r1:free': {'input': Decimal('0'), 'output': Decimal('0')}, + 'deepseek/deepseek-r1': { + 'input': Decimal('176.0') / 1_000_000, + 'output': Decimal('528.0') / 1_000_000, + }, + } + PRICE_BIAS = Decimal('0.05') + + def calculate_price( + self, version: str, input_tokens: int, output_tokens: int + ) -> Decimal: + price_map = self.TOKENS_COST[version] + price = ( + input_tokens * price_map['input'] + + output_tokens * price_map['output'] + + self.PRICE_BIAS + ) + 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]: + info = input_message.info.copy() + version = info.pop('version') + start_time = time.time() + with httpx.Client( + base_url='https://openrouter.ai/api/v1', + headers={'Authorization': f'Bearer {settings.OPENROUTER_API_KEY}'}, + ) as client: + resp = client.post( + 'chat/completions', + json={ + 'model': version, + 'messages': [{'role': 'user', 'content': input_message.content}], + }, + ) + if ( + (data := resp.json()) + and data.get('choices') + and ( + content := ','.join( + [choice['message']['content'] for choice in data.get('choices')] + ) + ) + ): + result = content + process_time = timedelta(seconds=(time.time() - start_time)) + self.handle_invoice( + input_message.content_object.model, + version=version, + input_tokens=data['usage']['prompt_tokens'], + output_tokens=data['usage']['completion_tokens'], + ) + msgs = self.save_results(result, process_time) + return msgs + raise Exception('No answer from Deepseek, please retry later') @@ -78,6 +78,10 @@ class NeuronModel(BaseModel, OrderedModel): def settings(self) -> 'ModelSettings': return self.model_settings + @property + def blocked(self) -> bool: + return self.inputs.count() > 0 and self.settings and self.settings.is_active + def __str__(self): return self.title @@ -50,6 +50,8 @@ class NeuronModelSerializer(serializers.ModelSerializer): class NeuronModelsSerializer(serializers.ModelSerializer): + blocked = serializers.BooleanField(source='blocked') + class Meta: model = NeuronModel exclude = ('created_at', 'updated_at', 'order', 'category', 'uid')