生成AIでAmazonWebServeceにメンション時のみ反応するGEMINI AI Discord Botを開発・設置する

skypeのサービス終了に伴いDiscordに移行

所内での事務連絡にskypeを使っていましたが、2025/5にskypeのサービスが終了するということで、Discordのボイスチャットに移行することにしました。

他サービスも検討しましたが、①メールアドレスのみで登録可能で登録時に電話番号不要、②ボイスチャット(三者通話)が時間無制限、③チャット履歴がログアウトしたあとも数ヶ月分は確認できる、という要件を満たすのはDiscordのみでした。

DiscordでAIを使いたい

個人的にMicrosoft CoPilotやGoogle GEMINIを使用していますが、チャット内でシームレスにAIを利用できると便利だろうなということで、GEMINI AI Botを設置することにしました。

Discord Botの作成・Discord Bot Tokenの発行

  • Discordに登録してチャンネルを作成します。
  • Discord Developer Portal でアプリケーションを作成します。
  • Botを作成し、Bot Tokenを取得します。
  • Botに必要な権限 (メッセージの読み取り、送信など) を付与します。
  • Botをサーバーに招待します。

 

Discordのチャンネル作成はリンク先を参照してください。

ボイスチャンネルを作成すると、音声通話(映像も可能)とチャットが使用できるようになります。

network.mobile.rakuten.co.jp

Discord botを作成します。

Botの作り方はリンク先をご参考に。

qiita.com

Botを作成した後、まず最初にInstallationのタブでInstall LinkをNoneに設定しておきます。こうしないと非公開Botに設定できないので。

権限を設定します。scopesの基本はbot、その他はご自由に。Bot permissionsはTEXTとVOICEを適宜。

一番右下のボタンを押してURLをコピーして、ブラウザのアドレス欄にペーストするとチャットにbotが呼び出せます。

次に左側のタブのBotのページで設定します。

PublicBotは必ずoffに設定しておきます。Publicのままにしておいて、どこかのチャットにBotが呼び出されて大量のやりとりが発生するとGEMINI使用の費用請求をされたり、こちらのチャットの内容が漏洩する可能性があるので、Publicのままにはしないでおきましょう。

中段の「Privileged Gateway Intents」はPresence Intent・Server Members Intent・Message Content Intentをオンにしておきます。

下段のText Permissionsは画像でチェックされているあたりにチェックを入れておきます。

設定が終わったら、「Token」の下にあるReset Tokenボタンを押します、Discordのパスワードが要求されるので、パスワードを入力した後、Tokenの値をメモ帳にでも保存しておきます。

保存しておかないと、閲覧できなくなって新しいTokenを発行するしかなくなるので注意してください。

GEMINI APIの発行

aistudio.google.com

  • Gemini API (Google AI Studio API) を有効にします。
  • APIキーを取得します。

 

リンク先にアクセスします。

なお、GoogleWorkspaceの契約をしていないとAPI発行ができないかもしれません。

上段のみチェックして同意する

「キー APIキーの作成」ボタンを押す

GEMINIのAPIキーが生成されるので、コピーボタンを押してメモ帳などに保存しておきます。

QNAPでDiscord Botを動作させる・GoogleColaboでDiscord Botを動作させる(いずれも失敗)

最初は事務所内で使用しているQNAP NASのcontainer station(仮想環境)でubuntu 22.04を動作させて後記引用するBotとは別のBotを動かすつもりでしたが、これが何度やってもどうにもダメで、CoPilotに提示された方法を全て試してもエラーが解消できなかったので諦めました。

かといってBot用マシンを一個立てた上で最後までエラーが出てもつらいので、次にGoogleColaboでBotを動かしてみました。

しかし設定した後でわかったんですが、GoogleColaboは一定時間ごとに環境設定・プログラムが更地にされてしまうので、恒久的なBot設定には向いてなかったですね。

ただGoogleColaboで環境設定して、プログラムを試験的に走らせてみてエラーが出るとエラー対策したコードをすぐに提示してくれるので、GoogleColaboでテストとして運用してみて、うまくいったら恒久的なサーバーに移行するのがいいのかもしれません。

という訳で、GoogleColaboでエラーが解消されるまでコードを訂正して、AmazonWebService(AWS) Lightsailに移行しました。

