import os from openai import OpenAI from dotenv import load_dotenv from typing import List, Optional from utils import log class GPTClient: def __init__(self, model: str, api_key: Optional[str] = None): # Initialize self.model = model self._init_environment(api_key) self.histories = list() self.token_usages = dict() self.__first_turn = True def _init_environment(self, api_key: Optional[str] = None) -> None: """ Initialize OpenAI client. Args: api_key (Optional[str]): API key for OpenAI. If not provided, it will be loaded from environment variables. """ if not api_key: load_dotenv(override=True) api_key = os.environ.get("OPENAI_API_KEY", None) self.client = OpenAI(api_key=api_key) def reset_history(self, verbose: bool = True) -> None: """ Reset the conversation history. Args: verbose (bool): Whether to print verbose output. Defaults to True. """ self.__first_turn = True self.histories = list() self.token_usages = dict() if verbose: log('Conversation history has been reset.', color=True) def __make_payload(self, user_prompt: str) -> List[dict]: """ Create a payload for API calls to the GPT model. Args: user_prompt (str): User prompt. Returns: List[dict]: Payload including prompts and image data. """ payloads = list() user_contents = {"role": "user", "content": []} # User prompts user_contents["content"].append( {"type": "text", "text": user_prompt} ) payloads.append(user_contents) return payloads def __call__(self, user_prompt: str, system_prompt: Optional[str] = None, using_multi_turn: bool = True, greeting: Optional[str] = None, verbose: bool = True, **kwargs) -> str: """ Sends a chat completion request to the model with optional image input and system prompt. Args: user_prompt (str): The main user prompt or query to send to the model. system_prompt (Optional[str], optional): An optional system-level prompt to set context or behavior. Defaults to None. using_multi_turn (bool): Whether to structure it as multi-turn. Defaults to True. greeting (Optional[str]): An optional greeting message to include in the conversation. Defaults to None. verbose (bool): Whether to print verbose output. Defaults to True. Raises: e: Any exception raised during the API call is re-raised. Returns: str: The model's response message. """ try: # To ensure empty history if not using_multi_turn: self.reset_history(verbose) if self.__first_turn: # System prompt if system_prompt: self.histories.append({"role": "system", "content": [{"type": "text", "text": system_prompt}]}) # Greeting if greeting and self.__first_turn: self.histories.append({"role": "assistant", "content": [{"type": "text", "text": greeting}]}) self.__first_turn = False # User prompt self.histories += self.__make_payload(user_prompt) # Model response response = self.client.chat.completions.create( model=self.model, messages=self.histories, **kwargs ) assistant_msg = response.choices[0].message self.histories.append({"role": assistant_msg.role, "content": [{"type": "text", "text": assistant_msg.content}]}) # Logging token usage if response.usage: self.token_usages.setdefault("prompt_tokens", []).append(response.usage.prompt_tokens) self.token_usages.setdefault("completion_tokens", []).append(response.usage.completion_tokens) self.token_usages.setdefault("total_tokens", []).append(response.usage.total_tokens) self.token_usages.setdefault("reasoning_tokens", []).append(response.usage.completion_tokens_details.reasoning_tokens) return assistant_msg.content except Exception as e: raise e