VOOZH about

URL: https://crazyrouter.com/en/blog/build-ai-discord-bot-multiple-llms-2026

โ‡ฑ How to Build an AI-Powered Discord Bot with Multiple LLMs - Crazyrouter


Back to Blog

How to Build an AI-Powered Discord Bot with Multiple LLMs#

Discord bots powered by AI are transforming community engagement. This comprehensive guide shows you how to build a production-ready Discord bot that supports multiple AI models (GPT-5, Claude, Gemini, and more) with slash commands, conversation context, and cost optimization.

What You'll Build#

By the end of this tutorial, you'll have a Discord bot that:

  • โœ… Supports 10+ AI models (GPT-5, Claude, Gemini, DeepSeek, etc.)
  • โœ… Uses slash commands for easy interaction
  • โœ… Maintains conversation context per channel
  • โœ… Handles rate limiting and errors gracefully
  • โœ… Costs 30-50% less than using official APIs
  • โœ… Includes admin controls and usage tracking

Live demo: /ask gpt-5 What is quantum computing?

Prerequisites#

  • Python 3.9+ installed
  • Discord account and server
  • Crazyrouter API key (or OpenAI/Anthropic keys)
  • Basic Python knowledge

Step 1: Set Up Discord Bot#

1.1 Create Discord Application#

  1. Go to Discord Developer Portal
  2. Click "New Application"
  3. Name your bot (e.g., "AI Assistant")
  4. Go to "Bot" tab โ†’ "Add Bot"
  5. Enable these intents:
    • Message Content Intent
    • Server Members Intent
    • Presence Intent

1.2 Get Bot Token#

  1. Under "Bot" tab, click "Reset Token"
  2. Copy the token (save it securely)
  3. Never share this token publicly

1.3 Invite Bot to Server#

  1. Go to "OAuth2" โ†’ "URL Generator"
  2. Select scopes: bot, applications.commands
  3. Select permissions:
    • Send Messages
    • Read Message History
    • Use Slash Commands
    • Embed Links
  4. Copy the generated URL and open in browser
  5. Select your server and authorize

Step 2: Install Dependencies#

bash
# Create project directory
mkdir ai-discord-bot
cd ai-discord-bot

# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate

# Install dependencies
pip install discord.py openai python-dotenv aiohttp

Step 3: Project Structure#

code
ai-discord-bot/
โ”œโ”€โ”€ .env # Environment variables
โ”œโ”€โ”€ bot.py # Main bot code
โ”œโ”€โ”€ models.py # AI model configurations
โ”œโ”€โ”€ context_manager.py # Conversation context
โ”œโ”€โ”€ utils.py # Helper functions
โ””โ”€โ”€ requirements.txt # Dependencies

Step 4: Configuration#

Create .env file:

bash
# Discord
DISCORD_TOKEN=your_discord_bot_token

# Crazyrouter (recommended - supports all models)
CRAZYROUTER_API_KEY=your_crazyrouter_key

# Or use individual providers
OPENAI_API_KEY=your_openai_key
ANTHROPIC_API_KEY=your_anthropic_key
GOOGLE_API_KEY=your_google_key

# Bot settings
MAX_CONTEXT_MESSAGES=10
DEFAULT_MODEL=gpt-5-mini
ADMIN_USER_IDS=123456789,987654321

Step 5: Model Configuration#

Create models.py:

python
"""AI model configurations and pricing"""

