Integrate OpenWebUI bot with AI service

This commit is contained in:
Iyeoluwa Akinrinola
2025-05-20 02:18:46 +01:00
parent 730009ae87
commit 0a27103875
46 changed files with 1749 additions and 3012 deletions
+9 -4
View File
@@ -1,7 +1,6 @@
# API configuration
API_HOST=0.0.0.0
API_PORT=5252
PUBLIC_URL=http://your-public-url:5252 # Public URL for webhooks, needed for OpenWebUI to send channel messages
# OpenWebUI configuration
OPENWEBUI_URL=http://104.225.217.215:8080
@@ -15,6 +14,12 @@ DEFAULT_MODEL=llama3.1
CHUNK_SIZE=1000
CHUNK_OVERLAP=200
# AI bot configuration
AI_TRIGGERS=@ai,@bot,@assistant,@chatbot # Comma-separated list of triggers that will make the AI respond
AI_RESPOND_TO_ALL=false # Set to 'true' to make the AI respond to all messages, 'false' to only respond to mentions
# Bot configuration
BOT_ENABLED=true # Set to 'true' to enable the OpenWebUI bot, 'false' to disable it
BOT_SYSTEM_PROMPT="You are a helpful AI assistant." # System prompt for the bot
BOT_TEMPERATURE=0.7 # Temperature for response generation (0.0 to 1.0)
BOT_MAX_TOKENS=2048 # Maximum number of tokens to generate
BOT_TOP_P=0.9 # Top-p sampling parameter (0.0 to 1.0)
BOT_TRIGGERS=@ai,@bot,@assistant,@chatbot # Comma-separated list of triggers that will make the bot respond
BOT_RESPOND_TO_ALL=false # Set to 'true' to make the bot respond to all messages, 'false' to only respond to mentions
+150 -367
View File
@@ -13,13 +13,14 @@ from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from typing import List, Optional
import uuid
import asyncio
from datetime import datetime, timezone
from ai_service.models.model_service import model_service
from ai_service.models.chat_service import chat_service
from ai_service.openwebui_api import router as openwebui_router
from ai_service.openwebui_channels import openwebui_channels
from ai_service.config import config
from ai_service import bot_manager
# Create FastAPI app
app = FastAPI(
@@ -43,26 +44,64 @@ app.include_router(openwebui_router, prefix="/api")
# Include Ollama proxy routes
app.include_router(openwebui_router, prefix="/ollama")
# Register webhook for channel messages on startup
# API startup event
@app.on_event("startup")
async def startup_event():
"""
Register webhook for channel messages on startup.
Startup event for the API.
"""
# Get the public URL of this service
service_url = f"http://{config.API_HOST}:{config.API_PORT}"
if config.PUBLIC_URL:
service_url = config.PUBLIC_URL
print("=" * 50)
print(f"Starting AI Service API on {config.API_HOST}:{config.API_PORT}")
print(f"OpenWebUI URL: {config.OPENWEBUI_URL}")
print(f"Default model: {config.DEFAULT_MODEL}")
# Register webhook
webhook_url = f"{service_url}/webhooks/channel-message"
print(f"Registering webhook for channel messages: {webhook_url}")
# Start the OpenWebUI bot if enabled
if config.BOT_ENABLED:
print("=" * 50)
print("Starting OpenWebUI bot...")
success = openwebui_channels.register_webhook(webhook_url)
if success:
print("Successfully registered webhook for channel messages")
# Start the bot with configuration
success = await bot_manager.start_bot(
openwebui_url=config.OPENWEBUI_URL,
api_key=config.OPENWEBUI_API_KEY,
model_id=config.DEFAULT_MODEL,
system_prompt=config.BOT_SYSTEM_PROMPT,
temperature=config.BOT_TEMPERATURE,
max_tokens=config.BOT_MAX_TOKENS,
top_p=config.BOT_TOP_P,
triggers=config.BOT_TRIGGERS,
respond_to_all=config.BOT_RESPOND_TO_ALL
)
if success:
print("Bot started successfully!")
else:
print("Failed to start bot. Check the logs for details.")
print("=" * 50)
else:
print("Failed to register webhook for channel messages")
print("OpenWebUI bot is disabled. Set BOT_ENABLED=true in .env to enable it.")
print("=" * 50)
# API shutdown event
@app.on_event("shutdown")
async def shutdown_event():
"""
Shutdown event for the API.
"""
print("=" * 50)
print("Shutting down AI Service API...")
# Stop the OpenWebUI bot if it's running
if bot_manager.is_bot_running():
print("Stopping OpenWebUI bot...")
success = await bot_manager.stop_bot()
if success:
print("Bot stopped successfully!")
else:
print("Failed to stop bot. Check the logs for details.")
print("=" * 50)
# Define API models for health check
class HealthResponse(BaseModel):
@@ -137,172 +176,6 @@ async def health_check():
"""
return {"status": "healthy"}
@app.get("/test-ollama")
async def test_ollama_connection():
"""
Test the connection to the Ollama API.
Returns:
Connection status and available models from Ollama.
"""
import requests
try:
# Try to connect to Ollama API
response = requests.get(f"{config.OLLAMA_API_URL}/api/tags", timeout=config.API_TIMEOUT)
response.raise_for_status()
# Return the models from Ollama
return {
"status": "success",
"message": "Successfully connected to Ollama API",
"ollama_url": config.OLLAMA_API_URL,
"models": response.json()
}
except requests.exceptions.Timeout as e:
return {
"status": "error",
"message": f"Timeout connecting to Ollama API: {str(e)}. The request exceeded the {config.API_TIMEOUT} second timeout.",
"ollama_url": config.OLLAMA_API_URL
}
except requests.exceptions.ConnectionError as e:
return {
"status": "error",
"message": f"Connection error to Ollama API: {str(e)}. Please check if Ollama is running at {config.OLLAMA_API_URL}.",
"ollama_url": config.OLLAMA_API_URL
}
except Exception as e:
return {
"status": "error",
"message": f"Failed to connect to Ollama API: {str(e)}",
"ollama_url": config.OLLAMA_API_URL
}
@app.post("/test-chat")
async def test_chat_completion():
"""
Test the chat completion with a simple prompt.
Returns:
Model response.
"""
try:
# Use the model service directly
response = model_service.generate_response(
model_id=config.DEFAULT_MODEL,
prompt="Hello, how are you?",
context=[],
use_rag=False
)
return {
"status": "success",
"model": config.DEFAULT_MODEL,
"response": response,
"ollama_url": config.OLLAMA_API_URL
}
except Exception as e:
return {
"status": "error",
"message": f"Failed to get chat completion: {str(e)}",
"ollama_url": config.OLLAMA_API_URL
}
@app.post("/test-rag")
async def test_rag_completion(query: str = "What information do you have in your knowledge database?"):
"""
Test the RAG (Retrieval Augmented Generation) functionality with a query.
This endpoint tests the integration with OpenWebUI's knowledge database.
Args:
query: The question to ask about documents in the knowledge database.
Returns:
Model response using RAG.
"""
try:
# Use the model service directly with RAG enabled
response = model_service.generate_response(
model_id=config.DEFAULT_MODEL,
prompt=query,
context=[],
use_rag=True # Enable RAG
)
return {
"status": "success",
"model": config.DEFAULT_MODEL,
"query": query,
"use_rag": True,
"response": response,
"openwebui_url": config.OPENWEBUI_URL
}
except Exception as e:
return {
"status": "error",
"message": f"Failed to get RAG completion: {str(e)}",
"openwebui_url": config.OPENWEBUI_URL
}
@app.post("/test-ollama-direct")
async def test_ollama_direct():
"""
Test the Ollama API directly with a simple chat request.
Returns:
Raw Ollama API response.
"""
import requests
try:
# Prepare a simple chat request
request_json = {
"model": config.DEFAULT_MODEL,
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello, how are you?"}
],
"stream": False
}
# Make the API call to Ollama
print(f"Sending direct request to Ollama API at: {config.OLLAMA_API_URL}/api/chat")
response = requests.post(
f"{config.OLLAMA_API_URL}/api/chat",
headers={"Content-Type": "application/json"},
json=request_json,
timeout=config.API_TIMEOUT
)
response.raise_for_status()
result = response.json()
return {
"status": "success",
"ollama_url": config.OLLAMA_API_URL,
"request": request_json,
"response": result
}
except requests.exceptions.Timeout as e:
return {
"status": "error",
"message": f"Timeout connecting to Ollama API: {str(e)}. The request exceeded the {config.API_TIMEOUT} second timeout.",
"ollama_url": config.OLLAMA_API_URL
}
except requests.exceptions.ConnectionError as e:
return {
"status": "error",
"message": f"Connection error to Ollama API: {str(e)}. Please check if Ollama is running at {config.OLLAMA_API_URL}.",
"ollama_url": config.OLLAMA_API_URL
}
except Exception as e:
return {
"status": "error",
"message": f"Failed to connect to Ollama API: {str(e)}",
"ollama_url": config.OLLAMA_API_URL
}
@app.get("/config")
async def get_config():
"""
@@ -318,38 +191,114 @@ async def get_config():
"ollama_api_url": config.OLLAMA_API_URL,
"default_model": config.DEFAULT_MODEL,
"api_timeout": config.API_TIMEOUT,
"bot": {
"enabled": config.BOT_ENABLED,
"running": bot_manager.is_bot_running(),
"model_id": config.DEFAULT_MODEL,
"system_prompt": config.BOT_SYSTEM_PROMPT[:50] + "..." if len(config.BOT_SYSTEM_PROMPT) > 50 else config.BOT_SYSTEM_PROMPT,
"temperature": config.BOT_TEMPERATURE,
"max_tokens": config.BOT_MAX_TOKENS,
"top_p": config.BOT_TOP_P,
"triggers": config.BOT_TRIGGERS,
"respond_to_all": config.BOT_RESPOND_TO_ALL
},
"available_models": list(model_service.AVAILABLE_MODELS.keys())
}
@app.get("/test-webhook")
async def test_webhook():
@app.get("/bot/status")
async def get_bot_status():
"""
Test the webhook registration.
Get the status and configuration of the OpenWebUI bot.
Returns:
Webhook registration status.
Bot status and configuration.
"""
# Get the public URL of this service
service_url = f"http://{config.API_HOST}:{config.API_PORT}"
if config.PUBLIC_URL:
service_url = config.PUBLIC_URL
# Webhook URL
webhook_url = f"{service_url}/webhooks/channel-message"
# Try to register the webhook again
success = openwebui_channels.register_webhook(webhook_url)
# Get all registered webhooks
webhooks = openwebui_channels.get_webhooks()
return {
"webhook_url": webhook_url,
"registration_success": success,
"registered_webhooks": webhooks
"enabled": config.BOT_ENABLED,
"running": bot_manager.is_bot_running(),
"config": {
"model_id": config.DEFAULT_MODEL,
"system_prompt": config.BOT_SYSTEM_PROMPT[:50] + "..." if len(config.BOT_SYSTEM_PROMPT) > 50 else config.BOT_SYSTEM_PROMPT,
"temperature": config.BOT_TEMPERATURE,
"max_tokens": config.BOT_MAX_TOKENS,
"top_p": config.BOT_TOP_P,
"triggers": config.BOT_TRIGGERS,
"respond_to_all": config.BOT_RESPOND_TO_ALL
}
}
@app.post("/bot/start")
async def start_bot(
model_id: str = None,
system_prompt: str = None,
temperature: float = None,
max_tokens: int = None,
top_p: float = None,
respond_to_all: bool = None
):
"""
Start the OpenWebUI bot with optional configuration.
Args:
model_id: ID of the model to use (default: config.DEFAULT_MODEL)
system_prompt: System prompt for the bot (default: config.BOT_SYSTEM_PROMPT)
temperature: Temperature for response generation (default: config.BOT_TEMPERATURE)
max_tokens: Maximum number of tokens to generate (default: config.BOT_MAX_TOKENS)
top_p: Top-p sampling parameter (default: config.BOT_TOP_P)
respond_to_all: Whether to respond to all messages (default: config.BOT_RESPOND_TO_ALL)
Returns:
Start status.
"""
if bot_manager.is_bot_running():
return {"status": "already_running", "message": "Bot is already running"}
# Use provided values or defaults from config
success = await bot_manager.start_bot(
openwebui_url=config.OPENWEBUI_URL,
api_key=config.OPENWEBUI_API_KEY,
model_id=model_id or config.DEFAULT_MODEL,
system_prompt=system_prompt or config.BOT_SYSTEM_PROMPT,
temperature=temperature if temperature is not None else config.BOT_TEMPERATURE,
max_tokens=max_tokens if max_tokens is not None else config.BOT_MAX_TOKENS,
top_p=top_p if top_p is not None else config.BOT_TOP_P,
triggers=config.BOT_TRIGGERS,
respond_to_all=respond_to_all if respond_to_all is not None else config.BOT_RESPOND_TO_ALL
)
if success:
return {
"status": "success",
"message": "Bot started successfully",
"config": {
"model_id": model_id or config.DEFAULT_MODEL,
"system_prompt": (system_prompt or config.BOT_SYSTEM_PROMPT)[:50] + "..." if len(system_prompt or config.BOT_SYSTEM_PROMPT) > 50 else (system_prompt or config.BOT_SYSTEM_PROMPT),
"temperature": temperature if temperature is not None else config.BOT_TEMPERATURE,
"max_tokens": max_tokens if max_tokens is not None else config.BOT_MAX_TOKENS,
"top_p": top_p if top_p is not None else config.BOT_TOP_P,
"respond_to_all": respond_to_all if respond_to_all is not None else config.BOT_RESPOND_TO_ALL
}
}
else:
return {"status": "error", "message": "Failed to start bot"}
@app.post("/bot/stop")
async def stop_bot():
"""
Stop the OpenWebUI bot.
Returns:
Stop status.
"""
if not bot_manager.is_bot_running():
return {"status": "not_running", "message": "Bot is not running"}
success = await bot_manager.stop_bot()
if success:
return {"status": "success", "message": "Bot stopped successfully"}
else:
return {"status": "error", "message": "Failed to stop bot"}
# Model endpoints
@app.get("/models", response_model=List[ModelInfo])
@@ -543,170 +492,4 @@ async def delete_chat(chat_id: str):
return {"status": "success", "message": "Chat deleted"}
# OpenWebUI Channels endpoints
@app.get("/channels")
async def get_openwebui_channels():
"""
Get all OpenWebUI channels.
Returns:
List of channels.
"""
channels = openwebui_channels.get_channels()
return channels
@app.get("/channels/{channel_id}")
async def get_openwebui_channel(channel_id: str):
"""
Get an OpenWebUI channel by ID.
Args:
channel_id: Channel ID.
Returns:
Channel information.
"""
channel = openwebui_channels.get_channel(channel_id)
if not channel:
raise HTTPException(status_code=404, detail="Channel not found")
return channel
@app.post("/channels")
async def create_openwebui_channel(name: str, description: str = "", is_private: bool = False):
"""
Create a new OpenWebUI channel.
Args:
name: Channel name.
description: Channel description.
is_private: Whether the channel is private.
Returns:
Created channel.
"""
channel = openwebui_channels.create_channel(name, description, is_private)
if not channel:
raise HTTPException(status_code=400, detail="Failed to create channel")
return channel
# Webhook endpoint for OpenWebUI channel messages
class ChannelMessageWebhook(BaseModel):
"""Model for channel message webhook."""
channel_id: str = Field(..., description="Channel ID")
message: str = Field(..., description="Message content")
user_id: str = Field(..., description="User ID")
timestamp: Optional[str] = Field(None, description="Message timestamp")
@app.post("/webhooks/channel-message")
async def channel_message_webhook(request: ChannelMessageWebhook):
"""
Webhook endpoint for receiving messages from OpenWebUI channels.
This endpoint is called by OpenWebUI when a message is sent in a channel.
The AI service will process the message and respond in the channel.
Args:
request: Channel message webhook request.
Returns:
Processing status.
"""
try:
print("=" * 50)
print("WEBHOOK RECEIVED")
print(f"Channel ID: {request.channel_id}")
print(f"User ID: {request.user_id}")
print(f"Message: {request.message}")
print(f"Timestamp: {request.timestamp}")
print("=" * 50)
# Find the chat associated with this OpenWebUI channel
print(f"Looking for chat with OpenWebUI channel ID: {request.channel_id}")
print(f"Number of chats in system: {len(chat_service.chats)}")
# Debug: Print all chats and their channel IDs
print("All chats in the system:")
for cid, chat in chat_service.chats.items():
is_team = chat.get('is_team_chat', False)
channel_id = chat.get('openwebui_channel_id', 'None')
print(f" Chat ID: {cid}, Is Team Chat: {is_team}, OpenWebUI Channel ID: {channel_id}")
chat_id = None
for cid, chat in chat_service.chats.items():
if chat.get('is_team_chat') and chat.get('openwebui_channel_id') == request.channel_id:
chat_id = cid
print(f"Found matching chat: {cid}")
break
if not chat_id:
print(f"No chat found for OpenWebUI channel {request.channel_id}")
# If no chat exists for this channel, create one
print("Creating a new team chat for this channel...")
try:
new_chat_id = chat_service.create_chat(
user_id=request.user_id,
title=f"Channel Chat {request.channel_id}",
model_id=config.DEFAULT_MODEL,
is_team_chat=True
)
# Manually set the OpenWebUI channel ID for this chat
chat_service.chats[new_chat_id]['openwebui_channel_id'] = request.channel_id
chat_service._save_chats()
print(f"Created new chat with ID {new_chat_id} for channel {request.channel_id}")
# Use this new chat
chat_id = new_chat_id
except Exception as e:
print(f"Error creating new chat for channel: {str(e)}")
return {"status": "error", "message": "No chat found for this channel and failed to create one"}
# Skip messages from the AI assistant to avoid loops
if request.user_id == "ai-assistant":
return {"status": "skipped", "message": "Skipping AI assistant message"}
# Check if we should respond to all messages or only to mentions
if not config.AI_RESPOND_TO_ALL:
# Check if the message mentions the AI using configured triggers
message_lower = request.message.lower()
is_triggered = False
for trigger in config.AI_TRIGGERS:
if trigger.lower() in message_lower:
is_triggered = True
break
# If no trigger is found, skip processing
if not is_triggered:
print(f"No AI mention found in message, skipping: {request.message[:50]}...")
return {"status": "skipped", "message": "No AI mention found in message"}
# Extract the actual message content (remove the trigger)
# This is a simple approach - for more complex cases, you might want more sophisticated parsing
processed_message = request.message
message_lower = request.message.lower()
# Only try to remove triggers if we're not responding to all messages
if not config.AI_RESPOND_TO_ALL:
for trigger in config.AI_TRIGGERS:
if trigger.lower() in message_lower:
# Remove the trigger from the message
processed_message = request.message.replace(trigger, "").strip()
break
# Process the message and generate a response
response = chat_service.get_chat_response(
chat_id=chat_id,
message=processed_message,
user_id=request.user_id
)
return {"status": "success", "message": "Message processed", "response": response}
except Exception as e:
print(f"Error processing channel message webhook: {str(e)}")
return {"status": "error", "message": f"Error processing message: {str(e)}"}
+170
View File
@@ -0,0 +1,170 @@
"""
Bot manager for the AI service.
This module provides functionality to manage the OpenWebUI bot.
"""
import os
import sys
import asyncio
import logging
from typing import Optional
# Set up logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
# 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__))
root_dir = os.path.dirname(current_dir)
openwebui_bot_dir = os.path.join(root_dir, 'openwebui-bot')
sys.path.insert(0, openwebui_bot_dir)
# Log the path for debugging
logger.info(f"Adding OpenWebUI bot directory to Python path: {openwebui_bot_dir}")
# Import the bot modules
try:
from env import WEBUI_URL, TOKEN
from examples.custom_ai import main as custom_bot_main
except ImportError as e:
print(f"Error importing OpenWebUI bot modules: {str(e)}")
print("Make sure the openwebui-bot directory exists and contains the required files.")
WEBUI_URL = None
TOKEN = None
custom_bot_main = None
# Global variable to store the bot task
bot_task = None
async def start_bot(
openwebui_url: str,
api_key: str,
model_id: str,
system_prompt: str = None,
temperature: float = None,
max_tokens: int = None,
top_p: float = None,
triggers: list = None,
respond_to_all: bool = None
) -> bool:
"""
Start the OpenWebUI bot.
Args:
openwebui_url: URL of the OpenWebUI instance.
api_key: API key for authentication.
model_id: ID of the model to use.
system_prompt: System prompt for the bot.
temperature: Temperature for response generation.
max_tokens: Maximum number of tokens to generate.
top_p: Top-p sampling parameter.
triggers: List of trigger words that will make the bot respond.
respond_to_all: Whether to respond to all messages or only to mentions.
Returns:
True if the bot was started successfully, False otherwise.
"""
global bot_task
# Check if the bot is already running
if bot_task is not None and not bot_task.done():
logger.warning("Bot is already running!")
return True
# Check if the bot modules were imported successfully
if custom_bot_main is None:
logger.error("OpenWebUI bot modules not found. Bot cannot be started.")
return False
# Set environment variables for the bot
os.environ["WEBUI_URL"] = openwebui_url
os.environ["TOKEN"] = api_key
os.environ["MODEL_ID"] = model_id
# Set optional parameters if provided
if system_prompt:
os.environ["SYSTEM_PROMPT"] = system_prompt
if temperature is not None:
os.environ["TEMPERATURE"] = str(temperature)
if max_tokens is not None:
os.environ["MAX_TOKENS"] = str(max_tokens)
if top_p is not None:
os.environ["TOP_P"] = str(top_p)
if triggers:
os.environ["TRIGGERS"] = ",".join(triggers)
if respond_to_all is not None:
os.environ["RESPOND_TO_ALL"] = str(respond_to_all).lower()
try:
# Start the bot in a background task
logger.info("Starting OpenWebUI bot...")
logger.info(f"OpenWebUI URL: {openwebui_url}")
logger.info(f"Model: {model_id}")
if system_prompt:
logger.info(f"System prompt: {system_prompt[:50]}...")
if temperature is not None:
logger.info(f"Temperature: {temperature}")
if max_tokens is not None:
logger.info(f"Max tokens: {max_tokens}")
if top_p is not None:
logger.info(f"Top-p: {top_p}")
if triggers:
logger.info(f"Triggers: {triggers}")
if respond_to_all is not None:
logger.info(f"Respond to all: {respond_to_all}")
# Create a task for the bot
bot_task = asyncio.create_task(custom_bot_main())
logger.info("Bot started successfully!")
return True
except Exception as e:
logger.error(f"Error starting bot: {str(e)}")
return False
async def stop_bot() -> bool:
"""
Stop the OpenWebUI bot.
Returns:
True if the bot was stopped successfully, False otherwise.
"""
global bot_task
# Check if the bot is running
if bot_task is None or bot_task.done():
logger.warning("Bot is not running!")
return True
try:
# Cancel the bot task
logger.info("Stopping OpenWebUI bot...")
bot_task.cancel()
try:
# Wait for the task to be cancelled
await bot_task
except asyncio.CancelledError:
pass
logger.info("Bot stopped successfully!")
return True
except Exception as e:
logger.error(f"Error stopping bot: {str(e)}")
return False
def is_bot_running() -> bool:
"""
Check if the bot is running.
Returns:
True if the bot is running, False otherwise.
"""
global bot_task
return bot_task is not None and not bot_task.done()
+8 -4
View File
@@ -34,9 +34,13 @@ class Config:
CHUNK_SIZE = int(os.environ.get('CHUNK_SIZE', 1000))
CHUNK_OVERLAP = int(os.environ.get('CHUNK_OVERLAP', 200))
# AI bot configuration
AI_TRIGGERS = os.environ.get('AI_TRIGGERS', '@ai,@bot,@assistant,@chatbot').split(',')
AI_RESPOND_TO_ALL = os.environ.get('AI_RESPOND_TO_ALL', 'false').lower() == 'true'
# Bot configuration
BOT_ENABLED = os.environ.get('BOT_ENABLED', 'true').lower() == 'true'
BOT_SYSTEM_PROMPT = os.environ.get('BOT_SYSTEM_PROMPT', 'You are a helpful AI assistant.')
BOT_TEMPERATURE = float(os.environ.get('BOT_TEMPERATURE', '0.7'))
BOT_MAX_TOKENS = int(os.environ.get('BOT_MAX_TOKENS', '2048'))
BOT_TOP_P = float(os.environ.get('BOT_TOP_P', '0.9'))
BOT_TRIGGERS = os.environ.get('BOT_TRIGGERS', '@ai,@bot,@assistant,@chatbot').split(',')
BOT_RESPOND_TO_ALL = os.environ.get('BOT_RESPOND_TO_ALL', 'false').lower() == 'true'
config = Config()
+189
View File
@@ -225,5 +225,194 @@
],
"team_members": [],
"openwebui_channel_id": null
},
"0967f62c-c5b9-4d40-b64e-8758dc42b124": {
"id": "0967f62c-c5b9-4d40-b64e-8758dc42b124",
"title": "Mention Test Chat 2025-05-19 16:31:41",
"user_id": "test_user",
"model_id": "llama3.1",
"is_team_chat": true,
"created_at": "2025-05-19T16:31:41.897128",
"updated_at": "2025-05-19T16:31:42.669052",
"messages": [
{
"id": "b6097881-e33e-4e52-897f-27c4f3ded33e",
"content": "This is a message without a mention: Can you help me with a project?",
"user_id": "test_user",
"is_user_message": true,
"timestamp": "2025-05-19T16:31:42.077261"
},
{
"id": "490c6646-f4c5-4f78-8982-2ad91977056f",
"content": "Connection error to Ollama API: HTTPConnectionPool(host='104.225.217.215', port=11434): Max retries exceeded with url: /api/chat (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x10f679110>: Failed to establish a new connection: [Errno 61] Connection refused')). Please check if Ollama is running at http://104.225.217.215:11434.",
"user_id": null,
"is_user_message": false,
"timestamp": "2025-05-19T16:31:42.346450"
},
{
"id": "ade9f283-35b8-467a-bf45-98a327e30c99",
"content": "@ai Can you help me with a project?",
"user_id": "test_user",
"is_user_message": true,
"timestamp": "2025-05-19T16:31:42.373840"
},
{
"id": "8e971524-ad12-4237-b1d1-ea9142c89887",
"content": "Connection error to Ollama API: HTTPConnectionPool(host='104.225.217.215', port=11434): Max retries exceeded with url: /api/chat (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x10f65fd50>: Failed to establish a new connection: [Errno 61] Connection refused')). Please check if Ollama is running at http://104.225.217.215:11434.",
"user_id": null,
"is_user_message": false,
"timestamp": "2025-05-19T16:31:42.669036"
}
],
"team_members": [
"test_user",
"test_user2"
],
"openwebui_channel_id": null
},
"5b6a2e66-035f-4810-b46f-9b035da6baa7": {
"id": "5b6a2e66-035f-4810-b46f-9b035da6baa7",
"title": "Mention Test Chat 2025-05-19 16:36:11",
"user_id": "test_user",
"model_id": "llama3.1",
"is_team_chat": true,
"created_at": "2025-05-19T16:36:11.848997",
"updated_at": "2025-05-19T16:36:12.626528",
"messages": [
{
"id": "2f27e470-1406-4863-86ea-c6dc83d0a114",
"content": "This is a message without a mention: Can you help me with a project?",
"user_id": "test_user",
"is_user_message": true,
"timestamp": "2025-05-19T16:36:11.983300"
},
{
"id": "606353ef-079e-40b0-ade9-247aa07b487c",
"content": "Connection error to Ollama API: HTTPConnectionPool(host='104.225.217.215', port=11434): Max retries exceeded with url: /api/chat (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x10f855890>: Failed to establish a new connection: [Errno 61] Connection refused')). Please check if Ollama is running at http://104.225.217.215:11434.",
"user_id": null,
"is_user_message": false,
"timestamp": "2025-05-19T16:36:12.280914"
},
{
"id": "4e2bdf6b-052c-4fa1-8c41-b180fcb6d9f3",
"content": "@ai Can you help me with a project?",
"user_id": "test_user",
"is_user_message": true,
"timestamp": "2025-05-19T16:36:12.333088"
},
{
"id": "4af0d91d-4b76-4ed1-9305-5ef999cfb3a6",
"content": "Connection error to Ollama API: HTTPConnectionPool(host='104.225.217.215', port=11434): Max retries exceeded with url: /api/chat (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x10f857f10>: Failed to establish a new connection: [Errno 61] Connection refused')). Please check if Ollama is running at http://104.225.217.215:11434.",
"user_id": null,
"is_user_message": false,
"timestamp": "2025-05-19T16:36:12.626493"
}
],
"team_members": [
"test_user",
"test_user2"
],
"openwebui_channel_id": null
},
"7e3d4d9a-3b53-4169-bdb5-49853d2ee147": {
"id": "7e3d4d9a-3b53-4169-bdb5-49853d2ee147",
"title": "Webhook Test Chat 2025-05-19 17:31:02",
"user_id": "test_user",
"model_id": "llama3.1",
"is_team_chat": true,
"created_at": "2025-05-19T17:31:02.808376",
"updated_at": "2025-05-19T17:31:02.809151",
"messages": [],
"team_members": [
"test_user"
],
"openwebui_channel_id": null
},
"db1531f0-23b8-44be-be0b-4468469ddfd1": {
"id": "db1531f0-23b8-44be-be0b-4468469ddfd1",
"title": "Channel mock-channel-123",
"user_id": "test_user",
"model_id": "llama3.1",
"is_team_chat": true,
"created_at": "2025-05-19T17:31:05.523198",
"updated_at": "2025-05-19T17:31:08.008697",
"messages": [
{
"id": "f1a41e2c-67d5-4713-9690-31de7957e880",
"content": "Can you help me with a project?",
"user_id": "test_user",
"is_user_message": true,
"timestamp": "2025-05-19T17:31:07.159077"
},
{
"id": "97e3ab10-31f8-4c4f-af0f-828e7d9b1583",
"content": "Connection error to Ollama API: HTTPConnectionPool(host='104.225.217.215', port=11434): Max retries exceeded with url: /api/chat (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x109592410>: Failed to establish a new connection: [Errno 61] Connection refused')). Please check if Ollama is running at http://104.225.217.215:11434.",
"user_id": null,
"is_user_message": false,
"timestamp": "2025-05-19T17:31:08.008671"
}
],
"team_members": [
"test_user"
],
"openwebui_channel_id": "mock-channel-123"
},
"cadd55b8-93f9-469a-99ca-3569fddf2a89": {
"id": "cadd55b8-93f9-469a-99ca-3569fddf2a89",
"title": "Test Team Chat",
"user_id": "test_user",
"model_id": "llama3.1",
"is_team_chat": true,
"created_at": "2025-05-19T18:05:04.943296",
"updated_at": "2025-05-19T18:05:57.781112",
"messages": [
{
"id": "38bf0784-ff2f-45ed-88b0-b7bbe4fe73da",
"content": "Hello, AI!",
"user_id": "test_user",
"is_user_message": true,
"timestamp": "2025-05-19T18:05:18.975944"
},
{
"id": "5b4b2c32-551a-413e-81f1-8d762f38309e",
"content": "How can I assist you today? Do you have any questions or need help with something specific?",
"user_id": null,
"is_user_message": false,
"timestamp": "2025-05-19T18:05:57.781076"
}
],
"team_members": [
"test_user"
],
"openwebui_channel_id": null
},
"a8ecdf7f-3d84-495b-85e3-846acffcfda6": {
"id": "a8ecdf7f-3d84-495b-85e3-846acffcfda6",
"title": "Channel test-channel-123",
"user_id": "test_user",
"model_id": "llama3.1",
"is_team_chat": true,
"created_at": "2025-05-19T18:06:19.115818",
"updated_at": "2025-05-19T18:07:05.899175",
"messages": [
{
"id": "6b2021b9-fea3-4b5b-b48e-f338dbf53338",
"content": "Hello",
"user_id": "test_user",
"is_user_message": true,
"timestamp": "2025-05-19T18:06:19.647395"
},
{
"id": "6770337d-fc5d-4ecc-9a0d-e272a02bd31c",
"content": "How can I assist you today? Do you have any questions or topics you'd like to discuss? I'm here to help with anything from answering general knowledge questions to providing guidance on a specific problem. Just let me know what's on your mind!",
"user_id": null,
"is_user_message": false,
"timestamp": "2025-05-19T18:07:05.899147"
}
],
"team_members": [
"test_user"
],
"openwebui_channel_id": "test-channel-123"
}
}
+22 -90
View File
@@ -11,7 +11,6 @@ from typing import List, Dict, Any, Optional
from ai_service.config import config
from ai_service.models.model_service import model_service
from ai_service.models.model_parameters import ModelParameters
from ai_service.openwebui_channels import openwebui_channels
class ChatService:
"""Service for chat functionality."""
@@ -65,34 +64,11 @@ class ChatService:
# Default title if none provided
chat_title = title or f"Chat {len(self.chats) + 1}"
# For team chats, create an OpenWebUI channel
openwebui_channel_id = None
# For team chats
if is_team_chat:
try:
print("=" * 50)
print(f"Creating team chat with title: {chat_title}")
print(f"OpenWebUI URL: {openwebui_channels.openwebui_url}")
print(f"OpenWebUI API Key: {openwebui_channels.openwebui_api_key[:5]}..." if openwebui_channels.openwebui_api_key else "No API key")
# Create a channel in OpenWebUI
channel_response = openwebui_channels.create_channel(
name=chat_title,
description=f"Team chat created by {user_id}",
is_private=True # Team chats are private by default
)
print(f"Channel response: {json.dumps(channel_response, indent=2) if channel_response else 'None'}")
if channel_response:
openwebui_channel_id = channel_response.get('id')
print(f"Created OpenWebUI channel with ID: {openwebui_channel_id}")
else:
print("Failed to create OpenWebUI channel, continuing with local team chat only")
print("=" * 50)
except Exception as e:
print("=" * 50)
print(f"Error creating OpenWebUI channel: {str(e)}")
print("=" * 50)
print("=" * 50)
print(f"Creating team chat with title: {chat_title}")
print("=" * 50)
# Create chat data
self.chats[chat_id] = {
@@ -104,8 +80,7 @@ class ChatService:
'created_at': datetime.now().isoformat(),
'updated_at': datetime.now().isoformat(),
'messages': [],
'team_members': [user_id] if is_team_chat else [],
'openwebui_channel_id': openwebui_channel_id
'team_members': [user_id] if is_team_chat else []
}
# Save chats to file
@@ -147,30 +122,18 @@ class ChatService:
# Update chat timestamp
chat['updated_at'] = datetime.now().isoformat()
# If this is a team chat with an OpenWebUI channel, send the message there too
if chat['is_team_chat'] and 'openwebui_channel_id' in chat and chat['openwebui_channel_id']:
try:
# For AI responses, use a special AI user ID
# This ensures the AI's messages are properly distinguished in OpenWebUI
sender_id = "ai-assistant" if not is_user_message else user_id
# If this is a team chat, log the message
if chat['is_team_chat']:
# For AI responses, use a special AI user ID
sender_id = "ai-assistant" if not is_user_message else user_id
# Format the message for OpenWebUI
if is_user_message:
# For user messages, use the regular format
formatted_content = content
else:
# For AI responses, add a special prefix to make it clear it's from the AI
# This helps visually distinguish AI responses in the channel
formatted_content = f"🤖 {content}"
# Format the message for logging
if is_user_message:
formatted_content = content
else:
formatted_content = f"🤖 {content}"
# Send message to OpenWebUI channel
openwebui_channels.send_message(
channel_id=chat['openwebui_channel_id'],
message=formatted_content,
user_id=sender_id
)
except Exception as e:
print(f"Error sending message to OpenWebUI channel: {str(e)}")
print(f"Team chat message in {chat_id} from {sender_id}: {formatted_content[:100]}...")
# Save chats to file
self._save_chats()
@@ -234,19 +197,8 @@ class ChatService:
if not chat['is_team_chat']:
return False
# Add to OpenWebUI channel if available
if 'openwebui_channel_id' in chat and chat['openwebui_channel_id']:
try:
# Add member to OpenWebUI channel
openwebui_success = openwebui_channels.add_member(
channel_id=chat['openwebui_channel_id'],
user_id=user_id
)
if not openwebui_success:
print(f"Warning: Failed to add user {user_id} to OpenWebUI channel {chat['openwebui_channel_id']}")
except Exception as e:
print(f"Error adding member to OpenWebUI channel: {str(e)}")
# Log the team member addition
print(f"Adding user {user_id} to team chat {chat_id}")
# Add to local team members list
if user_id not in chat['team_members']:
@@ -274,19 +226,8 @@ class ChatService:
if not chat['is_team_chat']:
return False
# Remove from OpenWebUI channel if available
if 'openwebui_channel_id' in chat and chat['openwebui_channel_id']:
try:
# Remove member from OpenWebUI channel
openwebui_success = openwebui_channels.remove_member(
channel_id=chat['openwebui_channel_id'],
user_id=user_id
)
if not openwebui_success:
print(f"Warning: Failed to remove user {user_id} from OpenWebUI channel {chat['openwebui_channel_id']}")
except Exception as e:
print(f"Error removing member from OpenWebUI channel: {str(e)}")
# Log the team member removal
print(f"Removing user {user_id} from team chat {chat_id}")
# Remove from local team members list
if user_id in chat['team_members']:
@@ -310,18 +251,9 @@ class ChatService:
chat = self.chats[chat_id]
# Delete OpenWebUI channel if this is a team chat
if chat['is_team_chat'] and 'openwebui_channel_id' in chat and chat['openwebui_channel_id']:
try:
# Delete the OpenWebUI channel
openwebui_success = openwebui_channels.delete_channel(
channel_id=chat['openwebui_channel_id']
)
if not openwebui_success:
print(f"Warning: Failed to delete OpenWebUI channel {chat['openwebui_channel_id']}")
except Exception as e:
print(f"Error deleting OpenWebUI channel: {str(e)}")
# Log the chat deletion
if chat['is_team_chat']:
print(f"Deleting team chat {chat_id}")
# Delete the chat from local storage
del self.chats[chat_id]
-300
View File
@@ -1,300 +0,0 @@
"""
OpenWebUI channels integration for team chats.
This module provides functions to interact with OpenWebUI channels API
for creating and managing team chats through OpenWebUI's channels feature.
It also includes functionality to listen for and respond to messages in OpenWebUI channels.
"""
import requests
import json
from typing import List, Dict, Any, Optional
from ai_service.config import config
class OpenWebUIChannels:
"""Class for interacting with OpenWebUI channels."""
def __init__(self):
"""Initialize the OpenWebUI channels integration."""
self.openwebui_url = config.OPENWEBUI_URL
self.openwebui_api_key = config.OPENWEBUI_API_KEY
self.headers = {
"Content-Type": "application/json"
}
# Add API key if available
if self.openwebui_api_key:
self.headers["Authorization"] = f"Bearer {self.openwebui_api_key}"
def create_channel(self, name: str, description: str = "", is_private: bool = False) -> Optional[Dict[str, Any]]:
"""
Create a new channel in OpenWebUI.
Args:
name: Name of the channel.
description: Description of the channel.
is_private: Whether the channel is private.
Returns:
Channel data if creation was successful, None otherwise.
"""
try:
# Prepare channel data
channel_data = {
"name": name,
"description": description,
"is_private": is_private
}
# Make API request to create channel
response = requests.post(
f"{self.openwebui_url}/api/channels",
headers=self.headers,
json=channel_data,
timeout=30
)
if response.status_code == 200 or response.status_code == 201:
return response.json()
else:
print(f"Error creating channel: {response.status_code} - {response.text}")
return None
except Exception as e:
print(f"Error creating channel: {str(e)}")
return None
def get_channel(self, channel_id: str) -> Optional[Dict[str, Any]]:
"""
Get a channel by ID.
Args:
channel_id: ID of the channel.
Returns:
Channel data if found, None otherwise.
"""
try:
# Make API request to get channel
response = requests.get(
f"{self.openwebui_url}/api/channels/{channel_id}",
headers=self.headers,
timeout=30
)
if response.status_code == 200:
return response.json()
else:
print(f"Error getting channel: {response.status_code} - {response.text}")
return None
except Exception as e:
print(f"Error getting channel: {str(e)}")
return None
def get_channels(self) -> List[Dict[str, Any]]:
"""
Get all channels.
Returns:
List of channel data.
"""
try:
# Make API request to get channels
response = requests.get(
f"{self.openwebui_url}/api/channels",
headers=self.headers,
timeout=30
)
if response.status_code == 200:
return response.json()
else:
print(f"Error getting channels: {response.status_code} - {response.text}")
return []
except Exception as e:
print(f"Error getting channels: {str(e)}")
return []
def add_member(self, channel_id: str, user_id: str) -> bool:
"""
Add a user to a channel.
Args:
channel_id: ID of the channel.
user_id: ID of the user to add.
Returns:
True if addition was successful, False otherwise.
"""
try:
# Prepare member data
member_data = {
"user_id": user_id
}
# Make API request to add member
response = requests.post(
f"{self.openwebui_url}/api/channels/{channel_id}/members",
headers=self.headers,
json=member_data,
timeout=30
)
return response.status_code == 200 or response.status_code == 201
except Exception as e:
print(f"Error adding member to channel: {str(e)}")
return False
def remove_member(self, channel_id: str, user_id: str) -> bool:
"""
Remove a user from a channel.
Args:
channel_id: ID of the channel.
user_id: ID of the user to remove.
Returns:
True if removal was successful, False otherwise.
"""
try:
# Make API request to remove member
response = requests.delete(
f"{self.openwebui_url}/api/channels/{channel_id}/members/{user_id}",
headers=self.headers,
timeout=30
)
return response.status_code == 200 or response.status_code == 204
except Exception as e:
print(f"Error removing member from channel: {str(e)}")
return False
def delete_channel(self, channel_id: str) -> bool:
"""
Delete a channel.
Args:
channel_id: ID of the channel.
Returns:
True if deletion was successful, False otherwise.
"""
try:
# Make API request to delete channel
response = requests.delete(
f"{self.openwebui_url}/api/channels/{channel_id}",
headers=self.headers,
timeout=30
)
return response.status_code == 200 or response.status_code == 204
except Exception as e:
print(f"Error deleting channel: {str(e)}")
return False
def register_webhook(self, webhook_url: str) -> bool:
"""
Register a webhook to receive channel messages.
Args:
webhook_url: URL of the webhook endpoint.
Returns:
True if registration was successful, False otherwise.
"""
try:
# Prepare webhook data
webhook_data = {
"url": webhook_url,
"events": ["channel_message"]
}
# Make API request to register webhook
response = requests.post(
f"{self.openwebui_url}/api/webhooks",
headers=self.headers,
json=webhook_data,
timeout=30
)
if response.status_code == 200 or response.status_code == 201:
print(f"Successfully registered webhook: {webhook_url}")
return True
else:
print(f"Error registering webhook: {response.status_code} - {response.text}")
return False
except Exception as e:
print(f"Error registering webhook: {str(e)}")
return False
def send_message(self, channel_id: str, message: str, user_id: str) -> Optional[Dict[str, Any]]:
"""
Send a message to a channel.
Args:
channel_id: ID of the channel.
message: Message content.
user_id: ID of the user sending the message.
Returns:
Message data if sending was successful, None otherwise.
"""
try:
# Prepare message data
message_data = {
"content": message,
"user_id": user_id
}
# Make API request to send message
response = requests.post(
f"{self.openwebui_url}/api/channels/{channel_id}/messages",
headers=self.headers,
json=message_data,
timeout=30
)
if response.status_code == 200 or response.status_code == 201:
return response.json()
else:
print(f"Error sending message to channel: {response.status_code} - {response.text}")
return None
except Exception as e:
print(f"Error sending message to channel: {str(e)}")
return None
def get_webhooks(self) -> List[Dict[str, Any]]:
"""
Get all registered webhooks.
Returns:
List of webhook data.
"""
try:
# Make API request to get webhooks
response = requests.get(
f"{self.openwebui_url}/api/webhooks",
headers=self.headers,
timeout=30
)
if response.status_code == 200:
return response.json()
else:
print(f"Error getting webhooks: {response.status_code} - {response.text}")
return []
except Exception as e:
print(f"Error getting webhooks: {str(e)}")
return []
# Create a singleton instance
openwebui_channels = OpenWebUIChannels()