週末文書

とりあえず、まぁ、週末です。

my first Flask app: 相づちくん

FlaskのTutorialを一通り追ったので、tutorialのアプリケーションを参考にして、Webアプリを一個作ってみることにしました。

とりあえずは、以前作っておいた「とにかく肯定的なあいづちを返す」人工無脳=相づちくんをFlaskでWeb化してみます。

f:id:medihen:20210723152943p:plain
Flaskのアイコン

作業は下の順番で進めることにしました。

なかなか迂遠ですが、Flaskも初めてなら、人工無脳的Webアプリ(つまりチャット的Webアプリ)というものをどう作るのかプログラミングレベルで考えたこともないので、ステップ・バイ・ステップでいくことにします。しかし、そのステップ分けが妥当かどうかも判断つかないレベルなので、う〜むという感じですが。

1. hello worldを表示

このステップは、プログラム自体はFlask Tutorialのものを使うことにして、自分のアプリ環境を整備するのがメインとなります。要するにディレクトリのセットアップということで、下記のmyapp以下のディレクトリ構成を作成しました。

flask
├── bin
├── include
├── lib
├── myapp
│   └── eyezoochi
│       └── templates
├── tutorial
│   └── flaskr
│       └── templates
└── var

flaskがflask一式をインストールしたディレクトリで、その下にmyappをいうディレクトリを作り、さらにその中にeyezoochi、その下にtemplatesディレクトリを作ります。

  • myapp
    • 自分で作成したwebアプリのプログラムを置く。flaskの実行もこのディレクトリで行う。
  • eyezoochi
  • templates
    • htmlファイルを生成するためのテンプレートを置く。

このmyapp/eyezoochiの下に下記の内容の__init__.py = Flask Tutorialで作ったHello Wolrdを置きました。

from flask import Flask

def create_app(test_config=None):
    # create and configure the App
    app = Flask(__name__, instance_relative_config=True)
    app.config.from_mapping(
        SECRET_KEY='dev'
    )

    @app.route("/")
    def hello_world():
        return "<p>Hello, World!</p>"

    return app

続いて下記のシェル上のコマンドで環境変数設定(Flaskにアプリの名前を指定する)した後、flaskを起動、http://127.0.0.1:5000/アクセスすると、無事、Hello, Worldのメッセージが表示されました。

$ export FLASK_APP=eyezoochi
$ export FLASK_ENV=development
$ flask run

2. 入力された文をそのまま表示

第2ステップ では、人工無脳の基本中の基本、『入力されたテキストをそのまま返す」プログラムを作ってみました。

__init__.py に下記2行を追加した後、別ファイルのeyezoochi.pyの中に、入力の受付と返信の作成を行う部分を作成しました。Flask特有のプログラムモジュールの分割機構であるblueprintとして、eyezoochi.pyを登録するということになります。

    from . import eyezoochi
    app.register_blueprint(eyezoochi.bp)

eyezoochi.pyの中は下記の通りとしました。
Tutorialのコードを使っているので余計なモジュールをimportしていますが、後日使うかもしれないので備えてそのままにしておきます。

import functools

from flask import (
    Blueprint, flash, g, redirect, render_template, request, session, url_for
)

bp = Blueprint('eyezoochi', __name__, url_prefix='/eyezoochi')

@bp.route('/What', methods=('GET', 'POST'))
def what():
    input_text=''
    if request.method == 'POST':
        input_text= request.form['input_text']
  
    return render_template('what.html', output_text=input_text)

『入力されたテキストをそのまま帰す」用のテンプレートは下記の通りです。

{% extends 'base.html' %}

{% block header %}
  <h1>{% block title %}What?{% endblock %}</h1>
{% endblock %}

{% block content %}
  <form method="post">
    {% if output_text %}
      <label for="input_text"`>あなたが入力したのは</label>
      <input type="text" value="" placeholder="{{ output_text }}" >
    {% else %}
    {% endif %}
    <br>
    <label for="input_text"`>何か書いて</label>
    <input name="input_text" id="input_text" required>
    <input type="submit" value="入力">
  </form>
{% endblock %}

頭で読み込んでいるbase.htmlは、テンプレート間で共通の要素を規定するものです。base.htmlは、Flask Tutorialのテンプレートから不要なものを差っ引いたものを使っています。

これらのファイルをディレクトリに配置後、Flaskを再起動して、次のようなスクリーンで動作を確認しました。(実際は細々したエラーがいろいろあり、修正に追われました^^;)

f:id:medihen:20210808185417p:plain
「入力をそのまま返す」Webアプリの画面

3. 「とにかく肯定的なあいづちを返す」人工無能との結合

次は既存の人工無脳コードとの結合ですが、ここまで来ると、Pythonプログラムの部分はあまり迷わないで済みました。pythonコードについては、前のステップで作成したeyezoochi.pyに下記を追加するだけです。

from .unmo import Unmo

@bp.route('/', methods=('GET', 'POST'))
def eyezoochi():
    input_text=''
    response=''
    if request.method == 'POST':
        proto = Unmo('proto')
        input_text= request.form['input_text']
        response = proto.dialogue(input_text)

    return render_template('eyezoochi.html',
        previous_text=input_text, output_text=response)

importしているUnmoは、Python初心者に送る「人工知能の作り方」 - すなぶろから借用している人工無脳のクラスです。この「相づちくん」については、人工無脳Unmoの「ファイルから読み込んだテキストからランダム選んだ1行を返す」という部分を使わせてもらっています。

このプログラムを使ってFlaskを起動したところ、なんとなく「肯定的なあいづちを返す」人工無脳らしきものができました。

f:id:medihen:20210808185746p:plain
「相づちくん」とりあえず版の画面

Webアプリのプログラム中で読み込むファイルをどこに置くか

このコードでは、辞書ファイル(相づちのテキストを登録したもの)を読み込むのですが、
そのファイルをどこに置くかで少々悩みました。考えてみれば当たり前な話で、flaskを起動するmyappディレクトリがワーキングディレクトリとなりますので、ここを起点に読み込むファイルを配置することになります。当初は、webアプリのコードを置くeyezoochiディレクトリが起点と思ってしまったため戸惑いました。

4. 連続して入出力結果を表示するチャット的な表示

5. デザインを改善(Bootstrapを利用してみる)

と、ここまで来たところで、本日はひとまず終了。
4.と5.については、別記事としたいと思います。