MODELS = {
 # OpenAI Models
 "gpt-5": {
 "name": "gpt-5.2",
 "display": "GPT-5.2",
 "provider": "openai",
 "max_tokens": 4096,
 "cost_per_1k": 0.015, # Input cost
 "description": "Most capable GPT model"
 },
 "gpt-5-mini": {
 "name": "gpt-5-mini",
 "display": "GPT-5 Mini",
 "provider": "openai",
 "max_tokens": 4096,
 "cost_per_1k": 0.001,
 "description": "Fast and affordable"
 },
 
 # Anthropic Models
 "claude": {
 "name": "claude-opus-4-6-20260101",
 "display": "Claude Opus 4.6",
 "provider": "anthropic",
 "max_tokens": 4096,
 "cost_per_1k": 0.015,
 "description": "Best reasoning and analysis"
 },
 "claude-sonnet": {
 "name": "claude-sonnet-4-5-20250929",
 "display": "Claude Sonnet 4.5",
 "provider": "anthropic",
 "max_tokens": 4096,
 "cost_per_1k": 0.003,
 "description": "Balanced performance"
 },
 
 # Google Models
 "gemini": {
 "name": "gemini-2.5-pro",
 "display": "Gemini 2.5 Pro",
 "provider": "google",
 "max_tokens": 8192,
 "cost_per_1k": 0.00125,
 "description": "Long context, multimodal"
 },
 "gemini-flash": {
 "name": "gemini-2.5-flash",
 "display": "Gemini 2.5 Flash",
 "provider": "google",
 "max_tokens": 8192,
 "cost_per_1k": 0.0001,
 "description": "Fastest, cheapest"
 },
 
 # Other Models
 "deepseek": {
 "name": "deepseek-v3.2",
 "display": "DeepSeek V3.2",
 "provider": "deepseek",
 "max_tokens": 4096,
 "cost_per_1k": 0.00027,
 "description": "Budget-friendly, good quality"
 },
 "grok": {
 "name": "grok-4.1-fast",
 "display": "Grok 4.1 Fast",
 "provider": "xai",
 "max_tokens": 4096,
 "cost_per_1k": 0.005,
 "description": "Fast, witty responses"
 }
}

def get_model_list():
 """Return formatted model list for Discord embed"""
 return "\n".join([
 f"**{key}**: {config['display']} - {config['description']}"
 for key, config in MODELS.items()
 ])

def get_model_config(model_key):
 """Get model configuration by key"""
 return MODELS.get(model_key, MODELS["gpt-5-mini"])

Step 6: Context Manager#

Create context_manager.py:

python
"""Manage conversation context per Discord channel"""

from collections import defaultdict, deque
import os

MAX_CONTEXT = int(os.getenv("MAX_CONTEXT_MESSAGES", 10))

class ContextManager:
 def __init__(self):
 # Store context per channel: {channel_id: deque([messages])}
 self.contexts = defaultdict(lambda: deque(maxlen=MAX_CONTEXT))
 
 def add_message(self, channel_id, role, content):
 """Add message to channel context"""
 self.contexts[channel_id].append({
 "role": role,
 "content": content
 })
 
 def get_context(self, channel_id):
 """Get conversation context for channel"""
 return list(self.contexts[channel_id])
 
 def clear_context(self, channel_id):
 """Clear context for channel"""
 self.contexts[channel_id].clear()
 
 def get_context_size(self, channel_id):
 """Get number of messages in context"""
 return len(self.contexts[channel_id])

# Global context manager
context_manager = ContextManager()

Step 7: Main Bot Code#

Create bot.py:

python
"""AI-powered Discord bot with multiple LLM support"""

import discord
from discord import app_commands
from discord.ext import commands
import openai
import os
from dotenv import load_dotenv
import asyncio
from models import MODELS, get_model_list, get_model_config
from context_manager import context_manager

# Load environment variables
load_dotenv()

# Initialize OpenAI client (works with Crazyrouter)
client = openai.OpenAI(
 api_key=os.getenv("CRAZYROUTER_API_KEY"),
 base_url="https://api.crazyrouter.com/v1"
)

# Bot setup
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)

# Admin user IDs
ADMIN_IDS = [int(id) for id in os.getenv("ADMIN_USER_IDS", "").split(",") if id]

@bot.event
async def on_ready():
 """Bot startup"""
 print(f"โœ… {bot.user} is online!")
 print(f"๐Ÿ“Š Connected to {len(bot.guilds)} servers")
 
 # Sync slash commands
 try:
 synced = await bot.tree.sync()
 print(f"โœ… Synced {len(synced)} slash commands")
 except Exception as e:
 print(f"โŒ Failed to sync commands: {e}")