AmazonWebService LightsailでGEMINIを利用したDiscord Botを動作させる(成功)

Discord Botを設置するという検索をすると無料サーバー等が出てくるのですが、設置までトラブルが多い印象なので、有料ですが低価格で一番手堅いAmazon Web Service(AWS) Lightsailを利用します。有料といっても他のDiscord Botサーバーでも最低利用料金が5ドルのようなので、同じ費用なら一番しっかりしてるAWSがいいと思います。設定時の原因不明トラブルで時間取られたくないですし。

aws.amazon.com

タイトルでは最低月額料金が月額3.5ドルになってますが、IPv4だと最低料金が月額5ドルです。

少なくとも今回設置するDiscord Botのプログラムでは5ドルのプランで十分なので、このプランを導入します。

Instance locationはTOKYO、PlatformはLinux/Unix、OperatingSystem OnlyでUbuntu 22.04LTSを選択しました。ただよく考えたらDiscordのサーバーとGEMINIのサーバーがどっちも北米にあるとしたら、Instance locationは北米を指定したほうが反応が多少よくなるのかもしれません。それとも日本ユーザーの場合にはどっちも国内のサーバーなんですかね。

コンソールにアクセスしたら、「すべてのサービスを表示」をクリック。Lightsailの契約をしたらLightsailだけにアクセスするんじゃないんですね。ちょっと最初わからなかった。

左上のLightsailをクリックすると、Lightsailアクセスできるので、インスタンスを一個作成します。

以下、PlatformはLinux/Unix、OperatingSystemはUbuntu 22.04LTSを前提としています。他のバージョンなりで試すときにエラーが出たらCoPilotにでも聞いてください。私に聞かれてもわかりませんので……。

また、以下の設定はコンソールにログインした後にディレクトリを移動したり作ったりしない・rootユーザーのまま(つまり全部コンソールログイン時のまま何もいじらず設定もしない)実行することが前提です。一つの仮想環境に一つのBotなら整理とか必要ないのでこれでいいんじゃないかと思います。例によって気になる人はCoPilotに聞いて頑張って書き換えてください。

以下、引用部分をコンソールにコピーしてペーストすればGEMINIのDiscord Botが動きます。

まず実行環境を設定

「SSHを利用して接続」ボタンを押すと、ブラウザでコンソールが開きます。わざわざソフトとかインストールしなくていいので大変便利。

コンソールに、以下のコマンド等を入力していきます。

コピーして、コンソール上でマウスの右クリック→貼り付けとやるとコマンド等がコピーできるので、必要に応じてリターンキーを押して実行してください。

sudo apt update -y && \
sudo apt upgrade -y && \
sudo apt install -y python3-venv python3-pip python3 tzdata && \
echo 'export PATH=$PATH:/home/ubuntu/.local/bin' >> ~/.bashrc && \
source ~/.bashrc && \
sudo pip install -q nest_asyncio discord.py && \
sudo pip install -q -U google-generativeai && \
sudo ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
sudo dpkg-reconfigure --frontend noninteractive tzdata

引用部分は各引用部分ごとにひとまとまりなので、折り返し部分で切ったりせずにまとめてコピーしてペーストしてください。なんか実行内容が重複してるんじゃないかとか言われそうですが、わかっている人はうまいことやってください。

なお途中で「WARNING: Running pip as the 'root' user can result in broken permissions and~」という警告が出ますが、無視して問題ないです。問題ないと言い切っていいのかはどうかと思いますし、CoPilotには仮想環境で設定しろと言われたのですが、そもそもAWSの環境自体が仮想環境でそこにさらに仮想環境作って実行しようとするとかえってうまくいかないんじゃないかと思います(QNAPは仮想環境上で実行しようとして失敗した)。

警告は出たんですが、少なくともこのあとのBot実行に問題はありませんし、Discord Botだけ実行させている分には大丈夫だと思います。もし他のサービスも実行したいとなったら、記憶領域にも相当余裕があるので別に仮想環境立ててやればいいんじゃないかと。

echo "export GOOGLE_API_KEY='実際のGOOGLE_API_KEYに書き換えてください'" > set_env.sh

