Python3
lambda
slackbot
serverless
ServerlessFramework

serverless framworkで画像認識して関係する映画を推薦するSlack Botを作ったまとめ (No Server November Challenge)


  • serverless frameworkを開発しているServerless, Inc.の企画で,No Server Novemberが開催中

  • 11月中に毎週課題が出される


    • Challenges that are designed to help experienced users level up, and brand new users get started



  • githubリポジトリのリンクを#noServerNovemberをつぶやくとなにか(official Serverless swag)もらえる?



  • Nov 12の課題であるAnimalBotとNov 19の課題であるSlack botを組み合わせて開発してみたので内容の紹介


    • Nov 12 AnimalBot: 画像URLをメンションすると写っている動物を返信するTwitter Botを作る課題

    • Nov 19 Slack bot: /actionとすると80年台アクション映画をランダムに教えてくれるSlack Botを作る課題




作ったもの


  • Slack上でBotに画像URLをメンションすると,写っている内容と,関連する映画を教えてくれるシステム


    • 画像認識にはAmazon Rekognitionを利用

    • 映画情報はThe Movie DatabaseからAPI経由で取得



animal-recog-slack-bot.png


結果


  • 猫の画像を送ると,猫が写っていることと,関連映画として魔女の宅急便を教えてくれた😺

screenshot.png


環境


  • MacOS Mojave

  • Python 3.6.5

  • Serverless Framework 1.32.0


つまづきメモ


  • [Lambdaプロキシ] POSTリクエスト本体はevent.bodyの中にStringではいる(JSONじゃない!)ので,event.body配下を再度JSONとして読み込み直す必要あり

def main(event, context):

body_str = event['body'] ## これだとただの文字列
body_json = json.loads(event['body']) ## これでJSONとして扱える
...


  • Lambda + Pythonで画像処理ライブラリPillow(PIL)を使う場合,ローカルがMac,実行環境はLinuxベースのため,ローカルでライブラリを同梱しても動作しない


    • よって,serverless-python-requirementsを用いてライブラリ管理をする (Amazon LinuxのDockerイメージを利用)

    • 関連する設定は以下




serverless.yml

...

provider:
name: aws
runtime: python3.6
...

plugins:
- serverless-python-requirements

custom:
pythonRequirements:
dockerizePip: true
...




  • Lambda上で一時的にファイルを作成したい場合は必ず/tmp配下を指定する


    • 今回はURLで指定された画像を一旦Lambdaローカルに保存し処理

    • その際,保存先は/tmp以外は不可 (権限なし)


      • OSError: [Errno 30] Read-only file system






  • Slack APIでメッセージをPostする際,画像などの付属情報を設定可能なattachmentsは,JSON内でStringとして格納しなければならない


    • つまり,attachmentsの値はjson.dumsする必要あり



# これはOK

data_correct = {
...
"attachments": json.dumps([
{
"title": movie_info['title'],
"image_url": movie_img_url
}
])
}
# これは駄目
data_err = {
....
"attachments": [
{
"title": movie_info['title'],
"image_url": movie_img_url
}
]
}


開発詳細

つくったものはGitHub - jagijagijag1/animal-recog-slack-botで公開



  1. serverless.yml, handler.pyを編集し,ひとまずBot処理を作成

  2. Slack,Movie DBで作業し,API Token取得


  3. serverless.ymlを再度編集し,Lambda環境変数にAPI Token情報


1. Serverless framework + Pythonで開始

$ sls create -t aws-python3 -p <project-name>


serverless.ymlの修正


  • 環境変数部分は後で埋めるのでひとまずブランク


serverless.yml

service: animal-recog-slack-bot

provider:
name: aws
runtime: python3.6
region: ap-northeast-1
iamRoleStatements:
- Effect: "Allow"
Action:
- "rekognition:DetectLabels"
Resource: "*"

plugins:
- serverless-python-requirements

custom:
pythonRequirements:
dockerizePip: true

functions:
hello:
handler: handler.main
events:
- http:
path: /
method: POST
environment:
OAUTH_TOKEN: ''
BOT_TOKEN: ''
MOVIE_DB_API_TOKEN: ''
timeout: 20



関数本体を作成


  • やや長いのでソースコードはこちら参照


  • 画像はURLで受付,一旦Lambda関数ローカルに保存し,RekognitionにByteとして受け渡す


  • Rekognitionではかなり一般的な単語(e.g. Animal, Pet)をラベル候補の上位に出してくるため,暫定処理としてNGワード(ignore_word)を設定し回避



  • 関連映画を取得する処理の概要は以下


    • Rekognitionのラベル検出結果から一語を選択

    • 選択した後がMovie DBでキーワード登録されているか確認(API: /search/keyword)し,登録ありの場合はID取得 (なしの場合は終了)

    • 獲得したキーワードIDを用いて映画検索(API: /discover/movie?with_keyword=)

    • 検索結果からランダムに選択した映画をSlackに返す




デプロイ

$ sls plugin install -n serverless-python-requirements

$ docker pull lambci``/lambda``:build-python3.6
$ sls deploy -v


2. Slack,Movie DBで作業し,API Token取得


Slack app準備



  • Building Slack apps | SlackでCreate

  • その後の画面で左側メニューの"Bot User"を選び,Bot Userを追加

  • 左メニュー"Event Subscriptions"からイベントを有効にし,"Request URL"にAPI GatewayのURLを指定し,challengeが帰ってくることを確認 (Lambdaコードに処理を埋め込み済)

  • challenge成功後,"Subscribe to Bot Events"で"app_mention"イベントを追加

  • 左メニュー"Installed App"からアプリをWorkspaceにインストール

  • 左メニュ「OAuth & Permissions」にてWorkspaceにAppをInstallすると"OAuth Access Token"と"Bot User OAuth Access Token"が発行される (あとでserverless.ymlに追記)


Movie DB準備


  • 登録し,Settingから申請可能



  • 申請完了後もSetting->APIでAPIキーを確認可能


    • 本アプリではv3 auth利用




3. serverless.ymlを再度編集し,Lambda環境変数にAPI Token情報を追記


  • OAUTH_TOKEN: SlackのOAuth Access Token

  • BOT_TOKEN: SlackのBot User OAuth Access Token

  • MOVIE_DB_API_TOKEN: The Movie DatabaseのAPIキー (v3 auth)


serverless.yml

...

functions:
hello:
...
environment:
OAUTH_TOKEN: <your-token>
BOT_TOKEN: <your-token>
MOVIE_DB_API_TOKEN: <your-token>
...



参考