@bot.tree.command(name="ask", description="Ask AI a question")
@app_commands.describe(
 model="AI model to use",
 prompt="Your question or prompt"
)
@app_commands.choices(model=[
 app_commands.Choice(name=config["display"], value=key)
 for key, config in list(MODELS.items())[:25] # Discord limit: 25 choices
])
async def ask(interaction: discord.Interaction, model: str, prompt: str):
 """Main AI chat command"""
 await interaction.response.defer(thinking=True)
 
 try:
 # Get model config
 model_config = get_model_config(model)
 
 # Get conversation context
 channel_id = interaction.channel_id
 context = context_manager.get_context(channel_id)
 
 # Add user message to context
 context_manager.add_message(channel_id, "user", prompt)
 
 # Build messages for API
 messages = context + [{"role": "user", "content": prompt}]
 
 # Call AI API
 response = await asyncio.to_thread(
 client.chat.completions.create,
 model=model_config["name"],
 messages=messages,
 max_tokens=model_config["max_tokens"],
 temperature=0.7
 )
 
 # Extract response
 ai_response = response.choices[0].message.content
 
 # Add AI response to context
 context_manager.add_message(channel_id, "assistant", ai_response)
 
 # Create embed
 embed = discord.Embed(
 title=f"๐Ÿค– {model_config['display']}",
 description=ai_response[:4000], # Discord limit
 color=discord.Color.blue()
 )
 embed.set_footer(text=f"Context: {context_manager.get_context_size(channel_id)} messages")
 
 await interaction.followup.send(embed=embed)
 
 except Exception as e:
 error_embed = discord.Embed(
 title="โŒ Error",
 description=f"Failed to get AI response: {str(e)}",
 color=discord.Color.red()
 )
 await interaction.followup.send(embed=error_embed, ephemeral=True)

@bot.tree.command(name="models", description="List available AI models")
async def models(interaction: discord.Interaction):
 """Show available models"""
 embed = discord.Embed(
 title="๐Ÿค– Available AI Models",
 description=get_model_list(),
 color=discord.Color.green()
 )
 embed.set_footer(text="Use /ask <model> <prompt> to chat")
 await interaction.response.send_message(embed=embed)

@bot.tree.command(name="clear", description="Clear conversation context")
async def clear(interaction: discord.Interaction):
 """Clear channel context"""
 channel_id = interaction.channel_id
 context_manager.clear_context(channel_id)
 
 embed = discord.Embed(
 title="๐Ÿงน Context Cleared",
 description="Conversation history has been reset.",
 color=discord.Color.orange()
 )
 await interaction.response.send_message(embed=embed, ephemeral=True)

@bot.tree.command(name="context", description="Show current conversation context")
async def show_context(interaction: discord.Interaction):
 """Display current context"""
 channel_id = interaction.channel_id
 context = context_manager.get_context(channel_id)
 
 if not context:
 await interaction.response.send_message("No context yet. Start chatting!", ephemeral=True)
 return
 
 context_text = "\n\n".join([
 f"**{msg['role'].title()}**: {msg['content'][:100]}..."
 for msg in context
 ])
 
 embed = discord.Embed(
 title=f"๐Ÿ’ฌ Conversation Context ({len(context)} messages)",
 description=context_text[:4000],
 color=discord.Color.purple()
 )
 await interaction.response.send_message(embed=embed, ephemeral=True)

@bot.tree.command(name="stats", description="Show bot usage statistics")
async def stats(interaction: discord.Interaction):
 """Show bot stats (admin only)"""
 if interaction.user.id not in ADMIN_IDS:
 await interaction.response.send_message("โŒ Admin only command", ephemeral=True)
 return
 
 total_contexts = len(context_manager.contexts)
 total_messages = sum(len(ctx) for ctx in context_manager.contexts.values())
 
 embed = discord.Embed(
 title="๐Ÿ“Š Bot Statistics",
 color=discord.Color.gold()
 )
 embed.add_field(name="Servers", value=len(bot.guilds), inline=True)
 embed.add_field(name="Active Channels", value=total_contexts, inline=True)
 embed.add_field(name="Total Messages", value=total_messages, inline=True)
 
 await interaction.response.send_message(embed=embed, ephemeral=True)

# Run bot
if __name__ == "__main__":
 token = os.getenv("DISCORD_TOKEN")
 if not token:
 print("โŒ DISCORD_TOKEN not found in .env")
 exit(1)
 
 bot.run(token)

Step 8: Run Your Bot#

bash
# Activate virtual environment
source venv/bin/activate

# Run bot
python bot.py

You should see:

code
โœ… AI Assistant#1234 is online!
๐Ÿ“Š Connected to 1 servers
โœ… Synced 5 slash commands

Step 9: Test Your Bot#

In Discord, try these commands:

code
/models
# Shows all available AI models

/ask gpt-5-mini What is quantum computing?
# Ask GPT-5 Mini a question

/ask claude Explain relativity in simple terms
# Ask Claude Opus

/context
# View conversation history

/clear
# Reset conversation context

/stats
# View bot statistics (admin only)

Advanced Features#

Feature 1: Image Generation#

Add to bot.py:

