Djangoアプリケーションにチャットボットを導入してみた

## はじめに
作成済みの**Djangoアプリケーション**に自然言語処理を使った**チャットボットを導入**できたので、その方法を共有したいと思います。

Django自体の説明は必要なもの以外は割愛させていただきます。

## イメージ
![スクリーンショット (72).pn](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3206726/c91eefcb-d37d-471a-4736-b07aeb2870cc.png)

## ルーティング(urls.py)
まず初めに、ルーティングの説明からします。
下記のようにurls.pyにページにとばすためのコードを追加します。
```Python:urls.py
from django.urls import path
from . import views

app_name = 'myapp'
urlpatterns = [
    ...
    #urlを追加！
    path('my_chat_bot/', views.My_chat_botView.as_view(), name="my_chat_bot"),
    ...
]
```
これでページにとべるようになります。

## ビュー(views.py)
次に、ビューのコードです。
```Python:views.py
import logging

from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from .forms import My_chat_botForm
from .models import Chat
from django.views.generic import FormView
from django.core import serializers
from .my_chat_bot import MyChatBot

logger = logging.getLogger(__name__)

...

#my_chat_bot
class My_chat_botView(LoginRequiredMixin, FormView):
    template_name = "my_chat_bot.html"
    form_class = My_chat_botForm
    success_url = '/my_chat_bot/'

    def form_valid(self, form):
        # ユーザーのチャットを取得する
        chats = Chat.objects.filter(user=self.request.user).order_by('created_at')

        # チャットが3つ以上なら最古のものを削除する
        if len(chats) >= 3:
            chats[0].delete()

        message = form.send_message()
        response = MyChatBot("AI：", message)
        chat = Chat(user=self.request.user, message=message, response=response)
        chat.save()
        data = serializers.serialize('json', [chat])
        return super().form_valid(form)

    def form_invalid(self, form):
        messages.error(self.request, "Invalid form")
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['chats'] = Chat.objects.filter(user=self.request.user)
        return context
```

このビューの役割は、ログインしているユーザーがフォームでメッセージを送信した際に、そのメッセージの内容を処理することです。

ユーザーがフォームに入力したメッセージを取得し、それをMyChatBotというクラスのオブジェクトに渡します。MyChatBotで、チャットボットの応答を生成しています。このメソッドは、Chatオブジェクトを作成し、それをデータベースに保存します。最後に、ビューはフォームのデフォルトのURLにリダイレクトします。

また、このチャットの履歴を表示するために今回はデータベースを使用しています。
ユーザーのチャットを取得し、それが3つ以上の場合に最古のチャットを消去しています。履歴を絶対に消去する必要は無いのですが、件数が多くなるとページが無駄に長くなってしまうため今回はこのような処理にしました。

## フォーム(forms.py)
次に、フォームのコードです。

```Python:forms.py
import os

from django import forms

class My_chat_botForm(forms.Form):
    message = forms.CharField(label='メッセージ', max_length=50)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.fields['message'].widget.attrs['class'] = 'form-control'
        self.fields['message'].widget.attrs['placeholder'] = 'メッセージをここに入力してください。'

    def send_message(self):
        message = self.cleaned_data['message']
        return message
```
このフォームクラスは、messageという1つのフィールドを持ち、CharFieldを使用しています。label引数は、フィールドのラベルを設定するために使用されます。max_length引数は、フィールドに許可される最大文字数を指定します。

send_messageメソッドは、フォームのmessageフィールドから入力されたメッセージを取得し、それを返します。ビューのform_validメソッドで使用されます。

このフォームクラスは、ビューで使用されるためにインポートされ、ビュークラスのform_class属性に割り当てられています。ビューでフォームが正しく送信されると、ビューはsend_messageメソッドを使用してメッセージを取得し、MyChatBotで処理されます。
## モデル(models.py)
```Python:models.py
from accounts.models import CustomUser
from django.db import models

class Chat(models.Model):
    user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
    message = models.CharField(max_length=255)
    response = models.CharField(max_length=255)
    created_at = models.DateTimeField(auto_now_add=True)

    @classmethod
    def create(cls, **kwargs):
        # データベース内の Chat インスタンス数を確認
        chats = Chat.objects.all()
        if chats.count() >= 3:
            # 古いものから削除
            oldest_chat = chats.order_by('created_at').first()
            oldest_chat.delete()

        # インスタンスを作成
        chat = cls(**kwargs)
        chat.save()
        return chat
```

