From 0779808db35e99541002b7d3aadf82d890797738 Mon Sep 17 00:00:00 2001 From: Karim Iskakov Date: Thu, 2 Mar 2023 21:31:54 +0300 Subject: [PATCH] Add support of ChatGPT API (#70) Co-authored-by: Karim Iskakov --- README.md | 4 +++ bot/bot.py | 9 ++++--- bot/chatgpt.py | 56 ++++++++++++++++++++++++++++----------- bot/config.py | 1 + config/config.example.yml | 1 + requirements.txt | 4 +-- 6 files changed, 54 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index dd05d385..ff31f177 100644 --- a/README.md +++ b/README.md @@ -14,11 +14,15 @@ This repo is ChatGPT re-created with GPT-3.5 LLM as Telegram Bot. **And it works You can deploy your own bot, or use mine: [@chatgpt_karfly_bot](https://t.me/chatgpt_karfly_bot) +## News +- *2 Mar 2023*: Added support of [ChatGPT API](https://platform.openai.com/docs/guides/chat/introduction). It's enabled by default and can be disabled with `use_chatgpt_api` option in config. Don't forget to **rebuild** you docker image (`--build`). + ## Features - Low latency replies (it usually takes about 3-5 seconds) - No request limits - Code highlighting - Special chat modes: 👩🏼‍🎓 Assistant, 👩🏼‍💻 Code Assistant, 🎬 Movie Expert. More soon +- Support of [ChatGPT API](https://platform.openai.com/docs/guides/chat/introduction) - List of allowed Telegram users - Track $ balance spent on OpenAI API diff --git a/bot/bot.py b/bot/bot.py index dbe1e88a..a85a0e5c 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -105,7 +105,8 @@ async def message_handle(update: Update, context: CallbackContext, message=None, try: message = message or update.message.text - answer, prompt, n_used_tokens, n_first_dialog_messages_removed = chatgpt.ChatGPT().send_message( + chatgpt_instance = chatgpt.ChatGPT(use_chatgpt_api=config.use_chatgpt_api) + answer, n_used_tokens, n_first_dialog_messages_removed = chatgpt_instance.send_message( message, dialog_messages=db.get_dialog_messages(user_id, dialog_id=None), chat_mode=db.get_user_attribute(user_id, "current_chat_mode"), @@ -194,10 +195,12 @@ async def show_balance_handle(update: Update, context: CallbackContext): db.set_user_attribute(user_id, "last_interaction", datetime.now()) n_used_tokens = db.get_user_attribute(user_id, "n_used_tokens") - n_spent_dollars = n_used_tokens * (0.02 / 1000) + + price = 0.002 if config.use_chatgpt_api else 0.02 + n_spent_dollars = n_used_tokens * (price / 1000) text = f"You spent {n_spent_dollars:.03f}$\n" - text += f"You used {n_used_tokens} tokens (price: 0.02$ per 1000 tokens)\n" + text += f"You used {n_used_tokens} tokens (price: {price}$ per 1000 tokens)\n" await update.message.reply_text(text, parse_mode=ParseMode.HTML) diff --git a/bot/chatgpt.py b/bot/chatgpt.py index ae9d0d59..4df2a9f8 100644 --- a/bot/chatgpt.py +++ b/bot/chatgpt.py @@ -30,10 +30,18 @@ }, } +OPENAI_COMPLETION_OPTIONS = { + "temperature": 0.7, + "max_tokens": 1000, + "top_p": 1, + "frequency_penalty": 0, + "presence_penalty": 0 +} + class ChatGPT: - def __init__(self): - pass + def __init__(self, use_chatgpt_api=True): + self.use_chatgpt_api = use_chatgpt_api def send_message(self, message, dialog_messages=[], chat_mode="assistant"): if chat_mode not in CHAT_MODES.keys(): @@ -42,22 +50,27 @@ def send_message(self, message, dialog_messages=[], chat_mode="assistant"): n_dialog_messages_before = len(dialog_messages) answer = None while answer is None: - prompt = self._generate_prompt(message, dialog_messages, chat_mode) try: - r = openai.Completion.create( - engine="text-davinci-003", - prompt=prompt, - temperature=0.7, - max_tokens=1000, - top_p=1, - frequency_penalty=0, - presence_penalty=0, - ) - answer = r.choices[0].text - answer = self._postprocess_answer(answer) + if self.use_chatgpt_api: + messages = self._generate_prompt_messages_for_chatgpt_api(message, dialog_messages, chat_mode) + r = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=messages, + **OPENAI_COMPLETION_OPTIONS + ) + answer = r.choices[0].message["content"] + else: + prompt = self._generate_prompt(message, dialog_messages, chat_mode) + r = openai.Completion.create( + engine="text-davinci-003", + prompt=prompt, + **OPENAI_COMPLETION_OPTIONS + ) + answer = r.choices[0].text + answer = self._postprocess_answer(answer) n_used_tokens = r.usage.total_tokens - + except openai.error.InvalidRequestError as e: # too many tokens if len(dialog_messages) == 0: raise ValueError("Dialog messages is reduced to zero, but still has too many tokens to make completion") from e @@ -67,7 +80,7 @@ def send_message(self, message, dialog_messages=[], chat_mode="assistant"): n_first_dialog_messages_removed = n_dialog_messages_before - len(dialog_messages) - return answer, prompt, n_used_tokens, n_first_dialog_messages_removed + return answer, n_used_tokens, n_first_dialog_messages_removed def _generate_prompt(self, message, dialog_messages, chat_mode): prompt = CHAT_MODES[chat_mode]["prompt_start"] @@ -86,6 +99,17 @@ def _generate_prompt(self, message, dialog_messages, chat_mode): return prompt + def _generate_prompt_messages_for_chatgpt_api(self, message, dialog_messages, chat_mode): + prompt = CHAT_MODES[chat_mode]["prompt_start"] + + messages = [{"role": "system", "content": prompt}] + for dialog_message in dialog_messages: + messages.append({"role": "user", "content": dialog_message["user"]}) + messages.append({"role": "assistant", "content": dialog_message["bot"]}) + messages.append({"role": "user", "content": message}) + + return messages + def _postprocess_answer(self, answer): answer = answer.strip() return answer \ No newline at end of file diff --git a/bot/config.py b/bot/config.py index beff917d..4b6c0dcd 100644 --- a/bot/config.py +++ b/bot/config.py @@ -14,6 +14,7 @@ # config parameters telegram_token = config_yaml["telegram_token"] openai_api_key = config_yaml["openai_api_key"] +use_chatgpt_api = config_yaml.get("use_chatgpt_api", True) allowed_telegram_usernames = config_yaml["allowed_telegram_usernames"] new_dialog_timeout = config_yaml["new_dialog_timeout"] mongodb_uri = f"mongodb://mongo:{config_env['MONGODB_PORT']}" diff --git a/config/config.example.yml b/config/config.example.yml index 1bf8a2a4..725429bd 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -1,4 +1,5 @@ telegram_token: "" openai_api_key: "" +use_chatgpt_api: true allowed_telegram_usernames: [] # if empty, the bot is available to anyone new_dialog_timeout: 600 # new dialog starts after timeout (in seconds) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index de7ac738..e4bed0f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -python-telegram-bot==20.0a0 -openai>=0.26.1 +python-telegram-bot==20.1 +openai>=0.27.0 PyYAML==6.0 pymongo==4.3.3 python-dotenv==0.21.0 \ No newline at end of file