python
@bot.tree.command(name="imagine", description="Generate an image with AI")
@app_commands.describe(prompt="Image description")
async def imagine(interaction: discord.Interaction, prompt: str):
 """Generate image with DALL-E"""
 await interaction.response.defer(thinking=True)
 
 try:
 response = await asyncio.to_thread(
 client.images.generate,
 model="dall-e-3",
 prompt=prompt,
 size="1024x1024",
 n=1
 )
 
 image_url = response.data[0].url
 
 embed = discord.Embed(
 title="๐ŸŽจ Generated Image",
 description=prompt,
 color=discord.Color.purple()
 )
 embed.set_image(url=image_url)
 
 await interaction.followup.send(embed=embed)
 
 except Exception as e:
 await interaction.followup.send(f"โŒ Error: {e}", ephemeral=True)

Feature 2: Model Comparison#

python
@bot.tree.command(name="compare", description="Compare responses from multiple models")
@app_commands.describe(prompt="Question to ask all models")
async def compare(interaction: discord.Interaction, prompt: str):
 """Compare responses from GPT, Claude, and Gemini"""
 await interaction.response.defer(thinking=True)
 
 models_to_compare = ["gpt-5-mini", "claude-sonnet", "gemini-flash"]
 responses = {}
 
 for model_key in models_to_compare:
 try:
 model_config = get_model_config(model_key)
 response = await asyncio.to_thread(
 client.chat.completions.create,
 model=model_config["name"],
 messages=[{"role": "user", "content": prompt}],
 max_tokens=500
 )
 responses[model_config["display"]] = response.choices[0].message.content[:500]
 except Exception as e:
 responses[model_config["display"]] = f"Error: {e}"
 
 embed = discord.Embed(
 title="๐Ÿ” Model Comparison",
 description=f"**Prompt**: {prompt}",
 color=discord.Color.teal()
 )
 
 for model_name, response_text in responses.items():
 embed.add_field(
 name=model_name,
 value=response_text[:1024],
 inline=False
 )
 
 await interaction.followup.send(embed=embed)

Feature 3: Usage Tracking#

python
import json
from datetime import datetime

class UsageTracker:
 def __init__(self):
 self.usage = {}
 
 def track(self, user_id, model, tokens):
 """Track API usage"""
 if user_id not in self.usage:
 self.usage[user_id] = []
 
 self.usage[user_id].append({
 "model": model,
 "tokens": tokens,
 "timestamp": datetime.now().isoformat()
 })
 
 def get_user_usage(self, user_id):
 """Get usage for user"""
 return self.usage.get(user_id, [])
 
 def save(self, filename="usage.json"):
 """Save usage to file"""
 with open(filename, "w") as f:
 json.dump(self.usage, f, indent=2)

usage_tracker = UsageTracker()

Cost Optimization#

Strategy 1: Use Cheaper Models for Simple Tasks#

python
def select_model_by_complexity(prompt):
 """Auto-select model based on prompt complexity"""
 if len(prompt) < 50:
 return "gemini-flash" # $0.0001/1K tokens
 elif any(word in prompt.lower() for word in ["code", "debug", "program"]):
 return "gpt-5-mini" # $0.001/1K tokens
 elif any(word in prompt.lower() for word in ["analyze", "research", "explain"]):
 return "claude-sonnet" # $0.003/1K tokens
 else:
 return "gpt-5-mini" # Default

Strategy 2: Limit Context Window#

python
# In .env
MAX_CONTEXT_MESSAGES=5 # Reduce from 10 to save tokens

Strategy 3: Use Crazyrouter#

Crazyrouter provides 30% savings automatically:

ModelOfficialCrazyrouterSavings
GPT-5$15/1M$10.50/1M30%
Claude Opus$15/1M$10.50/1M30%
Gemini Pro$1.25/1M$0.875/1M30%

Monthly savings (10K requests, 500 tokens avg):

  • Official APIs: ~$75
  • Crazyrouter: ~$52.50
  • Savings: $22.50/month

Deployment#

Deploy to Railway#

  1. Create Procfile:
code
worker: python bot.py
  1. Create requirements.txt:
code
discord.py==2.3.2
openai==1.12.0
python-dotenv==1.0.0
aiohttp==3.9.3
  1. Push to GitHub
  2. Connect to Railway
  3. Add environment variables
  4. Deploy!

Deploy to Heroku#

