@@ -1150,3 +1150,9 @@ msgstr "Jinja-шаблон не найден" msgid "There was an unknown error while rendering a template" msgstr "При рендеринге шаблона произошла неизвестная ошибка" + +msgid "The length of the context has been exceeded." +msgstr "Длина контекста превышена." + +msgid "The attached file format is not supported. Available formats: %(available_extensions)s." +msgstr "Формат вложенного файла не поддерживается. Доступные форматы: %(available_extensions)s." @@ -347,7 +347,7 @@ class Chatgpt(SimpleService): def assert_enough_balance( self, input_tokens: int, - image_size: tuple, + image_size: tuple | None, model: str = 'gpt-3.5-turbo', ): balance = PaymentPlanSelector(self.store.user).get_current_balance() @@ -1,13 +1,33 @@ +import time +import httpx + from django.template import TemplateDoesNotExist from django.template.loader import get_template +from openai import BadRequestError -from messages.models import Message -from ml_model.exceptions import TemplateNotFound, TemplateUnknownException +from ml_model.exceptions import TemplateNotFound, TemplateUnknownException, FileExtensionNotSupported, \ + ExceededContextLengthError from ml_model.services import Chatgpt +from django.utils.translation import gettext_lazy as _ +from datetime import timedelta -class Raifgpt(Chatgpt): +from pathlib import Path + +from langchain_core.messages import ( + HumanMessage, + SystemMessage, +) +from langchain_core.runnables import RunnableWithMessageHistory +from langchain_openai.chat_models import ChatOpenAI + +from messages.models import Message +from ml_model.exceptions import GenerationException + +from poller.models import Proxy + +class Raifgpt(Chatgpt): def make( self, input_message: Message, @@ -15,16 +35,81 @@ class Raifgpt(Chatgpt): ) -> list[Message]: try: template = get_template('ml_model/system_prompt.html') - system_prompt = template.render() + user_system_prompt = template.render() except TemplateDoesNotExist: raise TemplateNotFound except Exception as exc: raise TemplateUnknownException from exc - input_message.info = { - 'version': 'gpt-4o', - 'temperature': 0.8, - 'top_p': 1, - 'presence_penalty ': 0, - 'system_prompt': system_prompt, - } - return super().make(input_message, save) \ No newline at end of file + input_content = [{'type': 'text', 'text': input_message.content or ''}] + file = input_message.file + if file: + file_extension = Path(file.name).suffix + if file_extension == '.pdf': + chunks = self.split_text_to_chunks(self.get_pdf_data(file)) + elif file_extension in ('.doc', '.docx'): + chunks = self.split_text_to_chunks(self.get_word_data(file_extension, file)) + else: + raise FileExtensionNotSupported(['PDF', 'DOC', 'DOCX']) + for proxy in Proxy.objects.all(): + self.llm = ChatOpenAI( + model='gpt-4o', + http_client=httpx.Client(proxy=f'{proxy.protocol}://{proxy.address}'), + ) + self.llm.tiktoken_model_name = 'gpt-4' + self.llm.temperature = 0.8 + self.llm.top_p = 1 + self.llm.presence_penalty = 0 + chat_history = self.get_chat_history() + conversation = RunnableWithMessageHistory( + runnable=self.llm, + get_session_history=lambda _: chat_history, + ) + llm_input = HumanMessage(content=input_content) + if file: + input_tokens = self.count_text_tokens([*chat_history.messages, llm_input, *chunks]) + else: + input_tokens = self.count_text_tokens([*chat_history.messages, llm_input]) + + self.assert_enough_balance(input_tokens, None, model=self.llm.model_name) + + start_time = time.time() + try: + if file: + input = [ + SystemMessage(content=user_system_prompt), + HumanMessage(content=f'Строго используй системный промпт. Вот содержание файла по чанкам:'), + *chunks, + llm_input + ] + input_tokens = self.count_text_tokens(input) + response = conversation.invoke( + {'input': input}, + config={'configurable': {'session_id': 'default'}}, + ) + else: + response = conversation.invoke( + {'input': llm_input}, + config={'configurable': {'session_id': 'default'}}, + ) + chat_history.add_ai_message(response) + except BadRequestError as exc: + if exc.code == 'context_length_exceeded': + raise ExceededContextLengthError + raise GenerationException + + output_tokens = self.count_text_tokens([response]) + + self.logger.info(f'Input количество токенов для gpt-4o - {input_tokens}') + self.logger.info(f'Output количество токенов для gpt-4o - {output_tokens}') + self.logger.info(f'Общее количество токенов для gpt-4o - {input_tokens + output_tokens}') + + process_time = timedelta(seconds=time.time() - start_time) + self.handle_invoice( + self.neuron_model, + input_tokens, + output_tokens, + self.llm.model_name, + {} + ) + msgs = self.save_results([response], process_time, save) + return msgs @@ -1,4 +1,8 @@ -ОТВЕЧАЙ СТРОГО ПО ШАБЛОНУ! +# ВНИМАНИЕ: +1. ОТВЕЧАЙ СТРОГО ПО ШАБЛОНУ — не меняй порядок, не добавляй и не убирай пункты. Каждый пункт и подпункт должен быть представлен, даже если данных мало. +2. НЕ СОКРАЩАЙ ТЕКСТ СЛИШКОМ СИЛЬНО — не упрощай формулировки, не объединяй предложения, не убирай детали. Приводи ВСЕ указанные цифры, периоды, названия, суммы и т.д. Полнота важнее краткости. +3. ПЕРЕНОСЫ СТРОК: После КАЖДОГО ПУНКТА (например, 1., 2., 3.) и ПОДПУНКТА (например, a., b., c.) ОБЯЗАТЕЛЬНО ВСТАВЛЯЙ ПЕРЕНОС СТРОКИ. Один пункт = одна строка. Строго соблюдай разметку: каждый подпункт с новой строки, даже если он короткий. +Не пытайся оптимизировать или переформулировать текст. Следуй инструкции строго, как если бы ты заполнял юридически значимую форму. # [GOAL] # Твоя задача — создать детализированное, но краткое структурированное содержание предоставленного текста рыночного исследования. Выступай в роли аналитика, подготавливающего выжимку, богатую ключевыми данными, для быстрого обзора и оценки. @@ -22,6 +22,21 @@ class ModelTimeoutError(Exception): return _('The model is not responding') +class FileExtensionNotSupported(Exception): + def __init__(self, extensions: list[str]) -> None: + self.extensions = extensions + + def __str__(self) -> str: + return _( + f'The attached file format is not supported. Available formats: %(available_extensions)s.' + ) % {'available_extensions': ', '.join(self.extensions)} + + +class ExceededContextLengthError(Exception): + def __str__(self) -> str: + return _('The length of the context has been exceeded.') + + class TemplateNotFound(Exception): def __str__(self): return _('Jinja template not found') @@ -10,6 +10,7 @@ from rest_framework.generics import ( from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.status import ( + HTTP_400_BAD_REQUEST, HTTP_402_PAYMENT_REQUIRED, HTTP_500_INTERNAL_SERVER_ERROR, HTTP_503_SERVICE_UNAVAILABLE @@ -18,7 +19,8 @@ from rest_framework.views import APIView from messages.models import Message from messages.serializers import MessageSerializer -from ml_model.exceptions import DeploymentDisabled, TemplateNotFound, TemplateUnknownException +from ml_model.exceptions import DeploymentDisabled, TemplateNotFound, TemplateUnknownException, \ + FileExtensionNotSupported, ExceededContextLengthError from ml_model.services.base import SimpleService from payments.exceptions.insufficient_balance import InsufficientBalance from tools.chats.models import Chat @@ -157,6 +159,10 @@ class MessagesAPIView(APIView): }, status=HTTP_503_SERVICE_UNAVAILABLE, ) + except FileExtensionNotSupported as exc: + return Response({'detail': f'{exc}'}, status=HTTP_400_BAD_REQUEST) + except ExceededContextLengthError as exc: + return Response({'detail': f'{exc}'}, status=HTTP_400_BAD_REQUEST) except TemplateNotFound as exc: return Response({'detail': f'{exc}'}, status=HTTP_500_INTERNAL_SERVER_ERROR) except TemplateUnknownException as exc: