@@ -39,6 +39,7 @@ from ml_model.models import ModelConfiguration, NeuronModel from ml_model.services.EmbeddingService import EmbeddingService from ml_model.services.FileService import FileProcessingService from ml_model.services.base import SimpleService +from ml_model.services.serper_mixin import SerperMixin from payments.exceptions.insufficient_balance import InsufficientBalance from payments.selectors.payment_plan_selector import PaymentPlanSelector from poller.models import Proxy @@ -47,7 +48,7 @@ from tools.copywrite.models import Copywrite from tools.public_api.models import APIStore -class Chatgpt_4(SimpleService): +class Chatgpt_4(SerperMixin, SimpleService): """ ChatGPT 4 Service contains abstract method make, which makes a generation @@ -167,7 +168,10 @@ class Chatgpt_4(SimpleService): ) output_tokens = 0 self.assert_enough_balance( - input_tokens, image_size, model=self.llm.model_name, embedding_tokens=input_embedding_tokens + input_tokens, + image_size, + model=self.llm.model_name, + embedding_tokens=input_embedding_tokens, ) if model_name == 'gpt-oss-120b': system = chat_history.messages.pop(0) @@ -215,8 +219,9 @@ class Chatgpt_4(SimpleService): (data := response.json()) and data.get('choices') and ( - content := ','.join( - [choice['message']['content'] for choice in data.get('choices')]) + content := ','.join( + [choice['message']['content'] for choice in data.get('choices')] + ) ) ): input_tokens = response.json()['usage']['prompt_tokens'] @@ -242,7 +247,9 @@ class Chatgpt_4(SimpleService): ] elif file: if sum([len(chunk.content) for chunk in chunks]) > 20_000: - document_name = chunks[0].content.partition(f':{chr(10)}')[2].split(f'{chr(10)}')[0][:100] + document_name = ( + chunks[0].content.partition(f':{chr(10)}')[2].split(f'{chr(10)}')[0][:100] + ) embedding_tokens, file_data = EmbeddingService.get_large_file_data( self.store.messages.first().pk, text_chunks, @@ -261,15 +268,17 @@ class Chatgpt_4(SimpleService): 'Используй системный промпт. Содержание файла: ' f'{"".join(text_chunks)}. Вопрос: {input_message.content}' ) - json_data = { - 'model': model_name, - 'messages': messages - } - input_tokens, output_tokens, response = self.call_openai_api(proxy=proxy, endpoint='chat/completions',json_data=json_data) + json_data = {'model': model_name, 'messages': messages} + input_tokens, output_tokens, response = self.call_openai_api( + proxy=proxy, endpoint='chat/completions', json_data=json_data + ) elif info.get('web_search', 'Отключено') != 'Отключено': system = chat_history.messages.pop(0) messages = [ - {'role': 'user' if isinstance(msg, HumanMessage) else 'assistant', 'content': msg.content} + { + 'role': 'user' if isinstance(msg, HumanMessage) else 'assistant', + 'content': msg.content, + } for msg in chat_history.messages ] messages.insert(0, {'role': 'system', 'content': system.content}) @@ -285,7 +294,9 @@ class Chatgpt_4(SimpleService): ] elif file: if sum([len(chunk.content) for chunk in chunks]) > 20_000: - document_name = chunks[0].content.partition(f':{chr(10)}')[2].split(f'{chr(10)}')[0][:100] + document_name = ( + chunks[0].content.partition(f':{chr(10)}')[2].split(f'{chr(10)}')[0][:100] + ) embedding_tokens, file_data = EmbeddingService.get_large_file_data( self.store.messages.first().pk, text_chunks, @@ -313,7 +324,9 @@ class Chatgpt_4(SimpleService): elif file: input_tokens = self.count_text_tokens([*chat_history.messages]) if sum([len(chunk.content) for chunk in chunks]) > 20_000: - document_name = chunks[0].content.partition(f':{chr(10)}')[2].split(f'{chr(10)}')[0][:100] + document_name = ( + chunks[0].content.partition(f':{chr(10)}')[2].split(f'{chr(10)}')[0][:100] + ) embedding_tokens, file_data = EmbeddingService.get_large_file_data( self.store.messages.first().pk, text_chunks, @@ -362,18 +375,16 @@ class Chatgpt_4(SimpleService): if output_tokens == 0: output_tokens = self.count_text_tokens([response]) - if ( - image - and normalized_image - and model_name != 'o3-mini' - ): + if image and normalized_image and model_name != 'o3-mini': self.logger.info(f'Input количество токенов БЕЗ картинки {model_name} - {input_tokens}') input_tokens += self.count_image_tokens(normalized_image.size, model_name) self.logger.info(f'Input количество токенов для {model_name} - {input_tokens}') self.logger.info(f'Output количество токенов для {model_name} - {output_tokens}') self.logger.info(f'Embedding количество токенов для {model_name} - {embedding_tokens}') - self.logger.info(f'Общее количество токенов для {model_name} - {input_tokens + output_tokens + embedding_tokens}') + self.logger.info( + f'Общее количество токенов для {model_name} - {input_tokens + output_tokens + embedding_tokens}' + ) process_time = timedelta(seconds=time.time() - start_time) self.handle_invoice( @@ -382,7 +393,7 @@ class Chatgpt_4(SimpleService): output_tokens, self.llm.model_name, info, - embedding_tokens + embedding_tokens, ) msgs = self.save_results([response], process_time, save) http_client.close() @@ -412,22 +423,25 @@ class Chatgpt_4(SimpleService): for message in air_messages.iterator(5): air_message = [ AIMessage(content=message.content or '') - if message.from_model else - HumanMessage(content=message.content or '') + if message.from_model + else HumanMessage(content=message.content or '') ] if self.count_text_tokens(air_message) + tokens > token_limits[model_name]: break tokens += self.count_text_tokens(air_message) history.append(air_message[0]) memory = InMemoryChatMessageHistory() - memory.add_message(SystemMessage( - content=( - 'Think step by step. Use full context. Prioritize depth, clarity, and justification. ' - 'Be thorough and expansive.\n' - f'{self.NO_FILE_GENERATION_POLICY}' + memory.add_message( + SystemMessage( + content=( + 'Think step by step. Use full context. Prioritize depth, clarity, and justification. ' + 'Be thorough and expansive.\n' + f'{self.NO_FILE_GENERATION_POLICY}' + ) ) - )) - memory.add_message(SystemMessage( + ) + memory.add_message( + SystemMessage( content=( 'Отныне все ответы должны быть представлены как единая строка (str). Не использовать никаких ' 'структурированных форматов, таких как JSON, словари (dict) или списки (list). ' @@ -435,7 +449,8 @@ class Chatgpt_4(SimpleService): 'Не генерируй файлы и не предоставляй ссылки на скачивание файлов. ' 'Весь контент давай прямо в тексте ответа.' ) - )) + ) + ) memory.add_messages(list(reversed(history))) return memory @@ -454,8 +469,7 @@ class Chatgpt_4(SimpleService): input_cost = self.TOKENS_COST[model]['input'] * total_tokens if embedding_tokens > 0: input_cost += ( - embedding_tokens - * self.TOOLS_TOKEN_COSTS[self.EMBEDDING_MODEL_FOR_BILLING]['output'] + embedding_tokens * self.TOOLS_TOKEN_COSTS[self.EMBEDDING_MODEL_FOR_BILLING]['output'] ) output_cost = self.TOKENS_COST[model]['output'] * output_tokens if input_cost + output_cost > balance: @@ -480,10 +494,7 @@ class Chatgpt_4(SimpleService): if info.get('code_interpreter', False): price += self.TOKENS_COST[model]['code_interpreter'] if embedding_tokens > 0: - price += ( - self.TOOLS_TOKEN_COSTS[self.EMBEDDING_MODEL_FOR_BILLING]['output'] - * embedding_tokens - ) + price += self.TOOLS_TOKEN_COSTS[self.EMBEDDING_MODEL_FOR_BILLING]['output'] * embedding_tokens return price.quantize(Decimal('0.1'), rounding='ROUND_UP') def count_image_tokens(self, image_size: tuple, model_version: str = 'gpt-4o') -> int: @@ -591,19 +602,12 @@ class Chatgpt_4(SimpleService): headers={'Authorization': f'Bearer {settings.OPENAI_API_KEY}'}, timeout=600, ) as client: - resp = client.post( - endpoint, - json=json_data - ) + resp = client.post(endpoint, json=json_data) if ( endpoint == 'chat/completions' and (data := resp.json()) and data.get('choices') - and ( - content := ','.join( - [choice['message']['content'] for choice in data.get('choices')] - ) - ) + and (content := ','.join([choice['message']['content'] for choice in data.get('choices')])) ): input_tokens = resp.json()['usage']['prompt_tokens'] output_tokens = resp.json()['usage']['completion_tokens'] @@ -819,21 +823,6 @@ class Chatgpt_4(SimpleService): output_tokens = self.count_text_tokens([response]) return input_tokens, output_tokens, response - @staticmethod - def run_serper(query: str, **kwargs): - headers = { - 'X-API-KEY': settings.SERPER_API_KEY, - 'Content-Type': 'application/json', - } - params = { - 'q': query, - **{key: value for key, value in kwargs.items() if value is not None}, - } - response = httpx.post('https://google.serper.dev/search', headers=headers, params=params) - response.raise_for_status() - search_results = response.json() - return search_results - @staticmethod def serper_to_openai_context(serp: dict, max_sources: int = 3) -> str: query = (serp.get('searchParameters') or {}).get('q', '').strip() @@ -3,17 +3,24 @@ from django.conf import settings class SerperMixin: + MAX_SERPER_QUERY_LEN = 400 + @staticmethod def run_serper(query: str, **kwargs): + query = (query or '').strip()[: SerperMixin.MAX_SERPER_QUERY_LEN] + headers = { 'X-API-KEY': settings.SERPER_API_KEY, 'Content-Type': 'application/json', } - params = { - 'q': query, - **{key: value for key, value in kwargs.items() if value is not None}, - } - response = httpx.post('https://google.serper.dev/search', headers=headers, params=params) + + payload = {'q': query, **{k: v for k, v in kwargs.items() if v is not None}} + + response = httpx.post( + 'https://google.serper.dev/search', + headers=headers, + json=payload, + ) response.raise_for_status() search_results = response.json() return search_results