Contents
スポンサードリンク
概要と全体像
- 目的:Discord サーバー上でリアルタイムに自然言語応答できる AI チャットボットを、数百行のコードだけで構築・デプロイする。
- 構成要素
- Discord Developer Portal にて Bot アプリを作成し、MESSAGE_CONTENT と GUILD_MEMBERS(必要に応じて)インテントを有効化。
- OpenAI の GPT‑4o / GPT‑4 Turbo(または低コスト版 gpt‑4o‑mini)用 API キーを取得し、従量課金と無料枠を把握。
discord.py(Python) ordiscord.js(Node.js)でスラッシュコマンド実装 → 会話履歴をメモリ/Redis に保持。- Docker 化し、GitHub Actions と Render / Railway / Fly.io 等の PaaS に自動デプロイ。
Discord Bot の作成と必須インテント
| 手順 | 操作概要 |
|---|---|
| 1 | https://discord.com/developers/applications にアクセスし New Application → 名前を入力して作成 |
| 2 | 左メニューの Bot タブで Add Bot。生成されたトークンは .env 等に安全に保存(決してリポジトリへコミットしない) |
| 3 | Privileged Gateway Intents で以下を ON にする • MESSAGE_CONTENT• GUILD_MEMBERS(メンバー情報が必要な場合) |
| 4 | OAuth2 → URL Generator でスコープ bot, applications.commands を選択。権限は Send Messages, Read Message History, Use Slash Commands のみ付与し、生成された URL からサーバへ招待 |
| 5 | Discord クライアント側で ユーザー設定 → 詳細設定 → 開発者モード を有効化。右クリックでチャンネル ID・メッセージ ID が取得できるようになる |
参考:FlowHunt の公式ガイドでも同様の手順が推奨されています。
【FlowHunt】Discord AI チャットボットの作り方
インテント名の正式表記
MESSAGE_CONTENT(旧称:MESSAGE CONTENT INTENT)GUILD_MEMBERS(旧称:SERVER MEMBERS INTENT)
コード内でも上記定数を使用してください。
OpenAI GPT 系 API の取得と最新料金概要
1. API キー取得手順
- https://platform.openai.com/account/api-keys にサインイン。
- Create new secret key をクリックし、生成されたキーをコピー。
.envや GitHub Secrets 等の安全な場所に保存。
2. 2024‑2025 年版(執筆時点)の従量課金プラン
| モデル | 入力 1k トークン料金 (USD) | 出力 1k トークン料金 (USD) | 無料枠(月間) |
|---|---|---|---|
| GPT‑4o | $0.005 | $0.015 | $18 相当(約 3M トークン) |
| GPT‑4 Turbo | $0.01 | $0.03 | 同上 |
| gpt‑4o‑mini (低コスト版) | $0.0015 | $0.002 | 同上 |
- 従量課金の流れ:使用したトークン数は月末に自動集計され、請求書が発行されます。
- 予算管理:OpenAI ダッシュボード > Usage Limits → Monthly Cap で上限額を設定可能です。
公式情報:最新料金は OpenAI の Pricing ページ を必ず参照してください。
最小構成サンプルコード(Python / Node.js)
共通設計方針
- スラッシュコマンド (
/chat) でユーザー入力を取得。 - 会話履歴はチャンネル単位で最大 10 件 を保持し、リクエスト時に直近 5 件だけ送信してトークン消費を抑制。
- エラーハンドリングとレートリミット対策は共通関数
request_with_retry(Python)/chatCompletion(Node.js)で実装。
1. Python (discord.py 2.3 系)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# bot.py import os, asyncio from dotenv import load_dotenv import discord from discord import app_commands import aiohttp load_dotenv() TOKEN = os.getenv("DISCORD_BOT_TOKEN") OPENAI_KEY = os.getenv("OPENAI_API_KEY") intents = discord.Intents.default() intents.message_content = True # MESSAGE_CONTENT インテント必須 bot = discord.Client(intents=intents) tree = app_commands.CommandTree(bot) # チャンネルごとの会話メモリ(最大 10 件) conversation_memory: dict[int, list[dict]] = {} async def request_with_retry(url: str, json_body: dict) -> dict: """429 / 5xx を自動リトライするユーティリティ""" backoff = 1 for _ in range(5): async with aiohttp.ClientSession() as session: async with session.post( url, headers={"Authorization": f"Bearer {OPENAI_KEY}"}, json=json_body, ) as resp: if resp.status == 429: # Discord のレートリミット retry = int(resp.headers.get("Retry-After", backoff)) await asyncio.sleep(retry) elif resp.status >= 500: # サーバエラーは指数バックオフ await asyncio.sleep(backoff) backoff *= 2 else: return await resp.json() raise RuntimeError("OpenAI request failed after retries") @tree.command(name="chat", description="AI に質問する") async def chat(interaction: discord.Interaction, prompt: str): channel_id = interaction.channel_id history = conversation_memory.get(channel_id, []) # 前履歴 + 今回プロンプトでリクエストペイロード作成 messages = [{"role": "system", "content": "You are a helpful assistant."}] messages.extend(history[-5:]) # 直近 5 件だけ送信 messages.append({"role": "user", "content": prompt}) payload = { "model": "gpt-4o-mini", "messages": messages, "max_tokens": 300, } data = await request_with_retry("https://api.openai.com/v1/chat/completions", payload) reply = data["choices"][0]["message"]["content"] # メモリ更新(user + assistant のペアだけ保存) history.append({"role": "user", "content": prompt}) history.append({"role": "assistant", "content": reply}) conversation_memory[channel_id] = history[-10:] # 上限 10 件 await interaction.response.send_message(reply, ephemeral=False) @bot.event async def on_ready(): await tree.sync() print(f"✅ Bot ready as {bot.user}") bot.run(TOKEN) |
2. Node.js (discord.js v14 系)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
// index.mjs import "dotenv/config"; import { Client, GatewayIntentBits, Partials, REST, Routes, } from "discord.js"; import fetch from "node-fetch"; const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.MessageContent], partials: [Partials.Channel], }); const DISCORD_TOKEN = process.env.DISCORD_BOT_TOKEN; const OPENAI_KEY = process.env.OPENAI_API_KEY; // チャンネルごとの会話履歴(最大10件) const memory = new Map(); async function chatCompletion(messages) { const maxAttempts = 5; let delay = 1000; // ms for (let i = 0; i < maxAttempts; i++) { const res = await fetch("https://api.openai.com/v1/chat/completions", { method: "POST", headers: { Authorization: `Bearer ${OPENAI_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: "gpt-4o-mini", messages, max_tokens: 300, }), }); if (res.status === 429) { // OpenAI のレートリミット const retryAfter = parseInt(res.headers.get("retry-after")) || delay; await new Promise(r => setTimeout(r, retryAfter)); } else if (!res.ok && res.status >= 500) { await new Promise(r => setTimeout(r, delay)); delay *= 2; // 指数バックオフ } else if (res.ok) { return await res.json(); } } throw new Error("OpenAI request failed after retries"); } client.once("ready", async () => { console.log(`✅ Logged in as ${client.user.tag}`); const commands = [ { name: "chat", description: "AI に質問する", options: [ { type: 3, // STRING name: "prompt", description: "質問内容", required: true, }, ], }, ]; const rest = new REST({ version: "10" }).setToken(DISCORD_TOKEN); await rest.put(Routes.applicationCommands(client.user.id), { body: commands }); }); client.on("interactionCreate", async (int) => { if (!int.isChatInputCommand() || int.commandName !== "chat") return; const prompt = int.options.getString("prompt"); const channelId = int.channelId; const hist = memory.get(channelId) ?? []; // メッセージ配列作成 const messages = [ { role: "system", content: "You are a helpful assistant." }, ...hist.slice(-5), { role: "user", content: prompt }, ]; try { const data = await chatCompletion(messages); const reply = data.choices[0].message.content; // メモリ更新 hist.push({ role: "user", content: prompt }); hist.push({ role: "assistant", content: reply }); memory.set(channelId, hist.slice(-10)); await int.reply(reply); } catch (e) { console.error(e); await int.reply("⚠️ エラーが発生しました。しばらくしてから再度お試しください。"); } }); client.login(DISCORD_TOKEN); |
ポイントまとめ
- スラッシュコマンドだけで UI が完結し、ユーザーは
/chat <質問>と入力するだけで AI に問い合わせ可能。 - 会話履歴を メモリ(開発段階)→本番では Redis / DynamoDB 等に差し替えるとスケールしやすい。
- エラーハンドリングは 429・5xx を自動リトライし、無限ループ防止のため最大試行回数を設定。
開発環境・依存管理のベストプラクティス
| 言語 | 仮想環境/バージョン管理 | 主なパッケージ(執筆時点) |
|---|---|---|
| Python | python -m venv .venv → source .venv/bin/activate | discord.py==2.3.2, aiohttp==3.9.5, python-dotenv==1.0.0 |
| Node.js | nvm install 20 && nvm use 20 | discord.js@14.15.3, node-fetch@2.6.12, dotenv@16.4.5 |
.env とシークレット管理
|
1 2 3 4 |
# .env (ローカル開発専用) DISCORD_BOT_TOKEN=YOUR_DISCORD_BOT_TOKEN OPENAI_API_KEY=YOUR_OPENAI_API_KEY |
- Git 除外:
.gitignoreに必ず.envを追加。 - CI/CD での注入:GitHub Actions の
secrets, Render, Railway, Fly.io 等の「Environment Variables」へ同名キーを登録し、ランタイム時に自動的に読み込む。
パッケージロックファイル
- Python →
pip freeze > requirements.txt - Node.js →
npm i --package-lock-only(package-lock.jsonが生成される)
これらをリポジトリにコミットすることで、再現性の高いビルド が保証されます。
Docker 化と CI/CD(GitHub Actions)で自動デプロイ
1. マルチステージ Dockerfile(Python 例)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# ---------- Builder ---------- FROM python:3.11-slim AS builder WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # ---------- Runtime ---------- FROM python:3.11-slim WORKDIR /app COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages COPY . . ENV PYTHONUNBUFFERED=1 CMD ["python", "bot.py"] |
Node.js 用 Dockerfile は同様に
npm ciでビルドし、node bot.js(またはnode index.mjs)を起動します。
2. GitHub Actions ワークフロー例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
name: CI/CD – Discord AI Bot on: push: branches: [main] jobs: build-test-deploy: runs-on: ubuntu-latest env: DOCKER_IMAGE: ghcr.io/${{ github.repository }}:${{ github.sha }} steps: # Checkout - uses: actions/checkout@v3 # ---------- Python ---------- - name: Set up Python 3.11 uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install deps & lint run: | pip install -r requirements.txt flake8 . # Lint(任意) # ---------- Test ---------- - name: Run tests (if any) run: pytest tests/ || echo "No tests found" # ---------- Docker build ---------- - name: Log in to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build & push image run: | docker build -t $DOCKER_IMAGE . docker push $DOCKER_IMAGE # ---------- Deploy ---------- - name: Deploy to Render (example) uses: joelittlejohn/render-deploy-action@v1 with: service-id: ${{ secrets.RENDER_SERVICE_ID }} api-key: ${{ secrets.RENDER_API_KEY }} |
3. 各 PaaS へのデプロイポイント
| プラットフォーム | ビルド設定例 | 環境変数注入 |
|---|---|---|
| Render | Dockerfile 自動検出 → docker build | Settings > Environment → DISCORD_BOT_TOKEN, OPENAI_API_KEY をシークレットとして登録 |
| Railway | 「Deploy from GitHub」→Docker が自動認識 | Variables タブに同名キーを追加 |
| Fly.io | fly launch --dockerfile Dockerfile → fly secrets set … で注入 |
ノーコード・ローコードオプション
| ツール | 主な特徴 | Discord 連携方法 |
|---|---|---|
| Botpress (OSS) | オープンソース、Docker デプロイが簡単。プラグインで Discord モジュールを追加可能。 | npm i @botpress/channel-discord → data/global/config/channel.discord.json にトークンとインテントを書き込み、フローで HTTP アクション(OpenAI)を呼び出す |
| Microsoft Power Virtual Agents | SaaS 型 UI ビルダー。Power Automate の「Discord – Send Message」コネクタと組み合わせて使用。 | カスタムコネクタに OpenAI Chat Completion API をインポートし、フローで取得したテキストを Discord に送信 |
Botpress 実装サンプル(抜粋)
|
1 2 3 4 5 6 |
// data/global/config/channel.discord.json { "token": "{{DISCORD_BOT_TOKEN}}", "intents": ["GUILDS","GUILD_MESSAGES","MESSAGE_CONTENT"] } |
フロー内で Action ノードに次のような JavaScript を記述:
|
1 2 3 4 5 6 7 8 |
const res = await bp.http.postJson('https://api.openai.com/v1/chat/completions', { model: 'gpt-4o-mini', messages: [{role:'system',content:'You are a helpful assistant.'}, ...event.state.history, {role:'user',content:event.payload.text}] }, { headers: {'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`} }); return res.choices[0].message.content; |
運用上のベストプラクティス(レートリミット・エラーハンドリング・セキュリティ・テスト)
1. レートリミット対策
| 対象 | 上限例 | 推奨回避策 |
|---|---|---|
| Discord API | 5 リクエスト/秒(Gateway) | 429 を受けたら Retry-After ヘッダー値でスリープ、またはトークンバケット方式で送信頻度を平準化 |
| OpenAI API | 60 req/min(モデル別に変動) | 失敗時は指数バックオフ (1s → 2s → 4s …)、同一ユーザーからの連続リクエストはキューでシリアライズ |
2. 共通エラーハンドリング例(Python)
|
1 2 3 4 5 6 7 8 9 10 |
async def safe_call(fn, *args, **kwargs): try: return await fn(*args, **kwargs) except aiohttp.ClientResponseError as e: if e.status == 429: retry = int(e.headers.get("Retry-After", "1")) await asyncio.sleep(retry) return await safe_call(fn, *args, **kwargs) # 再試行 raise |
3. セキュリティ
| 項目 | ベストプラクティス |
|---|---|
| 最小権限 | OAuth2 スコープは bot + applications.commands のみ。不要な管理系権限(例: MANAGE_GUILD)は付与しない |
| シークレット保管 | GitHub Secrets、Render Environment Variables、Azure Key Vault 等のマネージドサービスを使用 |
| 入力検証・コンテンツフィルタ | OpenAI Moderation API でユーザー入力と AI 応答を事前チェックし、不適切コンテンツは送信しない |
| 監査ログ | logging(Python)/winston(Node.js)で INFO/ERROR を分離し、CloudWatch や Papertrail に転送 |
4. テスト・デバッグ手法
- ローカルエミュレーション:
discord.pyのBot.test_guilds = [YOUR_GUILD_ID]、discord.jsのGUILD_ID環境変数で限定的に実行。 - ユニットテスト:
pytest + respx(Python)/jest + nock(Node.js)で OpenAI エンドポイントをモックし、レートリミット・エラーケースを検証。 - ログ分析:JSON 形式の構造化ログを CloudWatch Insights 等でクエリし、異常率やレイテンシを定量的に把握。
参考リンク集
| 項目 | URL |
|---|---|
| Discord Developer Portal | https://discord.com/developers/applications |
| OpenAI Pricing(公式) | https://openai.com/pricing |
| FlowHunt ガイド(Discord AI Bot) | https://flowhunt.com/guide/discord-ai-chatbot |
| discord.py ドキュメント | https://docs.discord.dev/en/latest/ |
| discord.js v14 ドキュメント | https://discordjs.guide/ |
| Botpress Discord Module | https://github.com/botpress/channel-discord |
| Power Virtual Agents – カスタムコネクタ作成 | https://learn.microsoft.com/power-virtual-agents/custom-connectors |
まとめ
- Bot 作成 → 必須インテント有効化(
MESSAGE_CONTENT,GUILD_MEMBERS)を最初に実施。 - OpenAI API キー取得し、公式料金表でコスト感覚を把握(無料枠・月額上限設定)。
- スラッシュコマンドと会話履歴管理を実装すれば、数十行のコードで実用的な AI ボットが完成。
- Docker + GitHub Actions で自動ビルド・デプロイし、シークレットは各 PaaS の機能で安全に注入。
- レートリミット対策・エラーハンドリング・最小権限を徹底すれば、運用時の障害やコスト超過リスクを大幅に低減できる。
この手順に沿って構築すれば、開発者は 「コードを書き始める」 だけでなく、「安全・安定に本番環境へデプロイ」 まで一貫して行えるようになります。 Happy coding!
スポンサードリンク