生成AIを利用してQNAPのContainerStation Dockerにメンション時のみ反応するGEMINI AI Discord Botを設置する

QNAP ContainerStationのUbuntu 22.04でDiscord Botを設置できた

AWS LightsailのUbuntu 22.04で設置できたのであればQNAP ContainerStationのUbuntu 22.04でも設置できるのでは、ということで試してみたらうまくいったので書いておきます。

前回記事と重複しますが、最初から最後まで手順を記載します。

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

  1. Discordに登録してチャンネルを作成します。
  2. Discord Developer Portal でアプリケーションを作成します。
  3. Botを作成し、Bot Tokenを取得します。
  4. Botに必要な権限 (メッセージの読み取り、送信など) を付与します。
  5. 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

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

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

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

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

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

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

QNAPのContainerStationをインストールして仮想環境を作成

QNAPでUbuntu(Linux)の仮想環境を作成する必要があるので、ContainerStationをインストールして、Ubuntu 22.04の仮想環境を作成します。

App Centerをクリック。

ユーティリティのContainerStationをインストール。

ホーム画面にContainerStationが追加されるのでクリックして起動。

アプリテンプレートのUbuntu 22.04 Dockerコンテナをデプロイ。
LXDだと、ログイン時にIDとパスワードを要求されるのですが、root/admin系の一般的なデフォルトパスワード、QNAPで使用しているIDとパスワードを試してみましたが全部ダメでログインが不可能でした。ネットで調べた情報も古いものしかなく、そこに書かれていたIDとパスワードで試してみてもログイン不可。QNAPのサイトでも言及がないので、あきらめました。

デプロイできたら、コンテナをクリックして、DOCKER横の「名前」の下をクリック(最初のデプロイで名前を変更していないなら、ubuntu-1になっています)。

ターミナルをアタッチをクリック。

ターミナルが表示されるので、コマンド等を入力していきます。

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

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

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

AWSのときと異なっているのは、AWSではsudo~としていたんですがQNAPではsudo不要になっています。

export DEBIAN_FRONTEND=noninteractive && \

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

引用部分は各引用部分ごとのまとまりなので、折り返し部分で切ったりせずにまとめてコピーしてペーストしてください。

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に呼びかけた場合)のみ反応するように変更しています。

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

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

cat > /set_env_and_script_runner.sh << 'EOF'
#!/bin/bash

# 無限ループ
while true; do
    echo "Starting your_script.py at $(date)"
    
    # 環境変数の読み込み
    if [ -f /set_env.sh ]; then
        source /set_env.sh
    else
        echo "set_env.sh not found!" >&2
        exit 1
    fi
    
    # スクリプトの実行
    if ! python3 /your_script.py; then
        echo "your_script.py crashed with exit code $? at $(date). Restarting in 10 seconds..." >&2
    fi
    
    # 5秒待機
    sleep 5
done
EOF

最後にこのスクリプトを実行します。

nohup bash /set_env_and_script_runner.sh > /var/log/set_env_and_script_runner.log 2>&1 &

AWSではsystemdを使用しましたが、QNAPのUbuntu 22.04 Dockerではsystemdが使えないので、スクリプトを無限ループさせる方法でサービス同様の実行をさせています。

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

ps aux | grep set_env_and_script_runner.sh

動いていればこんな風に表示されます。

最後に、QNAPを再起動したときにこのスクリプトを自動的に実行させるように設定。

export DEBIAN_FRONTEND=noninteractive && \
apt update -y && \
apt install -y cron && \
service cron start && \
(crontab -l 2>/dev/null; echo "@reboot nohup bash /set_env_and_script_runner.sh > /var/log/set_env_and_script_runner.log 2>&1 &") | crontab -

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

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

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