Discord.pyとハギングフェイスを用いてローカルLLMで動くDiscord_botを作る

:::note info
Discord.pyの使い方は[こちらの動画](https://www.youtube.com/watch?v=1o4fM4UQhko&list=LL&index=1)を参考にさせていただきました。
:::
## 目次
・Discor Developer Portal の使い方、設定
・Discord.py のインストール
・Discord bot の記述
・LLMが使用できるようにする

## Discor Developer Portal の使い方、設定
まずは[Discor Developer Portal](https://discord.com/developers/applications)にアクセスします。
そうすると以下の画面が出てくるはずなので、右上の**new application**を選択し、名前を入力します（この名前はボットの表示名になるわけではないです。）

![Discord Developer Portal — My Applications - Sidekick 2024_04_07 13_50_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763201/39624bb4-6675-733f-27f9-9b96e92c998c.png)

![Discord Developer Portal — My Applications - Sidekick 2024_04_07 13_51_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763201/cc93244a-2d2f-5cee-7855-bdac38a8c337.png)


その後、**settings**の**bot**ページを開き、**Privileged Gateway Intents**の3つの項目をすべてオンにし、下にホップアップで出てくる**save changes**を押します。

![Discord Developer Portal — My Applications - Sidekick 2024_04_07 13_55_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763201/fdfb1b70-f2d5-8aa0-b73e-658046f571c9.png)

終わったら、**Oauth2**タグに移動し、**SCOPES**では**BOT**をオンにし、**BOT PERMISSIONS**では**Administrator**をオンにします。

![Discord Developer Portal — My Applications - Sidekick 2024_04_07 14_26_35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763201/2bd8c03f-32d7-a892-476b-3c53c66e0767.png)

オンにしたら、下のURLにアクセスし、サーバーに追加します。

![Discord Developer Portal — My Applications - Sidekick 2024_04_07 14_26_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763201/3ad53610-0cab-0691-6d6f-2e57b46da40a.png)

その後、**bot**タグに移動し**reset token**を押します。パスコードを入力後、出てきたトークンをコピーし、保存しておきます。

![Discord Developer Portal — My Applications - Sidekick 2024_04_07 14_46_48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763201/40b52075-a6d7-599c-dd20-ee91e742e67f.png)


## Discord.py のインストール
次は環境にDiscord.pyをインストールします。

:::note warn
Discord.pyがPython 3.12 では動かない場合があるので、動かなかったらPython 3.11をインストールして、実行してみてください。
:::

### 環境の用意
venvを用いて仮想環境を作ります。
```
$ python -m venv env 
$ ./env/Script/activate
```
:::note warn
仮想環境では動かない場合があるので、自分は本番環境でやりました。
:::
Discord.pyをインストール
```
$ pip install Discord.py
```
その後、パイソンファイル（自分の場合はDiscordbot.py）をつくり、以下の内容を記述します。一番下の**TOKEN**のところに先ほどコピーしたトークンを入力します。

```python:Discordbot.py
import discord
from discord.ext import commands

bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())


#起動したときに起こるイベント
@bot.event
async def on_ready():
    print("準備完了")
    await bot.tree.sync()

#/hello
@bot.tree.command(name="hello", description="hello.")
async def ping(interaction: discord.Interaction):
    await interaction.response.send_message("hello ! ")


bot.run("TOKEN")
```
これを実行し、ボットのいるサーバーで"/hello"と入力すると、**hello !**　とかえって来るはずです。

![Discord _ アカウントへのアクセスを許可します - Sidekick 2024_04_07 15_15_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3763201/b1e66c24-25da-e8d5-0633-dc6e4ed66268.png)

## LLMが使用できるようにする
LLMが使用できるようにします。今回使用するのは自分が開発した[ArrowSmart_1.7b_instruction](https://huggingface.co/DataPilot/ArrowSmart_1.7b_instruction)を使います。

### ライブラリをインストール
LLMを使えるようにするため、ライブラリをインストールします。
```
$ pip install transformers
$ pip install pipline
$ pip install protobuf
$ pip install accelerate
$ pip install sentencepiece
$ pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
```
もしNvidiaのGPUがない場合は一番下のpip install を以下のものに変えます。
```
$ pip install torch
```

### discordbot.pyの内容を変更
内容を以下のものに変更します。def con が推論するための関数です。
```python:Discordbot.py
import discord
from discord.ext import commands
from discord import app_commands

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline

model_path = "DataPilot/ArrowSmart_1.7b_instruction"

model = AutoModelForCausalLM.from_pretrained(model_path,device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_path)

generator = pipeline("text-generation", model=model, tokenizer=tokenizer, device=0)

torch.cuda.empty_cache()

bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())

def con(in_text):
    text = generator(
        f"ユーザー: {in_text} システム: ",
        max_length = 500,
        do_sample = True,
        temperature = 0.7,
        top_p = 0.9,
        top_k = 0,
        repetition_penalty = 1.1,
        num_beams = 1,
        pad_token_id = tokenizer.pad_token_id,
        num_return_sequences = 1,
        truncation=True
    )
    json_text = text[0]
    result_text = json_text["generated_text"]
    output_text = result_text.replace('ユーザー: '+str(in_text)+' システム:', '')
    output_text = result_text.replace('<0x0A>','')
    return output_text


#起動したときに起こるイベント
@bot.event
async def on_ready():
    print("準備完了")
    await bot.tree.sync()


#/hello
@bot.tree.command(name="hello", description="hello.")
async def ping(interaction: discord.Interaction):
    await interaction.response.send_message("hello ! ")


@bot.tree.command(name="say", description="LLMが返信します。")
@app_commands.describe(saying_msg="送信したい文章を入れてください。")
async def say(interaction: discord.Interaction, saying_msg: str):
    await interaction.response.defer()  # ここで処理中であることを伝える
    res = con(saying_msg)
    await interaction.followup.send(f"{res}") 


bot.run("TOKEN")
```
このプログラムを実行後、ボットがいるサーバー上で"/sayと打ち、メッセージを送信するとLLMが考えた返信が返ってきます。

:::note info
初回ロード時にはモデルを読み込むため、時間がかかりますが正常です。
:::

## 終わり
これで、自作のLLMをdiscord上で友達に見せられますし、簡単にボットを実装することができます。