├── images └── .gitkeep ├── sessions └── .gitkeep ├── config.py ├── requirements.txt ├── README.md ├── .gitignore └── Gemini-Bot.py /images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sessions/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | ## CONFIGURATION FILE 2 | 3 | # Bot session name 4 | session_name_bot = "sessions/bot" 5 | 6 | # Follow this guide to obtain your API credentials: 7 | # https://core.telegram.org/api/obtaining_api_id 8 | API_ID = "" 9 | API_HASH = "" 10 | 11 | # Write to @BotFather in Telegram to create a bot token 12 | BOT_TOKEN = "" 13 | 14 | # Configurations of vertexAi 15 | project_id = "" 16 | location = "" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cachetools==5.3.2 2 | certifi==2024.2.2 3 | charset-normalizer==3.3.2 4 | google-api-core==2.17.1 5 | google-auth==2.28.0 6 | google-cloud-aiplatform==1.42.1 7 | google-cloud-bigquery==3.17.2 8 | google-cloud-core==2.4.1 9 | google-cloud-resource-manager==1.12.1 10 | google-cloud-storage==2.14.0 11 | google-crc32c==1.5.0 12 | google-resumable-media==2.7.0 13 | googleapis-common-protos==1.62.0 14 | grpc-google-iam-v1==0.13.0 15 | grpcio==1.60.1 16 | grpcio-status==1.60.1 17 | idna==3.6 18 | numpy==1.26.4 19 | packaging==23.2 20 | pillow==10.2.0 21 | proto-plus==1.23.0 22 | protobuf==4.25.3 23 | pyaes==1.6.1 24 | pyasn1==0.5.1 25 | pyasn1-modules==0.3.0 26 | python-dateutil==2.8.2 27 | requests==2.31.0 28 | rsa==4.9 29 | shapely==2.0.3 30 | six==1.16.0 31 | Telethon==1.34.0 32 | urllib3==2.2.0 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **Gemini Bot** 2 | 3 | Check the video explanation here: https://youtu.be/xmSuoA4blmk 4 | 5 | This code provides a Python implementation of a Telegram Chatbot powered by Vertex AI's generative models. The bot uses the Telethon Python library to interact with the Telegram Bot API and Google Cloud's Vertex AI API for generative models. 6 | 7 | ## **Installation** 8 | 9 | Clone the repository and navigate to the project directory: 10 | 11 | ```bash 12 | git clone https://github.com/yourusername/Gemini-Bot.git 13 | cd Gemini-Bot 14 | ``` 15 | 16 | Install the required packages: 17 | 18 | ```bash 19 | pip install -r requirements.txt 20 | ``` 21 | 22 | ## **Configuration** 23 | 24 | The bot requires Google Cloud credentials and Telegram Bot API credentials to function. You must create a **`config.py`** file in the same directory as the **`gemini-bot.py`** file and include the following variables: 25 | 26 | ```python 27 | # config.py 28 | API_ID = 'your_telegram_api_id' 29 | API_HASH = 'your_telegram_api_hash' 30 | BOT_TOKEN = 'your_telegram_bot_token' 31 | project_id = 'your_google_cloud_project_id' 32 | location = 'your_google_cloud_location' 33 | session_name_bot = 'gemini_bot' 34 | ``` 35 | 36 | You can obtain a Telegram API ID and hash by following the instructions [here](https://core.telegram.org/api/obtaining_api_id). To obtain a bot token, you can talk to [BotFather](https://telegram.me/botfather) on Telegram. For Google Cloud credentials, follow the instructions on the [Google Cloud Documentation](https://cloud.google.com/vertex-ai/docs/python-sdk/use-vertex-ai-python-sdk). 37 | 38 | ## **Usage** 39 | 40 | To start the bot, run the following command in your terminal: 41 | 42 | ```bash 43 | python gemini-bot.py 44 | ``` 45 | 46 | This will start the bot and wait for user queries. Once the bot is running, you can interact with it by sending messages to it in Telegram. 47 | 48 | The bot will respond to any message it receives by generating a response using the Vertex AI API and sending it back to the user. The bot will continue generating responses until the conversation times out or the user clicks the "Stop and reset conversation" button. 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | -------------------------------------------------------------------------------- /Gemini-Bot.py: -------------------------------------------------------------------------------- 1 | # Importing Libraries 2 | import telethon # Library to interact with Telegram's API as a user or through a bot account 3 | from telethon.tl.custom import Button 4 | from telethon import TelegramClient, events 5 | 6 | import asyncio # Provides infrastructure for writing asynchronous code using coroutines. 7 | 8 | # Imports for handling images and bytes 9 | from io import BytesIO 10 | from PIL import Image 11 | 12 | import config # Custom file containing configuration settings for the bot. 13 | 14 | # Import necessary modules from the vertexai library 15 | import vertexai 16 | from vertexai.generative_models._generative_models import HarmCategory, HarmBlockThreshold 17 | from vertexai.preview.generative_models import ( 18 | GenerativeModel, 19 | ChatSession, 20 | Part 21 | ) 22 | 23 | # Configuration settings for the generative model 24 | generation_config = { 25 | "temperature": 0.7, # Controls the randomness of generated output (lower values make output more deterministic) 26 | "top_p": 1, # Top-p nucleus sampling parameter (controls the probability mass to consider for sampling) 27 | "top_k": 1, # Top-k sampling parameter (controls the number of highest probability tokens to consider for sampling) 28 | "max_output_tokens": 2048, # Maximum number of tokens to generate in the output 29 | } 30 | 31 | # Safety settings to control harmful content blocking thresholds 32 | safety_settings = { 33 | HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE, # No blocking for dangerous content 34 | HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_NONE, # No blocking for hate speech 35 | HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_NONE, # No blocking for harassment 36 | HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE, # No blocking for sexually explicit content 37 | } 38 | 39 | # Initialize Vertex AI with project and location 40 | vertexai.init(project=config.project_id, location=config.location) 41 | 42 | # Initialize generative models 43 | model = GenerativeModel("gemini-pro", generation_config=generation_config, safety_settings=safety_settings) 44 | vision_model = GenerativeModel("gemini-pro-vision", generation_config=generation_config, safety_settings=safety_settings) 45 | 46 | # Configure Telegram client 47 | client = TelegramClient(config.session_name_bot, config.API_ID, config.API_HASH).start(bot_token=config.BOT_TOKEN) 48 | 49 | # Define button templates 50 | keyboard_stop = [[Button.inline("Stop and reset conversation", b"stop")]] 51 | 52 | # Define helper function to retrieve a message from a conversation and handle button clicks 53 | async def send_question_and_retrieve_result(prompt, conv, keyboard): 54 | """ 55 | Sends a question to the user and retrieves their response. 56 | 57 | Args: 58 | prompt (str): The question to ask the user. 59 | conv (telethon.client.conversations.Conversation): The conversation object to use for sending the message. 60 | keyboard (list): The keyboard to send with the message. 61 | 62 | Returns: 63 | Tuple[Union[events.callbackquery.CallbackQuery.Event, str], telethon.types.Message]: A tuple containing the user's response and the message object. 64 | """ 65 | # Send the prompt with the keyboard to the user and store the sent message object 66 | message = await conv.send_message(prompt, buttons = keyboard) 67 | 68 | loop = asyncio.get_event_loop() 69 | 70 | # Create tasks to wait for the user to respond or tap a button 71 | task1 = loop.create_task( 72 | conv.wait_event(events.CallbackQuery()) 73 | ) 74 | task2 = loop.create_task( 75 | conv.get_response() 76 | ) 77 | 78 | # Wait for the user to respond or tap a button using asyncio.wait() 79 | done, _ = await asyncio.wait({task1, task2}, return_when=asyncio.FIRST_COMPLETED) 80 | 81 | # Retrieve the result of the completed coroutine and delete the sent message 82 | result = done.pop().result() 83 | await message.delete() 84 | 85 | # Return the user's response or None if they tapped a button 86 | if isinstance(result, events.CallbackQuery.Event): 87 | return None 88 | else: 89 | return result 90 | 91 | 92 | # Define the main chatbot handler 93 | @client.on(events.NewMessage(pattern="(?i)/chat")) 94 | async def handle_chat_command(event): 95 | """ 96 | Starts a new conversation with the user. 97 | 98 | Args: 99 | event (telethon.events.NewMessage): The event that triggered this function. 100 | """ 101 | 102 | def get_chat_response(chat: ChatSession, prompt: str) -> str: 103 | """ 104 | Sends a prompt to the chat model and returns the response. 105 | 106 | Args: 107 | chat (vertexai.preview.generative_models.ChatSession): The chat session. 108 | prompt (str): The prompt to send to the chat model. 109 | 110 | Returns: 111 | str: The response from the chat model. 112 | """ 113 | response = chat.send_message(prompt) 114 | return response.text 115 | 116 | # Get the sender's ID 117 | SENDER = event.sender_id 118 | 119 | try: 120 | # Start a conversation 121 | async with client.conversation(await event.get_chat(), exclusive=True, timeout=600) as conv: 122 | # Create an empty history to store chat history 123 | chat = model.start_chat(history=[]) 124 | 125 | # Keep asking for input and generating responses until the conversation times out or the user clicks the stop button 126 | while True: 127 | # Prompt the user for input 128 | prompt = "Provide your input to Gemini Bot..." 129 | user_input = await send_question_and_retrieve_result(prompt, conv, keyboard_stop) 130 | 131 | # Check if the user clicked the stop button 132 | if user_input is None: 133 | # If the user clicked the stop button, send a prompt to reset the conversation 134 | prompt = "Conversation will be reset. Type /chat to start a new one!" 135 | await client.send_message(SENDER, prompt) 136 | break 137 | else: 138 | user_input = user_input.message.strip() 139 | # Send a "I'm thinking message..." 140 | prompt = "Received! I'm thinking about the response..." 141 | thinking_message = await client.send_message(SENDER, prompt) 142 | 143 | # Retrieve Gemini response 144 | response = (get_chat_response(chat, user_input)) 145 | 146 | # Delete the Thinking message 147 | await thinking_message.delete() 148 | 149 | # Send the response to the user 150 | await client.send_message(SENDER, response, parse_mode='Markdown') 151 | 152 | 153 | except asyncio.TimeoutError: 154 | # Conversation timed out 155 | await client.send_message(SENDER, "Conversation ended✔️\nIt's been too long since your last response.", parse_mode='html') 156 | return 157 | 158 | except telethon.errors.common.AlreadyInConversationError: 159 | # User already in conversation 160 | pass 161 | 162 | except Exception as e: 163 | # Something went wrong 164 | await client.send_message(SENDER, "Conversation ended✔️\nSomething went wrong.", parse_mode='html') 165 | return 166 | 167 | 168 | @client.on(events.NewMessage(pattern="(?i)/image")) 169 | async def handle_image_command(event): 170 | """ 171 | Handles the /image command, where the bot requests the user to send an image 172 | and then processes the image using a vision model. 173 | 174 | Args: 175 | event (telethon.events.NewMessage): The event that triggered this function. 176 | """ 177 | 178 | # Get the sender's ID 179 | SENDER = event.sender_id 180 | try: 181 | # Start a conversation to handle image processing 182 | async with client.conversation(await event.get_chat(), exclusive=True, timeout=600) as conv: 183 | prompt = "Send me an image, and I'll tell you what is shown inside." 184 | 185 | # Ask the user for input and retrieve the response 186 | user_input = await send_question_and_retrieve_result(prompt, conv, keyboard_stop) 187 | 188 | # Check if the user clicked the stop button 189 | if user_input is None: 190 | await client.send_message(SENDER, "Conversation will be reset. Type /image to send me another image.", parse_mode='Markdown') 191 | return 192 | else: 193 | # Check if the user provided a valid image 194 | if user_input.photo: 195 | 196 | prompt = "Received! I'm thinking about the response..." 197 | thinking_message = await client.send_message(SENDER, prompt) 198 | 199 | photo_entity = user_input.photo 200 | 201 | # Download the image and open it using PIL 202 | photo_path = await client.download_media(photo_entity, file="images/") 203 | image = Image.open(photo_path) 204 | 205 | # Convert the image to JPEG format 206 | image_buf = BytesIO() 207 | image.save(image_buf, format="JPEG") 208 | image_bytes = image_buf.getvalue() 209 | 210 | # Generate content using the vision model 211 | response = vision_model.generate_content( 212 | [ 213 | Part.from_data( 214 | image_bytes, mime_type="image/jpeg" 215 | ), 216 | "What is shown in this image?", 217 | ] 218 | ) 219 | 220 | # Delete the Thinking message 221 | await thinking_message.delete() 222 | 223 | # Send the response to the user 224 | await client.send_message(SENDER, response.text, parse_mode='Markdown') 225 | 226 | else: 227 | # Inform the user that the input is not valid 228 | await client.send_message(SENDER, "Input not valid. Please send me an image after using the /image command.", parse_mode='Markdown') 229 | 230 | except asyncio.TimeoutError: 231 | # Conversation timed out 232 | await client.send_message(SENDER, "Conversation ended✔️\nIt's been too long since your last response.", parse_mode='html') 233 | return 234 | 235 | except telethon.errors.common.AlreadyInConversationError: 236 | # User already in conversation 237 | pass 238 | 239 | except Exception as e: 240 | # Something went wrong 241 | await client.send_message(SENDER, "Conversation ended✔️\nSomething went wrong.", parse_mode='html') 242 | return 243 | 244 | 245 | @client.on(events.NewMessage(pattern="(?i)/start")) 246 | async def handle_start_command(event): 247 | text = """Hello there! I'm Gemini 🤖, your friendly chatbot. I can answer your questions in a conversational manner and even recognize the contents of images. Let's get started! 248 | 249 | /chat: Initiate a chat with me. 250 | /image: Share an image and learn about its contents. 251 | 252 | Feel free to explore and ask me anything!""" 253 | 254 | await client.send_message(event.sender_id, text) 255 | 256 | ## Main function 257 | if __name__ == "__main__": 258 | print("Bot Started...") 259 | client.run_until_disconnected() # Start the bot! 260 | 261 | 262 | --------------------------------------------------------------------------------