ChatterBotのチャットボットとWebアプリでやり取りできるようにする

今回の位置づけ

前回までの復習

  • チャットボット=自動応答するプログラム(チャットボットの概要
  • ライブラリ chatterbot を使ったPythonスクリプトをまず動かした
  • スクリプトだけでなく、Webアプリとしてもチャットボットを動かしたい
  • オウム返しするチャットボットとやり取りするWebアプリを作った

今回やること

  • Pythonスクリプトのチャットボット(コーパスから訓練)をWebアプリで動かす
    • 訓練(Pythonスクリプトの訓練)
    • 応答(画面でやり取りできるようにする)

前回の開発の続きができるように準備

前回の状態のファイルをzipにまとめて配布しています。 前回の「今回の位置づけ」の「前回の開発の続きができるように準備」を参照して、準備してください(手順は共通です)。

前回の状態のアプリケーションが手元で動いている状態が、今回の前提になっています。

Djangoのカスタムコマンド

カスタムコマンドとは

Djangoの カスタムコマンド という仕組みを使います。 アプリケーションごとにコマンドを持たされます。

開発者が使えるよう、Djangoにデフォルトで用意されているコマンドがあります。 python manage.py --help でコマンドの一覧が見られます。

  • 例えば、runserverdjango アプリケーションのコマンドです。

カスタムコマンドを実装して、このコマンド一覧にチャットボットを訓練するコマンドを追加します。

カスタムコマンドに必要なディレクトリ構成

Djangoのカスタムコマンドは ディレクトリ構成のルールが決まっています。 ルールに沿ってディレクトリとファイルを配置し、実装します。

https://docs.djangoproject.com/ja/3.2/howto/custom-management-commands/

独自のコマンドを追加するためには、``management/commands``ディレクトリをアプリケーションに追加してください。
$ mkdir -p chat/management/commands
$ touch chat/management/__init__.py
$ touch chat/management/commands/__init__.py
$ touch chat/management/commands/bot_train.py

__init__.py はPythonパッケージにするために置きます。 中身は空で大丈夫です。

chat/management/commands/bot_train.py を編集していきます。

初めてのカスタムコマンド!

まずは、簡単なカスタムコマンドを作りましょう。 chat/management/commands/bot_train.py に以下を書いてください。

chat/management/commands/bot_train.py
1from django.core.management.base import BaseCommand
2
3
4class Command(BaseCommand):
5    def handle(self, *args, **kwargs):
6        self.stdout.write("bot_trainコマンドです")

実行してみましょう。

$ python manage.py bot_train
bot_trainコマンドです

python manage.py --help で見られるコマンド一覧にも追加されていることが確認できます。

プログラムの解説

アプリケーションの management/commands に置いたファイルの名前がコマンド名になります(bot_train)。

ファイル名以外にも、カスタムコマンドでは「こう実装してください」というルールが決まっています。

BaseCommand クラスもしくはその サブクラス の一つを継承した Command クラスを定義する必要が有ります。

https://docs.djangoproject.com/ja/3.2/howto/custom-management-commands/

これに沿って Command クラスを実装しています(4行目)。

Command クラスには handle というインスタンスメソッドを実装します。 コマンドラインに出力するために self.stdout.write を使います。

管理コマンドを利用してコンソールへの標準出力を行いたい場合、stdout と stderr に直接文字列を渡すのではなく、self.stdout および self.stderr を利用するべきです。(上と同じリンクより)

ヒント

色付きでコマンドラインに出力できる

self.stdout.write の引数に、色などの スタイル を指定した文字列を渡せます。

  • self.style.SUCCESS("bot_trainコマンドです") :緑色で表示されます
  • self.style.ERROR("bot_trainコマンドです") :赤色で表示されます

引数 "bot_trainコマンドです" を上記に変えて試してみてください。 スタイル指定は訓練コマンドでも使います。

チャットボット訓練コマンド

コーパスを使って訓練したチャットボット(日本語) で行ったのと同じ訓練を、Webアプリでもできるようにします。 bot_train コマンドを実装します。

以下のファイルを変更します。

  • プロジェクトの settings.py
  • アプリケーションの management/commands/bot_train.py

設定変更

  • INSTALLED_APPS にChatterBotのDjangoアプリケーションを追加
  • ChatterBot用の設定を示す変数を追加

INSTALLED_APPS

app/djangochatbot/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    "chat.apps.ChatConfig",

    "chatterbot.ext.django_chatterbot",
]

参考:https://chatterbot.readthedocs.io/en/stable/django/index.html#installed-apps

ChatterBot用の設定追加

コーパスを使って訓練したチャットボット(日本語) で作ったスクリプトは、以下のように ChatBot クラスのインスタンスを初期化しています。

my_chatbot.py
11    chatbot = ChatBot("Training from corpus bot", tagger_language=CustomJPN)

この引数をプロジェクトの settings.py で指定します。 末尾に追加してください。

app/djangochatbot/settings.py
# ChatterBot settings
from chatterbot.languages import JPN


class CustomJPN(JPN):
    ISO_639_1 = "ja_core_news_sm"


CHATTERBOT = {
    "name": "Training from corpus bot on Web app",
    "tagger_language": CustomJPN
}

参考:https://chatterbot.readthedocs.io/en/stable/django/settings.html

辞書を指す変数 CHATTERBOT を追加しています。 この変数は、Webアプリで ChatBot クラスのインスタンスを初期化するときに使われます。 実は ChatBot というイニシャライザの第1引数は name です。

chatbot = ChatBot(
    name="Training from corpus bot",
    tagger_language=CustomJPN
)

