Initial release of scripts and knowledge base
This commit is contained in:
17
discord_connector/Dockerfile
Normal file
17
discord_connector/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
# We use an official Python runtime as a parent image
|
||||
FROM python:3.12-alpine
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the dependencies file to the working directory
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install any needed packages specified in requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy the rest of the application's code
|
||||
COPY . .
|
||||
|
||||
# Run the bot when the container launches
|
||||
CMD ["python", "open-webui_to_discord.py"]
|
||||
7
discord_connector/docker-compose.yaml
Normal file
7
discord_connector/docker-compose.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
discord-bot:
|
||||
build: .
|
||||
container_name: discord-sc-answer-bot
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./config.yml:/app/config.yml
|
||||
22
discord_connector/example.config.yml
Normal file
22
discord_connector/example.config.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
# ─────────── Discord Settings ───────────
|
||||
discord_token: "YOUR_DISCORD_BOT_TOKEN_HERE"
|
||||
|
||||
whitelist_channels:
|
||||
- 123456789123456789 # <-- channel id
|
||||
- 987654321987654321 # <-- another channel id
|
||||
|
||||
allow_dms: false # set to true if you want the bot to answer private DMs (Currently not working)
|
||||
|
||||
# ─────────── Open‑WebUI Settings ───────────
|
||||
open_webui_url: "http://your_open-webui_ip_or_domain:port"
|
||||
open-webui_api_key: "user_api_key_from_open_webui"
|
||||
model_name: "model_id_from_open-webui"
|
||||
knowledge_base: "knowledge_base_id_from_open-webui"
|
||||
|
||||
# tools map – label → tool‑id that your Open‑WebUI instance recognises
|
||||
tools:
|
||||
- Tool_ID_1
|
||||
- Tool_ID_2
|
||||
|
||||
# optional system prompt (you can leave it empty to use the default one or the systemprompt given in open-webui for the specific model)
|
||||
system_prompt: ""
|
||||
144
discord_connector/open-webui_to_discord.py
Normal file
144
discord_connector/open-webui_to_discord.py
Normal file
@@ -0,0 +1,144 @@
|
||||
import yaml
|
||||
import asyncio
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
import aiohttp
|
||||
|
||||
# ────────────────────────────────────────────────
|
||||
def load_config(path: str):
|
||||
"""Return a dict loaded from a YAML file."""
|
||||
with open(path, "r", encoding="utf‑8") as fh:
|
||||
return yaml.safe_load(fh)
|
||||
|
||||
config = load_config("config.yml") # <- loads the config file
|
||||
|
||||
DISCORD_TOKEN = config["discord_token"] # Discord bot token
|
||||
WHITELIST_CHANNELS = set(map(int, config.get("whitelist_channels", []))) # Set of whitelisted channel IDs (int)
|
||||
|
||||
OPENWEBUI_URL = config["open_webui_url"].rstrip('/') # Ensure no trailing slash
|
||||
OPENWEBUI_API_KEY = config["open-webui_api_key"] # API key for Open-WebUI
|
||||
MODEL_NAME = config["model_name"] # Model name to use, e.g., "gpt-3.5-turbo"
|
||||
KNOW_BASE = config["knowledge_base"] # Knowledge base to use, e.g., "knowledge_base_v1"
|
||||
|
||||
TOOLS = config.get("tools", []) # list of tool-ids
|
||||
|
||||
SYSTEM_PROMPT = config.get("system_prompt", None) # Optional system prompt to prepend to user messages
|
||||
ALLOW_DMS = config.get("allow_dms", False) # Allow DMs to the bot (default: False)
|
||||
|
||||
async def _query_openwebui(user_text: str, channel_id: int, tools_list: list):
|
||||
"""
|
||||
Payload structure for the OpenAI-compatible endpoint.
|
||||
|
||||
Args:
|
||||
user_text (str): The user's message to send to the Open-WebUI.
|
||||
channel_id (int): The Discord channel ID where the message was sent.
|
||||
tools_list (list): List of tool IDs to use, if any.
|
||||
"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# This payload structure is for the OpenAI-compatible endpoint from open-webui
|
||||
payload = {
|
||||
"model": MODEL_NAME,
|
||||
"stream": False,
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": user_text
|
||||
}
|
||||
]
|
||||
}
|
||||
# Attach tools if provided in the config file
|
||||
if tools_list:
|
||||
payload["tool_ids"] = tools_list
|
||||
print(f"🔧 Using tools: {payload['tool_ids']}")
|
||||
|
||||
# The endpoint path for your instance appears to be /api/chat/completions
|
||||
async with session.post(f"{OPENWEBUI_URL}/api/chat/completions",
|
||||
json=payload,
|
||||
headers={"Authorization": f"Bearer {OPENWEBUI_API_KEY}"}) as resp:
|
||||
|
||||
if resp.status != 200:
|
||||
# If the response is not 200, raise an error with the response text
|
||||
data = await resp.text()
|
||||
raise RuntimeError(f"Open‑WebUI responded {resp.status}: {data}")
|
||||
|
||||
# If the response is OK, parse the JSON and return the content
|
||||
response_data = await resp.json()
|
||||
return response_data['choices'][0]['message']['content']
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Discord bot logic – discord.py
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
intents = discord.Intents.default() # Default intents
|
||||
intents.message_content = True # Required to read message content
|
||||
intents.members = True # Required to read member info (if needed)
|
||||
|
||||
bot = commands.Bot(command_prefix='!', intents=intents) # Command prefix is '!' by default to allow commands like !ping
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f"✅ Logged in as {bot.user} (id={bot.user.id})")
|
||||
|
||||
# Only a test for commands, I add later
|
||||
@bot.command(name="ping")
|
||||
async def ping(ctx):
|
||||
await ctx.send("🏓 Pong!")
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Main logic – only respond to allowed channels / DM flag
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
@bot.event
|
||||
async def on_message(message):
|
||||
# Ignore messages from bots (incl. the bot itself)
|
||||
if message.author.bot: return
|
||||
|
||||
# Allow commands to be processed
|
||||
await bot.process_commands(message)
|
||||
|
||||
# Skip if we are in a DM and that is disabled
|
||||
if not ALLOW_DMS and isinstance(message.channel, discord.DMChannel): return
|
||||
|
||||
# --- debugging ---
|
||||
print(f"ℹ️ Message received in channel: {message.channel.id}")
|
||||
print(f"📢 Whitelisted channels are: {WHITELIST_CHANNELS}")
|
||||
# -----------------------------
|
||||
|
||||
# Allow only the whitelist channels – empty list means “all channels”
|
||||
if WHITELIST_CHANNELS and message.channel.id not in WHITELIST_CHANNELS:
|
||||
return
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# A. Prepare payload
|
||||
# ----------------------------------------------------------------------- #
|
||||
# The OpenAI endpoint works better without the extra context in the prompt
|
||||
prompt = message.content
|
||||
if SYSTEM_PROMPT:
|
||||
# The system prompt is handled differently in the OpenAI-compatible API
|
||||
# For simplicity, we'll prepend it here. A more robust solution
|
||||
# would add it as a separate message with the 'system' role.
|
||||
prompt = f"{SYSTEM_PROMPT}\n\nUser Question: {message.content}"
|
||||
|
||||
# ----------------------------------------------------------------------- #
|
||||
# B. Query Open‑WebUI and show typing indicator
|
||||
# ----------------------------------------------------------------------- #
|
||||
try:
|
||||
async with message.channel.typing():
|
||||
# Query the Open-WebUI API while showing "Bot is typing..."
|
||||
reply = await _query_openwebui(prompt, message.channel.id, TOOLS)
|
||||
# Send the reply
|
||||
await message.reply(reply)
|
||||
except Exception as e:
|
||||
await message.reply(f"⚠ Error contacting the Open‑WebUI API: {e}")
|
||||
# No need to return here as the function ends after this block.
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Start bot
|
||||
# --------------------------------------------------------------------------- #
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(bot.run(DISCORD_TOKEN))
|
||||
except KeyboardInterrupt:
|
||||
print("🤖 Shutting down…")
|
||||
3
discord_connector/requirements.txt
Normal file
3
discord_connector/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
PyYAML
|
||||
discord.py
|
||||
aiohttp
|
||||
Reference in New Issue
Block a user