import asyncio import itertools import json import os import torch import openai class ChatService: def __init__(self, api="openai", model_id = "gpt-3.5-turbo"): self._api = api self._device = "cuda:0" if torch.cuda.is_available() else "cpu" openai.api_key = os.getenv("OPENAI_API_KEY") self._model_id = model_id def _should_we_send_to_voice(self, sentence): sentence_termination_characters = [".", "?", "!"] close_brackets = ['"', ')', ']'] temination_charicter_present = any(c in sentence for c in sentence_termination_characters) # early exit if we don't have a termination character if not temination_charicter_present: return None # early exit the last char is a termination character if sentence[-1] in sentence_termination_characters: return None # early exit the last char is a close bracket if sentence[-1] in close_brackets: return None termination_indices = [sentence.rfind(char) for char in sentence_termination_characters] # Filter out termination indices that are not followed by whitespace or end of string termination_indices = [i for i in termination_indices if sentence[i+1].isspace()] last_termination_index = max(termination_indices) # handle case of close bracket while last_termination_index+1 < len(sentence) and sentence[last_termination_index+1] in close_brackets: last_termination_index += 1 text_to_speak = sentence[:last_termination_index+1] return text_to_speak def ignore_sentence(self, text_to_speak): # exit if empty, white space or an single breaket if text_to_speak.isspace(): return True # exit if not letters or numbers has_letters = any(char.isalpha() for char in text_to_speak) has_numbers = any(char.isdigit() for char in text_to_speak) if not has_letters and not has_numbers: return True return False async def get_responses_as_sentances_async(self, messages, cancel_event=None): llm_response = "" current_sentence = "" delay = 0.1 while True: try: response = await openai.ChatCompletion.acreate( model=self._model_id, messages=messages, temperature=1.0, # use 0 for debugging/more deterministic results stream=True ) async for chunk in response: if cancel_event is not None and cancel_event.is_set(): return chunk_message = chunk['choices'][0]['delta'] if 'content' in chunk_message: chunk_text = chunk_message['content'] current_sentence += chunk_text llm_response += chunk_text text_to_speak = self._should_we_send_to_voice(current_sentence) if text_to_speak: current_sentence = current_sentence[len(text_to_speak):] yield text_to_speak, True else: yield current_sentence, False if cancel_event is not None and cancel_event.is_set(): return if len(current_sentence) > 0: yield current_sentence, True return except openai.error.APIError as e: print(f"OpenAI API returned an API Error: {e}") print(f"Retrying in {delay} seconds...") await asyncio.sleep(delay) delay *= 2 except openai.error.APIConnectionError as e: print(f"Failed to connect to OpenAI API: {e}") print(f"Retrying in {delay} seconds...") await asyncio.sleep(delay) delay *= 2 except openai.error.RateLimitError as e: print(f"OpenAI API request exceeded rate limit: {e}") print(f"Retrying in {delay} seconds...") await asyncio.sleep(delay) delay *= 2 except Exception as e: print(f"OpenAI API unknown error: {e}") print(f"Retrying in {delay} seconds...") await asyncio.sleep(delay) delay *= 2