diff --git a/OPENWEBUI_BOT_README.md b/OPENWEBUI_BOT_README.md new file mode 100644 index 0000000..0c8d81b --- /dev/null +++ b/OPENWEBUI_BOT_README.md @@ -0,0 +1,77 @@ +# OpenWebUI Bot for Channels Integration + +This project integrates the [Open WebUI bot](https://github.com/open-webui/bot) to provide AI responses in OpenWebUI channels. + +## Overview + +The OpenWebUI bot connects to your OpenWebUI instance via WebSocket and listens for messages in channels. When a message mentions the AI (using trigger words like `@ai`), the bot processes the message and sends a response back to the channel. + +## Running the Bot Locally + +To run the bot locally: + +1. Make sure you have Python 3.7+ installed +2. Install the required dependencies: + ```bash + pip install python-socketio aiohttp python-dotenv + ``` +3. Configure the bot by creating a `.env` file in the `openwebui-bot` directory: + ``` + WEBUI_URL=http://104.225.217.215:8080 + TOKEN=GdCU4ieYDqHsLfH2 + MODEL_ID=llama3.1 + ``` +4. Run the bot: + ```bash + python run_openwebui_bot.py + ``` + +## Deploying the Bot on the Server + +To deploy the bot on your server: + +1. Run the deployment script: + ```bash + ./deploy_bot.sh + ``` +2. Follow the instructions provided by the script to copy the deployment package to your server and run the deployment. + +## Bot Configuration + +The bot is configured using environment variables in the `.env` file: + +- `WEBUI_URL`: URL of your OpenWebUI instance (e.g., `http://104.225.217.215:8080`) +- `TOKEN`: API key for authentication +- `MODEL_ID`: ID of the model to use (e.g., `llama3.1`) + +Additional configuration options can be added to the `.env` file: + +``` +SYSTEM_PROMPT="You are a helpful AI assistant." +TEMPERATURE=0.7 +MAX_TOKENS=2048 +TOP_P=0.9 +TRIGGERS=@ai,@bot,@assistant,@chatbot +RESPOND_TO_ALL=false +``` + +## Using the Bot + +Once the bot is running, you can use it in OpenWebUI channels: + +1. Create a channel in OpenWebUI +2. Send a message that mentions the bot (e.g., `@ai What is the capital of France?`) +3. The bot will process your message and send a response + +## Troubleshooting + +If the bot is not responding to messages: + +1. Check that the bot is running and connected to OpenWebUI +2. Verify that you're using one of the trigger words (e.g., `@ai`) +3. Check the logs for any errors +4. Make sure your OpenWebUI URL and API key are correct + +## Logs + +The bot logs to both the console and a file named `bot_debug.log`. You can check this file for detailed information about what the bot is doing and any errors it encounters. diff --git a/ai_service/bot_manager.py b/ai_service/bot_manager.py index 75f948e..dc84518 100644 --- a/ai_service/bot_manager.py +++ b/ai_service/bot_manager.py @@ -216,12 +216,43 @@ async def start_bot( for attempt in range(1, max_retries + 1): try: logger.info(f"Starting bot (attempt {attempt}/{max_retries})...") - bot_task = asyncio.create_task(custom_bot_main()) - logger.info("Bot started successfully!") + + # Define a monitoring task to restart the bot if it fails + async def monitor_bot_task(): + global bot_task + try: + # Start the main bot task + main_task = asyncio.create_task(custom_bot_main()) + + # Wait for the task to complete or fail + try: + await main_task + logger.warning("Bot task completed unexpectedly") + except asyncio.CancelledError: + logger.info("Bot task was cancelled") + raise + except Exception as e: + logger.error(f"Bot task failed with error: {str(e)}") + logger.error(traceback.format_exc()) + + # Try to restart the bot + logger.info("Attempting to restart the bot...") + await asyncio.sleep(5) # Wait a bit before restarting + restart_task = asyncio.create_task(custom_bot_main()) + bot_task = restart_task # Update the global task reference + logger.info("Bot restarted successfully") + except Exception as e: + logger.error(f"Error in monitor task: {str(e)}") + logger.error(traceback.format_exc()) + + # Start the monitoring task + bot_task = asyncio.create_task(monitor_bot_task()) + logger.info("Bot started successfully with monitoring!") return True except Exception as e: last_error = e logger.error(f"Error starting bot (attempt {attempt}/{max_retries}): {str(e)}") + logger.error(traceback.format_exc()) if attempt < max_retries: logger.info(f"Retrying in {retry_delay} seconds...") await asyncio.sleep(retry_delay) diff --git a/deploy_bot.sh b/deploy_bot.sh new file mode 100755 index 0000000..fb4f9e5 --- /dev/null +++ b/deploy_bot.sh @@ -0,0 +1,130 @@ +#!/bin/bash + +# Script to deploy the OpenWebUI bot on the server + +# Set the server details +SERVER_IP="104.225.217.215" +SERVER_USER="root" +DEPLOY_DIR="/root/openwebui" + +# Create a temporary directory for deployment files +TEMP_DIR=$(mktemp -d) +echo "Created temporary directory: $TEMP_DIR" + +# Copy the necessary files to the temporary directory +echo "Copying files to temporary directory..." +cp -r openwebui-bot $TEMP_DIR/ +cp run_openwebui_bot.py $TEMP_DIR/ + +# Create a .env file for the bot +echo "Creating .env file..." +cat > $TEMP_DIR/openwebui-bot/.env << EOF +WEBUI_URL=http://104.225.217.215:8080 +TOKEN=GdCU4ieYDqHsLfH2 +MODEL_ID=llama3.1 +EOF + +# Create a systemd service file for the bot +echo "Creating systemd service file..." +cat > $TEMP_DIR/openwebui-bot.service << EOF +[Unit] +Description=OpenWebUI Bot Service +After=network.target + +[Service] +Type=simple +User=root +WorkingDirectory=$DEPLOY_DIR +ExecStart=/usr/bin/python3 $DEPLOY_DIR/run_openwebui_bot.py +Restart=always +RestartSec=10 + +[Install] +WantedBy=multi-user.target +EOF + +# Create a deployment script +echo "Creating deployment script..." +cat > $TEMP_DIR/deploy.sh << EOF +#!/bin/bash + +# Install required packages +pip install python-socketio aiohttp python-dotenv + +# Copy the bot files to the deployment directory +mkdir -p $DEPLOY_DIR +cp -r openwebui-bot $DEPLOY_DIR/ +cp run_openwebui_bot.py $DEPLOY_DIR/ + +# Set up the systemd service +cp openwebui-bot.service /etc/systemd/system/ +systemctl daemon-reload +systemctl enable openwebui-bot.service +systemctl start openwebui-bot.service + +echo "Bot deployed and started!" +echo "To check the status: systemctl status openwebui-bot.service" +echo "To view logs: journalctl -u openwebui-bot.service -f" +EOF + +# Make the deployment script executable +chmod +x $TEMP_DIR/deploy.sh + +# Create a README file +echo "Creating README file..." +cat > $TEMP_DIR/README.md << EOF +# OpenWebUI Bot Deployment + +This package contains the OpenWebUI bot for channels integration. + +## Files + +- \`openwebui-bot/\`: The bot code +- \`run_openwebui_bot.py\`: Script to run the bot +- \`openwebui-bot.service\`: Systemd service file +- \`deploy.sh\`: Deployment script + +## Deployment + +1. Run the deployment script: + \`\`\` + ./deploy.sh + \`\`\` + +2. Check the status of the bot: + \`\`\` + systemctl status openwebui-bot.service + \`\`\` + +3. View the logs: + \`\`\` + journalctl -u openwebui-bot.service -f + \`\`\` + +## Configuration + +The bot is configured using the \`.env\` file in the \`openwebui-bot\` directory. +EOF + +# Create a tar archive of the deployment files +echo "Creating tar archive..." +cd $TEMP_DIR +tar -czf openwebui-bot-deploy.tar.gz * +cd - + +# Copy the tar archive to the current directory +cp $TEMP_DIR/openwebui-bot-deploy.tar.gz . + +echo "Deployment package created: openwebui-bot-deploy.tar.gz" +echo "To deploy on the server:" +echo "1. Copy the package to the server:" +echo " scp openwebui-bot-deploy.tar.gz $SERVER_USER@$SERVER_IP:~/" +echo "2. SSH into the server:" +echo " ssh $SERVER_USER@$SERVER_IP" +echo "3. Extract the package:" +echo " mkdir -p bot-deploy && tar -xzf openwebui-bot-deploy.tar.gz -C bot-deploy" +echo "4. Run the deployment script:" +echo " cd bot-deploy && ./deploy.sh" + +# Clean up +rm -rf $TEMP_DIR diff --git a/openwebui-bot/examples/custom_ai.py b/openwebui-bot/examples/custom_ai.py index 2de04b9..49d4067 100644 --- a/openwebui-bot/examples/custom_ai.py +++ b/openwebui-bot/examples/custom_ai.py @@ -1,15 +1,27 @@ -# Custom AI bot for our specific needs +# Custom AI bot with improved error handling and connection methods import asyncio import socketio import os -import sys import traceback +import logging +import aiohttp +import sys from env import WEBUI_URL, TOKEN from utils import send_message, send_typing -import aiohttp -# Get configuration from environment variables +# Set up logging +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler(), + logging.FileHandler('bot_debug.log') + ] +) +logger = logging.getLogger('openwebui_bot') + +# Get model configuration from environment variables MODEL_ID = os.getenv("MODEL_ID", "llama3.1") SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT", "You are a helpful AI assistant.") TEMPERATURE = float(os.getenv("TEMPERATURE", "0.7")) @@ -24,11 +36,11 @@ sio = socketio.AsyncClient(logger=False, engineio_logger=False) # Event handlers @sio.event async def connect(): - print("Connected to OpenWebUI!") + logger.info("Connected to OpenWebUI!") @sio.event async def disconnect(): - print("Disconnected from OpenWebUI!") + logger.info("Disconnected from OpenWebUI!") # Function to call the OpenAI-compatible API async def openai_chat_completion(messages): @@ -43,218 +55,268 @@ async def openai_chat_completion(messages): try: async with aiohttp.ClientSession() as session: - try: - async with session.post( - f"{WEBUI_URL}/api/chat/completions", - headers={"Authorization": f"Bearer {TOKEN}"}, - json=payload, - timeout=300 # 5-minute timeout - ) as response: - if response.status == 200: - return await response.json() - else: - # Handle errors or return raw response text - error_text = await response.text() - print(f"API error: {response.status} - {error_text}") - return {"error": error_text, "status": response.status} - except aiohttp.ClientError as e: - print(f"HTTP request error: {str(e)}") - return {"error": f"HTTP request error: {str(e)}", "status": 500} + logger.info(f"Sending request to {WEBUI_URL}/api/chat/completions") + logger.debug(f"Payload: {payload}") + + async with session.post( + f"{WEBUI_URL}/api/chat/completions", + headers={"Authorization": f"Bearer {TOKEN}"}, + json=payload, + timeout=300 # 5-minute timeout + ) as response: + if response.status == 200: + result = await response.json() + logger.info("API request successful") + return result + else: + error_text = await response.text() + logger.error(f"API error: {response.status} - {error_text}") + return {"error": error_text, "status": response.status} except Exception as e: - print(f"Unexpected error in openai_chat_completion: {str(e)}") - return {"error": f"Unexpected error: {str(e)}", "status": 500} + logger.error(f"Error in openai_chat_completion: {str(e)}") + logger.error(traceback.format_exc()) + return {"error": f"Error: {str(e)}", "status": 500} # Helper function to send typing indicators while waiting for a response async def send_typing_until_complete(channel_id, coro): """ Sends typing indicators every second until the provided coroutine completes. """ - task = asyncio.create_task(coro) # Begin the provided coroutine task + task = asyncio.create_task(coro) try: - # While the task is running, send typing indicators every second while not task.done(): await send_typing(sio, channel_id) await asyncio.sleep(1) - # Await the actual result of the coroutine return await task except Exception as e: task.cancel() - raise e # Propagate any exceptions that occurred in the coroutine + raise e # Define a function to handle channel events def events(user_id): - # Use the configured triggers and respond_to_all setting - global TRIGGERS, RESPOND_TO_ALL - @sio.on("channel-events") async def channel_events(data): - if data["user"]["id"] == user_id: + try: + logger.debug(f"Received channel event: {data}") + # Ignore events from the bot itself - return - - if data["data"]["type"] == "message": - message_content = data["data"]["data"]["content"] - channel_id = data["channel_id"] - sender_name = data["user"]["name"] - - print(f"{sender_name}: {message_content}") - - # Check if we should respond - should_respond = RESPOND_TO_ALL - message_lower = message_content.lower() - - if not should_respond: - # Check if the message mentions the bot - for trigger in TRIGGERS: - trigger_lower = trigger.lower() - if trigger_lower in message_lower: - should_respond = True - break - - if not should_respond: - # Skip messages that don't mention the bot + if data["user"]["id"] == user_id: + logger.debug(f"Ignoring message from self (bot ID: {user_id})") return - # Remove the trigger from the message - processed_message = message_content + # Only process message events + if data["data"]["type"] == "message": + message_content = data["data"]["data"]["content"] + channel_id = data["channel_id"] + sender_name = data["user"]["name"] - # Only try to remove triggers if we're not responding to all messages - if not RESPOND_TO_ALL: - for trigger in TRIGGERS: - trigger_lower = trigger.lower() - if trigger_lower in message_lower: - # Find the trigger in the message - index = message_lower.find(trigger_lower) - if index != -1: - # Remove the trigger - processed_message = processed_message[:index] + processed_message[index + len(trigger):].strip() + logger.info(f"Message in channel: {sender_name}: {message_content}") - # If the message is empty after removing the trigger, use a default prompt - if not processed_message.strip(): - processed_message = "Hello, how can I help you?" + # Check if we should respond + should_respond = RESPOND_TO_ALL + message_lower = message_content.lower() + + if not should_respond: + # Check if the message mentions the bot + for trigger in TRIGGERS: + trigger_lower = trigger.lower() + if trigger_lower in message_lower: + logger.info(f"Trigger detected: {trigger}") + should_respond = True break - # Show typing indicator - await send_typing(sio, channel_id) + if not should_respond: + logger.debug("No trigger detected, skipping message") + return - try: - # Prepare the messages for the API - messages = [ - {"role": "system", "content": SYSTEM_PROMPT}, - {"role": "user", "content": processed_message} - ] + # Remove the trigger from the message + processed_message = message_content - # Call the API while showing typing indicators - response = await send_typing_until_complete( - channel_id, openai_chat_completion(messages) - ) + # Only try to remove triggers if we're not responding to all messages + if not RESPOND_TO_ALL: + for trigger in TRIGGERS: + trigger_lower = trigger.lower() + if trigger_lower in message_lower: + # Find the trigger in the message + index = message_lower.find(trigger_lower) + if index != -1: + # Remove the trigger + processed_message = processed_message[:index] + processed_message[index + len(trigger):].strip() - # Process the response - if response.get("choices"): - completion = response["choices"][0]["message"]["content"] - # Add a robot emoji to the response - formatted_response = f"🤖 {completion}" - await send_message(channel_id, formatted_response) - else: - error_message = response.get("error", "I'm sorry, I couldn't generate a response.") - await send_message(channel_id, f"🤖 Error: {error_message}") - except Exception as e: - print(f"Error generating response: {str(e)}") - await send_message( - channel_id, - "🤖 Something went wrong while processing your request." - ) + # If the message is empty after removing the trigger, use a default prompt + if not processed_message.strip(): + processed_message = "Hello, how can I help you?" + break + + # Show typing indicator + await send_typing(sio, channel_id) + + try: + # Prepare the messages for the API + messages = [ + {"role": "system", "content": SYSTEM_PROMPT}, + {"role": "user", "content": processed_message} + ] + + # Call the API while showing typing indicators + response = await send_typing_until_complete( + channel_id, openai_chat_completion(messages) + ) + + # Process the response + if response.get("choices"): + completion = response["choices"][0]["message"]["content"] + # Add a robot emoji to the response + formatted_response = f"🤖 {completion}" + await send_message(channel_id, formatted_response) + else: + error_message = response.get("error", "I'm sorry, I couldn't generate a response.") + await send_message(channel_id, f"🤖 Error: {error_message}") + except Exception as e: + logger.error(f"Error generating response: {str(e)}") + logger.error(traceback.format_exc()) + await send_message( + channel_id, + "🤖 Something went wrong while processing your request." + ) + except Exception as e: + logger.error(f"Error processing channel event: {str(e)}") + logger.error(traceback.format_exc()) # Define an async function for the main workflow async def main(): - max_retries = 3 + max_retries = 5 retry_delay = 5 # seconds for attempt in range(1, max_retries + 1): try: - print(f"Connecting to {WEBUI_URL}... (Attempt {attempt}/{max_retries})") - await sio.connect( - WEBUI_URL, socketio_path="/ws/socket.io", transports=["websocket"] - ) - print("Connection established!") + # Ensure the URL is properly formatted + base_url = WEBUI_URL.rstrip('/') + logger.info(f"Connecting to {base_url}... (Attempt {attempt}/{max_retries})") + + # Try different connection methods + connection_methods = [ + # Method 1: Standard connection + { + "url": base_url, + "socketio_path": "/ws/socket.io", + "transports": ["websocket"], + "description": "Standard WebSocket connection" + }, + # Method 2: Alternative socket.io path + { + "url": base_url, + "socketio_path": "/socket.io", + "transports": ["websocket"], + "description": "Alternative socket.io path" + }, + # Method 3: Try with polling transport + { + "url": base_url, + "socketio_path": "/ws/socket.io", + "transports": ["polling", "websocket"], + "description": "Polling transport" + }, + # Method 4: Alternative path with polling + { + "url": base_url, + "socketio_path": "/socket.io", + "transports": ["polling", "websocket"], + "description": "Alternative path with polling" + } + ] + + # Try each connection method + connected = False + for method in connection_methods: + if connected: + break + + try: + logger.info(f"Trying {method['description']}...") + await sio.connect( + method["url"], + socketio_path=method["socketio_path"], + transports=method["transports"] + ) + logger.info(f"Connection successful using {method['description']}!") + connected = True + except Exception as conn_error: + logger.error(f"{method['description']} failed: {str(conn_error)}") + + if not connected: + raise Exception("All connection methods failed") + + logger.info("Connection established!") break # Connection successful, exit the retry loop except Exception as e: - print(f"Failed to connect: {e}") + logger.error(f"Failed to connect: {e}") + logger.error(traceback.format_exc()) if attempt < max_retries: - print(f"Retrying in {retry_delay} seconds...") + logger.info(f"Retrying in {retry_delay} seconds...") await asyncio.sleep(retry_delay) else: - print("Maximum connection attempts reached. Exiting.") + logger.error("Maximum connection attempts reached. Exiting.") return try: # Callback function for user-join async def join_callback(*args): try: + logger.info(f"Join callback received: {args}") if args and len(args) > 0: data = args[0] - print(f"Received callback data: {data}") if isinstance(data, dict) and "id" in data: bot_id = data["id"] - print(f"Bot connected with ID: {bot_id}") - events(bot_id) # Attach the event handlers dynamically + logger.info(f"Bot connected with ID: {bot_id}") + events(bot_id) # Attach the event handlers else: - print(f"Invalid callback data format: {data}") + logger.warning(f"Invalid callback data: {data}") + events("bot-default-id") # Use a default ID else: - print("No callback data received") - # If no data is received, use a default ID - print("Using default bot ID") - events("bot-default-id") # Attach the event handlers with a default ID + logger.warning("No callback data received") + events("bot-default-id") # Use a default ID except Exception as e: - print(f"Error in join_callback: {str(e)}") - print(traceback.format_exc()) + logger.error(f"Error in join callback: {str(e)}") + logger.error(traceback.format_exc()) # Authenticate with the server - print("Authenticating with the server...") + logger.info("Authenticating with the server...") await sio.emit("user-join", {"auth": {"token": TOKEN}}, callback=join_callback) - print("Authentication request sent") + + # Register for channel events directly since the callback might not provide the bot ID + logger.info("Registering for channel events directly...") + events("bot-user") # Use a default bot ID # Wait indefinitely to keep the connection open - print("Waiting for events...") + logger.info("Waiting for events...") await sio.wait() except Exception as e: - print(f"Error in main loop: {str(e)}") + logger.error(f"Error in main loop: {str(e)}") + logger.error(traceback.format_exc()) -# Actually run the async `main` function using `asyncio` +# Graceful shutdown async def shutdown(): - """Gracefully shut down the bot.""" - print("\nShutting down bot...") + logger.info("Shutting down bot...") if sio.connected: - print("Disconnecting from OpenWebUI...") await sio.disconnect() - print("Bot shutdown complete.") + logger.info("Bot shutdown complete.") if __name__ == "__main__": - print("Starting custom AI bot...") - print(f"OpenWebUI URL: {WEBUI_URL}") - print(f"Model: {MODEL_ID}") - print(f"System prompt: {SYSTEM_PROMPT[:50]}..." if len(SYSTEM_PROMPT) > 50 else f"System prompt: {SYSTEM_PROMPT}") - print(f"Temperature: {TEMPERATURE}") - print(f"Max tokens: {MAX_TOKENS}") - print(f"Top-p: {TOP_P}") - print(f"Triggers: {TRIGGERS}") - print(f"Respond to all: {RESPOND_TO_ALL}") - print("Press Ctrl+C to stop") + logger.info("Starting custom AI bot...") + logger.info(f"OpenWebUI URL: {WEBUI_URL}") + logger.info(f"Model: {MODEL_ID}") + logger.info(f"Triggers: {TRIGGERS}") + logger.info(f"Respond to all: {RESPOND_TO_ALL}") try: - # Run the main function asyncio.run(main()) except KeyboardInterrupt: - print("\nBot stopped by user") - # Run the shutdown function + logger.info("Bot stopped by user") try: asyncio.run(shutdown()) except Exception as e: - print(f"Error during shutdown: {str(e)}") + logger.error(f"Error during shutdown: {str(e)}") except Exception as e: - print(f"Error running bot: {str(e)}") - # Try to shut down gracefully - try: - asyncio.run(shutdown()) - except Exception as shutdown_error: - print(f"Error during shutdown: {str(shutdown_error)}") + logger.error(f"Error running bot: {str(e)}") + logger.error(traceback.format_exc()) diff --git a/openwebui-bot/requirements.txt b/openwebui-bot/requirements.txt new file mode 100644 index 0000000..0132b01 --- /dev/null +++ b/openwebui-bot/requirements.txt @@ -0,0 +1,4 @@ +python-socketio>=5.10.0 +aiohttp>=3.9.1 +python-dotenv>=1.0.0 +websockets>=12.0 diff --git a/run_openwebui_bot.py b/run_openwebui_bot.py index e3f1036..46164a9 100755 --- a/run_openwebui_bot.py +++ b/run_openwebui_bot.py @@ -2,7 +2,7 @@ """ Script to run the OpenWebUI AI bot. -This script runs the AI bot example from the openwebui-bot repository. +This script runs the custom AI bot from the openwebui-bot repository. """ import sys @@ -10,17 +10,16 @@ import os import asyncio # Add the openwebui-bot directory to the Python path -# Use a path relative to the current file's directory to ensure it works in any environment current_dir = os.path.dirname(os.path.abspath(__file__)) openwebui_bot_dir = os.path.join(current_dir, 'openwebui-bot') sys.path.insert(0, openwebui_bot_dir) print(f"Adding OpenWebUI bot directory to Python path: {openwebui_bot_dir}") -# Import the main function from the AI example -from examples.ai import main +# Import the main function from the custom AI example +from examples.custom_ai import main, shutdown if __name__ == "__main__": - print("Starting OpenWebUI AI bot...") + print("Starting OpenWebUI bot...") print("Press Ctrl+C to stop") try: @@ -28,7 +27,11 @@ if __name__ == "__main__": asyncio.run(main()) except KeyboardInterrupt: print("\nBot stopped by user") - # No need to call shutdown here as it's handled in the main function + # Run the shutdown function + try: + asyncio.run(shutdown()) + except Exception as e: + print(f"Error during shutdown: {str(e)}") except Exception as e: print(f"Error running bot: {str(e)}") print("Check that the OpenWebUI server is running and accessible.") diff --git a/send_test_message.py b/send_test_message.py new file mode 100755 index 0000000..f7bc0eb --- /dev/null +++ b/send_test_message.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +""" +Script to send a test message to an OpenWebUI channel. +""" + +import asyncio +import aiohttp +import json +import sys + +# Configuration +OPENWEBUI_URL = "http://104.225.217.215:8080" +API_KEY = "GdCU4ieYDqHsLfH2" + +async def create_channel(): + """Create a test channel.""" + print("Creating a test channel...") + + async with aiohttp.ClientSession() as session: + headers = {"Authorization": f"Bearer {API_KEY}"} + channel_data = { + "name": "Bot Test Channel", + "description": "Channel for testing the bot" + } + + try: + async with session.post( + f"{OPENWEBUI_URL}/api/channels", + headers=headers, + json=channel_data + ) as response: + if response.status == 200: + channel = await response.json() + channel_id = channel["id"] + print(f"Created channel with ID: {channel_id}") + return channel_id + else: + print(f"Failed to create channel: {response.status} - {await response.text()}") + return None + except Exception as e: + print(f"Error creating channel: {str(e)}") + return None + +async def send_message(channel_id, message): + """Send a message to a channel.""" + print(f"Sending message to channel {channel_id}: {message}") + + async with aiohttp.ClientSession() as session: + headers = {"Authorization": f"Bearer {API_KEY}"} + message_data = { + "content": message + } + + try: + async with session.post( + f"{OPENWEBUI_URL}/api/v1/channels/{channel_id}/messages/post", + headers=headers, + json=message_data + ) as response: + if response.status == 200: + print("Message sent successfully!") + return True + else: + print(f"Failed to send message: {response.status} - {await response.text()}") + return False + except Exception as e: + print(f"Error sending message: {str(e)}") + return False + +async def main(): + """Main function.""" + # Create a channel + channel_id = await create_channel() + if not channel_id: + print("Failed to create a channel. Exiting.") + return False + + # Send a test message + success = await send_message(channel_id, "@ai Hello, are you working?") + if success: + print(f"Test complete! Check the channel at {OPENWEBUI_URL}/channels/{channel_id}") + return True + else: + print("Failed to send test message.") + return False + +if __name__ == "__main__": + try: + result = asyncio.run(main()) + if result: + print("Test successful!") + sys.exit(0) + else: + print("Test failed!") + sys.exit(1) + except KeyboardInterrupt: + print("Test interrupted by user.") + sys.exit(130) + except Exception as e: + print(f"Unexpected error: {str(e)}") + sys.exit(1) diff --git a/test_openwebui_bot.py b/test_openwebui_bot.py new file mode 100755 index 0000000..f5333f0 --- /dev/null +++ b/test_openwebui_bot.py @@ -0,0 +1,198 @@ +#!/usr/bin/env python3 +""" +Test script for OpenWebUI bot. + +This script tests the connection to OpenWebUI and the bot's functionality. +""" + +import sys +import os +import asyncio +import aiohttp +import json +import logging + +# Set up logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger('bot_test') + +# Configuration +OPENWEBUI_URL = "http://104.225.217.215:8080" +API_KEY = "GdCU4ieYDqHsLfH2" +MODEL_ID = "llama3.1" + +async def test_openwebui_api(): + """Test the OpenWebUI API connection.""" + logger.info("Testing OpenWebUI API connection...") + + try: + async with aiohttp.ClientSession() as session: + # Test 1: Check if the API is accessible + logger.info("Testing API access...") + headers = {"Authorization": f"Bearer {API_KEY}"} + async with session.get( + f"{OPENWEBUI_URL}/api/models", + headers=headers + ) as response: + if response.status == 200: + models = await response.json() + logger.info(f"API accessible. Available models: {json.dumps(models, indent=2)}") + else: + logger.error(f"API error: {response.status} - {await response.text()}") + return False + + # Test 2: Test authentication + logger.info("Testing API authentication...") + headers = {"Authorization": f"Bearer {API_KEY}"} + async with session.get( + f"{OPENWEBUI_URL}/api/channels", + headers=headers + ) as response: + if response.status == 200: + channels = await response.json() + logger.info(f"Authentication successful. Channels: {json.dumps(channels, indent=2)}") + else: + logger.error(f"Authentication error: {response.status} - {await response.text()}") + return False + + # Test 3: Test chat completion + logger.info("Testing chat completion...") + payload = { + "model": MODEL_ID, + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Say hello in one short sentence."} + ], + "stream": False + } + + async with session.post( + f"{OPENWEBUI_URL}/api/chat/completions", + headers=headers, + json=payload + ) as response: + if response.status == 200: + result = await response.json() + if "choices" in result and len(result["choices"]) > 0: + content = result["choices"][0]["message"]["content"] + logger.info(f"Chat completion successful. Response: {content}") + else: + logger.error(f"Chat completion error: Invalid response format - {json.dumps(result, indent=2)}") + return False + else: + logger.error(f"Chat completion error: {response.status} - {await response.text()}") + return False + + logger.info("All API tests passed!") + return True + + except Exception as e: + logger.error(f"API test error: {str(e)}") + return False + +async def test_channel_message(): + """Test sending a message to a channel.""" + logger.info("Testing channel message...") + + try: + async with aiohttp.ClientSession() as session: + # Get available channels + headers = {"Authorization": f"Bearer {API_KEY}"} + async with session.get( + f"{OPENWEBUI_URL}/api/channels", + headers=headers + ) as response: + if response.status == 200: + channels = await response.json() + if not channels or len(channels) == 0: + logger.warning("No channels available. Creating a test channel...") + + # Create a test channel + channel_data = { + "name": "Bot Test Channel", + "description": "Channel for testing the bot" + } + + async with session.post( + f"{OPENWEBUI_URL}/api/channels", + headers=headers, + json=channel_data + ) as create_response: + if create_response.status == 200: + channel = await create_response.json() + channel_id = channel["id"] + logger.info(f"Created test channel with ID: {channel_id}") + else: + logger.error(f"Failed to create channel: {create_response.status} - {await create_response.text()}") + return False + else: + # Use the first available channel + channel_id = channels[0]["id"] + logger.info(f"Using existing channel with ID: {channel_id}") + + # Send a test message to the channel + message_data = { + "content": "@ai Hello, are you working?" + } + + async with session.post( + f"{OPENWEBUI_URL}/api/v1/channels/{channel_id}/messages/post", + headers=headers, + json=message_data + ) as message_response: + if message_response.status == 200: + logger.info("Test message sent successfully!") + logger.info("Check the channel to see if the bot responds.") + logger.info(f"Channel URL: {OPENWEBUI_URL}/channels/{channel_id}") + return True + else: + logger.error(f"Failed to send message: {message_response.status} - {await message_response.text()}") + return False + else: + logger.error(f"Failed to get channels: {response.status} - {await response.text()}") + return False + + except Exception as e: + logger.error(f"Channel test error: {str(e)}") + return False + +async def main(): + """Run all tests.""" + logger.info("Starting OpenWebUI bot tests...") + + # Test 1: OpenWebUI API + api_result = await test_openwebui_api() + if not api_result: + logger.error("API tests failed!") + return False + + # Test 2: Channel message + channel_result = await test_channel_message() + if not channel_result: + logger.error("Channel tests failed!") + return False + + logger.info("All tests completed successfully!") + logger.info("Note: The bot should respond to the test message in the channel.") + logger.info("Check the bot logs to see if it received and processed the message.") + + return True + +if __name__ == "__main__": + try: + result = asyncio.run(main()) + if result: + logger.info("Tests passed!") + sys.exit(0) + else: + logger.error("Tests failed!") + sys.exit(1) + except KeyboardInterrupt: + logger.info("Tests interrupted by user.") + sys.exit(130) + except Exception as e: + logger.error(f"Unexpected error: {str(e)}") + sys.exit(1)