チャットボットとやり取りできるようにする¶
今回の位置づけ¶
前回までの復習¶
初回¶
- チャットボット=自動応答するプログラム(チャットボットの概要)
- ライブラリ
chatterbot
を使ったPythonスクリプト
前回¶
- チャットボットをWebアプリとして動かすための 準備
- Djangoを使って手元のPCで動くアプリケーションを開発
- チャットボットと会話できるWebアプリ
Djangoの設定
- プロジェクトとアプリケーション(プロジェクト/アプリケーション作成)
- URL設定、ビュー、テンプレート(HTMLを表示する)
ここまでの実装内容¶
- URL設定:http://127.0.0.1:8000/ (パスが
""
(空文字列))へのリクエストに対しては、ビューのhome
(関数)を呼び出す - ビュー:
home
関数は、テンプレートchat/home.html
を含んだレスポンスを返す - テンプレート:空のHTMLファイルを置いただけ
chat/home.html
にHTMLを書くと、ブラウザに表示できます
今回やること¶
表示したHTML上で、 チャットボットとやり取りできる ようにしていきます。
前回の開発の続きができるように準備¶
$ cd ~/programming/chatbot
$ source env/bin/activate
$ cd app
$ python manage.py runserver
注釈
今回からの方向け
前回の状態のファイルをzipにまとめて配布しています。
ダウンロードし、解凍してできた app
ディレクトリを ~/programming/chatbot
以下に置いてください。
$ cd ~/programming/chatbot # チャットボットの講義用ディレクトリ
$ python3.7 -m venv env # 仮想環境をまだ作っていない場合は作る
$ source env/bin/activate
$ pip install 'Django<4' # 仮想環境にDjangoをインストール
$ cd app # ダウンロードして解凍したディレクトリを ~/programming/chatbot/app として配置済み
$ python manage.py runserver
http://127.0.0.1:8000/ をブラウザで開くと、真っ白い画面が表示される状態から始めます(前回はエラーを解決しながら設定しました)
開発tips:VSCodeの別のタブで runserver しておく
チャットボットとやり取りできる画面を作る¶
http://127.0.0.1:8000/ で表示される画面にメッセージ入力欄を作ります。
メッセージ入力欄はHTMLの form
タグで実装します。
https://developer.mozilla.org/ja/docs/Web/HTML/Element/form
HTMLを書いてもいいのですが、Djangoがサポートしているフォーム(Djangoフォーム)を使います。 Djangoの流儀に沿ってPythonを書けば、HTMLのフォームができあがります。 (個人的な経験ですが、フォームのためにテンプレートに長くHTMLを書いたあとで、DjangoフォームにしておけばPythonで済ませられたことに気づき、Djangoフォームを選んでいればもっと開発しやすかったなと感じました)
フォーム¶
chat
アプリケーションに forms.py
を作ります。
touch chat/forms.py
1from django import forms
2
3
4class ChatMessageForm(forms.Form):
5 message = forms.CharField(required=True, max_length=100)
Djangoが提供する Form
クラスを継承して、 ChatMessageForm
クラスを定義します(4行目)。
フォームの入力欄はテキストが入力できる CharField
のみとします(入力必須で、100文字まで)(5行目)。
https://docs.djangoproject.com/ja/3.2/ref/forms/fields/#charfield
テンプレートにフォームを表示¶
- ビューでフォームのインスタンスを作り、テンプレートに渡します
- テンプレートはフォームを表示します
ビュー¶
1from .forms import ChatMessageForm
2
3
4def home(request):
5 form = ChatMessageForm()
6 context = {"form": form}
7 return render(request, "chat/home.html", context)
chat/forms.py
に作った ChatMessageForm
をインスタンス化します(5行目)。
render
の第3引数に辞書(context
という名前にしました)を渡します(7行目)。
context
はビューの form
(ChatMessageForm
インスタンス)を form
という名前で渡します(6行目)。
テンプレートとビューとで異なる名前を設定することもできますが、混乱を避けるため、私は揃えることが多いです。
テンプレート¶
空っぽの chat/home.html
を以下のようにします。
1<!DOCTYPE html>
2<html lang="ja">
3 <head>
4 <meta charset="UTF-8" />
5 <meta
6 name="viewport"
7 content="width=device-width, initial-scale=1, shrink-to-fit=no"
8 />
9 <title>Chatbot app</title>
10 </head>
11 <body>
12 <h1>Talk with chatbot</h1>
13
14 <form method="POST">
15 {% csrf_token %} {{ form.as_p }}
16 <button type="submit">Send</button>
17 </form>
18 </body>
19</html>
<body>
の部分に注目してください(<head>
については説明を省略します)。
{{ form }}
とすればHTMLにメッセージ入力欄が表示されます(テンプレートに変数を埋め込む書き方です)。
Djangoフォームではinputができるので、<form>
タグと <button>
タグは書く必要があります。
注釈
参考情報
- 理解して使いこなすDjangoのForm機能
- Django Congress JP 2021 の発表資料です(YouTubeにアーカイブもあります)
- Djangoフォームについて詳細に説明しています
- 0章、Django shellでフォームを扱うのはわかりやすいと思いました(ここだけ確認、オススメです!)
- 現場で使える Django のセキュリティ対策
- Django Congress JP 2019 の発表資料です(YouTubeにアーカイブもあります)
{% csrf_token %}
がなぜ必要なのかも解説もあります- クロスサイトリクエストフォージェリ(=CSRF)への対策のためなのですが、詳しくはスライド63〜を見てみてください
オウム返しするチャットボットを作る¶
フォームを表示できるようになりました! しかし、「こんにちは」のように入力して Send をクリックすると、入力した文字が消えます。 今の設定ではこれは正常な挙動です。
チャットボットが応答するURLを用意する¶
チャットボットが応答するようにしましょう。 単純なチャットボットとして、オウム返しするチャットボットで考えます。
- 追加するURL:http://127.0.0.1:8000/bot-response/
- URL設定を追加する
- フォームの
action
属性に設定(入力をチャットボットに送る)
- フォーム入力されたデータの POST リクエストに対して、同じ
message
を返す- ビューに関数を追加する
URL設定¶
chat/urls.py
の urlpatterns
が指すリストに path
の行を1つ追加します。
1from django.urls import path
2
3from chat import views
4
5app_name = "chat"
6
7urlpatterns = [
8 path("", views.home, name="home"),
9 path("bot-response/", views.bot_response, name="bot_response"),
10]
chat/views.py
に bot_response
という関数がまだないのでエラーとなり、サーバが起動しません。
ヒント
urlpatterns
の指す値は リスト なので、末尾のカンマが落ちていると SyntaxError
が送出されます。
urlpatterns = [
path("", views.home, name="home") # 末尾のカンマ忘れに注意!
path("bot-response/", views.bot_response, name="bot_response"),
]
ビュー¶
エラーを解消するための実装¶
bot_response
関数を作ります。
ビューの関数は引数に request
を受け取り、Djangoの HttpResponse
を返します。
from django.http import HttpResponse # importの行に追加
def bot_response(request):
response = HttpResponse()
response.write("レスポンスです")
return response
HttpResponse
の write
メソッドでレスポンスの内容を書き込めます:
https://docs.djangoproject.com/ja/3.2/ref/request-response/#django.http.HttpResponse.write
ヒント
ここでフォームの action
属性を設定すると、Sendしたあと「レスポンスです」とブラウザに表示されます。
<form method="POST" action="http://127.0.0.1:8000/bot-response/">
のちほど設定について案内します(もっといい設定方法があります)。
注釈
home
関数で呼び出している render
も実は HttpResponse
を返しています。
https://docs.djangoproject.com/ja/3.2/intro/tutorial03/#a-shortcut-render
オウム返しするための実装¶
引数 request
には フォームから送信されたデータが含まれる ので、それを取り出して、レスポンスに含めます。
この実装により、決め打ちのレスポンスからオウム返しのレスポンスに変わります。
from django.http import HttpResponse
from django.shortcuts import render
from .forms import ChatMessageForm
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
request.method
でPOSTリクエストかどうか判定します(2行目)。
フォームから送信するとPOSTリクエストになります(method="POST"
と指定しているためです)。
POSTリクエストでは、request.POST
にフォームから送信されたデータがあります。
これを渡して ChatMessageForm
のインスタンスを作ります(3行目)。
4行目はフォームから送信されたデータの検証です。
検証がパスした場合、 data
属性(辞書)のキー "message"
の値を取得します(5行目)。
これがフォームに入力されたメッセージです。
Djangoフォームで message = ...
と指定したので、キーも "message"
となります。
注釈
request
についてドキュメントより
request.POST は辞書のようなオブジェクトです。
https://docs.djangoproject.com/ja/3.2/intro/tutorial04/#write-a-minimal-form
テンプレート¶
<form>
タグの action
属性を指定します。
<form method="POST" action="{% url 'chat:bot_response' %}">
{% csrf_token %} {{ form.as_p }}
<button type="submit">Send</button>
</form>
{% url %}
はDjangoのテンプレートで使えるタグの1つです。
第1引数は、URL設定にある name
を指定します。
'chat:bot_response'
は '<app_name>:<name>'
という形式です。
chat アプリケーションの name="bot_response"
というURL設定が見つかります。
結果として、 action
属性に http://127.0.0.1:8000/bot-response/ が設定されます。
注釈
なぜ {% url %}
を使うのか?
ずばり、変更しやすくする ためです。
URLを直接書く(ハードコードする)と、パスの文字列の変更(例 "bot-response/"
→ "response/"
)に追従させるのが大変です。
{% url %}
タグで name
から逆引きすることで、パスの文字列を変えやすくしています。
このハードコードされた、密結合のアプローチの問題は、プロジェクトにテンプレートが多数ある場合、URLの変更が困難になってしまうことです。
https://docs.djangoproject.com/ja/3.2/intro/tutorial03/#removing-hardcoded-urls-in-templates
以上で、フォームからメッセージを送ると、画面遷移し、オウム返しされたメッセージが表示されるようになりました!
画面遷移なしでチャットボットとやり取りできる¶
フォームを送信すると、チャットボットはオウム返ししますが、画面繊維を伴います。 フォームがある画面にチャットボットのやり取りを出し、チャットが蓄積するようにします。
画面遷移なしにするには Ajax (Asynchronous JavaScript And XML)を使います。
テンプレートの chat/home.html
にJavaScriptを書いていきます。
今回は jQuery
というライブラリを使用してAjaxによる通信を組み込みます。
(現在のJavaScript周りの状況を見ると jQuery
は下火ですが、学習コストは小さいので今回採用しています。VueやReactでやってもらってもかまいません)
使うメソッドについては次のメモにまとめました: https://scrapbox.io/nikkie-memos/jQuery%E3%83%A1%E3%83%A2%EF%BC%88%E3%83%81%E3%83%A3%E3%83%83%E3%83%88%E3%83%9C%E3%83%83%E3%83%88Django%E3%82%A2%E3%83%97%E3%83%AA%EF%BC%89
フォームが送信されたときの処理を指定する¶
テンプレートに <script>
タグを書いていきます。
HTMLの <body>
タグの末尾に書きます。
<!DOCTYPE html>
<html lang="ja">
<head>
<!-- 省略 -->
</head>
<body>
<h1>Talk with chatbot</h1>
<form method="POST">
{% csrf_token %} {{ form.as_p }}
<button type="submit">Send</button>
</form>
</body>
<!-- ここに script タグを書いていきます -->
</html>
まずは以下のように書いてください。
1<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
2<script>
3 $("form").submit(function (event) {
4 event.preventDefault();
5 const form = $(this);
6
7 console.log(form.prop("action"));
8 console.log(form.prop("method"));
9 console.log(form.serialize());
10 });
11</script>
ライブラリ jQuery
をCDN(Contents Delivery Network)から読み込みます(1行目)。
ドキュメント https://jquery.com/download/#using-jquery-with-a-cdn の中からGoogleのAPIを選びました。
この行により jQuery
のメソッドが使えるようになります。
3行目で、フォームの送信というイベントについて処理する関数(ハンドラ)を登録します。
4行目 event.preventDefault()
で画面遷移というフォームのデフォルトの挙動を無効化しています。
ここではフォームの属性の値やフォームに入力される値の取得の仕方を確認するために、ログ出力しました(5行目〜)。
ブラウザで http://127.0.0.1:8000/ を開き、開発者ツールのコンソールを開いてから、メッセージを送信してみてください。
preventDefault
の効果で 画面は切り替わらず に、ログが出力されます。
form.prop("action") |
http://127.0.0.1:8000/bot-response/ |
form.prop("method") |
post |
form.serialize() |
csrfmiddlewaretoken=省略&message=hello%20world |
form.serialize()
は日本語をエンコードします(ブラウザのURL欄の日本語と同じです)。
フォームが送信されたときにAjaxで通信する¶
上で確認した項目を使い、チャットボットが応答するURLにAjaxで通信します。
1<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
2<script>
3 $("form").submit(function (event) {
4 event.preventDefault();
5 const form = $(this);
6
7 $.ajax({
8 url: form.prop("action"),
9 type: form.prop("method"),
10 data: form.serialize(),
11 dataType: "text",
12 })
13 .done(function (statement) {
14 console.log(statement);
15 });
16</script>
フォームの action
属性のURLに、method
属性のHTTPメソッドで、フォームに入力されたデータを送ります。
成功した場合、done
イベントが発火し、登録されているハンドラにより返ってきたレスポンスをコンソールに出力します(14行目)。
ブラウザで http://127.0.0.1:8000/ を開き、開発者ツールのコンソールを開いてから、メッセージを送信します。 送ったのと同じメッセージがコンソールに表示されます。
コンソールを使って、ユーザとチャットボットがやり取りしているようにログ出力してみましょう。
1<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
2<script>
3 $("form").submit(function (event) {
4 event.preventDefault();
5 const form = $(this);
6
7 $.ajax({
8 url: form.prop("action"),
9 type: form.prop("method"),
10 data: form.serialize(),
11 dataType: "text",
12 })
13 .done(function (statement) {
14 const input = $("#id_message");
15 console.log("You -> " + input.val());
16 console.log("bot: " + statement);
17 input.val("");
18 });
19</script>
id id_message
はDjangoにより、メッセージ入力の input
要素に設定されています
(フォームのクラス定義の中で message
という名前にしたので、 id_message
となりました)。
input
要素には id
が指定されている¶<input type="text" name="message" maxlength="100" required="" id="id_message">
IDを指定して、この input
要素を jQuery
で取得します。
val
は input
要素のvalue(入力された文字列)です。
ユーザの入力をログ出力し、チャットボットのレスポンスも出力したあとで、フォームの入力を空文字列とし(17行目)、続けてやり取りできるようにします。
フォームが送信されたあと、画面にチャットを表示する¶
ログ出力したフォーマットで画面に出力しましょう。
まず、フォームの上に、チャットの履歴を表示 するための <div>
要素を追加します。
<h1>Talk with chatbot</h1>
<div id="chat-log"></div>
<form method="POST" action="{% url 'chat:bot_response' %}">
<!-- 以下、省略 -->
Ajaxの通信が成功したときにチャットの履歴を表示する(画面に書き足す)ようにします。
1<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
2<script>
3 const chatLog = $("#chat-log");
4
5 function createRow(text) {
6 const row = $("<p></p>");
7 row.text(text);
8 chatLog.append(row);
9 }
10
11 $("form").submit(function (event) {
12 event.preventDefault();
13 const form = $(this);
14
15 $.ajax({
16 url: form.prop("action"),
17 type: form.prop("method"),
18 data: form.serialize(),
19 dataType: "text",
20 })
21 .done(function (statement) {
22 const input = $("#id_message");
23 createRow("You -> " + input.val());
24 createRow("bot: " + statement);
25 input.val("");
26 });
27 });
28</script>
画面の表示を変えるために createRow
関数を作りました。
これは id=chat-log
の要素(=上で加えた <div>
要素)の中に、 <p>
要素を追加します。
<p>
の文字列は createRow
関数の引数です。
jQuery
を使って <p>
タグを作って、 <div>
要素の中に 付け足し ています。
Ajaxが成功したときのハンドラは、console.log
から createRow
関数に差し替えました(22, 23行目)。
ブラウザで http://127.0.0.1:8000/ を開くと、オウム返しするチャットボットとやり取りできます!!
最終形¶
chat/home.html
は最終的には以下のようになります。
変更箇所を優先して示したため、一部のタグ(<title>
など)は省略しています。
1 <head>
2 <style>
3 .log-window {
4 max-width: 500px;
5 max-height: 300px;
6 overflow-y: scroll;
7 }
8 </style>
9 </head>
10 <body>
11 <h1>Talk with chatbot</h1>
12 <div id="chat-log" class="log-window"></div>
13
14 <form method="POST" action="{% url 'chat:bot_response' %}">
15 {% csrf_token %} {{ form.as_p }}
16 <button type="submit">Send</button>
17 </form>
18
19 <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
20 <script>
21 const chatLog = $("#chat-log");
22
23 function createRow(text) {
24 const row = $("<p></p>");
25 row.text(text);
26 chatLog.append(row);
27 }
28
29 $("form").submit(function (event) {
30 event.preventDefault();
31 const form = $(this);
32
33 $.ajax({
34 url: form.prop("action"),
35 type: form.prop("method"),
36 data: form.serialize(),
37 dataType: "text",
38 })
39 .done(function (statement) {
40 const input = $("#id_message");
41 createRow("You -> " + input.val());
42 createRow("bot: " + statement);
43 input.val("");
44 chatLog[0].scrollTop = chatLog[0].scrollHeight;
45 })
46 .fail(function () {
47 window.alert("もう一度やり直してください");
48 });
49 });
50 </script>
51 </body>
(1)フォームの上のチャット履歴表示部分は、高さを決めて自動でスクロールするようにしました。
12行目でclassを設定し、 <style>
タグで最大の高さと、それを超えたときにスクロールするように設定します(2〜8行目)。
Ajax通信が成功したときのイベントハンドラに44行目を追加し、チャット履歴は常に一番下までスクロールした状態(=最新のメッセージとそれへのボットの応答が見える状態)としています。
(2)Ajax通信が失敗するときもあるので、 fail
イベントのハンドラを登録しました(46〜48行目)。
以上で、単純なチャットボットとやり取りするWebアプリが手元のPCで動くようになりました!
このあとはチャットボットをより賢くしていきます (初回で導入した chatterbot
ライブラリを使います)。
Djangoについてはあまり触らず、チャットボットを賢くすることにフォーカスしていきます。