my_chatbot.py 同様に日本語を扱うように指定しています(tagger_language)。 説明は ChatterBot はじめの一歩 を参照してください。

訓練するカスタムコマンドを実装

chat/management/commands/bot_train.py
 1from chatterbot import ChatBot
 2from chatterbot.ext.django_chatterbot import settings
 3from chatterbot.trainers import ChatterBotCorpusTrainer
 4from django.core.management.base import BaseCommand
 5
 6
 7class Command(BaseCommand):
 8    def handle(self, *args, **kwargs):
 9        chatbot = ChatBot(**settings.CHATTERBOT)
10
11        trainer = ChatterBotCorpusTrainer(chatbot)
12        trainer.train("chatterbot.corpus.japanese")
13
14        self.stdout.write(self.style.SUCCESS("Training completed."))

9〜11行目は my_chatbot.py でコーパスから訓練しているコードと同様です。 13行目は訓練終了を出力しています。

ヒント

**settings.CHATTERBOT について

この書き方は初めて見たときに混乱するかもしれません。 辞書に ** を付けて、関数に渡すと、辞書のキー=値キーワード引数を渡した ことになります。

>>> def f(a, b):
...   print("a", a)
...   print("b", b)
...
>>> f(a=1, b=2)
a 1
b 2
>>> d = {"a": 1, "b": 2}
>>> f(**d)  # f(a=1, b=2) という呼び出しと同じになる
a 1
b 2

訓練コマンドを動かすために

  • データベースのセットアップ
  • ライブラリ chatterbot_corpus インストール

後者のインストールについては コーパスを使って訓練したチャットボット(日本語) に対処法を追加しています。

データベースのセットアップ

ChatterBotは訓練に使うコーパスを データベースに保存 します。

Djangoでデータベースの操作を担うのが モデル です。

今回の実装の範囲で、私たちがモデルを使ってデータベースを操作することはありませんが、仕組みとして紹介しました (ChatterBot側でモデルを使ったコードが用意されています)。

データベースのセットアップには python manage.py migrate を実行します。 migrate(マイグレート)はデータベースの定義をバージョン管理するイメージです。 DjangoやChatterBotが用意したデータベースの定義を適用しています。

データベースのセットアップが終わったら python manage.py bot_train を実行しましょう。 訓練に使ったコーパスの文章がデータベースに保存され、ChatBot が検索して返せる状態になります。

発展:訓練コマンドの最終形

chat/management/commands/bot_train.py
 1from chatterbot import ChatBot
 2from chatterbot.ext.django_chatterbot import settings
 3from chatterbot.ext.django_chatterbot.models import Statement
 4from chatterbot.trainers import ChatterBotCorpusTrainer
 5from django.core.management.base import BaseCommand
 6
 7
 8class Command(BaseCommand):
 9    help = "Train chatbot with corpus"
10
11    def handle(self, *args, **kwargs):
12        statements = Statement.objects.all()
13        if statements:
14            statements.delete()
15            self.stdout.write(
16                self.style.SUCCESS("All data have been deleted.")
17            )
18
19        chatbot = ChatBot(**settings.CHATTERBOT)
20
21        trainer = ChatterBotCorpusTrainer(chatbot)
22        trainer.train("chatterbot.corpus.japanese")
23
24        self.stdout.write(self.style.SUCCESS("Training completed."))
  • 9行目の help 変数が指す文字列は python manage.py bot_train --help で表示されます
  • すでに訓練されている場合、データベースに Statement が保存されています。保存が重複しないよう、データベースに保存されたStatementをすべて削除する という実装にしています(この Statement がモデルです)

訓練したチャットボットとやり取りできるようにする

カスタムコマンドでチャットボットを訓練したら、ユーザーがやり取りできるようにしましょう。 オウム返しするチャットボットと差し替えます。

これまでにチャットボットの応答をWebアプリに表示する部分を作成済みなので、 チャットボットを差し替えるだけ です。 アプリケーションの views.py を修正します。

チャットボットが応答するようにビューを変更

my_chatbot.py でチャットボットとやり取りする部分の実装は以下のようになっています。

my_chatbot.py
19            response = chatbot.get_response(user_input)

chatbotget_response メソッドを呼んでいますね。

Webアプリでも、ビューの bot_response 関数も同様の実装にします (フォームから bot-response/ にPOSTリクエストが送られるたびに呼ばれる関数でしたね)。

参考

変更前

app/chat/views.py
1def bot_response(request):
2    if request.method == "POST":
3        form = ChatMessageForm(request.POST)
4        if form.is_valid():
5            response_message = form.data["message"]
6            http_response = HttpResponse()
7            http_response.write(response_message)
8            return http_response

オウム返しするチャットボットなので、入力されたテキストをそのまま返しています。

変更後

ChatBot インスタンスを初期化します。 ビュー関数が呼び出されるたびに初期化したくないので、関数の外側に書いています。

app/chat/views.py
from chatterbot import ChatBot
from chatterbot.ext.django_chatterbot import settings
from django.http import HttpResponse
from django.shortcuts import render

from .forms import ChatMessageForm

chatbot = ChatBot(**settings.CHATTERBOT)

入力されたテキストを get_response メソッドに渡し、その返り値をレスポンスとして返します。

app/chat/views.py
1def bot_response(request):
2    if request.method == "POST":
3        form = ChatMessageForm(request.POST)
4        if form.is_valid():
5            input_message = form.data["message"]
6            response_message = chatbot.get_response(input_message)
7            http_response = HttpResponse()
8            http_response.write(response_message)
9            return http_response

python manage.py runserver してから http://127.0.0.1:8000/ をブラウザで開くと、訓練したチャットボットとやり取りできます!!