2024-08-17 13:00:37 -05:00
import json
2024-08-16 12:43:59 -05:00
import os
import importlib
import inspect
2024-08-17 18:31:53 -05:00
import logging
2024-08-18 18:00:22 -05:00
import asyncio
from telegram import error as TelegramErrors , Update , __version__ as telegram_version , InlineKeyboardButton , InlineKeyboardMarkup
2024-08-18 15:57:45 -05:00
from telegram . ext import Application , CommandHandler , MessageHandler , filters , ContextTypes , CallbackQueryHandler
2024-08-18 14:19:41 -05:00
from openai import OpenAI
2024-08-16 12:43:59 -05:00
from dotenv import load_dotenv
from tools . base_tool import BaseTool
2024-08-18 14:27:20 -05:00
from anthropic import Anthropic
2024-08-16 12:43:59 -05:00
# Load environment variables
load_dotenv ( )
2024-08-18 14:19:41 -05:00
openai_client = OpenAI ( )
2024-08-18 14:27:20 -05:00
anthropic_client = Anthropic (
2024-08-18 14:19:41 -05:00
api_key = os . environ . get ( " ANTHROPIC_API_KEY " ) ,
default_headers = { " anthropic-beta " : " max-tokens-3-5-sonnet-2024-07-15 " }
)
GPT_4O = " gpt-4o "
GPT_4O_MINI = " gpt-4o-mini "
model_max_tokens = {
GPT_4O : 4096 ,
GPT_4O_MINI : 16384
}
use_smart_model = False
use_anthropic = True
2024-08-17 19:30:38 -05:00
# Set up logging to console and file
2024-08-18 07:35:52 -05:00
logging . basicConfig ( level = logging . WARNING , handlers = [
2024-08-17 18:32:08 -05:00
logging . StreamHandler ( ) ,
logging . FileHandler ( ' logs/output.log ' , mode = ' a ' )
] )
2024-08-17 19:30:18 -05:00
2024-08-18 18:00:22 -05:00
# Set up Telegram bot
TELEGRAM_BOT_TOKEN = os . getenv ( ' TELEGRAM_BOT_TOKEN ' )
2024-08-16 12:43:59 -05:00
2024-08-17 17:53:25 -05:00
# Load system prompt
with open ( " prompts/developer_prompt.txt " , " r " ) as file :
system_prompt = file . read ( ) . strip ( )
2024-08-16 12:43:59 -05:00
# Dictionary to store conversation history for each user
conversation_history = { }
2024-08-18 15:57:45 -05:00
# Dictionary to store processing status for each user
processing_status = { }
2024-08-16 12:43:59 -05:00
# Load tools
tools = [ ]
tools_dir = os . path . join ( os . path . dirname ( __file__ ) , ' tools ' )
for filename in os . listdir ( tools_dir ) :
if filename . endswith ( ' .py ' ) and filename != ' __init__.py ' and filename != ' base_tool.py ' :
module_name = f ' tools. { filename [ : - 3 ] } '
module = importlib . import_module ( module_name )
for name , obj in inspect . getmembers ( module ) :
if inspect . isclass ( obj ) and issubclass ( obj , BaseTool ) and obj != BaseTool :
tools . append ( obj ( ) )
# Collect all function definitions
functions = [ ]
for tool in tools :
functions . extend ( tool . get_functions ( ) )
async def start ( update : Update , context : ContextTypes . DEFAULT_TYPE ) - > None :
2024-08-17 18:32:08 -05:00
logging . info ( " Bot started " )
2024-08-17 09:28:17 -05:00
await update . message . reply_text ( " Hello! I ' m your AI assistant. How can I help you today? You can send me images and then ask questions about them. " )
2024-08-16 12:43:59 -05:00
async def clear ( update : Update , context : ContextTypes . DEFAULT_TYPE ) - > None :
user_id = update . effective_user . id
if user_id in conversation_history :
del conversation_history [ user_id ]
2024-08-18 09:19:29 -05:00
for tool in tools :
tool . clear ( )
2024-08-17 18:32:08 -05:00
logging . info ( f " Cleared conversation history and image for user { user_id } " )
2024-08-17 09:28:17 -05:00
await update . message . reply_text ( " Conversation history and image cleared. Let ' s start fresh! " )
2024-08-18 15:57:45 -05:00
async def update_status_message ( context : ContextTypes . DEFAULT_TYPE , chat_id : int , message_id : int , status : str ) :
keyboard = [
[ InlineKeyboardButton ( " Abort " , callback_data = ' abort ' ) ]
]
reply_markup = InlineKeyboardMarkup ( keyboard )
await context . bot . edit_message_text (
chat_id = chat_id ,
message_id = message_id ,
text = f " Current status: { status } " ,
reply_markup = reply_markup
)
2024-08-16 12:43:59 -05:00
async def handle_message ( update : Update , context : ContextTypes . DEFAULT_TYPE ) - > None :
try :
user_id = update . effective_user . id
user_message = update . message . text
2024-08-17 18:32:08 -05:00
logging . info ( f " Message from user { user_id } : { user_message } " )
2024-08-16 12:43:59 -05:00
if user_id not in conversation_history :
conversation_history [ user_id ] = [ ]
conversation_history [ user_id ] . append ( { " role " : " user " , " content " : user_message } )
2024-08-18 15:57:45 -05:00
# Send initial status message
status_message = await update . message . reply_text ( " Processing your request... " , reply_markup = InlineKeyboardMarkup ( [ [ InlineKeyboardButton ( " Abort " , callback_data = ' abort ' ) ] ] ) )
processing_status [ user_id ] = { " processing " : True , " message_id " : status_message . message_id }
2024-08-18 08:55:22 -05:00
messages = conversation_history [ user_id ]
2024-08-16 12:43:59 -05:00
2024-08-18 14:19:41 -05:00
response = get_chat_response ( messages )
tool_calls = [ ]
if use_anthropic :
2024-08-18 15:54:57 -05:00
fullMessage = [ ]
for message_part in response . content :
fullMessage . append ( message_part )
if message_part . type == " tool_use " :
tool_calls . append ( message_part )
messages . append ( { " role " : " assistant " , " content " : fullMessage } )
2024-08-18 14:19:41 -05:00
else :
assistant_message = response . choices [ 0 ] . message
if hasattr ( assistant_message , ' function_call ' ) and assistant_message . function_call is not None :
tool_calls . append ( assistant_message . function_call )
2024-08-16 12:43:59 -05:00
2024-08-18 10:40:59 -05:00
toolUseCount = 0
2024-08-16 12:43:59 -05:00
2024-08-18 15:57:45 -05:00
while len ( tool_calls ) > 0 and toolUseCount < 50 and processing_status [ user_id ] [ " processing " ] :
2024-08-18 15:54:57 -05:00
tool_use_results = [ ]
while len ( tool_calls ) > 0 :
tool_call = tool_calls . pop ( 0 )
function_name = tool_call . name
2024-08-18 15:57:45 -05:00
# Update status message
await update_status_message ( context , update . effective_chat . id , status_message . message_id , f " Using tool: { function_name } " )
2024-08-18 15:54:57 -05:00
tool_response = call_tool ( tool_call )
tool_use_results . append ( { " type " : " tool_result " , " tool_use_id " : tool_call . id , " content " : json . dumps ( tool_response ) } )
2024-08-18 14:19:41 -05:00
formatted_result = { }
2024-08-18 18:00:22 -05:00
formatted_result = { " role " : " user " , " content " : tool_use_results }
2024-08-18 14:19:41 -05:00
2024-08-18 12:16:03 -05:00
messages . append ( formatted_result )
2024-08-18 10:40:59 -05:00
2024-08-18 14:19:41 -05:00
response = get_chat_response ( messages )
assistant_message = " "
if use_anthropic :
2024-08-18 15:54:57 -05:00
fullMessage = [ ]
for message_part in response . content :
fullMessage . append ( message_part )
if message_part . type == " tool_use " :
tool_calls . append ( message_part )
messages . append ( { " role " : " assistant " , " content " : fullMessage } )
2024-08-18 14:19:41 -05:00
else :
assistant_message = response . choices [ 0 ] . message
conversation_history [ user_id ] . append ( { " role " : " assistant " , " content " : assistant_message } )
if hasattr ( assistant_message , ' function_call ' ) and assistant_message . function_call is not None :
tool_calls . append ( assistant_message . function_call )
else :
conversation_history [ user_id ] . append ( { " role " : " assistant " , " content " : assistant_message } )
assistant_reply = assistant_message
2024-08-18 10:40:59 -05:00
toolUseCount + = 1
2024-08-18 14:19:41 -05:00
if ( toolUseCount == 0 ) :
if use_anthropic :
assistant_reply = response . content
else :
assistant_reply = assistant_message
2024-08-18 12:16:03 -05:00
conversation_history [ user_id ] . append ( { " role " : " assistant " , " content " : assistant_reply } )
2024-08-18 10:40:59 -05:00
if len ( conversation_history [ user_id ] ) > 20 :
conversation_history [ user_id ] = conversation_history [ user_id ] [ - 20 : ]
2024-08-18 15:57:45 -05:00
# Remove the status message
await context . bot . delete_message ( chat_id = update . effective_chat . id , message_id = status_message . message_id )
del processing_status [ user_id ]
2024-08-18 18:00:22 -05:00
try :
2024-08-18 14:19:41 -05:00
await update . message . reply_text ( messages [ - 1 ] [ " content " ] [ 0 ] . text )
2024-08-18 18:00:22 -05:00
except TelegramErrors . BadRequest as e :
logging . error ( f " An error occurred when trying to send a message in telegram: { str ( e ) } " )
2024-08-16 12:43:59 -05:00
except Exception as e :
2024-08-17 18:31:53 -05:00
logging . error ( f " An error occurred: { str ( e ) } " )
2024-08-16 12:43:59 -05:00
await update . message . reply_text ( " Sorry, an error occurred while processing your request. " )
2024-08-18 07:35:52 -05:00
def call_tool ( function_call ) :
2024-08-18 14:19:41 -05:00
function_name = function_call . name if use_anthropic else function_call . name
function_args = json . dumps ( function_call . input ) if use_anthropic else function_call . arguments
2024-08-17 13:00:37 -05:00
for tool in tools :
if function_name in [ f [ " name " ] for f in tool . get_functions ( ) ] :
2024-08-18 14:19:41 -05:00
return tool . execute ( function_name , * * json . loads ( function_args ) )
def get_chat_response ( messages ) :
return get_claude_response ( messages ) if use_anthropic else get_openai_response ( messages )
def get_openai_response ( messages ) :
model = GPT_4O if use_smart_model else GPT_4O_MINI
response = openai_client . chat . completions . create (
model = model ,
messages = [ { " role " : " system " , " content " : system_prompt } ] + messages ,
functions = functions ,
function_call = " auto " ,
max_tokens = model_max_tokens [ model ]
)
return response
def get_claude_response ( messages ) :
anthropic_tools = [
{
" name " : function [ ' name ' ] ,
" description " : function [ ' description ' ] ,
" input_schema " : function [ ' parameters ' ] if function [ ' parameters ' ] not in [ None , { } ] else { " type " : " object " , " properties " : { " param1 " : { " type " : " string " , " description " : " Unnecessary " } } , " required " : [ ] }
}
for function in functions
]
try :
response = anthropic_client . messages . create (
model = " claude-3-5-sonnet-20240620 " ,
system = system_prompt ,
messages = messages ,
max_tokens = 8192 ,
tools = anthropic_tools
)
except Exception as e :
logging . error ( f " An error occurred: { str ( e ) } " )
return None
return response
2024-08-18 07:57:18 -05:00
2024-08-18 08:55:22 -05:00
async def switch ( update : Update , context : ContextTypes . DEFAULT_TYPE ) - > None :
2024-08-18 14:19:41 -05:00
global use_smart_model
use_smart_model = not use_smart_model
model = GPT_4O if use_smart_model else GPT_4O_MINI
logging . info ( f " Switched to model: { model } " )
await update . message . reply_text ( f " Switched to model: { model } " )
2024-08-18 07:35:52 -05:00
2024-08-18 10:40:59 -05:00
async def switch_providers ( update : Update , context : ContextTypes . DEFAULT_TYPE ) - > None :
2024-08-18 12:16:03 -05:00
await clear ( update , context )
2024-08-18 14:19:41 -05:00
global use_anthropic
use_anthropic = not use_anthropic
logging . info ( " Using Anthropic " if use_anthropic else " Using OpenAI " )
await update . message . reply_text ( " Using Anthropic " if use_anthropic else " Using OpenAI " )
2024-08-18 07:57:18 -05:00
2024-08-18 07:35:52 -05:00
async def status ( update : Update , context : ContextTypes . DEFAULT_TYPE ) - > None :
2024-08-18 14:19:41 -05:00
if use_anthropic :
await update . message . reply_text ( " Currently using claude-3-5-sonnet-20240620 " )
2024-08-18 07:58:19 -05:00
else :
2024-08-18 14:19:41 -05:00
model = GPT_4O if use_smart_model else GPT_4O_MINI
await update . message . reply_text ( f " Currently using: { model } " )
2024-08-18 07:35:52 -05:00
2024-08-18 15:57:45 -05:00
async def abort_processing ( update : Update , context : ContextTypes . DEFAULT_TYPE ) - > None :
query = update . callback_query
await query . answer ( )
user_id = query . from_user . id
if user_id in processing_status :
processing_status [ user_id ] [ " processing " ] = False
await context . bot . edit_message_text (
chat_id = query . message . chat_id ,
message_id = query . message . message_id ,
text = " Processing aborted. "
)
await clear ( update , context )
else :
await query . edit_message_text ( text = " No active processing to abort. " )
2024-08-16 12:43:59 -05:00
def main ( ) - > None :
# Create the Application and pass it your bot's token
2024-08-18 18:00:22 -05:00
application = Application . builder ( ) . token ( TELEGRAM_BOT_TOKEN ) . build ( )
# Add handlers
application . add_handler ( CommandHandler ( " start " , start ) )
application . add_handler ( CommandHandler ( " clear " , clear ) )
application . add_handler ( CommandHandler ( " switch " , switch ) )
application . add_handler ( CommandHandler ( " toggle " , switch_providers ) )
application . add_handler ( CommandHandler ( " status " , status ) )
application . add_handler ( MessageHandler ( filters . TEXT & ~ filters . COMMAND , handle_message ) )
application . add_handler ( CallbackQueryHandler ( abort_processing , pattern = ' ^abort$ ' ) )
# Start the Bot
logging . info ( " Bot is running... " )
application . run_polling ( )
2024-08-16 12:43:59 -05:00
if __name__ == ' __main__ ' :
2024-08-17 13:00:37 -05:00
main ( )