@@ -79,6 +79,10 @@ class Chatgpt(SimpleService): 'input': Decimal('0.001375'), 'output': Decimal('0.005500'), }, + 'gpt-4.5-preview': { + 'input': Decimal('0.075'), + 'output': Decimal('0.075'), + }, } def __init__(self, store: BaseStore) -> None: @@ -121,7 +125,8 @@ class Chatgpt(SimpleService): image_url = f'data:{mime},base64,{base64.b64encode(buf.getvalue()).decode("utf-8")}' buf.close() image_size = normalized_image.size - input_content.append({'type': 'image_url', 'image_url': {'url': image_url}}) + image_data = {'type': 'image_url', 'image_url': {'url': image_url}} + input_content.append(image_data) except UnidentifiedImageError: raise Exception(_('Unable to recognize the image. (Supported formats are PNG, JPG, JPEG)')) for proxy in Proxy.objects.all(): @@ -153,6 +158,7 @@ class Chatgpt(SimpleService): 'o1-preview', 'o1-mini', 'o3-mini', + 'gpt-4.5-preview', ): self.llm.tiktoken_model_name = 'gpt-4' chat_history = self.get_chat_history() @@ -167,10 +173,48 @@ class Chatgpt(SimpleService): input_tokens = self.count_text_tokens([llm_input]) else: input_tokens = self.count_text_tokens([*chat_history.messages, llm_input]) + output_tokens = 0 self.assert_enough_balance(input_tokens, image_size, model=self.llm.model_name) - if image: + if image and model_name not in ('o3-mini', 'gpt-4.5-preview'): response = self.llm.invoke([llm_input]) chat_history.add_ai_message(response) + elif model_name in ('o3-mini', 'gpt-4.5-preview'): + with httpx.Client( + 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: + messages = [ + {'role': 'user' if isinstance(msg, HumanMessage) else 'assistant', 'content': msg.content} + for msg in chat_history.messages + ] + if image: + messages[-1]['content'] = [ + {'type': 'text', 'text': input_message.content}, + image_data, + ] + resp = client.post( + 'chat/completions', + json={ + 'model': model_name, + 'messages': messages + }, + ) + if ( + (data := resp.json()) + and 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'] + response = AIMessage(content=content) + else: + raise Exception('GPT not answer correctly, please retry later') elif file: human_messages = [] chunk_responses = ['Содержание файла: '] @@ -226,37 +270,6 @@ class Chatgpt(SimpleService): } )['output'] ) - elif model_name == 'o3-mini': - with httpx.Client( - 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', - json={ - 'model': model_name, - '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')] - ) - ) - ): - response = AIMessage(content=content) - else: - raise Exception('GPT not answer correctly, please retry later') else: # Somehow this chain doesn't support Vision, even though ChatOpenAI (above) does. response = conversation.invoke( @@ -265,13 +278,14 @@ class Chatgpt(SimpleService): ) chat_history.add_ai_message(response) - output_tokens = self.count_text_tokens([response]) + if output_tokens == 0: + output_tokens = self.count_text_tokens([response]) if file and not image: output_tokens += self.count_text_tokens( [AIMessage(chunk_response) for chunk_response in chunk_responses] ) - if image and normalized_image: + if image and normalized_image and model_name not in ('o3-mini', 'gpt-4.5-preview'): self.logger.info(f'Input количество токенов БЕЗ картинки {model_name} - {input_tokens}') input_tokens += self.count_image_tokens(normalized_image.size, model_name) @@ -28,7 +28,11 @@ class Claude(SimpleService): 'input': Decimal('3000'), 'output': Decimal('3000'), 'input_imgs': Decimal('960'), - }, # 1M tokens + }, + 'claude-3.5-haiku': { + 'input': Decimal('800'), + 'output': Decimal('800'), + }, # 1M tokens } def calculate_price(