このコードは、チャットのメッセージと応答を保存するためのChatモデルを定義しています。

モデルのフィールドは以下の通りです：

**user:** このメッセージを送信したユーザーを示す外部キー。accounts.models.CustomUserを参照しています。
**message:** チャットメッセージのテキストを格納するCharField。
**response:** 応答メッセージのテキストを格納するCharField。
**created_at:** レコードが作成された日時を示すDateTimeField。
さらに、create()というクラスメソッドが定義されています。このメソッドは、Chatインスタンスを作成する際に使用され、引数として渡されたデータを使用して新しいチャットメッセージを作成し、データベースに保存します。ただし、チャットの数が3つを超えた場合は、最も古いチャットメッセージを削除してから新しいチャットメッセージを作成します。

## チャットボット(my_chat_bot)
続いて、肝心のチャットボットのコードの説明です。
```Python:my_chat_bot.py
import myapp.dictionary.res as re
from janome.tokenizer import Tokenizer
import random

def MyChatBot(name, message):
    
    def get_words(text):
        t = Tokenizer()
        words = [token.surface for token in t.tokenize(text)]
        return words
    
    responses = re.response()
    prompt = tuple(get_words(message))
    flag = True
    for word in prompt:
        if word in responses:
            flag = False
            response = random.choice(responses[word])
        elif word in re.bye:
            flag = False
            response = random.choice(re.bye)
        
    if flag:
        response = random.choice(re.no)
        
    response = name + response
    return response
```
このチャットボットは、受け取ったメッセージを単語分割にするためにjanomeパッケージのTokenizerを使用しています。


次に、受信したメッセージに対する応答を決定するために、myapp.dictionary.resモジュールから応答を取得します。応答は辞書型で、キーにはキーワードの単語が、値にはキーワードに対する応答が格納されています。

このチャットボットは、プロンプトの単語を順番に調べ、それぞれに対応する応答があるかどうかを確認します。トークンに対する応答がある場合、ランダムに1つを選択し、その応答を返します。

単語に対する応答がない場合、チャットボットは、myapp.dictionary.resモジュールのnoリストからランダムに1つを選択し、それを返します。

最後に、チャットボットは応答に名前を追加し、それをビューに返します。