echo "export DISCORD_BOT_TOKEN='実際のDISCORD_BOT_TOKENに書き換えてください'" >> set_env.sh

ここが重要で、 実際のGOOGLE_API_KEYに書き換えてください のところをメモしたAPIキーに書き換え、 実際のDISCORD_BOT_TOKENに書き換えてください のところをメモしたDiscord Tokeに書き換えをしてから、コピーしてペーストして実行してください。なお、前の ' と後の '" はそのままにしておく必要があります。

ここに実際のAPIキー/Tokenを入れないと絶対にうまく動きません。

chmod +x set_env.sh && source set_env.sh

ここまでがプログラム設置前段階。

Discord Bot用プログラム

cat > your_script.py << 'EOF'
import os
import nest_asyncio
import discord
import google.generativeai as genai
import asyncio

# Google Generative AI の設定
genai.configure(api_key=os.environ["GOOGLE_API_KEY"])
model = genai.GenerativeModel('gemini-2.0-pro-exp')

# Discord クライアントのセットアップ
intents = discord.Intents.default()
intents.message_content = True
discord_client = discord.Client(intents=intents)

def split_text(text, chunk_size=1500):
    """テキストをチャンクに分割します。"""
    return [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]

# ユーザーごとのチャット履歴を保存する辞書
chat_histories = {}

@discord_client.event
async def on_ready():
    print(f'Logged in as {discord_client.user}')
    print(f"Bot's mention string: <@{discord_client.user.id}>")
    print(f"google-generativeai version: {genai.__version__}")

@discord_client.event
async def on_message(message):
    if message.author == discord_client.user:
        return
    if message.author.bot:
        return

    mention_string = f"<@{discord_client.user.id}>"
    if message.content.startswith(mention_string + " "):
        input_text = message.content[len(mention_string) + 1:].strip()

        user_id = message.author.id
        if user_id not in chat_histories:
            chat_histories[user_id] = model.start_chat(history=[])
        chat = chat_histories[user_id]

        await message.channel.send("---")

        try:
            # --- 堅牢な非同期処理 (タスクの作成) ---
            async def send_and_process():
                response = await chat.send_message_async(input_text)
                # ここでは条件付き await は不要。常にタスクを await する。
                for chunk in split_text(response.text):
                    await message.channel.send(chunk)

            await send_and_process() # コルーチンを直接実行

        except Exception as e:
            await message.channel.send(f"Error: {e}")
            print(f"Full traceback: {e.__traceback__}")  # より詳細なエラー情報

nest_asyncio.apply()
discord_client.run(os.environ['DISCORD_BOT_TOKEN'])
EOF

ここまでを全部コピーして、コンソールに貼り付けてリターンキーを押してください。

なおこのプログラムは以下のサイトを参考にしました。

note.com

ただもとのプロクラムだと、チャットに入力した内容全部にAIが反応するのでメンション(@~とチャット内でAIに呼びかけた場合)のみ反応するように変更しています。

全てのチャットに返答するような設定だとAIの返答で埋め尽くされて人間同士のコミュニケーションも阻害されかねないので、複数人で使うことが目的のチャットであればAIはメンション時のみ反応にした方がいいと思いますね。チャット内に人間が1人のみなら、メンションせずともAIが全部反応するようなプログラムのほうがいいんでしょうけど。

それとmodel = genai.GenerativeModel('gemini-2.0-pro-exp')とGemini 2.0 Proを使うような指定をこちらではしているんですが、これは結構怪しいです。

Google側でどのモデルを使うか制御している雰囲気で、こちらで指定してもあんまり意味ないかもしれません。

一応調べたところ、現時点ではgoogle-generativeaiでは以下リストアップされていましたが、明示的にユーザー側で指定できているのかは未知数です。

