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
+140
View File
@@ -0,0 +1,140 @@
# OpenWebUI Bot Integration
This project integrates the [Open WebUI bot](https://github.com/open-webui/bot) to provide AI responses in OpenWebUI channels.
## Overview
The bot connects to OpenWebUI 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.
## Integration with AI Service
The bot is integrated with the AI service and starts automatically when the service starts (if `BOT_ENABLED=true` in the `.env` file). You don't need to run the bot separately unless you want to test it independently.
## Configuration
The bot is configured using the following environment variables in the `.env` file:
```
# OpenWebUI configuration
OPENWEBUI_URL=http://your-openwebui-url:8080
OPENWEBUI_API_KEY=your-openwebui-api-key
# Model configuration
DEFAULT_MODEL=llama3.1
# Bot configuration
BOT_ENABLED=true # Set to 'true' to enable the 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
```
These settings control how the bot behaves:
- `BOT_SYSTEM_PROMPT`: The system prompt that sets the bot's personality and behavior
- `BOT_TEMPERATURE`: Controls the randomness of the responses (higher values = more random)
- `BOT_MAX_TOKENS`: Maximum length of the generated responses
- `BOT_TOP_P`: Controls the diversity of the responses (higher values = more diverse)
- `BOT_TRIGGERS`: Words that will trigger the bot to respond (e.g., `@ai`)
- `BOT_RESPOND_TO_ALL`: If true, the bot will respond to all messages, not just those with triggers
## Bot API Endpoints
The AI service provides the following endpoints to manage the bot:
- `GET /bot/status`: Get the status and configuration of the bot
- `POST /bot/start`: Start the bot with optional configuration parameters:
- `model_id`: ID of the model to use (e.g., "llama3.1")
- `system_prompt`: System prompt for the bot
- `temperature`: Temperature for response generation (0.0 to 1.0)
- `max_tokens`: Maximum number of tokens to generate
- `top_p`: Top-p sampling parameter (0.0 to 1.0)
- `respond_to_all`: Whether to respond to all messages (true/false)
- `POST /bot/stop`: Stop the bot if it's running
You can also check the bot status and configuration in the configuration endpoint:
- `GET /config`: Get the current configuration, including detailed bot status and settings
### Examples
Start the bot with default settings:
```bash
curl -X POST http://localhost:5252/bot/start
```
Start the bot with custom settings:
```bash
curl -X POST http://localhost:5252/bot/start \
-H "Content-Type: application/json" \
-d '{
"model_id": "gemma3",
"system_prompt": "You are a friendly and helpful AI assistant.",
"temperature": 0.8,
"max_tokens": 1024,
"top_p": 0.95,
"respond_to_all": false
}'
```
Get the bot status:
```bash
curl http://localhost:5252/bot/status
```
Stop the bot:
```bash
curl -X POST http://localhost:5252/bot/stop
```
## Running the Bot Separately
If you want to run the bot separately from the AI service, you can use the provided scripts:
### 1. Using the Original AI Example
```bash
./run_openwebui_bot.py
```
This runs the original AI example from the Open WebUI bot repository.
### 2. Using Our Custom AI Bot
```bash
./run_custom_bot.py
```
This runs our custom AI bot implementation, which includes additional features like:
- Responding only to messages that mention the bot (using trigger words like `@ai`)
- Adding a robot emoji (🤖) to responses
- Better error handling
- Longer timeout for API calls
## How It Works
The bot uses WebSockets to maintain a persistent connection to OpenWebUI and receive real-time events. When a message is received in a channel, the bot checks if it mentions the AI. If it does, the bot processes the message and sends a response back to the channel.
The bot uses the OpenAI-compatible API provided by OpenWebUI to generate responses to messages.
## Troubleshooting
If the bot is not responding to messages, check the following:
1. Make sure the OpenWebUI URL and API key are correct in the `.env` file
2. Verify that the bot is connected to OpenWebUI by checking the logs
3. Make sure you're using one of the trigger words in your messages (e.g., `@ai`)
4. Check that the model ID is correct and available in your OpenWebUI instance
## Differences from Webhook Approach
This bot uses WebSockets to connect to OpenWebUI, which is different from the webhook approach used in the previous implementation. The main differences are:
1. **WebSockets**: The bot maintains a persistent connection to OpenWebUI and receives real-time events.
2. **Webhooks**: The previous implementation used webhooks, where OpenWebUI sends HTTP requests to your service when messages are posted in channels.
The WebSocket approach is more efficient for real-time communication and doesn't require your service to be publicly accessible.
-160
View File
@@ -1,160 +0,0 @@
# Local Testing with Remote Server Resources
This guide explains how to set up local testing that connects to remote server resources for the chatbot project.
## Overview
These scripts allow you to:
1. Test the connection to the remote Ollama server
2. Create SSH tunnels to access remote resources locally
3. Run tests against the remote Ollama models from your local machine
## Setup Instructions
### 1. Initial Setup
Run the setup script to create a virtual environment and install dependencies:
```bash
chmod +x setup_local_test_env.sh
./setup_local_test_env.sh
```
This will:
- Create a Python virtual environment
- Install required dependencies
- Create a `local_test.env` file from the template
### 2. Configure Environment Variables
Edit the `local_test.env` file with your server credentials:
```bash
# Remote server configuration
SERVER_IP=104.225.217.215
SERVER_PORT=22
SERVER_USER=root
SERVER_PASSWORD=your_password_here # Add your actual password
# Ollama configuration
OLLAMA_API_URL=http://104.225.217.215:11434
# OpenWebUI configuration
OPENWEBUI_URL=http://104.225.217.215:8080
OPENWEBUI_API_KEY=your_openwebui_api_key_here
```
### 3. Create SSH Tunnels (Optional)
If you want to access the remote resources through localhost (recommended for security and to avoid firewall issues), create SSH tunnels:
```bash
chmod +x create_ssh_tunnel.sh
./create_ssh_tunnel.sh
```
This will create SSH tunnels for:
- Ollama API (localhost:11434 → remote:11434)
- OpenWebUI (localhost:8080 → remote:8080)
If you use SSH tunnels, update your `local_test.env` file to use localhost URLs:
```bash
# Ollama configuration
OLLAMA_API_URL=http://localhost:11434
# OpenWebUI configuration
OPENWEBUI_URL=http://localhost:8080
```
### 4. Run Tests
Activate the virtual environment and run the test script:
```bash
source venv/bin/activate
python test_remote_ollama.py
```
#### Test Options
You can customize the test with command-line arguments:
```bash
# Test with a specific model
python test_remote_ollama.py --model llama3.3
# Test with a custom prompt
python test_remote_ollama.py --prompt "Explain quantum computing"
# Test with a different Ollama URL
python test_remote_ollama.py --ollama-url http://your-server-ip:11434
# Test with a different timeout
python test_remote_ollama.py --timeout 600
```
### 5. Stop SSH Tunnels
When you're done testing, stop the SSH tunnels:
```bash
chmod +x stop_ssh_tunnels.sh
./stop_ssh_tunnels.sh
```
## Troubleshooting
### Connection Issues
If you can't connect to the remote server:
1. Check if the server is reachable:
```bash
ping 104.225.217.215
```
2. Verify that Ollama is running on the server:
```bash
ssh root@104.225.217.215 "curl http://localhost:11434/api/tags"
```
3. Check if there are firewall rules blocking the connection:
```bash
ssh root@104.225.217.215 "iptables -L"
```
### SSH Tunnel Issues
If the SSH tunnels aren't working:
1. Make sure you have SSH access to the server:
```bash
ssh root@104.225.217.215
```
2. Check if the ports are already in use:
```bash
netstat -tuln | grep 11434
netstat -tuln | grep 8080
```
3. Try creating the tunnels manually:
```bash
ssh -N -L 11434:localhost:11434 root@104.225.217.215
```
## Advanced Usage
### Testing with OpenWebUI's RAG Capabilities
To test document-based question answering using OpenWebUI's knowledge database:
```bash
python test_remote_ollama.py --prompt "What information do you have about our project?" --model llama3.1
```
### Integrating with Your Local Development
You can use these scripts as a foundation for local development that connects to the remote resources. This allows you to develop and test locally while using the remote server's models and data.
+34 -4
View File
@@ -8,7 +8,7 @@ A backend service for OpenWebUI that provides OpenWebUI-compatible API endpoints
- Ollama API proxy
- Chat functionality with model switching
- Support for multiple LLM models (gemma3, llama3.3, llama3.1, mistral, deepseek)
- Team chat integration with OpenWebUI channels
- Team chat functionality
## Technology Stack
@@ -26,11 +26,13 @@ ai_service/
├── embeddings/ # Document processing for RAG
│ └── document_service.py
├── openwebui_api.py # OpenWebUI-compatible API endpoints
├── openwebui_channels.py # OpenWebUI channels integration
├── config.py # Configuration settings
├── api.py # FastAPI application
└── deploy.sh # Deployment script
openwebui-bot/ # OpenWebUI bot integration
run_ai_service.py # Script to run the service
run_openwebui_bot.py # Script to run the OpenWebUI bot
run_custom_bot.py # Script to run a custom OpenWebUI bot
deploy_ai_service.sh # Local deployment script
remote_deploy.sh # Remote deployment script
```
@@ -91,6 +93,34 @@ To configure OpenWebUI to use this service as its backend:
2. Restart OpenWebUI to apply the changes.
## Team Chat Feature
## OpenWebUI Bot Integration
The service now integrates with OpenWebUI's channels feature to provide team chat functionality. See [TEAM_CHAT_GUIDE.md](TEAM_CHAT_GUIDE.md) for detailed instructions on how to use team chats.
This project includes integration with the [Open WebUI bot](https://github.com/open-webui/bot) to provide AI responses in OpenWebUI channels. See [BOT_README.md](BOT_README.md) for detailed instructions on how to use the bot.
### Bot Integration
The bot is integrated with the AI service and starts automatically when the service starts (if `BOT_ENABLED=true` in the `.env` file). The bot connects to OpenWebUI 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.
### Bot API Endpoints
The AI service provides the following endpoints to manage the bot:
- `GET /bot/status`: Get the status of the bot (enabled and running)
- `POST /bot/start`: Start the bot if it's not already running
- `POST /bot/stop`: Stop the bot if it's running
You can also check the bot status in the configuration endpoint:
- `GET /config`: Get the current configuration, including bot status
### Running the Bot Separately
If you want to run the bot separately from the AI service, you can use the provided scripts:
```bash
# Run the original AI example
./run_openwebui_bot.py
# Or run our custom AI bot implementation
./run_custom_bot.py
```
-73
View File
@@ -1,73 +0,0 @@
# Document-Based Question Answering with OpenWebUI
This document explains how to use the document-based question answering (RAG) functionality in the AI service.
## Overview
The AI service now supports Retrieval Augmented Generation (RAG) by leveraging OpenWebUI's built-in knowledge database. This allows users to:
1. Upload documents to OpenWebUI
2. Ask questions about those documents
3. Receive responses that incorporate information from the documents
## How It Works
When RAG is enabled:
1. The AI service forwards the request to OpenWebUI with `use_knowledge=True`
2. OpenWebUI searches its knowledge database for relevant information
3. The retrieved information is used to augment the model's response
4. The response is returned to the user
## Using RAG in API Requests
To enable RAG in your API requests, set the `use_rag` parameter to `true`:
```json
POST /chats/{chat_id}/messages
{
"message": "What information do you have about project X?",
"user_id": "user123",
"use_rag": true
}
```
## Testing RAG Functionality
You can test the RAG functionality using the `/test-rag` endpoint:
```
POST /test-rag?query=What information do you have about project X?
```
This will return a response that includes information from documents in OpenWebUI's knowledge database.
## Uploading Documents to OpenWebUI
To use RAG effectively, you need to upload documents to OpenWebUI:
1. Log in to OpenWebUI at your configured URL (default: http://104.225.217.215:8080)
2. Navigate to the Knowledge section
3. Upload your documents (PDF, TXT, DOCX, etc.)
4. OpenWebUI will automatically process and index the documents
## Troubleshooting
If RAG is not working as expected:
1. Ensure OpenWebUI is running and accessible
2. Check that documents are properly uploaded and indexed in OpenWebUI
3. Verify that the `use_rag` parameter is set to `true` in your requests
4. Check the logs for any errors related to OpenWebUI API calls
If there are connection issues with OpenWebUI, the AI service will automatically fall back to using the direct Ollama API without RAG.
## Configuration
The following configuration settings affect RAG functionality:
- `OPENWEBUI_URL`: URL of your OpenWebUI instance
- `OPENWEBUI_API_KEY`: API key for OpenWebUI (if required)
- `API_TIMEOUT`: Timeout for API requests (in seconds)
These can be set in your environment variables or in the `.env` file.
-153
View File
@@ -1,153 +0,0 @@
# Team Chat Guide
This guide explains how to use the team chat feature with OpenWebUI channels.
## Overview
The chatbot now integrates with OpenWebUI's channels feature to provide team chat functionality. When you create a team chat in the chatbot, it automatically creates a corresponding channel in OpenWebUI, allowing multiple users to participate in the same conversation.
## Using Team Chats
### AI Responses in Team Chats
When you send a message in a team chat, the AI will respond both in your local chat and in the OpenWebUI channel. The AI's responses in OpenWebUI channels:
- Are prefixed with a robot emoji (🤖) to clearly identify them as AI responses
- Appear with a special "ai-assistant" user ID to distinguish them from human users
- Are visible to all members of the channel in real-time
This allows all team members to see the conversation with the AI and collaborate effectively.
### How the AI Bot is Triggered
The AI bot can be triggered to respond in two ways:
1. **Through the API**: When you send a message using the `/chats/{chat_id}/messages` endpoint, the AI automatically processes it and responds.
2. **Directly in OpenWebUI**: When someone mentions the AI in an OpenWebUI channel that's linked to a team chat:
- The message is sent to your service through a webhook
- If it contains an AI mention (like `@ai`, `@bot`, etc.), the AI processes it
- The AI sends its response back to the OpenWebUI channel
By default, the AI only responds when explicitly mentioned with one of these triggers:
- `@ai`
- `@bot`
- `@assistant`
- `@chatbot`
You can customize these triggers or make the AI respond to all messages by changing the settings in your `.env` file:
```
# To customize the triggers that activate the AI
AI_TRIGGERS=@ai,@bot,@assistant,@chatbot
# To make the AI respond to all messages (not just mentions)
AI_RESPOND_TO_ALL=false # Change to 'true' to respond to everything
```
This mention-based approach ensures the AI only joins the conversation when explicitly invited, making team chats more focused and preventing the AI from responding to every message.
### Creating a Team Chat
You can create a team chat in two ways:
1. **Through the API**:
```
POST /chats
{
"user_id": "your-user-id",
"title": "Team Chat Name",
"model_id": "llama3.1",
"is_team_chat": true
}
```
2. **Through OpenWebUI**:
- Log in to OpenWebUI at http://104.225.217.215:8080/
- Navigate to the Channels section (look for a "#" or group icon in the sidebar)
- Click "Create Channel" or "+" button
- Give your channel a name and description
- Choose whether it should be private or public
- Click "Create"
### Adding Members to a Team Chat
1. **Through the API**:
```
POST /chats/{chat_id}/members/{user_id}
```
2. **Through OpenWebUI**:
- Open the channel in OpenWebUI
- Look for a "Members" or "Invite" option
- Add users by their username or email
### Sending Messages in a Team Chat
1. **Through the API**:
```
POST /chats/{chat_id}/messages
{
"message": "Your message",
"user_id": "your-user-id"
}
```
2. **Through OpenWebUI**:
- Open the channel in OpenWebUI
- Type your message in the input box
- Press Enter to send
### Viewing Team Chats
1. **Through the API**:
```
GET /chats/user/{user_id}
```
This will return all chats for the user, including team chats where they are a member.
2. **Through OpenWebUI**:
- Log in to OpenWebUI
- Navigate to the Channels section
- You'll see all channels you're a member of
## Technical Implementation
The team chat feature works by:
1. Creating an OpenWebUI channel when a team chat is created
2. Adding members to both the local team chat and the OpenWebUI channel
3. Sending messages to both the local chat and the OpenWebUI channel
4. Deleting the OpenWebUI channel when the team chat is deleted
## Troubleshooting
If you encounter issues with team chats:
1. **Channel not appearing in OpenWebUI**:
- Check if the OpenWebUI server is running
- Verify that the OpenWebUI URL and API key are correctly configured in the `.env` file
2. **Cannot add members to a team chat**:
- Ensure the user exists in OpenWebUI
- Check if the team chat was properly created with `is_team_chat: true`
3. **Messages not appearing in OpenWebUI channel**:
- Check the logs for any errors when sending messages
- Verify that the OpenWebUI channel ID is correctly stored in the chat data
## API Reference
### Team Chat Endpoints
- `POST /chats` - Create a new chat (set `is_team_chat: true` for team chats)
- `GET /chats/user/{user_id}` - Get all chats for a user (includes team chats)
- `POST /chats/{chat_id}/members/{user_id}` - Add a user to a team chat
- `DELETE /chats/{chat_id}/members/{user_id}` - Remove a user from a team chat
- `DELETE /chats/{chat_id}` - Delete a chat (also deletes the OpenWebUI channel)
### OpenWebUI Channel Endpoints
- `GET /channels` - Get all OpenWebUI channels
- `GET /channels/{channel_id}` - Get an OpenWebUI channel by ID
- `POST /channels` - Create a new OpenWebUI channel
+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()
-76
View File
@@ -1,76 +0,0 @@
#!/bin/bash
# Script to create an SSH tunnel to the remote server
# Load environment variables from local_test.env
if [ -f "local_test.env" ]; then
source local_test.env
else
echo "Error: local_test.env file not found. Please run setup_local_test_env.sh first."
exit 1
fi
# Check if required variables are set
if [ -z "$SERVER_IP" ] || [ -z "$SERVER_PORT" ] || [ -z "$SERVER_USER" ]; then
echo "Error: SERVER_IP, SERVER_PORT, or SERVER_USER not set in local_test.env."
exit 1
fi
# Check if password is set
if [ -z "$SERVER_PASSWORD" ]; then
echo "Warning: SERVER_PASSWORD not set in local_test.env. You will be prompted for the password."
fi
# Function to create SSH tunnel
create_tunnel() {
local local_port=$1
local remote_port=$2
echo "Creating SSH tunnel from localhost:$local_port to $SERVER_IP:$remote_port..."
# Check if the tunnel is already established
if netstat -tuln | grep -q ":$local_port "; then
echo "Port $local_port is already in use. Tunnel may already be established."
return 0
fi
# Create the SSH tunnel
if [ -n "$SERVER_PASSWORD" ]; then
# Use sshpass if available
if command -v sshpass &> /dev/null; then
sshpass -p "$SERVER_PASSWORD" ssh -N -L $local_port:localhost:$remote_port -p $SERVER_PORT $SERVER_USER@$SERVER_IP &
else
echo "sshpass not installed. You will be prompted for the password."
ssh -N -L $local_port:localhost:$remote_port -p $SERVER_PORT $SERVER_USER@$SERVER_IP &
fi
else
ssh -N -L $local_port:localhost:$remote_port -p $SERVER_PORT $SERVER_USER@$SERVER_IP &
fi
# Check if the tunnel was established
if [ $? -eq 0 ]; then
echo "SSH tunnel established. Local port $local_port is now forwarded to $SERVER_IP:$remote_port."
echo "Tunnel process ID: $!"
echo $! >> tunnel_pids.txt
else
echo "Failed to establish SSH tunnel."
return 1
fi
}
# Create tunnels for Ollama and OpenWebUI
echo "Setting up SSH tunnels to remote server at $SERVER_IP..."
# Create a file to store tunnel process IDs
touch tunnel_pids.txt
# Create tunnel for Ollama (port 11434)
create_tunnel 11434 11434
# Create tunnel for OpenWebUI (port 8080)
create_tunnel 8080 8080
echo "SSH tunnels established. You can now access:"
echo "- Ollama API at http://localhost:11434"
echo "- OpenWebUI at http://localhost:8080"
echo ""
echo "To stop the tunnels, run: ./stop_ssh_tunnels.sh"
+2 -53
View File
@@ -34,61 +34,10 @@ if pgrep -f "run_ai_service.py" > /dev/null; then
echo "Check ai_service.log for output"
echo "To stop the service, run: pkill -f \"run_ai_service.py\""
# Test the health endpoint
echo -e "\nTesting health endpoint..."
# Check the health endpoint
echo -e "\nChecking health endpoint..."
curl -s http://localhost:5252/health
echo -e "\n"
# Test creating a chat and sending a message
echo "Testing chat creation and message sending..."
if [ -f "$PYTHON_PATH" ]; then
# Create a simple test script
cat > test_api.py << 'EOF'
import requests
import json
# Create a chat
response = requests.post(
"http://localhost:5252/chats",
json={
"user_id": "test_user",
"title": "Test Chat",
"model_id": "llama3.1"
}
)
if response.status_code == 200:
chat_id = response.json()["id"]
print(f"Chat created with ID: {chat_id}")
# Send a message with parameters
response = requests.post(
f"http://localhost:5252/chats/{chat_id}/messages",
json={
"message": "Hello, AI!",
"user_id": "test_user",
"temperature": 0.7,
"max_tokens": 100
}
)
if response.status_code == 200:
print("Message sent successfully")
print(f"Response: {response.json()['content'][:100]}...")
else:
print(f"Error sending message: {response.status_code}")
print(response.text)
else:
print(f"Error creating chat: {response.status_code}")
print(response.text)
EOF
# Run the test script with the virtual environment's Python
$PYTHON_PATH test_api.py
rm test_api.py
else
echo "Skipping API test as virtual environment Python is not available"
fi
else
echo "Failed to start AI Service. Check ai_service.log for errors."
exit 1
-18
View File
@@ -1,18 +0,0 @@
# Remote server configuration
SERVER_IP=104.225.217.215
SERVER_PORT=22
SERVER_USER=root
SERVER_PASSWORD=S5qqENZNqc
# Ollama configuration
OLLAMA_API_URL=http://104.225.217.215:8080/ollama
# OpenWebUI configuration
OPENWEBUI_URL=http://104.225.217.215:8080
OPENWEBUI_API_KEY=your_openwebui_api_key_here
# Model configuration
DEFAULT_MODEL=llama3.1
# API timeout (in seconds)
API_TIMEOUT=300
-18
View File
@@ -1,18 +0,0 @@
# Remote server configuration
SERVER_IP=104.225.217.215
SERVER_PORT=22
SERVER_USER=root
SERVER_PASSWORD=your_password_here
# Ollama configuration
OLLAMA_API_URL=http://104.225.217.215:11434
# OpenWebUI configuration
OPENWEBUI_URL=http://104.225.217.215:8080
OPENWEBUI_API_KEY=your_openwebui_api_key_here
# Model configuration
DEFAULT_MODEL=llama3.1
# API timeout (in seconds)
API_TIMEOUT=300
-35
View File
@@ -1,35 +0,0 @@
"""
Configuration for local testing with remote server resources.
"""
import os
from dotenv import load_dotenv
# Try to load environment variables from .env file
dotenv_path = os.path.join(os.path.dirname(__file__), 'local_test.env')
load_dotenv(dotenv_path=dotenv_path)
# Configuration class
class LocalTestConfig:
"""Configuration for local testing with remote server resources."""
# Remote server configuration
SERVER_IP = os.environ.get('SERVER_IP', '104.225.217.215')
SERVER_PORT = os.environ.get('SERVER_PORT', '22')
SERVER_USER = os.environ.get('SERVER_USER', 'root')
# Ollama configuration
OLLAMA_API_URL = os.environ.get('OLLAMA_API_URL', f'http://{SERVER_IP}:11434')
# OpenWebUI configuration
OPENWEBUI_URL = os.environ.get('OPENWEBUI_URL', f'http://{SERVER_IP}:8080')
OPENWEBUI_API_KEY = os.environ.get('OPENWEBUI_API_KEY', '')
# Model configuration
DEFAULT_MODEL = os.environ.get('DEFAULT_MODEL', 'llama3.1')
# API timeout (in seconds)
API_TIMEOUT = int(os.environ.get('API_TIMEOUT', 300)) # 5 minutes
# Create a singleton instance
config = LocalTestConfig()
+171
View File
@@ -0,0 +1,171 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# PyPI configuration file
.pypirc
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Open WebUI
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+30
View File
@@ -0,0 +1,30 @@
# open-webui/bot
This repository provides an experimental boilerplate for building bots compatible with the **Open WebUI** "Channels" feature (introduced in version 0.5.0). It serves as a proof of concept to demonstrate bot-building capabilities while highlighting the potential of asynchronous communication enabled by Channels.
## ⚡ Key Highlights
- **Highly Experimental**: This is an early-stage project showcasing basic bot-building functionality. Expect major API changes in the future.
- **Extensible Framework**: Designed as a foundation for further development, with plans to enhance APIs, developer tooling, and usability.
- **Asynchronous Communication**: Leverages Open WebUI Channels for event-driven workflows.
## 🛠️ Getting Started with Examples
This repository includes an `/examples` folder containing runnable example bots that demonstrate basic functionality.
To run an example, execute the corresponding module using the `-m` flag in Python. For example, to run the `ai` example:
```bash
python -m examples.ai
```
> **Note**: Ensure that your current working directory (PWD) is the root of this repository when running examples, as this is required for proper execution.
Replace `ai` in the command above with the specific example youd like to execute from the `/examples` folder.
## 🚧 Disclaimer
This project is an early-stage proof of concept. **APIs will break** and existing functionality may change as Open WebUI evolves to include native bot support. This repository is not production-ready and primarily serves experimental and exploratory purposes.
## 🎯 Future Vision
We aim to introduce improved APIs, enhanced developer tooling, and seamless native support for bots directly within Open WebUI. The ultimate goal is to make building bots easier, faster, and more intuitive.
---
Contributions, feedback, and experimentation are encouraged. Join us in shaping the future of bot-building on Open WebUI!
View File
+12
View File
@@ -0,0 +1,12 @@
import os
try:
from dotenv import load_dotenv
load_dotenv()
except ImportError:
print("dotenv not installed, skipping...")
WEBUI_URL = os.getenv("WEBUI_URL", "http://localhost:8080")
TOKEN = os.getenv("TOKEN", "")
+132
View File
@@ -0,0 +1,132 @@
# WARNING: This might not work in the future. Do NOT use this in production.
import asyncio
import socketio
from env import WEBUI_URL, TOKEN
from utils import send_message, send_typing
MODEL_ID = "llama3.2:latest"
# Create an asynchronous Socket.IO client instance
sio = socketio.AsyncClient(logger=False, engineio_logger=False)
# Event handlers
@sio.event
async def connect():
print("Connected!")
@sio.event
async def disconnect():
print("Disconnected from the server!")
import aiohttp
import asyncio
async def openai_chat_completion(messages):
payload = {
"model": MODEL_ID,
"messages": messages,
"stream": False,
}
async with aiohttp.ClientSession() as session:
async with session.post(
f"{WEBUI_URL}/api/chat/completions",
headers={"Authorization": f"Bearer {TOKEN}"},
json=payload,
) as response:
if response.status == 200:
return await response.json()
else:
# Optional: Handle errors or return raw response text
return {"error": await response.text(), "status": response.status}
# Define a function to handle channel events
def events(user_id):
@sio.on("channel-events")
async def channel_events(data):
if data["user"]["id"] == user_id:
# Ignore events from the bot itself
return
if data["data"]["type"] == "message":
print(f'{data["user"]["name"]}: {data["data"]["data"]["content"]}')
await send_typing(sio, data["channel_id"])
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
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
# OpenAI API coroutine
# This uses naive implementation of OpenAI API, that does not utilize the context of the conversation
openai_task = openai_chat_completion(
[
{"role": "system", "content": "You are a friendly AI."},
{"role": "user", "content": data["data"]["data"]["content"]},
]
)
try:
# Run OpenAI coroutine while showing typing indicators
response = await send_typing_until_complete(
data["channel_id"], openai_task
)
if response.get("choices"):
completion = response["choices"][0]["message"]["content"]
await send_message(data["channel_id"], completion)
else:
await send_message(
data["channel_id"], "I'm sorry, I don't understand."
)
except Exception:
await send_message(
data["channel_id"],
"Something went wrong while processing your request.",
)
# Define an async function for the main workflow
async def main():
try:
print(f"Connecting to {WEBUI_URL}...")
await sio.connect(
WEBUI_URL, socketio_path="/ws/socket.io", transports=["websocket"]
)
print("Connection established!")
except Exception as e:
print(f"Failed to connect: {e}")
return
# Callback function for user-join
async def join_callback(data):
events(data["id"]) # Attach the event handlers dynamically
# Authenticate with the server
await sio.emit("user-join", {"auth": {"token": TOKEN}}, callback=join_callback)
# Wait indefinitely to keep the connection open
await sio.wait()
# Actually run the async `main` function using `asyncio`
if __name__ == "__main__":
asyncio.run(main())
+247
View File
@@ -0,0 +1,247 @@
# Custom AI bot for our specific needs
import asyncio
import socketio
import os
import sys
from env import WEBUI_URL, TOKEN
from utils import send_message, send_typing
import aiohttp
# Get 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"))
MAX_TOKENS = int(os.getenv("MAX_TOKENS", "2048"))
TOP_P = float(os.getenv("TOP_P", "0.9"))
TRIGGERS = os.getenv("TRIGGERS", "@ai,@bot,@assistant,@chatbot").split(",")
RESPOND_TO_ALL = os.getenv("RESPOND_TO_ALL", "false").lower() == "true"
# Create an asynchronous Socket.IO client instance
sio = socketio.AsyncClient(logger=False, engineio_logger=False)
# Event handlers
@sio.event
async def connect():
print("Connected to OpenWebUI!")
@sio.event
async def disconnect():
print("Disconnected from OpenWebUI!")
# Function to call the OpenAI-compatible API
async def openai_chat_completion(messages):
payload = {
"model": MODEL_ID,
"messages": messages,
"stream": False,
"temperature": TEMPERATURE,
"max_tokens": MAX_TOKENS,
"top_p": TOP_P
}
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}
except Exception as e:
print(f"Unexpected error in openai_chat_completion: {str(e)}")
return {"error": f"Unexpected 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
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
# 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:
# 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
return
# Remove the trigger from the message
processed_message = message_content
# 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()
# 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:
print(f"Error generating response: {str(e)}")
await send_message(
channel_id,
"🤖 Something went wrong while processing your request."
)
# Define an async function for the main workflow
async def main():
max_retries = 3
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!")
break # Connection successful, exit the retry loop
except Exception as e:
print(f"Failed to connect: {e}")
if attempt < max_retries:
print(f"Retrying in {retry_delay} seconds...")
await asyncio.sleep(retry_delay)
else:
print("Maximum connection attempts reached. Exiting.")
return
try:
# Callback function for user-join
async def join_callback(data):
try:
bot_id = data["id"]
print(f"Bot connected with ID: {bot_id}")
events(bot_id) # Attach the event handlers dynamically
except Exception as e:
print(f"Error in join_callback: {str(e)}")
# Authenticate with the server
print("Authenticating with the server...")
await sio.emit("user-join", {"auth": {"token": TOKEN}}, callback=join_callback)
print("Authentication request sent")
# Wait indefinitely to keep the connection open
print("Waiting for events...")
await sio.wait()
except Exception as e:
print(f"Error in main loop: {str(e)}")
# Actually run the async `main` function using `asyncio`
async def shutdown():
"""Gracefully shut down the bot."""
print("\nShutting down bot...")
if sio.connected:
print("Disconnecting from OpenWebUI...")
await sio.disconnect()
print("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")
try:
# Run the main function
asyncio.run(main())
except KeyboardInterrupt:
print("\nBot stopped by user")
# 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)}")
# Try to shut down gracefully
try:
asyncio.run(shutdown())
except Exception as shutdown_error:
print(f"Error during shutdown: {str(shutdown_error)}")
+100
View File
@@ -0,0 +1,100 @@
# WARNING: This might not work in the future. Do NOT use this in production.
import asyncio
import socketio
from smolagents import ToolCallingAgent, LiteLLMModel, DuckDuckGoSearchTool
from env import WEBUI_URL, TOKEN
from utils import send_message, send_typing
search_tool = DuckDuckGoSearchTool()
MODEL_ID = "gpt-4o"
model = LiteLLMModel(
model_id=f"openai/{MODEL_ID}", api_base=f"{WEBUI_URL}/api/", api_key=TOKEN
)
agent = ToolCallingAgent(tools=[search_tool], model=model)
# Create an asynchronous Socket.IO client instance
sio = socketio.AsyncClient(logger=False, engineio_logger=False)
# Event handlers
@sio.event
async def connect():
print("Connected!")
@sio.event
async def disconnect():
print("Disconnected from the server!")
# Define a function to handle channel events
def events(user_id):
@sio.on("channel-events")
async def channel_events(data):
if data["user"]["id"] == user_id:
# Ignore events from the bot itself
return
if data["data"]["type"] == "message":
print(f'{data["user"]["name"]}: {data["data"]["data"]["content"]}')
# Send typing events every second while processing the input
async def simulate_typing(channel_id):
try:
while not processing_event.is_set():
await send_typing(sio, channel_id)
await asyncio.sleep(1)
except asyncio.CancelledError:
pass
# Create an asyncio.Event to manage typing simulation
processing_event = asyncio.Event()
typing_task = asyncio.create_task(simulate_typing(data["channel_id"]))
try:
# Run the blocking agent.run in a non-blocking way using asyncio
loop = asyncio.get_running_loop()
output = await loop.run_in_executor(
None, agent.run, data["data"]["data"]["content"]
)
finally:
# Signal that typing simulation should stop
processing_event.set()
# Wait for the typing task to finish
await typing_task
# Send the generated output as a message
await send_message(data["channel_id"], f"{output}")
# Define an async function for the main workflow
async def main():
try:
print(f"Connecting to {WEBUI_URL}...")
await sio.connect(
WEBUI_URL, socketio_path="/ws/socket.io", transports=["websocket"]
)
print("Connection established!")
except Exception as e:
print(f"Failed to connect: {e}")
return
# Callback function for user-join
async def join_callback(data):
events(data["id"]) # Attach the event handlers dynamically
# Authenticate with the server
await sio.emit("user-join", {"auth": {"token": TOKEN}}, callback=join_callback)
# Wait indefinitely to keep the connection open
await sio.wait()
# Actually run the async `main` function using `asyncio`
if __name__ == "__main__":
asyncio.run(main())
+103
View File
@@ -0,0 +1,103 @@
# WARNING: This might not work in the future. Do NOT use this in production.
import asyncio
import socketio
from smolagents import CodeAgent, LiteLLMModel, DuckDuckGoSearchTool
from env import WEBUI_URL, TOKEN
from utils import send_message, send_typing
# search_tool = DuckDuckGoSearchTool()
MODEL_ID = "llama3.2:latest"
model = LiteLLMModel(
model_id=f"openai/{MODEL_ID}", api_base=f"{WEBUI_URL}/api/", api_key=TOKEN
)
agent = CodeAgent(
tools=[], model=model, additional_authorized_imports=["requests", "bs4"]
)
# Create an asynchronous Socket.IO client instance
sio = socketio.AsyncClient(logger=False, engineio_logger=False)
# Event handlers
@sio.event
async def connect():
print("Connected!")
@sio.event
async def disconnect():
print("Disconnected from the server!")
# Define a function to handle channel events
def events(user_id):
@sio.on("channel-events")
async def channel_events(data):
if data["user"]["id"] == user_id:
# Ignore events from the bot itself
return
if data["data"]["type"] == "message":
print(f'{data["user"]["name"]}: {data["data"]["data"]["content"]}')
# Send typing events every second while processing the input
async def simulate_typing(channel_id):
try:
while not processing_event.is_set():
await send_typing(sio, channel_id)
await asyncio.sleep(1)
except asyncio.CancelledError:
pass
# Create an asyncio.Event to manage typing simulation
processing_event = asyncio.Event()
typing_task = asyncio.create_task(simulate_typing(data["channel_id"]))
try:
# Run the blocking agent.run in a non-blocking way using asyncio
loop = asyncio.get_running_loop()
output = await loop.run_in_executor(
None, agent.run, data["data"]["data"]["content"]
)
finally:
# Signal that typing simulation should stop
processing_event.set()
# Wait for the typing task to finish
await typing_task
# Send the generated output as a message
await send_message(data["channel_id"], f"{output}")
# Define an async function for the main workflow
async def main():
try:
print(f"Connecting to {WEBUI_URL}...")
await sio.connect(
WEBUI_URL, socketio_path="/ws/socket.io", transports=["websocket"]
)
print("Connection established!")
except Exception as e:
print(f"Failed to connect: {e}")
return
# Callback function for user-join
async def join_callback(data):
events(data["id"]) # Attach the event handlers dynamically
# Authenticate with the server
await sio.emit("user-join", {"auth": {"token": TOKEN}}, callback=join_callback)
# Wait indefinitely to keep the connection open
await sio.wait()
# Actually run the async `main` function using `asyncio`
if __name__ == "__main__":
asyncio.run(main())
+61
View File
@@ -0,0 +1,61 @@
import asyncio
import socketio
from env import WEBUI_URL, TOKEN
from utils import send_message, send_typing
# Create an asynchronous Socket.IO client instance
sio = socketio.AsyncClient(logger=False, engineio_logger=False)
# Event handlers
@sio.event
async def connect():
print("Connected!")
@sio.event
async def disconnect():
print("Disconnected from the server!")
# Define a function to handle channel events
def events(user_id):
@sio.on("channel-events")
async def channel_events(data):
if data["user"]["id"] == user_id:
# Ignore events from the bot itself
return
if data["data"]["type"] == "message":
print(f'{data["user"]["name"]}: {data["data"]["data"]["content"]}')
await send_typing(sio, data["channel_id"])
await asyncio.sleep(1) # Simulate a delay
await send_message(data["channel_id"], "Pong!")
# Define an async function for the main workflow
async def main():
try:
print(f"Connecting to {WEBUI_URL}...")
await sio.connect(
WEBUI_URL, socketio_path="/ws/socket.io", transports=["websocket"]
)
print("Connection established!")
except Exception as e:
print(f"Failed to connect: {e}")
return
# Callback function for user-join
async def join_callback(data):
events(data["id"]) # Attach the event handlers dynamically
# Authenticate with the server
await sio.emit("user-join", {"auth": {"token": TOKEN}}, callback=join_callback)
# Wait indefinitely to keep the connection open
await sio.wait()
# Actually run the async `main` function using `asyncio`
if __name__ == "__main__":
asyncio.run(main())
+33
View File
@@ -0,0 +1,33 @@
import aiohttp
import socketio
from env import WEBUI_URL, TOKEN
async def send_message(channel_id: str, message: str):
url = f"{WEBUI_URL}/api/v1/channels/{channel_id}/messages/post"
headers = {"Authorization": f"Bearer {TOKEN}"}
data = {"content": str(message)}
async with aiohttp.ClientSession() as session:
async with session.post(url, headers=headers, json=data) as response:
if response.status != 200:
# Raise an exception if the request fails
raise aiohttp.ClientResponseError(
request_info=response.request_info,
history=response.history,
status=response.status,
message=await response.text(),
headers=response.headers,
)
# Return response JSON if successful
return await response.json()
async def send_typing(sio: socketio.AsyncClient, channel_id: str):
await sio.emit(
"channel-events",
{
"channel_id": channel_id,
"data": {"type": "typing", "data": {"typing": True}},
},
)
-10
View File
@@ -1,10 +0,0 @@
# Core dependencies
flask==2.3.3
fastapi==0.103.1
uvicorn==0.23.2
flask-sqlalchemy==3.0.5
python-dotenv==1.0.0
pydantic==2.3.0
# For future implementation
pinecone-client==2.2.2
+5
View File
@@ -9,3 +9,8 @@ langchain-text-splitters==0.3.8
# Utilities
requests==2.32.3
# WebSocket and async support
python-socketio==5.10.0
aiohttp==3.9.1
websockets==12.0
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""
Script to run the custom OpenWebUI AI bot.
This script runs our custom AI bot implementation.
"""
import sys
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 our custom AI example
from examples.custom_ai import main
if __name__ == "__main__":
print("Starting custom OpenWebUI AI bot...")
print("Press Ctrl+C to stop")
try:
# Run the main function
asyncio.run(main())
except KeyboardInterrupt:
print("\nBot stopped by user")
# No need to call shutdown here as it's handled in the main function
except Exception as e:
print(f"Error running bot: {str(e)}")
print("Check that the OpenWebUI server is running and accessible.")
+42
View File
@@ -0,0 +1,42 @@
#!/bin/bash
# Script to run the AI service locally for testing
# This script activates the virtual environment and runs the service with uvicorn
# Check if the virtual environment exists
if [ ! -d "venv" ]; then
echo "Virtual environment not found. Creating one..."
python -m venv venv
source venv/bin/activate
pip install -r requirements.txt
else
source venv/bin/activate
fi
# Create a directory for logs
mkdir -p logs
# Set environment variables for local testing
export API_HOST=0.0.0.0
export API_PORT=5252
export PUBLIC_URL=http://localhost:5252 # For local testing
export PYTHONPATH=$PYTHONPATH:$(pwd)
# Check if .env file exists
if [ ! -f "ai_service/.env" ]; then
echo "Warning: .env file not found in ai_service directory."
echo "Creating a basic .env file from .env.example..."
if [ -f "ai_service/.env.example" ]; then
cp ai_service/.env.example ai_service/.env
echo "Created .env file from .env.example. Please edit it with your actual values."
else
echo "Error: .env.example file not found. Please create a .env file manually."
exit 1
fi
fi
# Run the service with uvicorn
echo "Starting AI service on http://localhost:5252"
echo "Press Ctrl+C to stop the service"
uvicorn ai_service.api:app --host $API_HOST --port $API_PORT --reload
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env python3
"""
Script to run the OpenWebUI AI bot.
This script runs the AI bot example from the openwebui-bot repository.
"""
import sys
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
if __name__ == "__main__":
print("Starting OpenWebUI AI bot...")
print("Press Ctrl+C to stop")
try:
# Run the main function
asyncio.run(main())
except KeyboardInterrupt:
print("\nBot stopped by user")
# No need to call shutdown here as it's handled in the main function
except Exception as e:
print(f"Error running bot: {str(e)}")
print("Check that the OpenWebUI server is running and accessible.")
-22
View File
@@ -1,22 +0,0 @@
#!/bin/bash
# Script to run the RAG test in a Python virtual environment
# Make the script executable
chmod +x test_rag.py
# Check if virtual environment is activated
if [[ -z "$VIRTUAL_ENV" ]]; then
echo "Virtual environment is not activated."
echo "Please activate your virtual environment first with:"
echo "source /path/to/your/venv/bin/activate"
exit 1
fi
echo "Running RAG tests against remote server..."
python test_rag.py --remote --verbose
# If you want to test against a different server, uncomment and modify this line:
# python test_rag.py --api-url "http://your-server-url:port" --verbose
# If you want to test with a specific query, uncomment and modify this line:
# python test_rag.py --remote --query "What information do you have about project X?" --verbose
-53
View File
@@ -1,53 +0,0 @@
#!/bin/bash
# Script to set up the local test environment
# Check if Python is installed
if ! command -v python3 &> /dev/null; then
echo "Python 3 is not installed. Please install Python 3 and try again."
exit 1
fi
# Create a virtual environment if it doesn't exist
if [ ! -d "venv" ]; then
echo "Creating virtual environment..."
python3 -m venv venv
if [ $? -ne 0 ]; then
echo "Failed to create virtual environment. Please check your Python installation."
exit 1
fi
echo "Virtual environment created successfully."
else
echo "Virtual environment already exists."
fi
# Activate the virtual environment
echo "Activating virtual environment..."
source venv/bin/activate
if [ $? -ne 0 ]; then
echo "Failed to activate virtual environment."
exit 1
fi
# Install dependencies
echo "Installing dependencies..."
pip install --upgrade pip
pip install requests python-dotenv argparse
# Create local_test.env file if it doesn't exist
if [ ! -f "local_test.env" ]; then
echo "Creating local_test.env file from template..."
cp local_test.env.example local_test.env
echo "Please edit local_test.env with your server credentials."
else
echo "local_test.env file already exists."
fi
# Make the test script executable
chmod +x test_remote_ollama.py
echo "Setup complete!"
echo "To run the test script:"
echo "1. Activate the virtual environment: source venv/bin/activate"
echo "2. Run the test script: python test_remote_ollama.py"
echo "3. To specify a different model or prompt: python test_remote_ollama.py --model llama3.3 --prompt \"Tell me about AI\""
echo "4. To specify a different Ollama URL: python test_remote_ollama.py --ollama-url http://your-server-ip:11434"
-29
View File
@@ -1,29 +0,0 @@
#!/bin/bash
# Script to stop SSH tunnels
# Check if the tunnel_pids.txt file exists
if [ ! -f "tunnel_pids.txt" ]; then
echo "No tunnel PIDs file found. No tunnels to stop."
exit 0
fi
# Read the PIDs from the file and kill each process
while read pid; do
if [ -n "$pid" ]; then
echo "Stopping tunnel with PID $pid..."
kill $pid 2>/dev/null || true
fi
done < tunnel_pids.txt
# Remove the PIDs file
rm tunnel_pids.txt
# Check for any remaining SSH tunnels
remaining_tunnels=$(ps aux | grep "ssh -N -L" | grep -v grep | wc -l)
if [ $remaining_tunnels -gt 0 ]; then
echo "Warning: There are still $remaining_tunnels SSH tunnels running."
echo "You may need to kill them manually:"
ps aux | grep "ssh -N -L" | grep -v grep
else
echo "All SSH tunnels have been stopped."
fi
-214
View File
@@ -1,214 +0,0 @@
"""
Test script for the AI service API.
"""
import requests
import json
import argparse
def test_health(api_url):
"""Test the health endpoint."""
print(f"Testing health endpoint at: {api_url}/health")
try:
response = requests.get(f"{api_url}/health", timeout=30)
response.raise_for_status()
print("Health check successful!")
print(f"Response: {response.json()}")
return True
except Exception as e:
print(f"ERROR: Health check failed: {str(e)}")
return False
def test_config(api_url):
"""Test the config endpoint."""
print(f"Testing config endpoint at: {api_url}/config")
try:
response = requests.get(f"{api_url}/config", timeout=30)
response.raise_for_status()
print("Config check successful!")
print("Configuration:")
config = response.json()
for key, value in config.items():
print(f" {key}: {value}")
return True, config
except Exception as e:
print(f"ERROR: Config check failed: {str(e)}")
return False, None
def test_ollama_connection(api_url):
"""Test the Ollama connection."""
print(f"Testing Ollama connection at: {api_url}/test-ollama")
try:
response = requests.get(f"{api_url}/test-ollama", timeout=60)
response.raise_for_status()
print("Ollama connection test successful!")
result = response.json()
print(f"Status: {result.get('status')}")
print(f"Message: {result.get('message')}")
print(f"Ollama URL: {result.get('ollama_url')}")
if 'models' in result:
print("Available models:")
for model in result.get('models', {}).get('models', []):
print(f" - {model.get('name')}")
return True
except Exception as e:
print(f"ERROR: Ollama connection test failed: {str(e)}")
return False
def test_chat_completion(api_url, model_id, prompt):
"""Test the chat completion endpoint."""
print(f"Testing chat completion at: {api_url}/test-ollama-direct")
print(f"Model: {model_id}")
print(f"Prompt: {prompt}")
try:
# First, create a chat
chat_response = requests.post(
f"{api_url}/chats",
headers={"Content-Type": "application/json"},
json={
"user_id": "test_user",
"title": "Test Chat",
"model_id": model_id
},
timeout=60
)
if chat_response.status_code != 200:
print(f"ERROR: Failed to create chat: {chat_response.status_code}")
print(chat_response.text)
# Try direct Ollama test instead
print("Trying direct Ollama test...")
response = requests.post(
f"{api_url}/test-ollama-direct",
headers={"Content-Type": "application/json"},
json={
"model": model_id,
"prompt": prompt
},
timeout=120
)
response.raise_for_status()
result = response.json()
print("\nResponse:")
print(result.get('response', 'No response'))
return True
chat_id = chat_response.json().get("id")
print(f"Chat created with ID: {chat_id}")
# Send a message to the chat
message_response = requests.post(
f"{api_url}/chats/{chat_id}/messages",
headers={"Content-Type": "application/json"},
json={
"message": prompt,
"user_id": "test_user",
"temperature": 0.7,
"max_tokens": 500
},
timeout=120
)
message_response.raise_for_status()
result = message_response.json()
print("\nResponse:")
print(result.get('content', 'No content in response'))
return True
except Exception as e:
print(f"ERROR: Chat completion test failed: {str(e)}")
return False
def test_rag_completion(api_url, prompt):
"""Test the RAG completion endpoint."""
print(f"Testing RAG completion at: {api_url}/ask-rag")
print(f"Prompt: {prompt}")
try:
response = requests.post(
f"{api_url}/ask-rag",
headers={"Content-Type": "application/json"},
json={"query": prompt},
timeout=120
)
response.raise_for_status()
result = response.json()
print("\nResponse:")
print(result.get('response', 'No response'))
return True
except Exception as e:
print(f"ERROR: RAG completion test failed: {str(e)}")
return False
def main():
"""Main function."""
parser = argparse.ArgumentParser(description='Test the AI service API')
parser.add_argument('--api-url', type=str, default='http://localhost:5252', help='AI service API URL')
parser.add_argument('--model', type=str, default='llama3.1', help='Model to use for testing')
parser.add_argument('--prompt', type=str, default='What is the capital of France?', help='Prompt to use for testing')
parser.add_argument('--rag-prompt', type=str, default='What information do you have in your knowledge base?', help='Prompt to use for RAG testing')
args = parser.parse_args()
print("=== AI Service API Test ===")
print(f"API URL: {args.api_url}")
print(f"Model: {args.model}")
print()
# Test health endpoint
print("=== Testing Health Endpoint ===")
health_success = test_health(args.api_url)
print()
# Test config endpoint
print("=== Testing Config Endpoint ===")
config_success, config = test_config(args.api_url)
print()
# Test Ollama connection
print("=== Testing Ollama Connection ===")
ollama_success = test_ollama_connection(args.api_url)
print()
# Test chat completion
if ollama_success:
print("=== Testing Chat Completion ===")
chat_success = test_chat_completion(args.api_url, args.model, args.prompt)
print()
# Test RAG completion
print("=== Testing RAG Completion ===")
rag_success = test_rag_completion(args.api_url, args.rag_prompt)
print()
else:
chat_success = False
rag_success = False
# Print summary
print("=== Test Summary ===")
print(f"Health Endpoint: {'SUCCESS' if health_success else 'FAILED'}")
print(f"Config Endpoint: {'SUCCESS' if config_success else 'FAILED'}")
print(f"Ollama Connection: {'SUCCESS' if ollama_success else 'FAILED'}")
print(f"Chat Completion: {'SUCCESS' if chat_success else 'FAILED'}")
print(f"RAG Completion: {'SUCCESS' if rag_success else 'FAILED'}")
if __name__ == "__main__":
main()
-76
View File
@@ -1,76 +0,0 @@
"""
Test script for the direct Ollama endpoint.
"""
import requests
import json
import argparse
import time
def test_direct_ollama(api_url, model_id, prompt):
"""Test the direct Ollama endpoint."""
print(f"Testing direct Ollama endpoint at: {api_url}/test-ollama-direct")
print(f"Model: {model_id}")
print(f"Prompt: {prompt}")
try:
start_time = time.time()
response = requests.post(
f"{api_url}/test-ollama-direct",
headers={"Content-Type": "application/json"},
json={
"model": model_id,
"prompt": prompt
},
timeout=300 # 5 minutes timeout
)
end_time = time.time()
elapsed_time = end_time - start_time
response.raise_for_status()
result = response.json()
print(f"\nResponse (took {elapsed_time:.2f} seconds):")
# Handle different response formats
content = None
if 'response' in result and isinstance(result['response'], dict) and 'message' in result['response']:
content = result['response']['message'].get('content')
elif 'response' in result and isinstance(result['response'], str):
content = result['response']
else:
content = str(result)
print(content or 'No content in response')
return True
except Exception as e:
print(f"ERROR: Direct Ollama test failed: {str(e)}")
return False
def main():
"""Main function."""
parser = argparse.ArgumentParser(description='Test the direct Ollama endpoint')
parser.add_argument('--api-url', type=str, default='http://localhost:5252', help='AI service API URL')
parser.add_argument('--model', type=str, default='llama3.1', help='Model to use for testing')
parser.add_argument('--prompt', type=str, default='What is the capital of France?', help='Prompt to use for testing')
args = parser.parse_args()
print("=== Direct Ollama Test ===")
print(f"API URL: {args.api_url}")
print(f"Model: {args.model}")
print(f"Prompt: {args.prompt}")
print()
# Test direct Ollama endpoint
success = test_direct_ollama(args.api_url, args.model, args.prompt)
# Print summary
print("\n=== Test Summary ===")
print(f"Direct Ollama Test: {'SUCCESS' if success else 'FAILED'}")
if __name__ == "__main__":
main()
-73
View File
@@ -1,73 +0,0 @@
"""
Test script for Ollama integration.
"""
import os
import sys
import requests
import json
# Add the parent directory to the path so we can import ai_service
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
from ai_service.config import config
from ai_service.models.model_service import model_service
def test_available_models():
"""Test getting available models."""
models = model_service.get_available_models()
print("Available models:")
for model in models:
print(f"- {model['name']} ({model['id']}): {model['description']}")
print()
def test_generate_response():
"""Test generating a response."""
model_id = "llama3.1" # Use a specific model instead of config.DEFAULT_MODEL
prompt = "What is the capital of France?"
print(f"Testing model: {model_id}")
print(f"Prompt: {prompt}")
response = model_service.generate_response(
model_id=model_id,
prompt=prompt,
use_rag=False
)
print("Response:")
print(response)
print()
def test_rag_response():
"""Test generating a response with RAG."""
model_id = "llama3.1" # Use a specific model instead of config.DEFAULT_MODEL
prompt = "Tell me about the documents in the knowledge base."
print(f"Testing RAG with model: {model_id}")
print(f"Prompt: {prompt}")
response = model_service.generate_response(
model_id=model_id,
prompt=prompt,
use_rag=True
)
print("Response with RAG:")
print(response)
print()
if __name__ == "__main__":
print("Testing Ollama integration")
print(f"OpenWebUI URL: {config.OPENWEBUI_URL}")
# Override the Ollama API URL to use OpenWebUI
model_service.ollama_api_url = f"{config.OPENWEBUI_URL}/ollama"
print(f"Using Ollama API URL: {model_service.ollama_api_url}")
print(f"Default model: {config.DEFAULT_MODEL}")
print()
test_available_models()
test_generate_response()
test_rag_response()
-104
View File
@@ -1,104 +0,0 @@
"""
Test script for the Ollama API with custom prompts.
"""
import requests
import json
import argparse
import time
class OllamaRequest:
"""Request model for Ollama API."""
def __init__(self, model, prompt, system_prompt=None):
self.model = model
self.prompt = prompt
self.system_prompt = system_prompt or "You are a helpful assistant."
def to_json(self):
"""Convert to JSON for the API request."""
messages = []
# Add system message
messages.append({"role": "system", "content": self.system_prompt})
# Add user message
messages.append({"role": "user", "content": self.prompt})
return {
"model": self.model,
"messages": messages,
"stream": False
}
def test_ollama_api(api_url, model, prompt, system_prompt=None):
"""Test the Ollama API directly."""
print(f"Testing Ollama API at: {api_url}")
print(f"Model: {model}")
print(f"Prompt: {prompt}")
if system_prompt:
print(f"System prompt: {system_prompt}")
# Create the request
request = OllamaRequest(model, prompt, system_prompt)
request_json = request.to_json()
try:
start_time = time.time()
# Make the API call
response = requests.post(
api_url,
headers={"Content-Type": "application/json"},
json=request_json,
timeout=300 # 5 minutes timeout
)
end_time = time.time()
elapsed_time = end_time - start_time
response.raise_for_status()
# Parse the response
result = response.json()
print(f"\nResponse (took {elapsed_time:.2f} seconds):")
print(json.dumps(result, indent=2))
# Extract the content
if 'message' in result:
content = result['message'].get('content', 'No content in response')
print("\nContent:")
print(content)
return True
except Exception as e:
print(f"ERROR: Ollama API test failed: {str(e)}")
return False
def main():
"""Main function."""
parser = argparse.ArgumentParser(description='Test the Ollama API with custom prompts')
parser.add_argument('--api-url', type=str, default='http://localhost:11434/api/chat', help='Ollama API URL')
parser.add_argument('--model', type=str, default='llama3.1', help='Model to use for testing')
parser.add_argument('--prompt', type=str, default='What is the capital of France?', help='Prompt to use for testing')
parser.add_argument('--system-prompt', type=str, default=None, help='System prompt to use for testing')
args = parser.parse_args()
print("=== Ollama API Test ===")
print(f"API URL: {args.api_url}")
print(f"Model: {args.model}")
print(f"Prompt: {args.prompt}")
if args.system_prompt:
print(f"System prompt: {args.system_prompt}")
print()
# Test Ollama API
success = test_ollama_api(args.api_url, args.model, args.prompt, args.system_prompt)
# Print summary
print("\n=== Test Summary ===")
print(f"Ollama API Test: {'SUCCESS' if success else 'FAILED'}")
if __name__ == "__main__":
main()
-309
View File
@@ -1,309 +0,0 @@
#!/usr/bin/env python
"""
Test script for RAG functionality in the AI service.
This script tests the document-based question answering capabilities
by making requests to the API endpoints.
"""
import os
import sys
import json
import argparse
import requests
from typing import Dict, Any, Optional
from pprint import pprint
# Default configuration
DEFAULT_API_URL = "http://localhost:5252" # Local development server
DEFAULT_REMOTE_API_URL = "http://157.157.221.29:5252" # Remote server
DEFAULT_MODEL = "llama3.1"
class RAGTester:
"""Test the RAG functionality of the AI service."""
def __init__(self, api_url: str, verbose: bool = False):
"""
Initialize the RAG tester.
Args:
api_url: URL of the AI service API.
verbose: Whether to print verbose output.
"""
self.api_url = api_url
self.verbose = verbose
self.session = requests.Session()
# Print configuration
print(f"Testing RAG functionality against API at: {self.api_url}")
def _log(self, message: str):
"""Log a message if verbose mode is enabled."""
if self.verbose:
print(f"[DEBUG] {message}")
def check_server_health(self) -> bool:
"""
Check if the server is healthy.
Returns:
True if the server is healthy, False otherwise.
"""
try:
response = self.session.get(f"{self.api_url}/health", timeout=10)
response.raise_for_status()
result = response.json()
if result.get("status") == "healthy":
print("✅ Server is healthy")
return True
else:
print("❌ Server health check failed")
print(f"Response: {result}")
return False
except Exception as e:
print(f"❌ Error checking server health: {str(e)}")
return False
def check_config(self) -> Dict[str, Any]:
"""
Check the server configuration.
Returns:
Server configuration.
"""
try:
response = self.session.get(f"{self.api_url}/config", timeout=10)
response.raise_for_status()
config = response.json()
print("✅ Server configuration:")
print(f" - API Host: {config.get('api_host')}")
print(f" - API Port: {config.get('api_port')}")
print(f" - OpenWebUI URL: {config.get('openwebui_url')}")
print(f" - Ollama API URL: {config.get('ollama_api_url')}")
print(f" - Default Model: {config.get('default_model')}")
print(f" - API Timeout: {config.get('api_timeout')} seconds")
print(f" - Available Models: {', '.join(config.get('available_models', []))}")
return config
except Exception as e:
print(f"❌ Error checking server configuration: {str(e)}")
return {}
def test_ollama_connection(self) -> bool:
"""
Test the connection to Ollama.
Returns:
True if the connection is successful, False otherwise.
"""
try:
response = self.session.get(f"{self.api_url}/test-ollama", timeout=30)
response.raise_for_status()
result = response.json()
if result.get("status") == "success":
print("✅ Successfully connected to Ollama API")
print(f" - Ollama URL: {result.get('ollama_url')}")
print(f" - Available models: {len(result.get('models', {}).get('models', []))}")
return True
else:
print("❌ Failed to connect to Ollama API")
print(f" - Error: {result.get('message')}")
print(f" - Ollama URL: {result.get('ollama_url')}")
return False
except Exception as e:
print(f"❌ Error testing Ollama connection: {str(e)}")
return False
def test_chat_completion(self, model_id: Optional[str] = None) -> bool:
"""
Test basic chat completion without RAG.
Args:
model_id: Optional model ID to use.
Returns:
True if the test is successful, False otherwise.
"""
try:
response = self.session.post(f"{self.api_url}/test-chat", timeout=60)
response.raise_for_status()
result = response.json()
if result.get("status") == "success":
print("✅ Successfully tested chat completion")
print(f" - Model: {result.get('model')}")
print(f" - Response: {result.get('response')[:100]}...") # First 100 chars
return True
else:
print("❌ Failed to test chat completion")
print(f" - Error: {result.get('message')}")
return False
except Exception as e:
print(f"❌ Error testing chat completion: {str(e)}")
return False
def test_rag_completion(self, query: str = "What information do you have in your knowledge database?") -> bool:
"""
Test RAG completion with a query.
Args:
query: Query to test with RAG.
Returns:
True if the test is successful, False otherwise.
"""
try:
response = self.session.post(
f"{self.api_url}/test-rag",
params={"query": query},
timeout=120 # Longer timeout for RAG
)
response.raise_for_status()
result = response.json()
if result.get("status") == "success":
print("✅ Successfully tested RAG completion")
print(f" - Model: {result.get('model')}")
print(f" - Query: {result.get('query')}")
print(f" - OpenWebUI URL: {result.get('openwebui_url')}")
print(f" - Response: {result.get('response')[:150]}...") # First 150 chars
# Print full response in verbose mode
if self.verbose:
print("\nFull response:")
print(result.get('response'))
return True
else:
print("❌ Failed to test RAG completion")
print(f" - Error: {result.get('message')}")
print(f" - OpenWebUI URL: {result.get('openwebui_url')}")
return False
except Exception as e:
print(f"❌ Error testing RAG completion: {str(e)}")
return False
def create_chat_and_test_rag(self, query: str, user_id: str = "test_user") -> bool:
"""
Create a chat and test RAG with a message.
Args:
query: Query to test with RAG.
user_id: User ID for the chat.
Returns:
True if the test is successful, False otherwise.
"""
try:
# Create a chat
create_response = self.session.post(
f"{self.api_url}/chats",
json={
"user_id": user_id,
"title": "RAG Test Chat",
"model_id": DEFAULT_MODEL,
"is_team_chat": False
},
timeout=30
)
create_response.raise_for_status()
chat = create_response.json()
chat_id = chat.get("id")
print(f"✅ Created chat with ID: {chat_id}")
# Send a message with RAG enabled
message_response = self.session.post(
f"{self.api_url}/chats/{chat_id}/messages",
json={
"message": query,
"user_id": user_id,
"use_rag": True,
"temperature": 0.7,
"max_tokens": 1000
},
timeout=120 # Longer timeout for RAG
)
message_response.raise_for_status()
message = message_response.json()
print("✅ Successfully sent message with RAG")
print(f" - Message ID: {message.get('id')}")
print(f" - Response: {message.get('content')[:150]}...") # First 150 chars
# Print full response in verbose mode
if self.verbose:
print("\nFull response:")
print(message.get('content'))
return True
except Exception as e:
print(f"❌ Error testing chat with RAG: {str(e)}")
return False
def run_all_tests(self, query: str = "What information do you have in your knowledge database?"):
"""
Run all tests.
Args:
query: Query to test with RAG.
"""
print("\n=== Running RAG Functionality Tests ===\n")
# Check server health
if not self.check_server_health():
print("❌ Server health check failed. Aborting tests.")
return
# Check configuration
config = self.check_config()
if not config:
print("❌ Failed to get server configuration. Continuing with tests...")
# Test Ollama connection
if not self.test_ollama_connection():
print("⚠️ Ollama connection test failed. Some tests may fail.")
# Test basic chat completion
if not self.test_chat_completion():
print("⚠️ Basic chat completion test failed. RAG tests may also fail.")
# Test RAG completion
print("\n--- Testing RAG Completion ---\n")
self.test_rag_completion(query)
# Test chat with RAG
print("\n--- Testing Chat with RAG ---\n")
self.create_chat_and_test_rag(query)
print("\n=== Tests Completed ===\n")
def main():
"""Main function to run the tests."""
parser = argparse.ArgumentParser(description="Test RAG functionality in the AI service")
parser.add_argument("--api-url", default=DEFAULT_API_URL, help=f"URL of the AI service API (default: {DEFAULT_API_URL})")
parser.add_argument("--remote", action="store_true", help=f"Use the remote API URL ({DEFAULT_REMOTE_API_URL})")
parser.add_argument("--query", default="What information do you have in your knowledge database?", help="Query to test with RAG")
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
args = parser.parse_args()
# Use remote URL if specified
api_url = DEFAULT_REMOTE_API_URL if args.remote else args.api_url
# Create and run the tester
tester = RAGTester(api_url=api_url, verbose=args.verbose)
tester.run_all_tests(query=args.query)
if __name__ == "__main__":
main()
-165
View File
@@ -1,165 +0,0 @@
"""
Test script for connecting to a remote Ollama server.
"""
import os
import sys
import requests
import json
import argparse
# Import local test configuration
from local_test_config import config
def test_ollama_connection():
"""Test the connection to the Ollama API."""
print(f"Testing connection to Ollama API at: {config.OLLAMA_API_URL}")
try:
# Try to connect to Ollama API via OpenWebUI
# OpenWebUI exposes Ollama models through its own API
response = requests.get(f"{config.OPENWEBUI_URL}/api/models", timeout=config.API_TIMEOUT)
response.raise_for_status()
# Print the models from OpenWebUI
print("Connection successful!")
print("Available models:")
models_data = response.json()
if isinstance(models_data, list):
for model in models_data:
print(f"- {model.get('id')}")
else:
print(f"Unexpected response format: {models_data}")
return True
except requests.exceptions.Timeout as e:
print(f"ERROR: Timeout connecting to Ollama API: {str(e)}. The request exceeded the {config.API_TIMEOUT} second timeout.")
return False
except requests.exceptions.ConnectionError as e:
print(f"ERROR: Connection error to Ollama API: {str(e)}. Please check if Ollama is running at {config.OLLAMA_API_URL}.")
return False
except Exception as e:
print(f"ERROR: Error connecting to Ollama API: {str(e)}")
return False
def test_generate_response(model_id=None, prompt=None):
"""Test generating a response from the model."""
if model_id is None:
model_id = config.DEFAULT_MODEL
if prompt is None:
prompt = "What is the capital of France?"
print(f"Testing model: {model_id}")
print(f"Prompt: {prompt}")
try:
# Prepare the request for OpenWebUI's chat completions API
request_json = {
"model": model_id,
"messages": [
{"role": "user", "content": prompt}
],
"stream": False,
"temperature": 0.7,
"max_tokens": 500
}
# Make the API call to OpenWebUI's chat completions API
print(f"Sending request to OpenWebUI API at: {config.OPENWEBUI_URL}/api/chat/completions")
response = requests.post(
f"{config.OPENWEBUI_URL}/api/chat/completions",
headers={"Content-Type": "application/json"},
json=request_json,
timeout=config.API_TIMEOUT
)
response.raise_for_status()
# Parse the response
response_data = response.json()
# Print the response (OpenWebUI uses OpenAI-compatible format)
print("\nResponse:")
content = response_data.get('choices', [{}])[0].get('message', {}).get('content', 'No content in response')
print(content)
return True
except requests.exceptions.Timeout as e:
print(f"ERROR: Timeout generating response: {str(e)}. The request exceeded the {config.API_TIMEOUT} second timeout.")
return False
except requests.exceptions.ConnectionError as e:
print(f"ERROR: Connection error to Ollama API: {str(e)}. Please check if Ollama is running at {config.OLLAMA_API_URL}.")
return False
except Exception as e:
print(f"ERROR: Error generating response: {str(e)}")
return False
def test_openwebui_connection():
"""Test the connection to OpenWebUI."""
print(f"Testing connection to OpenWebUI at: {config.OPENWEBUI_URL}")
try:
# Try to connect to OpenWebUI
response = requests.get(f"{config.OPENWEBUI_URL}/api/health", timeout=config.API_TIMEOUT)
response.raise_for_status()
# Print the response
print("Connection successful!")
print(f"OpenWebUI status: {response.text}")
return True
except requests.exceptions.Timeout as e:
print(f"ERROR: Timeout connecting to OpenWebUI: {str(e)}. The request exceeded the {config.API_TIMEOUT} second timeout.")
return False
except requests.exceptions.ConnectionError as e:
print(f"ERROR: Connection error to OpenWebUI: {str(e)}. Please check if OpenWebUI is running at {config.OPENWEBUI_URL}.")
return False
except Exception as e:
print(f"ERROR: Error connecting to OpenWebUI: {str(e)}")
return False
def main():
"""Main function."""
parser = argparse.ArgumentParser(description='Test remote Ollama and OpenWebUI connections')
parser.add_argument('--model', type=str, default=config.DEFAULT_MODEL, help='Model to use for testing')
parser.add_argument('--prompt', type=str, default="What is the capital of France?", help='Prompt to use for testing')
parser.add_argument('--ollama-url', type=str, default=config.OLLAMA_API_URL, help='Ollama API URL')
parser.add_argument('--openwebui-url', type=str, default=config.OPENWEBUI_URL, help='OpenWebUI URL')
parser.add_argument('--timeout', type=int, default=config.API_TIMEOUT, help='API timeout in seconds')
args = parser.parse_args()
# Update configuration with command-line arguments
config.OLLAMA_API_URL = args.ollama_url
config.OPENWEBUI_URL = args.openwebui_url
config.API_TIMEOUT = args.timeout
print("=== Remote Connection Test ===")
print(f"Ollama API URL: {config.OLLAMA_API_URL}")
print(f"OpenWebUI URL: {config.OPENWEBUI_URL}")
print(f"API Timeout: {config.API_TIMEOUT} seconds")
print()
# Test Ollama connection
print("=== Testing Ollama Connection ===")
ollama_success = test_ollama_connection()
print()
# Test OpenWebUI connection
print("=== Testing OpenWebUI Connection ===")
openwebui_success = test_openwebui_connection()
print()
# Test generating a response
if ollama_success:
print("=== Testing Model Response ===")
test_generate_response(args.model, args.prompt)
print("\n=== Test Summary ===")
print(f"Ollama Connection: {'SUCCESS' if ollama_success else 'FAILED'}")
print(f"OpenWebUI Connection: {'SUCCESS' if openwebui_success else 'FAILED'}")
if __name__ == "__main__":
main()
-2
View File
@@ -1,2 +0,0 @@
requests>=2.28.0
pydantic>=2.0.0
-91
View File
@@ -1,91 +0,0 @@
#!/usr/bin/env python3
"""
Simple test script for team chat functionality.
"""
import requests
import argparse
import sys
def main():
"""Main function."""
parser = argparse.ArgumentParser(description='Simple test for team chat functionality')
parser.add_argument('--api-url', type=str, default='http://localhost:5252', help='AI service API URL')
parser.add_argument('--model', type=str, default='llama3.1', help='Model to use for testing')
parser.add_argument('--user-id', type=str, default='test_user', help='User ID for testing')
parser.add_argument('--chat-id', type=str, help='Existing chat ID to use (optional)')
parser.add_argument('--message', type=str, default='Hello team!', help='Message to send')
parser.add_argument('--with-mention', action='store_true', help='Add @ai mention to message')
args = parser.parse_args()
print(f"API URL: {args.api_url}")
print(f"Model: {args.model}")
print(f"User ID: {args.user_id}")
# Create a chat if no chat ID is provided
chat_id = args.chat_id
if not chat_id:
print("\nCreating a new team chat...")
try:
response = requests.post(
f"{args.api_url}/chats",
headers={"Content-Type": "application/json"},
json={
"user_id": args.user_id,
"title": "Simple Team Chat Test",
"model_id": args.model,
"is_team_chat": True
},
timeout=30
)
response.raise_for_status()
chat = response.json()
chat_id = chat.get("id")
print(f"Created team chat with ID: {chat_id}")
print(f"OpenWebUI Channel ID: {chat.get('openwebui_channel_id', 'Not available')}")
print(f"Team Members: {chat.get('team_members', [])}")
except Exception as e:
print(f"Error creating team chat: {str(e)}")
sys.exit(1)
else:
print(f"\nUsing existing chat ID: {chat_id}")
# Prepare message
message = args.message
if args.with_mention:
message = f"@ai {message}"
print(f"\nSending message WITH @ai mention: {message}")
else:
print(f"\nSending message WITHOUT @ai mention: {message}")
# Send message
try:
response = requests.post(
f"{args.api_url}/chats/{chat_id}/messages",
headers={"Content-Type": "application/json"},
json={
"message": message,
"user_id": args.user_id
},
timeout=10
)
response.raise_for_status()
result = response.json()
print("\nMessage sent successfully!")
print(f"AI Response: {result.get('content', 'No content in response')}")
print("\nNote: When using the API directly, the AI will always respond")
print("The @ai mention functionality only applies to messages sent through OpenWebUI channels")
print(f"\nYou can check the OpenWebUI interface at: http://104.225.217.215:8080/")
print(f"Chat ID: {chat_id}")
except Exception as e:
print(f"Error sending message: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()
-266
View File
@@ -1,266 +0,0 @@
#!/usr/bin/env python3
"""
Test script for team chat mentions functionality.
This script tests the AI response to @ai mentions in team chats.
"""
import requests
import json
import argparse
import time
import sys
from typing import Dict, Any, List, Tuple, Optional
def test_health(api_url: str) -> bool:
"""Test the health endpoint."""
print(f"Testing health endpoint at: {api_url}/health")
try:
response = requests.get(f"{api_url}/health", timeout=30)
response.raise_for_status()
print("✅ Health check successful!")
print(f"Response: {response.json()}")
return True
except Exception as e:
print(f"❌ ERROR: Health check failed: {str(e)}")
return False
def test_create_team_chat(api_url: str, user_id: str, title: str, model_id: str) -> Tuple[bool, Optional[str]]:
"""
Test creating a team chat.
Args:
api_url: API URL
user_id: User ID
title: Chat title
model_id: Model ID
Returns:
Tuple of (success, chat_id)
"""
print(f"Creating team chat with title: {title}")
try:
response = requests.post(
f"{api_url}/chats",
headers={"Content-Type": "application/json"},
json={
"user_id": user_id,
"title": title,
"model_id": model_id,
"is_team_chat": True
},
timeout=60
)
response.raise_for_status()
chat = response.json()
chat_id = chat.get("id")
print(f"✅ Team chat created successfully with ID: {chat_id}")
print(f"OpenWebUI Channel ID: {chat.get('openwebui_channel_id', 'Not available')}")
print(f"Team Members: {chat.get('team_members', [])}")
return True, chat_id
except Exception as e:
print(f"❌ ERROR: Failed to create team chat: {str(e)}")
return False, None
def test_add_team_member(api_url: str, chat_id: str, user_id: str) -> bool:
"""
Test adding a member to a team chat.
Args:
api_url: API URL
chat_id: Chat ID
user_id: User ID to add
Returns:
Success status
"""
print(f"Adding user {user_id} to team chat {chat_id}")
try:
response = requests.post(
f"{api_url}/chats/{chat_id}/members/{user_id}",
timeout=30
)
response.raise_for_status()
print(f"✅ User {user_id} added to team chat successfully")
return True
except Exception as e:
print(f"❌ ERROR: Failed to add user to team chat: {str(e)}")
return False
def test_send_team_message_with_mention(api_url: str, chat_id: str, user_id: str, message: str, mention: str) -> bool:
"""
Test sending a message with an @ai mention to a team chat.
Args:
api_url: API URL
chat_id: Chat ID
user_id: User ID
message: Message content
mention: Mention to use (@ai, @bot, etc.)
Returns:
Success status
"""
full_message = f"{mention} {message}"
print(f"Sending message to team chat {chat_id}: {full_message}")
try:
response = requests.post(
f"{api_url}/chats/{chat_id}/messages",
headers={"Content-Type": "application/json"},
json={
"message": full_message,
"user_id": user_id,
"temperature": 0.7,
"max_tokens": 500
},
timeout=60
)
response.raise_for_status()
result = response.json()
print(f"✅ Message with mention sent successfully")
print(f"AI Response: {result.get('content', 'No content in response')[:100]}...")
return True
except Exception as e:
print(f"❌ ERROR: Failed to send message to team chat: {str(e)}")
return False
def test_send_team_message_without_mention(api_url: str, chat_id: str, user_id: str, message: str) -> bool:
"""
Test sending a message without an @ai mention to a team chat.
Args:
api_url: API URL
chat_id: Chat ID
user_id: User ID
message: Message content
Returns:
Success status
"""
print(f"Sending message without mention to team chat {chat_id}: {message}")
try:
response = requests.post(
f"{api_url}/chats/{chat_id}/messages",
headers={"Content-Type": "application/json"},
json={
"message": message,
"user_id": user_id,
"temperature": 0.7,
"max_tokens": 500
},
timeout=60
)
response.raise_for_status()
result = response.json()
print(f"✅ Message without mention sent successfully")
# Note: When using the API directly, the AI will always respond
# The AI_RESPOND_TO_ALL setting only applies to messages received through the webhook
print(f"Note: AI responded because we're using the API directly: {result.get('content', 'No content in response')[:100]}...")
print("The @ai mention functionality only applies to messages sent through OpenWebUI channels")
return True
except Exception as e:
print(f"❌ ERROR: Failed to send message to team chat: {str(e)}")
return False
def main():
"""Main function."""
parser = argparse.ArgumentParser(description='Test team chat mentions functionality')
parser.add_argument('--api-url', type=str, default='http://localhost:5252', help='AI service API URL')
parser.add_argument('--model', type=str, default='llama3.1', help='Model to use for testing')
parser.add_argument('--user-id', type=str, default='test_user', help='User ID for testing')
parser.add_argument('--second-user-id', type=str, default='test_user2', help='Second user ID for testing team membership')
parser.add_argument('--mention', type=str, default='@ai', help='Mention to use (@ai, @bot, etc.)')
parser.add_argument('--message', type=str, default='Can you help me with a project?', help='Message to send to the team chat')
args = parser.parse_args()
print("=== Team Chat Mentions Functionality Test ===")
print(f"API URL: {args.api_url}")
print(f"Model: {args.model}")
print(f"User ID: {args.user_id}")
print(f"Mention: {args.mention}")
print()
# Test health endpoint
print("=== Testing Health Endpoint ===")
health_success = test_health(args.api_url)
print()
if not health_success:
print("❌ Health check failed. Aborting tests.")
sys.exit(1)
# Test creating a team chat
print("=== Creating Team Chat ===")
chat_success, chat_id = test_create_team_chat(
args.api_url,
args.user_id,
f"Mention Test Chat {time.strftime('%Y-%m-%d %H:%M:%S')}",
args.model
)
print()
if not chat_success or not chat_id:
print("❌ Failed to create team chat. Aborting tests.")
sys.exit(1)
# Test adding a team member
print("=== Adding Team Member ===")
member_success = test_add_team_member(args.api_url, chat_id, args.second_user_id)
print()
# Test sending a message without a mention
print("=== Sending Message WITHOUT Mention ===")
no_mention_success = test_send_team_message_without_mention(
args.api_url,
chat_id,
args.user_id,
f"This is a message without a mention: {args.message}"
)
print()
# Test sending a message with a mention
print("=== Sending Message WITH Mention ===")
mention_success = test_send_team_message_with_mention(
args.api_url,
chat_id,
args.user_id,
args.message,
args.mention
)
print()
# Print summary
print("=== Test Summary ===")
print(f"Health Endpoint: {'✅ SUCCESS' if health_success else '❌ FAILED'}")
print(f"Create Team Chat: {'✅ SUCCESS' if chat_success else '❌ FAILED'}")
print(f"Add Team Member: {'✅ SUCCESS' if member_success else '❌ FAILED'}")
print(f"Send Message WITHOUT Mention: {'✅ SUCCESS' if no_mention_success else '❌ FAILED'}")
print(f"Send Message WITH Mention: {'✅ SUCCESS' if mention_success else '❌ FAILED'}")
if chat_success and chat_id:
print(f"\nCreated team chat ID: {chat_id}")
print("You can now check the OpenWebUI interface to see if the channel was created")
print("and if the AI responded to the message with the mention.")
print(f"OpenWebUI URL: http://104.225.217.215:8080/")
if __name__ == "__main__":
main()
-243
View File
@@ -1,243 +0,0 @@
#!/usr/bin/env python3
"""
Test script for team chats functionality.
This script tests the team chat features of the AI service, including:
- Creating team chats
- Adding members to team chats
- Sending messages to team chats
- Verifying integration with OpenWebUI channels
"""
import requests
import json
import argparse
import time
import sys
from typing import Dict, Any, List, Tuple, Optional
def test_health(api_url: str) -> bool:
"""Test the health endpoint."""
print(f"Testing health endpoint at: {api_url}/health")
try:
response = requests.get(f"{api_url}/health", timeout=30)
response.raise_for_status()
print("✅ Health check successful!")
print(f"Response: {response.json()}")
return True
except Exception as e:
print(f"❌ ERROR: Health check failed: {str(e)}")
return False
def test_create_team_chat(api_url: str, user_id: str, title: str, model_id: str) -> Tuple[bool, Optional[str]]:
"""
Test creating a team chat.
Args:
api_url: API URL
user_id: User ID
title: Chat title
model_id: Model ID
Returns:
Tuple of (success, chat_id)
"""
print(f"Creating team chat with title: {title}")
try:
response = requests.post(
f"{api_url}/chats",
headers={"Content-Type": "application/json"},
json={
"user_id": user_id,
"title": title,
"model_id": model_id,
"is_team_chat": True
},
timeout=60
)
response.raise_for_status()
chat = response.json()
chat_id = chat.get("id")
print(f"✅ Team chat created successfully with ID: {chat_id}")
print(f"OpenWebUI Channel ID: {chat.get('openwebui_channel_id', 'Not available')}")
print(f"Team Members: {chat.get('team_members', [])}")
return True, chat_id
except Exception as e:
print(f"❌ ERROR: Failed to create team chat: {str(e)}")
return False, None
def test_add_team_member(api_url: str, chat_id: str, user_id: str) -> bool:
"""
Test adding a member to a team chat.
Args:
api_url: API URL
chat_id: Chat ID
user_id: User ID to add
Returns:
Success status
"""
print(f"Adding user {user_id} to team chat {chat_id}")
try:
response = requests.post(
f"{api_url}/chats/{chat_id}/members/{user_id}",
timeout=30
)
response.raise_for_status()
print(f"✅ User {user_id} added to team chat successfully")
return True
except Exception as e:
print(f"❌ ERROR: Failed to add user to team chat: {str(e)}")
return False
def test_send_team_message(api_url: str, chat_id: str, user_id: str, message: str) -> bool:
"""
Test sending a message to a team chat.
Args:
api_url: API URL
chat_id: Chat ID
user_id: User ID
message: Message content
Returns:
Success status
"""
print(f"Sending message to team chat {chat_id}: {message}")
try:
response = requests.post(
f"{api_url}/chats/{chat_id}/messages",
headers={"Content-Type": "application/json"},
json={
"message": message,
"user_id": user_id,
"temperature": 0.7,
"max_tokens": 500
},
timeout=120
)
response.raise_for_status()
result = response.json()
print(f"✅ Message sent successfully")
print(f"AI Response: {result.get('content', 'No content in response')[:100]}...")
return True
except Exception as e:
print(f"❌ ERROR: Failed to send message to team chat: {str(e)}")
return False
def test_get_user_chats(api_url: str, user_id: str) -> Tuple[bool, List[Dict[str, Any]]]:
"""
Test getting all chats for a user.
Args:
api_url: API URL
user_id: User ID
Returns:
Tuple of (success, chats)
"""
print(f"Getting chats for user {user_id}")
try:
response = requests.get(
f"{api_url}/chats/user/{user_id}",
timeout=30
)
response.raise_for_status()
chats = response.json()
team_chats = [chat for chat in chats if chat.get("is_team_chat", False)]
print(f"✅ Found {len(chats)} chats for user {user_id}, {len(team_chats)} are team chats")
return True, chats
except Exception as e:
print(f"❌ ERROR: Failed to get user chats: {str(e)}")
return False, []
def main():
"""Main function."""
parser = argparse.ArgumentParser(description='Test team chats functionality')
parser.add_argument('--api-url', type=str, default='http://localhost:5252', help='AI service API URL')
parser.add_argument('--model', type=str, default='llama3.1', help='Model to use for testing')
parser.add_argument('--user-id', type=str, default='test_user', help='User ID for testing')
parser.add_argument('--second-user-id', type=str, default='test_user2', help='Second user ID for testing team membership')
parser.add_argument('--message', type=str, default='Hello team! Can you help me with a project?', help='Message to send to the team chat')
args = parser.parse_args()
print("=== Team Chats Functionality Test ===")
print(f"API URL: {args.api_url}")
print(f"Model: {args.model}")
print(f"User ID: {args.user_id}")
print()
# Test health endpoint
print("=== Testing Health Endpoint ===")
health_success = test_health(args.api_url)
print()
if not health_success:
print("❌ Health check failed. Aborting tests.")
sys.exit(1)
# Test creating a team chat
print("=== Creating Team Chat ===")
chat_success, chat_id = test_create_team_chat(
args.api_url,
args.user_id,
f"Team Chat Test {time.strftime('%Y-%m-%d %H:%M:%S')}",
args.model
)
print()
if not chat_success or not chat_id:
print("❌ Failed to create team chat. Aborting tests.")
sys.exit(1)
# Test adding a team member
print("=== Adding Team Member ===")
member_success = test_add_team_member(args.api_url, chat_id, args.second_user_id)
print()
# Test sending a message to the team chat
print("=== Sending Message to Team Chat ===")
message_success = test_send_team_message(args.api_url, chat_id, args.user_id, args.message)
print()
# Test getting user chats
print("=== Getting User Chats ===")
chats_success, chats = test_get_user_chats(args.api_url, args.user_id)
print()
# Print summary
print("=== Test Summary ===")
print(f"Health Endpoint: {'✅ SUCCESS' if health_success else '❌ FAILED'}")
print(f"Create Team Chat: {'✅ SUCCESS' if chat_success else '❌ FAILED'}")
print(f"Add Team Member: {'✅ SUCCESS' if member_success else '❌ FAILED'}")
print(f"Send Team Message: {'✅ SUCCESS' if message_success else '❌ FAILED'}")
print(f"Get User Chats: {'✅ SUCCESS' if chats_success else '❌ FAILED'}")
if chat_success and chat_id:
print(f"\nCreated team chat ID: {chat_id}")
print("You can now check the OpenWebUI interface to see if the channel was created")
print("and if the message was sent to the channel.")
print(f"OpenWebUI URL: http://104.225.217.215:8080/")
if __name__ == "__main__":
main()