## 辞書(myapp/dictionary/res.py)
次に、キーワードとなる単語とそれに対する応答を用意します。
```
# 辞書型の会話の応答をしてくれる辞書を返す関数
def response():
    res = {
        "はじめまして": ["はじめまして！", "チャットボットです！", "何かご用件はありますか？"],
        "初めまして": ["はじめまして！", "チャットボットです！", "何かご用件はありますか？"],
        "こんにちは": ["こんにちは！", "やあ！", "こんにちは。"],
        "こんにちわ": ["こんにちは！", "やあ！", "こんにちは。"],
        "おはよう": ["おはようございます！", "おはよう！", "朝だー！"],
        "こんばんは": ["こんばんは！", "こんばんはー！", "夜だー！"],
        "こんばんわ": ["こんばんは！", "こんばんはー！", "夜だー！"],
        "ありがとう": ["どういたしまして！", "いいえ、こちらこそ！", "何でもおっしゃってください。"],
        "天気": ["晴れです。", "雨です。", "曇りです。", "雪です。", "嵐です。", "雷です。"],
        "スポーツ": ["楽しかった？", "日記に残そう", "またやりたいね！"],
        "旅行": ["日記を書こう！", "楽しかった？", "次はどこに行こうか。"],
        "行った": ["日記を書こう！", "楽しかった？", "次はどこに行こうか。"],
        "家族": ["何があった？", "日記に残そう。", "よかったですね。"],
        "食事": ["おいしそうですね。", "私も食べたい。", "いいね！"],
        "サッカー": ["楽しかった？", "日記に残そう", "またやりたいね！"],
        "野球": ["楽しかった？", "日記に残そう", "またやりたいね！"],
       }
    return res

bye = ["バイバイ","さようなら","またね"]
no = ["理解できませんでした。","理解不能！","もう一回言ってくれますか？"]
```
my_chat_bot.pyからimportされたファイルです。
キーに設定されているキーワードを発見したらそれに対する返しを値からランダムで返されます。
## HTML(myapp/templates/my_chat_bot.html)
次に、ページを表示するためのHTMLのコードです。
```HTML:my_chat_bot.html
{% extends 'base.html' %}

{% block title %}chat_bot | MYSITE{% endblock %}

{% block active_inquiry %}active{% endblock %}

{% block contents %}
<div class="container">
    <div class="my-div-style">
        {% for chat in chats %}
        <p>{{ chat.user.username }}: {{ chat.message }}</p>
        <hr>
        <p>{{ chat.response }}</p>
        <hr>
        {% endfor %}
        <form method="post">
            {% csrf_token %}

            {{ form.non_field_errors }}

            {% for field in form %}
            <div class="mb-4 col-8">
                <label for="{{ field.id_for_label }}" class="form-label">
                    <strong>{{ field.label_tag }}</strong>
                </label>
                {{ field }}
                {{ field.errors }}
            </div>
            {% endfor %}

            <button class="btn btn-primary" type="submit">送信</button>
        </form>
    </div>
</div>
{% endblock %}
```
**履歴の表示**
{% for chat in chats %}と{% endfor %}は、Djangoのforループテンプレートタグです。chats変数に格納されたChatオブジェクトを反復処理して、それらを画面に表示します。これにより、チャットの履歴が表示されます。
\<p>{{ chat.user.username }}: {{ chat.message }}\</p>は、チャットメッセージの送信者と内容を表示します。
\<p>{{ chat.response }}\</p>は、チャットボットの応答を表示します。

**送信**
\<form method="post">は、ユーザーの入力を受け付けるためのフォームを開始するためのHTMLフォームタグです。method="post"は、フォームデータをPOSTメソッドでサーバーに送信することを指定します。
\<button class="btn btn-primary" type="submit">送信\</button>は、フォームの送信ボタンを定義するためのHTMLコードであり、class="btn btn-primary"でスタイリングされます。

## 実行確認
最後にマイグレーションをして実行確認しましょう。
```
python manage.py makemigrations
```
```
python manage.py migrate
```
```
python manage.py runserver
```
ターミナル上に表示されたローカルのリンクにアクセス。

## まとめ
今回は、Djangoアプリケーションにチャットボットを導入する紹介でした。
辞書型のチャットボットでしたが、応用すればシナリオ型の会話も可能です。
ChatGPT等のAPIを取得してメッセージを生成させればさらにすごいチャットボットを導入できると思います。

こちらから全体のコードを確認できます。
[GitHubリポジトリ](https://github.com/ishizukuma/venv_private_diary.git)

**余談**
今回は、形態素解析ライブラリのJanomeを使用しています。MeCabは設定が面倒なことや上手く動かない人が多いみたいです。Janomeはpipインストールしただけであとはコードを書いただけで一発で動いてくれたのでJanome最強です！処理速度はMeCabより遅いのですが、今回は短い文章の解析なので問題がありません！解析精度は同等のようです！
Janomeすごい！天才！！神！！！

## 参考
Djangoアプリケーションの作成はこちらを参考にしました。
[動かして学ぶ! Python Django開発入門 第2版](https://www.amazon.co.jp/%E5%8B%95%E3%81%8B%E3%81%97%E3%81%A6%E5%AD%A6%E3%81%B6-Python-Django%E9%96%8B%E7%99%BA%E5%85%A5%E9%96%80-%E7%AC%AC2%E7%89%88-NEXT/dp/479817419X/ref=sr_1_8?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&crid=3VOPDHG09OZYA&keywords=Django&qid=1679745402&s=books&sprefix=django%2Cstripbooks%2C340&sr=1-8)

**チャットボットについてはオリジナルです。**

**ご覧頂きありがとうございました！**