Add OpenWebUI bot integration for channels feature
This commit is contained in:
@@ -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.
|
||||||
@@ -216,12 +216,43 @@ async def start_bot(
|
|||||||
for attempt in range(1, max_retries + 1):
|
for attempt in range(1, max_retries + 1):
|
||||||
try:
|
try:
|
||||||
logger.info(f"Starting bot (attempt {attempt}/{max_retries})...")
|
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
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
last_error = e
|
last_error = e
|
||||||
logger.error(f"Error starting bot (attempt {attempt}/{max_retries}): {str(e)}")
|
logger.error(f"Error starting bot (attempt {attempt}/{max_retries}): {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
if attempt < max_retries:
|
if attempt < max_retries:
|
||||||
logger.info(f"Retrying in {retry_delay} seconds...")
|
logger.info(f"Retrying in {retry_delay} seconds...")
|
||||||
await asyncio.sleep(retry_delay)
|
await asyncio.sleep(retry_delay)
|
||||||
|
|||||||
Executable
+130
@@ -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
|
||||||
@@ -1,15 +1,27 @@
|
|||||||
# Custom AI bot for our specific needs
|
# Custom AI bot with improved error handling and connection methods
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import socketio
|
import socketio
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import traceback
|
import traceback
|
||||||
|
import logging
|
||||||
|
import aiohttp
|
||||||
|
import sys
|
||||||
from env import WEBUI_URL, TOKEN
|
from env import WEBUI_URL, TOKEN
|
||||||
from utils import send_message, send_typing
|
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")
|
MODEL_ID = os.getenv("MODEL_ID", "llama3.1")
|
||||||
SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT", "You are a helpful AI assistant.")
|
SYSTEM_PROMPT = os.getenv("SYSTEM_PROMPT", "You are a helpful AI assistant.")
|
||||||
TEMPERATURE = float(os.getenv("TEMPERATURE", "0.7"))
|
TEMPERATURE = float(os.getenv("TEMPERATURE", "0.7"))
|
||||||
@@ -24,11 +36,11 @@ sio = socketio.AsyncClient(logger=False, engineio_logger=False)
|
|||||||
# Event handlers
|
# Event handlers
|
||||||
@sio.event
|
@sio.event
|
||||||
async def connect():
|
async def connect():
|
||||||
print("Connected to OpenWebUI!")
|
logger.info("Connected to OpenWebUI!")
|
||||||
|
|
||||||
@sio.event
|
@sio.event
|
||||||
async def disconnect():
|
async def disconnect():
|
||||||
print("Disconnected from OpenWebUI!")
|
logger.info("Disconnected from OpenWebUI!")
|
||||||
|
|
||||||
# Function to call the OpenAI-compatible API
|
# Function to call the OpenAI-compatible API
|
||||||
async def openai_chat_completion(messages):
|
async def openai_chat_completion(messages):
|
||||||
@@ -43,7 +55,9 @@ async def openai_chat_completion(messages):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
try:
|
logger.info(f"Sending request to {WEBUI_URL}/api/chat/completions")
|
||||||
|
logger.debug(f"Payload: {payload}")
|
||||||
|
|
||||||
async with session.post(
|
async with session.post(
|
||||||
f"{WEBUI_URL}/api/chat/completions",
|
f"{WEBUI_URL}/api/chat/completions",
|
||||||
headers={"Authorization": f"Bearer {TOKEN}"},
|
headers={"Authorization": f"Bearer {TOKEN}"},
|
||||||
@@ -51,53 +65,52 @@ async def openai_chat_completion(messages):
|
|||||||
timeout=300 # 5-minute timeout
|
timeout=300 # 5-minute timeout
|
||||||
) as response:
|
) as response:
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
return await response.json()
|
result = await response.json()
|
||||||
|
logger.info("API request successful")
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
# Handle errors or return raw response text
|
|
||||||
error_text = await response.text()
|
error_text = await response.text()
|
||||||
print(f"API error: {response.status} - {error_text}")
|
logger.error(f"API error: {response.status} - {error_text}")
|
||||||
return {"error": error_text, "status": response.status}
|
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}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Unexpected error in openai_chat_completion: {str(e)}")
|
logger.error(f"Error in openai_chat_completion: {str(e)}")
|
||||||
return {"error": f"Unexpected error: {str(e)}", "status": 500}
|
logger.error(traceback.format_exc())
|
||||||
|
return {"error": f"Error: {str(e)}", "status": 500}
|
||||||
|
|
||||||
# Helper function to send typing indicators while waiting for a response
|
# Helper function to send typing indicators while waiting for a response
|
||||||
async def send_typing_until_complete(channel_id, coro):
|
async def send_typing_until_complete(channel_id, coro):
|
||||||
"""
|
"""
|
||||||
Sends typing indicators every second until the provided coroutine completes.
|
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:
|
try:
|
||||||
# While the task is running, send typing indicators every second
|
|
||||||
while not task.done():
|
while not task.done():
|
||||||
await send_typing(sio, channel_id)
|
await send_typing(sio, channel_id)
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
# Await the actual result of the coroutine
|
|
||||||
return await task
|
return await task
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
task.cancel()
|
task.cancel()
|
||||||
raise e # Propagate any exceptions that occurred in the coroutine
|
raise e
|
||||||
|
|
||||||
# Define a function to handle channel events
|
# Define a function to handle channel events
|
||||||
def events(user_id):
|
def events(user_id):
|
||||||
# Use the configured triggers and respond_to_all setting
|
|
||||||
global TRIGGERS, RESPOND_TO_ALL
|
|
||||||
|
|
||||||
@sio.on("channel-events")
|
@sio.on("channel-events")
|
||||||
async def channel_events(data):
|
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
|
# Ignore events from the bot itself
|
||||||
|
if data["user"]["id"] == user_id:
|
||||||
|
logger.debug(f"Ignoring message from self (bot ID: {user_id})")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Only process message events
|
||||||
if data["data"]["type"] == "message":
|
if data["data"]["type"] == "message":
|
||||||
message_content = data["data"]["data"]["content"]
|
message_content = data["data"]["data"]["content"]
|
||||||
channel_id = data["channel_id"]
|
channel_id = data["channel_id"]
|
||||||
sender_name = data["user"]["name"]
|
sender_name = data["user"]["name"]
|
||||||
|
|
||||||
print(f"{sender_name}: {message_content}")
|
logger.info(f"Message in channel: {sender_name}: {message_content}")
|
||||||
|
|
||||||
# Check if we should respond
|
# Check if we should respond
|
||||||
should_respond = RESPOND_TO_ALL
|
should_respond = RESPOND_TO_ALL
|
||||||
@@ -108,11 +121,12 @@ def events(user_id):
|
|||||||
for trigger in TRIGGERS:
|
for trigger in TRIGGERS:
|
||||||
trigger_lower = trigger.lower()
|
trigger_lower = trigger.lower()
|
||||||
if trigger_lower in message_lower:
|
if trigger_lower in message_lower:
|
||||||
|
logger.info(f"Trigger detected: {trigger}")
|
||||||
should_respond = True
|
should_respond = True
|
||||||
break
|
break
|
||||||
|
|
||||||
if not should_respond:
|
if not should_respond:
|
||||||
# Skip messages that don't mention the bot
|
logger.debug("No trigger detected, skipping message")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Remove the trigger from the message
|
# Remove the trigger from the message
|
||||||
@@ -159,102 +173,150 @@ def events(user_id):
|
|||||||
error_message = response.get("error", "I'm sorry, I couldn't generate a response.")
|
error_message = response.get("error", "I'm sorry, I couldn't generate a response.")
|
||||||
await send_message(channel_id, f"🤖 Error: {error_message}")
|
await send_message(channel_id, f"🤖 Error: {error_message}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error generating response: {str(e)}")
|
logger.error(f"Error generating response: {str(e)}")
|
||||||
|
logger.error(traceback.format_exc())
|
||||||
await send_message(
|
await send_message(
|
||||||
channel_id,
|
channel_id,
|
||||||
"🤖 Something went wrong while processing your request."
|
"🤖 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
|
# Define an async function for the main workflow
|
||||||
async def main():
|
async def main():
|
||||||
max_retries = 3
|
max_retries = 5
|
||||||
retry_delay = 5 # seconds
|
retry_delay = 5 # seconds
|
||||||
|
|
||||||
for attempt in range(1, max_retries + 1):
|
for attempt in range(1, max_retries + 1):
|
||||||
try:
|
try:
|
||||||
print(f"Connecting to {WEBUI_URL}... (Attempt {attempt}/{max_retries})")
|
# 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(
|
await sio.connect(
|
||||||
WEBUI_URL, socketio_path="/ws/socket.io", transports=["websocket"]
|
method["url"],
|
||||||
|
socketio_path=method["socketio_path"],
|
||||||
|
transports=method["transports"]
|
||||||
)
|
)
|
||||||
print("Connection established!")
|
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
|
break # Connection successful, exit the retry loop
|
||||||
except Exception as e:
|
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:
|
if attempt < max_retries:
|
||||||
print(f"Retrying in {retry_delay} seconds...")
|
logger.info(f"Retrying in {retry_delay} seconds...")
|
||||||
await asyncio.sleep(retry_delay)
|
await asyncio.sleep(retry_delay)
|
||||||
else:
|
else:
|
||||||
print("Maximum connection attempts reached. Exiting.")
|
logger.error("Maximum connection attempts reached. Exiting.")
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Callback function for user-join
|
# Callback function for user-join
|
||||||
async def join_callback(*args):
|
async def join_callback(*args):
|
||||||
try:
|
try:
|
||||||
|
logger.info(f"Join callback received: {args}")
|
||||||
if args and len(args) > 0:
|
if args and len(args) > 0:
|
||||||
data = args[0]
|
data = args[0]
|
||||||
print(f"Received callback data: {data}")
|
|
||||||
if isinstance(data, dict) and "id" in data:
|
if isinstance(data, dict) and "id" in data:
|
||||||
bot_id = data["id"]
|
bot_id = data["id"]
|
||||||
print(f"Bot connected with ID: {bot_id}")
|
logger.info(f"Bot connected with ID: {bot_id}")
|
||||||
events(bot_id) # Attach the event handlers dynamically
|
events(bot_id) # Attach the event handlers
|
||||||
else:
|
else:
|
||||||
print(f"Invalid callback data format: {data}")
|
logger.warning(f"Invalid callback data: {data}")
|
||||||
|
events("bot-default-id") # Use a default ID
|
||||||
else:
|
else:
|
||||||
print("No callback data received")
|
logger.warning("No callback data received")
|
||||||
# If no data is received, use a default ID
|
events("bot-default-id") # Use a default ID
|
||||||
print("Using default bot ID")
|
|
||||||
events("bot-default-id") # Attach the event handlers with a default ID
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error in join_callback: {str(e)}")
|
logger.error(f"Error in join callback: {str(e)}")
|
||||||
print(traceback.format_exc())
|
logger.error(traceback.format_exc())
|
||||||
|
|
||||||
# Authenticate with the server
|
# 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)
|
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
|
# Wait indefinitely to keep the connection open
|
||||||
print("Waiting for events...")
|
logger.info("Waiting for events...")
|
||||||
await sio.wait()
|
await sio.wait()
|
||||||
except Exception as e:
|
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():
|
async def shutdown():
|
||||||
"""Gracefully shut down the bot."""
|
logger.info("Shutting down bot...")
|
||||||
print("\nShutting down bot...")
|
|
||||||
if sio.connected:
|
if sio.connected:
|
||||||
print("Disconnecting from OpenWebUI...")
|
|
||||||
await sio.disconnect()
|
await sio.disconnect()
|
||||||
print("Bot shutdown complete.")
|
logger.info("Bot shutdown complete.")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("Starting custom AI bot...")
|
logger.info("Starting custom AI bot...")
|
||||||
print(f"OpenWebUI URL: {WEBUI_URL}")
|
logger.info(f"OpenWebUI URL: {WEBUI_URL}")
|
||||||
print(f"Model: {MODEL_ID}")
|
logger.info(f"Model: {MODEL_ID}")
|
||||||
print(f"System prompt: {SYSTEM_PROMPT[:50]}..." if len(SYSTEM_PROMPT) > 50 else f"System prompt: {SYSTEM_PROMPT}")
|
logger.info(f"Triggers: {TRIGGERS}")
|
||||||
print(f"Temperature: {TEMPERATURE}")
|
logger.info(f"Respond to all: {RESPOND_TO_ALL}")
|
||||||
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")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Run the main function
|
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nBot stopped by user")
|
logger.info("Bot stopped by user")
|
||||||
# Run the shutdown function
|
|
||||||
try:
|
try:
|
||||||
asyncio.run(shutdown())
|
asyncio.run(shutdown())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error during shutdown: {str(e)}")
|
logger.error(f"Error during shutdown: {str(e)}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error running bot: {str(e)}")
|
logger.error(f"Error running bot: {str(e)}")
|
||||||
# Try to shut down gracefully
|
logger.error(traceback.format_exc())
|
||||||
try:
|
|
||||||
asyncio.run(shutdown())
|
|
||||||
except Exception as shutdown_error:
|
|
||||||
print(f"Error during shutdown: {str(shutdown_error)}")
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
python-socketio>=5.10.0
|
||||||
|
aiohttp>=3.9.1
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
websockets>=12.0
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"""
|
"""
|
||||||
Script to run the OpenWebUI AI bot.
|
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
|
import sys
|
||||||
@@ -10,17 +10,16 @@ import os
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
# Add the openwebui-bot directory to the Python path
|
# 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__))
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
openwebui_bot_dir = os.path.join(current_dir, 'openwebui-bot')
|
openwebui_bot_dir = os.path.join(current_dir, 'openwebui-bot')
|
||||||
sys.path.insert(0, openwebui_bot_dir)
|
sys.path.insert(0, openwebui_bot_dir)
|
||||||
print(f"Adding OpenWebUI bot directory to Python path: {openwebui_bot_dir}")
|
print(f"Adding OpenWebUI bot directory to Python path: {openwebui_bot_dir}")
|
||||||
|
|
||||||
# Import the main function from the AI example
|
# Import the main function from the custom AI example
|
||||||
from examples.ai import main
|
from examples.custom_ai import main, shutdown
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("Starting OpenWebUI AI bot...")
|
print("Starting OpenWebUI bot...")
|
||||||
print("Press Ctrl+C to stop")
|
print("Press Ctrl+C to stop")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -28,7 +27,11 @@ if __name__ == "__main__":
|
|||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\nBot stopped by user")
|
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:
|
except Exception as e:
|
||||||
print(f"Error running bot: {str(e)}")
|
print(f"Error running bot: {str(e)}")
|
||||||
print("Check that the OpenWebUI server is running and accessible.")
|
print("Check that the OpenWebUI server is running and accessible.")
|
||||||
|
|||||||
Executable
+101
@@ -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)
|
||||||
Executable
+198
@@ -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)
|
||||||
Reference in New Issue
Block a user