aws chaliceとlambda(python)でalexaスキルをサックリ作る話。DialogDelegateも使うでよ。


前回書いた この記事とだいたい同じ、というか続きみたいなもんです。

(ちなみに魚へんクイズは別のクイズに作り変えて申請しました。申請通ったら記事にしよう)

で、気づいたんだけど
スキル開発者への特典の12月までに4つ以上スキル公開したらEcho Showを無料で購入できるクーポンをプレゼントってやつ、これやっぱり期間中に新たに4つって意味かなぁ。2つあるから2つ追加して4つだ、って都合よく思い込んでた。

そんなわけで、4つ作るかはわからんがもう一つ思いついたのを作っておこう。

開発言語予報

というスキルにしますよ。
プロジェクトコードは、alexa-proglangで。
まずは前回同様、プロジェクトを作ります。

1.まずgithubでレポジトリ作る(なんかローカルからだと弾かれたので)
2

$ cd !$
$ git init
$ git add remote origin [email protected]:ikegam1/alexa-proglang.git
$ git merge --allow-unrelated-histories origin/master <- git pullしたら.gitignoreがぶつかったのでmergeした
$ git add . && git commit -m 'init' && git push -u origin master
$ source ../venv/bin/activate <- 一つ上にvenv関連のがある

3

.chalice/config.jsonを少し編集。もうprodのみにしたった。

config.json
{
  "version": "2.0",
  "app_name": "alexa-proglang",
  "prod": {
      "environment_variables":
      {
          "DEBUG_MODE": "1"
      },
      "lambda_timeout": 120,
      "lambda_memory_size": 128,
      "lambda_functions": {
          "default": {}
      }
  }
}

app.pyをHelloWorld的に

app.py
from chalice import Chalice
import logging
import json
import random
import re
import os
import sys
import alexa-speech

app = Chalice(app_name='alexa-proglang')
logger = logging.getLogger()
debug = os.environ.get('DEBUG_MODE')
if debug == '1':
    logger.setLevel(logging.INFO)
else:
    logger.setLevel(logging.ERROR)


@app.lambda_function()
def default(event, context):
    return welcomeIntent()

def welcomeIntent():
    #何を聞かれてもこんにちはと返しておきます
    return alexa-speech.QuestionSpeech('こんにちは').build()

alexaへのレスポンスjson関連はvendor/alexa_speech.pyにした

alexa_speech.py
class BaseSpeech:
    def __init__(self, speech_text, should_end_session, session_attributes=None, reprompt=None):

        """
        引数:
            speech_text: Alexaに喋らせたいテキスト
            should_end_session: このやり取りでスキルを終了させる場合はTrue, 続けるならFalse
            session_attributes: 引き継ぎたいデータが入った辞書
            reprompt:
        """
        if session_attributes is None:
            session_attributes = {}

        self._response = {
            'version': '1.0',
            'sessionAttributes': session_attributes,
            'response': {
                'outputSpeech': {
                    'type': 'SSML',
                    'ssml': '<speak>'+speech_text+'</speak>'
                },
                'shouldEndSession': should_end_session,
            },
        }

        if reprompt is None:
           pass
        else:
           """リプロンプトを追加する"""
           self._response['response']['reprompt'] = {
                'outputSpeech': {
                    'type': 'SSML',
                    'ssml': '<speak>'+reprompt+'</speak>'
                }
           }

        self.speech_text = speech_text
        self.should_end_session = should_end_session
        self.session_attributes = session_attributes

    def build(self):
        return self._response


class OneSpeech(BaseSpeech):
    """1度だけ発話する(ユーザーの返事は待たず、スキル終了)"""

    def __init__(self, speech_text, session_attributes=None):
        super().__init__(speech_text, True, session_attributes)


class QuestionSpeech(BaseSpeech):
    """発話し、ユーザーの返事を待つ"""

    def __init__(self, speech_text, session_attributes=None, reprompt=None):
        super().__init__(speech_text, False, session_attributes, reprompt)

class DialogDelegate(BaseSpeech):

    def __init__(self, speech_text='', session_attributes=None, reprompt=None):
        super().__init__(speech_text, False, session_attributes, reprompt)

    def build(self):
        self._response['response'] = {
                "directives": [{
                    "type": "Dialog.Delegate"
                }]
            }
        return self._response
  1. そしてレッツdeploy.
$ chalice deploy

ちなみにalexa-speech.pyにDialogDelegateってClassを追加してあるのがポイント。

さてここまでやったら一回alexa skillの方へ。

alexa skills console

まず流れを考えよう

  1. QuestionIntent 今日の気分とかを問いかけ(5段階)
    1. StatusSlot
    2. HangrySlot
    3. EmotionSlot
    4. HopeSlot
    5. WeatherSlot

実はこんだけ。alexa上のIntentはこれだけにしたった。
もう起動したらQuestionIntentで発話集めて回答しておしまい。ちょーシンプル。

で、今回はダイアログとやらを使ってみる。

1.

2.

alexa skillの方はこんな感じ。
5つ必須スロットを作ってそれぞれに質問とサンプル発話を設定します。
でもこれだけだとまだ足りなくて、バックエンドでこのDialogを使うようにしてやる必要がある。
必須dialogが設定してあるintentの場合、requestにdialogStateというのが乗ってくるのですが、そこにdialogの状態が書いてある。状態は3つ。いや、実はもっとあるかも。でも意識してるのはこれだけです。

  • STATED
  • IN_PROGRESS
  • COMPLETE

つまり、これをバックエンドで拾って、dialog使いたい時にはやり取りをalexaにぶん投げて(delegate)やればいい。

具体的にはこんな感じ。

main.py
def onDialogState(request, intent, dialogState):
    if dialogState == 'STARTED':
        return alexa_speech.DialogDelegate().build()
    if dialogState == 'IN_PROGRESS':
        if 'value' not in intent['slots']['emotionSlot']:
            return alexa_speech.DialogDelegate().build()
        elif 'value' not in intent['slots']['hopeSlot']:
            return alexa_speech.DialogDelegate().build()
        elif 'value' not in intent['slots']['statusSlot']:
            return alexa_speech.DialogDelegate().build()
        elif 'value' not in intent['slots']['weatherSlot']:
            return alexa_speech.DialogDelegate().build()
        elif 'value' not in intent['slots']['hangrySlot']:
            return alexa_speech.DialogDelegate().build()
        else:
            return answerIntent(request, intent, intent['slots'])

DialogDelegateの中身はこの記事の上の方にあるalexa_speech.pyの中身です。
まああんまり状態は気にせずにslotが埋まってるかどうかを見てる感じ。

結果、うまくいきました。早くこれ、使ってればよかったなー。

ソースはこちら

後記

あ、なんか3つ申請したやつ全部通りました。

魚へんの漢字
国名の漢字
開発言語予報

雑なやつですんません。
あと一個でEcho Showいけるな。そっちはすでに作り始めてるのでまた記事にします。
今度はディスプレイ使わんと。