@@ -82,7 +82,7 @@ class BusinessHostSelector: else: raise Exception(_("You haven't rights to access host account information")) return BusinessHostSerializer( - host, context={'worker_amount': host.accounts.count()} + host, context={'worker_amount': host.accounts.count(), 'token_cap_enabled': host.token_cap_enabled} ) def get_all_account_statistics(self) -> BusinessAccountStatisticsSerializer: @@ -187,9 +187,14 @@ class BusinessHostService: token_cap_enabled := serializer.validated_data.get('token_cap_enabled', None) ) is not None: company.token_cap_enabled = token_cap_enabled + if ( + company.token_cap_enabled + and (token_cap := serializer.validated_data.get('token_cap', None)) is not None + ): + company.token_cap = token_cap company.save() if serialize: - return BusinessHostSerializer(company) + return BusinessHostSerializer(company, context={'token_cap_enabled': company.token_cap_enabled}) return company def delete(self, request: Request): @@ -192,6 +192,7 @@ class BusinessHostUpdateSerializer(serializers.Serializer): child=serializers.EmailField(), required=False ) token_cap_enabled = serializers.BooleanField(required=False) + token_cap = serializers.DecimalField(max_digits=15, decimal_places=2, required=False) class DeletedAccountDataSerializer(serializers.Serializer): @@ -259,12 +260,21 @@ class BusinessHostSerializer(serializers.Serializer): read_only=True, source='ip_whitelist.is_enabled' ) is_log_history_enabled = serializers.BooleanField(read_only=True) + token_cap = serializers.DecimalField(max_digits=15, decimal_places=2) token_cap_emails = serializers.ListField(child=serializers.EmailField()) token_cap_enabled = serializers.BooleanField() def get_worker_amount(self, obj): return self.context.get('worker_amount') + def get_fields(self): + fields = super().get_fields() + + if not self.context.get('token_cap_enabled', False): + fields.pop('token_cap') + + return fields + class AddModelsSerializer(serializers.Serializer): models = serializers.ListField(child=serializers.CharField()) @@ -18,8 +18,8 @@ from django.core.files.uploadedfile import UploadedFile from langchain import hub from langchain.agents import AgentExecutor, create_structured_chat_agent from langchain.chains import ConversationChain -from langchain.memory import ConversationTokenBufferMemory from langchain_community.tools.google_serper import GoogleSerperResults +from langchain_core.chat_history import InMemoryChatMessageHistory from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage from langchain_core.prompts.prompt import PromptTemplate from langchain_core.runnables import RunnableWithMessageHistory @@ -47,6 +47,8 @@ from tools.copywrite.models import Copywrite from tools.public_api.models import APIStore +from backend import settings + class Chatgpt(SimpleService): """ ChatGPT Service @@ -181,26 +183,25 @@ class Chatgpt(SimpleService): chat_history = self.get_chat_history() conversation = RunnableWithMessageHistory( runnable=self.llm, - get_session_history=lambda _: self.get_chat_history().chat_memory, + get_session_history=lambda _: self.get_chat_history(), ) llm_input = HumanMessage(content=input_content) if file and not image: input_tokens = self.count_text_tokens( - [*chat_history.buffer_as_messages, llm_input, *chunks] + [*chat_history.messages, llm_input, *chunks] ) elif image: input_tokens = self.count_text_tokens([llm_input]) else: input_tokens = self.count_text_tokens( - [*chat_history.buffer_as_messages, llm_input] + [*chat_history.messages, llm_input] ) self.assert_enough_balance( input_tokens, image_size, model=self.llm.model_name ) if image: response = self.llm.invoke([llm_input]) - print('gay') - chat_history.chat_memory.add_ai_message(response) + chat_history.add_ai_message(response) elif file: human_messages = [] chunk_responses = ['Содержание файла: '] @@ -250,7 +251,7 @@ class Chatgpt(SimpleService): content=agent_executor.invoke( { 'input': [llm_input], - 'chat_history': chat_history.buffer_as_messages + 'chat_history': chat_history.messages + [ SystemMessage( content='Учитывай язык диалога перед выдачей ответа' @@ -264,8 +265,10 @@ class Chatgpt(SimpleService): ) elif model_name == 'o3-mini': with httpx.Client( - base_url='https://openai.com', + base_url='https://api.openai.com/v1', proxy=f'{proxy.protocol}://{proxy.address}', + headers={'Authorization': f'Bearer {settings.OPENAI_API_KEY}'}, + timeout=600, ) as client: resp = client.post( 'chat/completions', @@ -297,7 +300,7 @@ class Chatgpt(SimpleService): {'input': llm_input.content[0]['text']}, config={'configurable': {'session_id': 'default'}}, ) - chat_history.chat_memory.add_ai_message(response) + chat_history.add_ai_message(response) output_tokens = self.count_text_tokens([response]) if file and not image: @@ -333,10 +336,10 @@ class Chatgpt(SimpleService): raise GenerationException def get_chat_history( - self, - message_limit: int = 10, - token_limit: int = 580, # ~ 1 AIR Token with GPT 3.5 - ) -> ConversationTokenBufferMemory: + self, + message_limit: int = 10, + token_limit: int = 580, # ~ 1 AIR Token with GPT 3.5 + ) -> InMemoryChatMessageHistory: if isinstance(self.store, Chat): air_messages = list( reversed( @@ -357,22 +360,21 @@ class Chatgpt(SimpleService): ).order_by('-created_at')[:message_limit] ) ) - memory = ConversationTokenBufferMemory(llm=self.llm, max_token_limit=token_limit) + memory = InMemoryChatMessageHistory() for msg in air_messages: content = msg.content or '' if msg.from_model: - memory.chat_memory.add_ai_message(content) + memory.add_message(AIMessage(content=content)) else: - memory.chat_memory.add_user_message(HumanMessage(content=content)) + memory.add_message(HumanMessage(content=content)) + + messages = memory.messages + tokens = self.llm.get_num_tokens_from_messages(messages) - buffer = memory.chat_memory.messages - curr_buffer_length = memory.llm.get_num_tokens_from_messages(buffer) + while tokens > token_limit: + messages.pop(0) + tokens = self.llm.get_num_tokens_from_messages(messages) - if curr_buffer_length > memory.max_token_limit: - pruned_memory = [] - while curr_buffer_length > memory.max_token_limit: - pruned_memory.append(buffer.pop(0)) - curr_buffer_length = memory.llm.get_num_tokens_from_messages(buffer) return memory def assert_enough_balance( @@ -549,7 +551,7 @@ class Chatgpt(SimpleService): 'AI:', ), ) - self.assert_enough_balance(chat_history.buffer_as_messages) + self.assert_enough_balance(chat_history.messages) for chunk in conversation.stream(input=input_message.content): if chunk: @@ -558,11 +560,11 @@ class Chatgpt(SimpleService): process_time = timedelta(seconds=time.time() - start_time) self.handle_invoice( self.neuron_model, - self.llm.get_num_tokens_from_messages(chat_history.chat_memory.messages), + self.llm.get_num_tokens_from_messages(chat_history.messages), self.llm.model_name, ) msgs = self.save_results( - [chat_history.chat_memory.messages[-1]], process_time, save + [chat_history.messages[-1]], process_time, save ) return msgs @@ -139,26 +139,26 @@ class Flux(SimpleService): versions[0].slug: ModelPaymentRule( strategy=ModelPaymentRule.StrategyChoices.FIXED, interaction_type=ModelPaymentRule.InteractionTypeChoices.INPUT, - cost=0.33, + cost=0.3, coefficient=5.00, ), versions[1].slug: ModelPaymentRule( strategy=ModelPaymentRule.StrategyChoices.FIXED, interaction_type=ModelPaymentRule.InteractionTypeChoices.INPUT, - cost=4.4, - coefficient=5.00, + cost=4.00, + coefficient=2.00, ), versions[2].slug: ModelPaymentRule( strategy=ModelPaymentRule.StrategyChoices.FIXED, interaction_type=ModelPaymentRule.InteractionTypeChoices.INPUT, - cost=2.75, - coefficient=5.00, + cost=2.5, + coefficient=2.00, ), versions[3].slug: ModelPaymentRule( strategy=ModelPaymentRule.StrategyChoices.FIXED, interaction_type=ModelPaymentRule.InteractionTypeChoices.INPUT, - cost=6.6, - coefficient=5.00, + cost=6.00, + coefficient=2.00, ), } @@ -134,7 +134,7 @@ class Stablediffusion(SimpleService): ), ] - TOKEN_PRICE = Decimal('11') + TOKEN_PRICE = Decimal('2') _API_KEY = settings.STABLE_DIFFUSION_API_KEY @@ -94,7 +94,7 @@ class NeuronModel(BaseModel, OrderedModel): @property def blocked(self) -> bool: - return bool(self.settings) and self.settings.is_active and self.inputs.count() > 0 + return self.settings and not self.settings.is_active def __str__(self): return self.title @@ -23,7 +23,7 @@ services: replicas: 1 update_config: parallelism: 1 - delay: 10s + delay: 3s order: start-first restart_policy: condition: on-failure