bash
heroku create your-bot-name
heroku config:set DISCORD_TOKEN=your_token
heroku config:set CRAZYROUTER_API_KEY=your_key
git push heroku main

Troubleshooting#

Bot doesn't respond to slash commands#

  1. Check bot has "applications.commands" scope
  2. Run await bot.tree.sync() in on_ready
  3. Wait 1 hour for Discord to propagate commands

"Missing Access" error#

  1. Ensure bot has these permissions:
    • Send Messages
    • Read Message History
    • Use Slash Commands
    • Embed Links

Rate limit errors#

  1. Implement exponential backoff
  2. Use Crazyrouter for higher rate limits
  3. Add request queuing

Context not working#

  1. Check MAX_CONTEXT_MESSAGES in .env
  2. Verify channel_id is consistent
  3. Clear context with /clear and retry

Best Practices#

  1. Always defer responses for AI calls (they take time)
  2. Use ephemeral messages for errors and admin commands
  3. Implement rate limiting per user to prevent abuse
  4. Log all API calls for debugging and cost tracking
  5. Use embeds for better formatting
  6. Handle errors gracefully with user-friendly messages
  7. Test in private server before public deployment

Frequently Asked Questions#

Can I use this bot commercially?#

Yes, but ensure you comply with Discord's Terms of Service and each AI provider's usage policies.

How much does it cost to run?#

With Crazyrouter and 1,000 requests/day (500 tokens avg):

  • Monthly cost: ~$15-30
  • Per request: ~$0.0005-0.001

Can I add more models?#

Yes! Add to models.py and the bot automatically supports them. Crazyrouter provides 300+ models.

How do I prevent spam?#

Implement per-user rate limiting:

python
from collections import defaultdict
import time

user_last_request = defaultdict(float)
COOLDOWN = 5 # seconds

@bot.tree.command(name="ask")
async def ask(interaction, model, prompt):
 user_id = interaction.user.id
 now = time.time()
 
 if now - user_last_request[user_id] < COOLDOWN:
 await interaction.response.send_message(
 f"โณ Please wait {COOLDOWN}s between requests",
 ephemeral=True
 )
 return
 
 user_last_request[user_id] = now
 # ... rest of command

Can I use this with other AI providers?#

Yes! Just change the base_url and api_key in the OpenAI client initialization.

Conclusion#

You now have a production-ready Discord bot that:

  • Supports 10+ AI models
  • Maintains conversation context
  • Costs 30-50% less with Crazyrouter
  • Handles errors gracefully
  • Includes admin controls

Next steps:

  1. Deploy to Railway/Heroku
  2. Add custom commands for your community
  3. Implement usage limits and tracking
  4. Add image generation and other features

Get your Crazyrouter API key at crazyrouter.com to access all models with one key and save 30% on costs.

Happy building! ๐Ÿค–

Implementation Guides

Topics

Related Posts

Model Distillation Explained: How Small AI Models Learn from Giants

"A complete guide to knowledge distillation in AI. Learn how DeepSeek, GPT-4o-mini, Gemini Flash, and Claude Haiku were built by distilling larger models, and how developers can use distillation to cut costs."

Mar 30

GLM-4.6 API Guide 2026: Building Chinese-First AI Applications

"Learn how to use the GLM-4.6 API for Chinese-first AI apps, bilingual assistants, and enterprise workflows. Includes code examples, architecture patterns, and pricing guidance."

Apr 18

Claude Code Builds a Multi-Model Odds Alert Router: claude-fable-5 vs GPT-5.5 vs Qwen

The third Claude Code World Cup analytics project: route the same odds alert JSON task across claude-fable-5, GPT-5.5, Qwen Plus, and Gemini to measure valid JSON rate, latency, and fallback behavior through Crazyrouter.

Jun 13

How to Integrate Suno AI Music API: Complete Developer Guide

This tutorial shows you how to integrate Suno AI music generation into your applications using the OpenAI-compatible API format. Generate songs, create lyrics, and build AI-powered music applications.

Jan 22

Kling AI API Tutorial: Build AI Video Generation into Your App

"Step-by-step tutorial on using Kling AI API for text-to-video and image-to-video generation. Python code examples, pricing, and production tips."

Feb 21

How to Get a Claude API Key in 2026: Secure Setup, Rotation, and Alternatives

how to get claude api key: practical 2026 developer guide with comparisons, code examples, pricing breakdown, FAQ, and Crazyrouter API routing tips.

Jun 18