models/gemini-1.0-pro-vision-latest
models/gemini-pro-vision
models/gemini-1.5-pro-latest
models/gemini-1.5-pro-001
models/gemini-1.5-pro-002
models/gemini-1.5-pro
models/gemini-1.5-flash-latest
models/gemini-1.5-flash-001
models/gemini-1.5-flash-001-tuning
models/gemini-1.5-flash
models/gemini-1.5-flash-002
models/gemini-1.5-flash-8b
models/gemini-1.5-flash-8b-001
models/gemini-1.5-flash-8b-latest
models/gemini-1.5-flash-8b-exp-0827
models/gemini-1.5-flash-8b-exp-0924
models/gemini-2.0-flash-exp
models/gemini-2.0-flash
models/gemini-2.0-flash-001
models/gemini-2.0-flash-exp-image-generation
models/gemini-2.0-flash-lite-001
models/gemini-2.0-flash-lite
models/gemini-2.0-flash-lite-preview-02-05
models/gemini-2.0-flash-lite-preview
models/gemini-2.0-pro-exp
models/gemini-2.0-pro-exp-02-05
models/gemini-exp-1206
models/gemini-2.0-flash-thinking-exp-01-21
models/gemini-2.0-flash-thinking-exp
models/gemini-2.0-flash-thinking-exp-1219
models/learnlm-1.5-pro-experimental
models/gemma-3-27b-it

Discord Botをサービスとして動かす

サーバー側でサービスとして動かすことで、Discord Botが恒久的に作動するようになります。そのため、以下の内容をコンソールにコピーしてペーストします。

sudo bash -c 'cat > /etc/systemd/system/your_script.service << EOF
[Unit]
Description=Your Script Service
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/home/ubuntu
ExecStart=/usr/bin/bash -c "source /home/ubuntu/set_env.sh && /usr/bin/python3 /home/ubuntu/your_script.py"
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF'

最後にこのサービスを実行します。

sudo systemctl daemon-reload && sudo systemctl start your_script.service && sudo systemctl enable your_script.service

サービスが実行されているか確認するには、以下のコマンドを実行します。

sudo systemctl status your_script.service

動いていればこんな風に表示されます。メモリ使用量は100Mもありませんし、CPUもほとんど使用していないので、5ドルプランで十分なのが確認できます。まあDiscord ChatのテキストデータをGoogleに流して、Googleから返ってきた内容をDiscord Chatに送信するブリッジのような役割なので、そんなにメモリもCPUパワーもいりませんよね。

コンソールに戻るには、Ctrl+Cで戻れます。

もしファイル等書き換えてサービスを再起動させたい場合は、以下のコマンドを実行。

sudo systemctl restart your_script.service

GEMINIが日本語以外で返答する場合の対処法

理由はよくわからないんですが、GEMINIからの回答でローマ字が入ったり英語が加わったりすることがあるかもしれません。

このときは、「日本語だけで回答して」等、チャット内で回答言語の明示をすればその後は日本語のみで回答するようになります。

たぶん費用がかかります

GEMINI APIは従量制で一定の費用がかかるようです(ただあんまりはっきりしていない)。とはいえ、メンション時のみ作動させるような設定だとそんなに多額にならないのではないかと思いますが。

プログラムの変更はGoogleColabo・GEMINI・CoPilotで行ないました

GoogleColaboで実行したところ、エラーが出たのでGoogleColaboのSuggestに従って変更。

その後、メンションした場合のみ反応するようGEMINIに指示してコードを変更。

これでメンション時のみ反応するように変更できたけれども、Botの回答に「Error: object GenerateContentResponse can't be used in 'await' expression」というエラーが出てしまったのでGEMINIに手直しするよう指示してコードを修正するも同様のエラーが出続けたため修正指示をしたところ「もし、前回の包括的な修正後も "Error: object GenerateContentResponse can't be used in 'await' expression" エラーが まだ 発生する場合、非常に特殊な、そして Colab 環境自体の内部に存在する、より深いレベルの競合が問題である可能性が極めて高いです。前回のバージョンのコードを 正確に 実装していれば、現時点ではコードの問題である可能性は 非常に 低いです。」とさじを投げられてしまう。

仕方ないのでCoPilotにコードとエラーを示したところ、ちゃんと動作するプログラムのコードを提示された(つまりGEMINIの回答が間違っていてコードの問題だった)のが、前述したプログラムになります。

AIはちまちま使っているんですが、コード生成はCoPiloのほうが一段レベルが高い感じしますね。 

なお私はわずかにコードを読める程度で、コードを書くのは非常に簡易なVBA以外は無理な人間なんですが、AIを使うとここまでできてしまうので、本業ではない作業については特にAI活用の利点が大きいとは感じています。