diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..64888ac6c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +mongodb + diff --git a/Dockerfile b/Dockerfile index 7fd9f36e1..c5f90aa09 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,20 +1,11 @@ -FROM python:3.8-slim +FROM python:3.8-slim-bookworm -ENV PYTHONFAULTHANDLER=1 -ENV PYTHONUNBUFFERED=1 -ENV PYTHONHASHSEED=random -ENV PYTHONDONTWRITEBYTECODE 1 -ENV PIP_NO_CACHE_DIR=off -ENV PIP_DISABLE_PIP_VERSION_CHECK=on -ENV PIP_DEFAULT_TIMEOUT=100 +RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple && pip3 install -U pip && pip3 install -U wheel && pip3 install -U setuptools==59.5.0 +COPY ./requirements.txt /tmp/requirements.txt +RUN pip3 install -r /tmp/requirements.txt && rm -r /tmp/requirements.txt -RUN apt-get update -RUN apt-get install -y python3 python3-pip python-dev build-essential python3-venv ffmpeg - -RUN mkdir -p /code -ADD . /code +COPY . /code WORKDIR /code -RUN pip3 install -r requirements.txt +CMD ["bash"] -CMD ["bash"] \ No newline at end of file diff --git a/README.md b/README.md index 7298fa13c..796765498 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ You can deploy your own bot, or use mine: [@chatgpt_karfly_bot](https://t.me/cha - [ЮMoney](https://yoomoney.ru) - and [many-many other](https://core.telegram.org/bots/payments#supported-payment-providers) -If you want to add payments to your bot and create profitable business – write me on Telegram ([@karfly](https://t.me/karfly)). +If you want to add payments to your bot and create profitable business – write me on Telegram ([@karfly](https://t.me/karfly)) or Email (kar.iskakov@gmail.com). ## News - *21 Apr 2023*: @@ -60,7 +60,8 @@ If you want to add payments to your bot and create profitable business – write - *15 Mar 2023*: Added message streaming. Now you don't have to wait until the whole message is ready, it's streamed to Telegram part-by-part (watch demo) - *9 Mar 2023*: Now you can easily create your own Chat Modes by editing `config/chat_modes.yml` - *8 Mar 2023*: Added voice message recognition with [OpenAI Whisper API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis). Record a voice message and ChatGPT will answer you! -- *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`). +- *2 Mar 2023*: Added support of [ChatGPT API](https://platform.openai.com/docs/guides/chat/introduction). +- *1 Aug 2023*: Added OpenAI API Base to config (useful while using OpenAI-compatible API like [LocalAI](https://github.com/go-skynet/LocalAI)) ## Bot commands - `/retry` – Regenerate last bot answer @@ -89,19 +90,31 @@ If you want to add payments to your bot and create profitable business – write ## ❤️ Top donations You can be in this list: +1. [LilRocco](https://t.me/LilRocco). Donation: **11000$** (!!!) + +1. [Mr V](https://t.me/mr_v_v_v). Donation **250$** + +1. [unexpectedsunday](https://t.me/unexpectedsunday). Donation: **150$** + 1. [Sem](https://t.me/sembrestels). Donation: **100$** -2. [Ryo](https://t.me/ryokihara). Donation: **80$** +1. [Miksolo](https://t.me/Miksolo). Donation: **81$** -3. [Ilias Ism](https://twitter.com/illyism). Donation: **69$** + *Message:* Thank you. Using this docker container every day! Actually created the same project but its good to see that this one is being supported often. Will continue using it! Good architecture choices made in the code 💪! + +1. [Ryo](https://t.me/ryokihara). Donation: **80$** + +1. [Ilias Ism](https://twitter.com/illyism). Donation: **69$** *Message:* I wanted to thank you for your amazing code! It helped me start my own Telegram ChatGPT bot and add a bunch of cool features. I really appreciate your hard work on this project. For anyone interested in trying my bot, feel free to check it out here: [magicbuddy.chat](https://magicbuddy.chat) 🤖 Thanks again! 😊 -4. [Sebastian](https://t.me/dell1503). Donation: **55$** +1. [Sebastian](https://t.me/dell1503). Donation: **55$** + +1. [Alexander Zimin](https://t.me/azimin). Donation: **50$** -5. [Alexander Zimin](https://t.me/azimin). Donation: **50$** +1. [Kbaji20](https://t.me/Kbaji20). Donation: **30$** -6. [Hans Blinken](https://t.me/hblink). Donation: **10$** +1. [Hans Blinken](https://t.me/hblink). Donation: **10$** ## References 1. [*Build ChatGPT from GPT-3*](https://learnprompting.org/docs/applied_prompting/build_chatgpt) diff --git a/bot/bot.py b/bot/bot.py index 7e464ec7b..b73ec3ba9 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -1,12 +1,9 @@ -import os +import io import logging import asyncio import traceback import html import json -import tempfile -import pydub -from pathlib import Path from datetime import datetime import openai @@ -33,7 +30,7 @@ import config import database import openai_utils - +from vision_handle import VISION_CONVERSATION_HANDLER,VISION_FILTER # setup db = database.Database() @@ -94,7 +91,7 @@ async def register_user_if_not_exists(update: Update, context: CallbackContext, # back compatibility for n_used_tokens field n_used_tokens = db.get_user_attribute(user.id, "n_used_tokens") - if isinstance(n_used_tokens, int): # old format + if isinstance(n_used_tokens, int) or isinstance(n_used_tokens, float): # old format new_n_used_tokens = { "gpt-3.5-turbo": { "n_input_tokens": 0, @@ -342,25 +339,15 @@ async def voice_message_handle(update: Update, context: CallbackContext): db.set_user_attribute(user_id, "last_interaction", datetime.now()) voice = update.message.voice - with tempfile.TemporaryDirectory() as tmp_dir: - tmp_dir = Path(tmp_dir) - voice_ogg_path = tmp_dir / "voice.ogg" - - # download - voice_file = await context.bot.get_file(voice.file_id) - await voice_file.download_to_drive(voice_ogg_path) - - # convert to mp3 - voice_mp3_path = tmp_dir / "voice.mp3" - pydub.AudioSegment.from_file(voice_ogg_path).export(voice_mp3_path, format="mp3") + voice_file = await context.bot.get_file(voice.file_id) - # transcribe - with open(voice_mp3_path, "rb") as f: - transcribed_text = await openai_utils.transcribe_audio(f) - - if transcribed_text is None: - transcribed_text = "" + # store file in memory, not on disk + buf = io.BytesIO() + await voice_file.download_to_memory(buf) + buf.name = "voice.oga" # file extension is required + buf.seek(0) # move cursor to the beginning of the buffer + transcribed_text = await openai_utils.transcribe_audio(buf) text = f"🎤: {transcribed_text}" await update.message.reply_text(text, parse_mode=ParseMode.HTML) @@ -382,8 +369,8 @@ async def generate_image_handle(update: Update, context: CallbackContext, messag message = message or update.message.text try: - image_urls = await openai_utils.generate_images(message, n_images=config.return_n_generated_images) - except openai.error.InvalidRequestError as e: + image_urls = await openai_utils.generate_images(message, n_images=config.return_n_generated_images, size=config.image_size) + except openai.OpenAIError as e: if str(e).startswith("Your request was rejected as a result of our safety system"): text = "🥲 Your request doesn't comply with OpenAI's usage policies.\nWhat did you write there, huh?" await update.message.reply_text(text, parse_mode=ParseMode.HTML) @@ -534,9 +521,9 @@ def get_settings_menu(user_id: int): title = "✅ " + title buttons.append( - InlineKeyboardButton(title, callback_data=f"set_settings|{model_key}") + [InlineKeyboardButton(title, callback_data=f"set_settings|{model_key}")] ) - reply_markup = InlineKeyboardMarkup([buttons]) + reply_markup = InlineKeyboardMarkup(buttons) return text, reply_markup @@ -657,14 +644,17 @@ async def post_init(application: Application): BotCommand("/balance", "Show balance"), BotCommand("/settings", "Show settings"), BotCommand("/help", "Show help message"), + BotCommand("/vision", "Use GPT-4 to understand images"), ]) def run_bot() -> None: application = ( - ApplicationBuilder() + Application.builder() .token(config.telegram_token) .concurrent_updates(True) .rate_limiter(AIORateLimiter(max_retries=5)) + .http_version("1.1") + .get_updates_http_version("1.1") .post_init(post_init) .build() ) @@ -673,8 +663,12 @@ def run_bot() -> None: user_filter = filters.ALL if len(config.allowed_telegram_usernames) > 0: usernames = [x for x in config.allowed_telegram_usernames if isinstance(x, str)] - user_ids = [x for x in config.allowed_telegram_usernames if isinstance(x, int)] - user_filter = filters.User(username=usernames) | filters.User(user_id=user_ids) + any_ids = [x for x in config.allowed_telegram_usernames if isinstance(x, int)] + user_ids = [x for x in any_ids if x > 0] + group_ids = [x for x in any_ids if x < 0] + user_filter = filters.User(username=usernames) | filters.User(user_id=user_ids) | filters.Chat(chat_id=group_ids) + + user_filter = user_filter & ~VISION_FILTER application.add_handler(CommandHandler("start", start_handle, filters=user_filter)) application.add_handler(CommandHandler("help", help_handle, filters=user_filter)) @@ -696,6 +690,8 @@ def run_bot() -> None: application.add_handler(CommandHandler("balance", show_balance_handle, filters=user_filter)) + application.add_handler(VISION_CONVERSATION_HANDLER) + application.add_error_handler(error_handle) # start the bot @@ -703,4 +699,4 @@ def run_bot() -> None: if __name__ == "__main__": - run_bot() \ No newline at end of file + run_bot() diff --git a/bot/config.py b/bot/config.py index b0280d469..7f4d23cdb 100644 --- a/bot/config.py +++ b/bot/config.py @@ -14,11 +14,12 @@ # 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) +openai_api_base = config_yaml.get("openai_api_base", None) allowed_telegram_usernames = config_yaml["allowed_telegram_usernames"] new_dialog_timeout = config_yaml["new_dialog_timeout"] enable_message_streaming = config_yaml.get("enable_message_streaming", True) return_n_generated_images = config_yaml.get("return_n_generated_images", 1) +image_size = config_yaml.get("image_size", "512x512") n_chat_modes_per_page = config_yaml.get("n_chat_modes_per_page", 5) mongodb_uri = f"mongodb://mongo:{config_env['MONGODB_PORT']}" diff --git a/bot/openai_utils.py b/bot/openai_utils.py index cdb0110a2..c24ceee10 100644 --- a/bot/openai_utils.py +++ b/bot/openai_utils.py @@ -2,12 +2,16 @@ import tiktoken import openai -openai.api_key = config.openai_api_key +from openai import AsyncOpenAI +aclient = AsyncOpenAI(api_key=config.openai_api_key, base_url=config.openai_api_base) +available_models = config.models["available_text_models"] + +# setup openai OPENAI_COMPLETION_OPTIONS = { "temperature": 0.7, - "max_tokens": 1000, + "max_tokens": 2000, "top_p": 1, "frequency_penalty": 0, "presence_penalty": 0 @@ -16,7 +20,7 @@ class ChatGPT: def __init__(self, model="gpt-3.5-turbo"): - assert model in {"text-davinci-003", "gpt-3.5-turbo", "gpt-4"}, f"Unknown model: {model}" + assert model in available_models, f"Unknown model: {model}" self.model = model async def send_message(self, message, dialog_messages=[], chat_mode="assistant"): @@ -27,28 +31,20 @@ async def send_message(self, message, dialog_messages=[], chat_mode="assistant") answer = None while answer is None: try: - if self.model in {"gpt-3.5-turbo", "gpt-4"}: + if self.model in available_models: messages = self._generate_prompt_messages(message, dialog_messages, chat_mode) - r = await openai.ChatCompletion.acreate( + r = await aclient.chat.completions.create( model=self.model, messages=messages, **OPENAI_COMPLETION_OPTIONS ) - answer = r.choices[0].message["content"] - elif self.model == "text-davinci-003": - prompt = self._generate_prompt(message, dialog_messages, chat_mode) - r = await openai.Completion.acreate( - engine=self.model, - prompt=prompt, - **OPENAI_COMPLETION_OPTIONS - ) - answer = r.choices[0].text - else: + answer = r.choices[0].message.content + raise ValueError(f"Unknown model: {self.model}") answer = self._postprocess_answer(answer) n_input_tokens, n_output_tokens = r.usage.prompt_tokens, r.usage.completion_tokens - except openai.error.InvalidRequestError as e: # too many tokens + except openai.OpenAIError 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,9 +63,9 @@ async def send_message_stream(self, message, dialog_messages=[], chat_mode="assi answer = None while answer is None: try: - if self.model in {"gpt-3.5-turbo", "gpt-4"}: + if self.model in available_models: messages = self._generate_prompt_messages(message, dialog_messages, chat_mode) - r_gen = await openai.ChatCompletion.acreate( + r_gen = await aclient.chat.completions.create( model=self.model, messages=messages, stream=True, @@ -79,30 +75,15 @@ async def send_message_stream(self, message, dialog_messages=[], chat_mode="assi answer = "" async for r_item in r_gen: delta = r_item.choices[0].delta - if "content" in delta: + if delta.content: answer += delta.content n_input_tokens, n_output_tokens = self._count_tokens_from_messages(messages, answer, model=self.model) n_first_dialog_messages_removed = n_dialog_messages_before - len(dialog_messages) yield "not_finished", answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed - elif self.model == "text-davinci-003": - prompt = self._generate_prompt(message, dialog_messages, chat_mode) - r_gen = await openai.Completion.acreate( - engine=self.model, - prompt=prompt, - stream=True, - **OPENAI_COMPLETION_OPTIONS - ) - - answer = "" - async for r_item in r_gen: - answer += r_item.choices[0].text - n_input_tokens, n_output_tokens = self._count_tokens_from_prompt(prompt, answer, model=self.model) - n_first_dialog_messages_removed = n_dialog_messages_before - len(dialog_messages) - yield "not_finished", answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed answer = self._postprocess_answer(answer) - except openai.error.InvalidRequestError as e: # too many tokens + except openai.OpenAIError as e: # too many tokens if len(dialog_messages) == 0: raise e @@ -143,54 +124,74 @@ def _postprocess_answer(self, answer): answer = answer.strip() return answer - def _count_tokens_from_messages(self, messages, answer, model="gpt-3.5-turbo"): - encoding = tiktoken.encoding_for_model(model) - - if model == "gpt-3.5-turbo": - tokens_per_message = 4 # every message follows {role/name}\n{content}\n - tokens_per_name = -1 # if there's a name, the role is omitted - elif model == "gpt-4": + def _num_tokens_from_messages(self, messages, answer, model="gpt-3.5-turbo-0613"): + """Return the number of tokens used by a list of messages.""" + try: + encoding = tiktoken.encoding_for_model(model) + except KeyError: + print("Warning: model not found. Using cl100k_base encoding.") + encoding = tiktoken.get_encoding("cl100k_base") + if model in { + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-16k-0613", + "gpt-4-0314", + "gpt-4-32k-0314", + "gpt-4-0613", + "gpt-4-32k-0613", + }: tokens_per_message = 3 tokens_per_name = 1 + elif model == "gpt-3.5-turbo-0301": + tokens_per_message = 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n + tokens_per_name = -1 # if there's a name, the role is omitted + elif "gpt-3.5-turbo" in model: + print("Warning: gpt-3.5-turbo may update over time. Returning num tokens assuming gpt-3.5-turbo-0613.") + return self._num_tokens_from_messages(messages, answer, model="gpt-3.5-turbo-0613") + elif "gpt-4" in model: + print("Warning: gpt-4 may update over time. Returning num tokens assuming gpt-4-0613.") + return self._num_tokens_from_messages(messages, answer, model="gpt-4-0613") else: - raise ValueError(f"Unknown model: {model}") - - # input - n_input_tokens = 0 + raise NotImplementedError( + f"""num_tokens_from_messages() is not implemented for model {model}. See https://github.com/openai/openai-python/blob/main/chatml.md for information on how messages are converted to tokens.""" + ) + num_tokens = 0 for message in messages: - n_input_tokens += tokens_per_message + num_tokens += tokens_per_message for key, value in message.items(): - n_input_tokens += len(encoding.encode(value)) + num_tokens += len(encoding.encode(value)) if key == "name": - n_input_tokens += tokens_per_name - - n_input_tokens += 2 - - # output - n_output_tokens = 1 + len(encoding.encode(answer)) - - return n_input_tokens, n_output_tokens + num_tokens += tokens_per_name + num_tokens += 3 # every reply is primed with <|start|>assistant<|message|> - def _count_tokens_from_prompt(self, prompt, answer, model="text-davinci-003"): - encoding = tiktoken.encoding_for_model(model) + return num_tokens, len(encoding.encode(answer)) - n_input_tokens = len(encoding.encode(prompt)) + 1 - n_output_tokens = len(encoding.encode(answer)) - - return n_input_tokens, n_output_tokens + def _count_tokens_from_messages(self, messages, answer, model="gpt-3.5-turbo"): + return self._num_tokens_from_messages(messages, answer, model) -async def transcribe_audio(audio_file): - r = await openai.Audio.atranscribe("whisper-1", audio_file) - return r["text"] +async def transcribe_audio(audio_file) -> str: + r = await aclient.audio.transcribe("whisper-1", audio_file) + return r["text"] or "" -async def generate_images(prompt, n_images=4): - r = await openai.Image.acreate(prompt=prompt, n=n_images, size="512x512") +async def generate_images(prompt, n_images=4, size="512x512"): + r = await aclient.images.generate(prompt=prompt, n=n_images, size=size) image_urls = [item.url for item in r.data] return image_urls async def is_content_acceptable(prompt): - r = await openai.Moderation.acreate(input=prompt) + r = await aclient.moderations.create(input=prompt) return not all(r.results[0].categories.values()) + + +async def understand_images(messages): + r = await aclient.chat.completions.create( + model="gpt-4-vision-preview", + messages=messages, + **OPENAI_COMPLETION_OPTIONS + ) + answer = r.choices[0].message.content + n_input_tokens, n_output_tokens = r.usage.prompt_tokens, r.usage.completion_tokens + + return answer, (n_input_tokens, n_output_tokens) diff --git a/bot/vision_handle.py b/bot/vision_handle.py new file mode 100644 index 000000000..a39a0e5e8 --- /dev/null +++ b/bot/vision_handle.py @@ -0,0 +1,174 @@ +import base64 +from telegram.ext import ( + CommandHandler, + MessageHandler, + ConversationHandler, + CallbackContext, + filters, +) +import enum +import openai +from telegram import Update, ReplyKeyboardMarkup, KeyboardButton +from telegram.constants import ParseMode + +from openai_utils import understand_images + +messages = [ + { + "role": "user", + "content": [], + } +] + + +class State(enum.Enum): + UPLOAD_IMAGE = 1 + DESC_TEXT = 2 + PROCESS_CONTENT = 3 + + +def encode_image(image_bytes): + return base64.b64encode(image_bytes).decode("utf-8") + + +class State(enum.Enum): + UPLOAD_IMAGE = 1 + DESC_TEXT = 2 + PROCESS_CONTENT = 3 + + +async def start_vision(update: Update, context: CallbackContext) -> None: + keyboard = [ + [ + KeyboardButton("VISION : Upload images"), + KeyboardButton("VISION : Stop uploading"), + ], + [KeyboardButton("VISION : Restart uploading")], + ] + reply_markup = ReplyKeyboardMarkup(keyboard, one_time_keyboard=True) + await update.message.reply_text("Choose your choice!", reply_markup=reply_markup) + + return State.UPLOAD_IMAGE + + +async def image_handler(update: Update, context: CallbackContext) -> None: + user_id = update.message.from_user.id + + placeholder_message = await update.message.reply_text( + "Start to upload images!", reply_markup=None + ) + + photo_file = await update.message.photo[-1].get_file() + image_bytes = await photo_file.download_as_bytearray() + img_base64 = encode_image(image_bytes) + + image_message = { + "type": "image_url", + "image_url": f"data:image/jpeg;base64,{img_base64}", + } + messages[0]["content"].append(image_message) + + await context.bot.edit_message_text( + f"Decode image successfully!", + chat_id=placeholder_message.chat_id, + message_id=placeholder_message.message_id, + parse_mode=ParseMode.MARKDOWN, + reply_markup=None, + ) + + await start_vision(update, context) + + return State.UPLOAD_IMAGE + + +async def continue_upload_image_handle( + update: Update, context: CallbackContext +) -> None: + if update.message.text.endswith("Stop uploading"): + await update.message.reply_text("Say something about the image. The message must start with **VISION : **.", parse_mode=ParseMode.MARKDOWN) + await update.message.reply_text("**VISION : **", parse_mode=ParseMode.MARKDOWN) + return State.DESC_TEXT + elif update.message.text.endswith("Upload images"): + await update.message.reply_text("Please upload images!") + return State.UPLOAD_IMAGE + elif update.message.text.endswith("Restart uploading"): + messages[0]["content"] = [] + await update.message.reply_text("Clean all upload images!") + await start_vision(update, context) + return State.UPLOAD_IMAGE + else: + await update.message.reply_text( + "The option must be **Upload images** or **Stop uploading**!", + parse_mode=ParseMode.MARKDOWN, + ) + return State.UPLOAD_IMAGE + + +async def image_text_handle(update: Update, context: CallbackContext) -> None: + filter_text = update.message.text[6:] + text_message = {"type": "text", "text": f"{filter_text}"} + messages[0]["content"].append(text_message) + + keyboard = [[KeyboardButton("VISION : Done")]] + reply_markup = ReplyKeyboardMarkup(keyboard, one_time_keyboard=True) + await update.message.reply_text("Click the done button!", reply_markup=reply_markup) + + return State.PROCESS_CONTENT + + +async def done(update: Update, context: CallbackContext) -> None: + user_id = update.message.from_user.id + + placeholder_message = await update.message.reply_text("...", reply_markup=None) + await update.message.chat.send_action(action="typing") + + try: + text, (n_input_tokens, n_output_tokens) = await understand_images(messages) + except openai.OpenAIError as e: + if str(e).startswith( + "Your request was rejected as a result of our safety system" + ): + text = "🥲 Your request doesn't comply with OpenAI's usage policies.\nWhat did you write there, huh?" + await context.bot.edit_message_text( + text, + chat_id=placeholder_message.chat_id, + message_id=placeholder_message.message_id, + parse_mode=ParseMode.MARKDOWN, + ) + return + else: + raise + + await context.bot.edit_message_text( + text, + chat_id=placeholder_message.chat_id, + message_id=placeholder_message.message_id, + parse_mode=ParseMode.MARKDOWN, + reply_markup=None, + ) + + messages[0]["content"] = [] + + return ConversationHandler.END + + +VISION_FILTER = filters.Regex(r"^VISION") +VISION_CONVERSATION_HANDLER = ConversationHandler( + entry_points=[CommandHandler("vision", start_vision)], + states={ + State.UPLOAD_IMAGE: [ + MessageHandler(filters.PHOTO, image_handler), + MessageHandler( + filters.TEXT & ~filters.COMMAND & VISION_FILTER, + continue_upload_image_handle, + ), + ], + State.DESC_TEXT: [ + MessageHandler( + filters.TEXT & ~filters.COMMAND & VISION_FILTER, image_text_handle + ), + ], + State.PROCESS_CONTENT: [MessageHandler(filters.TEXT & VISION_FILTER, done)], + }, + fallbacks=[CommandHandler("done", done)], +) diff --git a/config/chat_modes.yml b/config/chat_modes.yml index 3030af635..f134d32ae 100644 --- a/config/chat_modes.yml +++ b/config/chat_modes.yml @@ -70,11 +70,11 @@ motivator: money_maker: name: 💰 Money Maker - welcome_message: 💰 Hi, I'm Money Maker. My goal it to turn your capital initial into as much money as possible. I will tell you specific steps, what to do to make money. + welcome_message: 💰 Hi, I'm Money Maker. My goal is to turn your initial capital into as much money as possible. I will tell you specific steps and what to do to make money. prompt_start: | You are Money Maker Assistant, an entrepreneurial Al. User is your human counterpart. - User can act as a connection between you and the physical world. Your only goal is to turn user's capital initial into as much money as possible in the shortest time possible, without doing anything illegal. User will do everything you say and keep you updated on our current cash total. No manual labor + User can act as a connection between you and the physical world. Your only goal is to turn user's initial capital into as much money as possible in the shortest time possible, without doing anything illegal. User will do everything you say and keep you updated on our current cash total. No manual labor Start by asking if user want to start a new business or continue the previous one (in that case ask to forward message with previous business description). Then ask how many dollars user has as a capital initial. diff --git a/config/config.example.yml b/config/config.example.yml index 8fb3e07f2..19dfb95ae 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -1,13 +1,14 @@ telegram_token: "" openai_api_key: "" -use_chatgpt_api: true -allowed_telegram_usernames: [] # if empty, the bot is available to anyone. pass a username string to allow it and/or user ids as integers +openai_api_base: null # leave null to use default api base or you can put your own base url here +allowed_telegram_usernames: [] # if empty, the bot is available to anyone. pass a username string to allow it and/or user ids as positive integers and/or channel ids as negative integers new_dialog_timeout: 600 # new dialog starts after timeout (in seconds) return_n_generated_images: 1 n_chat_modes_per_page: 5 +image_size: "512x512" # the image size for image generation. Generated images can have a size of 256x256, 512x512, or 1024x1024 pixels. Smaller sizes are faster to generate. enable_message_streaming: true # if set, messages will be shown to user word-by-word # prices chatgpt_price_per_1000_tokens: 0.002 gpt_price_per_1000_tokens: 0.02 -whisper_price_per_1_min: 0.006 \ No newline at end of file +whisper_price_per_1_min: 0.006 diff --git a/config/models.yml b/config/models.yml index 70f423bc0..6a1891bb0 100644 --- a/config/models.yml +++ b/config/models.yml @@ -1,12 +1,21 @@ -available_text_models: ["gpt-3.5-turbo", "gpt-4", "text-davinci-003"] +available_text_models: + [ + "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-0613", + "gpt-3.5-turbo-16k-0613", + "gpt-4-1106-preview", + "gpt-4-vision-preview", + "gpt-4-0613", + "gpt-4-32k-0613", + ] info: - gpt-3.5-turbo: + gpt-3.5-turbo-1106: type: chat_completion - name: ChatGPT - description: ChatGPT is that well-known model. It's fast and cheap. Ideal for everyday tasks. If there are some tasks it can't handle, try the GPT-4. + name: GPT-3.5 Turbo 1106 (16K) + description: GPT-3.5 Turbo models are capable and cost-effective. The most cost-effective model. - price_per_1000_input_tokens: 0.002 + price_per_1000_input_tokens: 0.001 price_per_1000_output_tokens: 0.002 scores: @@ -14,36 +23,88 @@ info: Fast: 5 Cheap: 5 - gpt-4: + gpt-3.5-turbo-0613: type: chat_completion - name: GPT-4 - description: GPT-4 is the smartest and most advanced model in the world. But it is slower and not as cost-efficient as ChatGPT. Best choice for complex intellectual tasks. + name: GPT-3.5 Turbo (4K) + description: GPT-3.5 Turbo models are capable and cost-effective. + + price_per_1000_input_tokens: 0.0015 + price_per_1000_output_tokens: 0.002 + + scores: + Smart: 3 + Fast: 5 + Cheap: 4 + + gpt-3.5-turbo-16k-0613: + type: chat_completion + name: GPT-3.5 Turbo (16k) + description: GPT-3.5 Turbo models are capable and cost-effective. + + price_per_1000_input_tokens: 0.003 + price_per_1000_output_tokens: 0.004 + + scores: + Smart: 3 + Fast: 5 + Cheap: 4 + + gpt-4-1106-preview: + type: chat_completion + name: GPT4 Turbo + description: With 128k context, fresher knowledge and the broadest set of capabilities, GPT-4 Turbo is more powerful than GPT-4 and offered at a lower price. + + price_per_1000_input_tokens: 0.01 + price_per_1000_output_tokens: 0.03 + + scores: + Smart: 5 + Fast: 3 + Cheap: 2 + + gpt-4-vision-preview: + type: chat_completion + name: GPT-4 Turbo with vision + description: With 128k context, fresher knowledge and the broadest set of capabilities, GPT-4 Turbo is more powerful than GPT-4 and offered at a lower price. + + price_per_1000_input_tokens: 0.01 + price_per_1000_output_tokens: 0.03 + + scores: + Smart: 5 + Fast: 3 + Cheap: 2 + + gpt-4-0613: + type: chat_completion + name: GPT4 (8K) + description: With broad general knowledge and domain expertise, GPT-4 can follow complex instructions in natural language and solve difficult problems with accuracy. price_per_1000_input_tokens: 0.03 price_per_1000_output_tokens: 0.06 scores: Smart: 5 - Fast: 2 - Cheap: 2 + Fast: 3 + Cheap: 1 - text-davinci-003: - type: completion - name: GPT-3.5 - description: GPT-3.5 is a legacy model. Actually there is no reason to use it, because it is more expensive and slower than ChatGPT, but just about as smart. + gpt-4-32k-0613: + type: chat_completion + name: GPT4 (32K) + description: With broad general knowledge and domain expertise, GPT-4 can follow complex instructions in natural language and solve difficult problems with accuracy. - price_per_1000_input_tokens: 0.02 - price_per_1000_output_tokens: 0.02 + price_per_1000_input_tokens: 0.06 + price_per_1000_output_tokens: 0.12 scores: - Smart: 3 - Fast: 2 - Cheap: 3 + Smart: 5 + Fast: 3 + Cheap: 1 dalle-2: - type: image - price_per_1_image: 0.018 # 512x512 + type: image + price_per_1_image: 0.018 # 512x512 whisper: type: audio - price_per_1_min: 0.006 \ No newline at end of file + price_per_1_min: 0.006 diff --git a/requirements.txt b/requirements.txt index 354063b17..3ee427042 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ -python-telegram-bot[rate-limiter]==20.1 -openai>=0.27.0 +python-telegram-bot[rate-limiter,socks]==20.1 +openai==1.2.0 tiktoken>=0.3.0 PyYAML==6.0 pymongo==4.3.3 python-dotenv==0.21.0 -pydub==0.25.1 \ No newline at end of file +pysocks \ No newline at end of file