My Alexa の知見5 ASK(Alexa Skills kit) Intent と Slots と Utterance

# はじめに
この投稿は私がラズパイで自作Alexaを開発した知見を投稿するシリーズです。

----

# 今回のテーマ
今回のテーマはASKで避けては通れない Intent, Slots, Utterance について、少し突っ込んだお話です。

----

# 前提
ASKについてざっくりと理解している。（出てくるワードがざっくりどういうものか理解できる程度です）

----

# 超概要
## スキルの種類
<img width="684" alt="スクリーンショット 2017-02-22 9.16.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/49986/be7f208c-8ca6-b1e1-28b7-872c0f29db62.png">

### Custom Skill
今回のテーマです。ユーザーの意図(Intent)とキーワード(Slots)を定義して作るスキルです。

### Smart Home Skill
デバイスに対してリクエストを送るためのスキルです。（「電気つけて」など）

### Flash Briefing Skill API
ユーザーのキーワードにあったニュースや記事を読み上げるスキルです。

他にも有益な記事がたくさんありますので、参考にしてください。

[Alexa Skills Kit の勘所](http://qiita.com/sawanoboly/items/0981502ab66e975f64a6)
[Alexa Skill を公開する流れ](http://qiita.com/sparkgene/items/ac3d14f1e8815ce8802a)
[Smart Home Skillsを使ってみる](http://qiita.com/sparkgene/items/055d7864c92a80b0c040])
[Amazon AlexaにJAWS-UGのイベントを教えてもらう](http://qiita.com/sparkgene/items/a7ae9dca006b3e4e0a8f)

----

##Intent
ユーザーの発話に対する意図を定義します。以下のようなjsonです。

<img width="1042" alt="スクリーンショット 2017-02-22 9.14.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/49986/7f7693d4-2f23-7119-798a-c56b227a624f.png">

```json
{
  "intents": [
    {
      "intent": "AppointmentIntent"
    },
    {
      "intent": "NotifyIntent"
    },
    {
      "intent": "DestinationIntent",
      "slots":[
        {
          "name":"Event",
          "type":"AMAZON.EventType"
        },
        {
          "name":"CustomEvent",
          "type":"CUSTOM_EVENTS"
        },
        {
          "name":"City",
          "type":"AMAZON.US_CITY"
        },
        {
          "name":"Date",
          "type":"AMAZON.DATE"
        },
        {
          "name":"Duration",
          "type":"AMAZON.DURATION"
        }
      ]
    },
    {
      "intent": "NotifyCancelIntent"
    },
    {
      "intent": "TurnLightsIntent"
    },
    {
      "intent": "AMAZON.YesIntent"
    },
    {
      "intent": "AMAZON.NoIntent"
    },
    {
      "intent": "AMAZON.StopIntent"
    },
    {
      "intent": "AMAZON.HelpIntent"
    }
  ]
}
```

----

##Slots
Intent に対して、期待するワード（キーワードとして採取したいワード）を定義します。developer.amazon.comのAlexa Skill のGUIで定義します。

<img width="858" alt="スクリーンショット 2017-02-22 9.30.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/49986/f9dc85fa-ff05-eb8b-7717-53ebd791a5e9.png">

----

##Utterance
各Intentに対して、ユーザーの発話パターンを記載します。

<img width="833" alt="スクリーンショット 2017-02-22 9.15.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/49986/3f7b245a-ea4d-ebd5-00d5-d25ce3a9d476.png">

```js
AppointmentIntent Can I get the appointment some hotel?
AppointmentIntent I want to get the appointment hotel.
AppointmentIntent book any hotel
AppointmentIntent book pleese

NotifyIntent notify
NotifyIntent notify please
NotifyIntent inform
NotifyIntent inform please
NotifyIntent please inform

NotifyCancelIntent cancel notification
NotifyCancelIntent cancel notification please
NotifyCancelIntent please cancel notification
NotifyCancelIntent cancel informing
NotifyCancelIntent cancel informing please
NotifyCancelIntent please cancel informing

DestinationIntent I have a {Event} on {Date} for {Duration}
DestinationIntent I have a {CustomEvent} on {Date} for {Duration}
DestinationIntent I have a {Event} on {Date} for a {Duration}
DestinationIntent I have a {CustomEvent} on {Date} for a {Duration}
DestinationIntent I will go to {City} for {Event} for {Duration}
DestinationIntent I will go to {City} for {CustomEvent} for {Duration}
DestinationIntent I will go to {City} for {Event} for a {Duration}
DestinationIntent Yes I will go to {City} for {Event} for a {Duration}
DestinationIntent I will go to {City} on {Date} for a {Duration}
DestinationIntent Yes I will go to {City} on {Date} for a {Duration}
DestinationIntent I will go to {City}
DestinationIntent I will go to {City} for {Duration}
DestinationIntent for {Event} I will go to {City} on {Date}
DestinationIntent for {CustomEvent} I will go to {City} on {Date}

TurnLightsIntent turn off please
TurnLightsIntent please turn off
TurnLightsIntent turn off
TurnLightsIntent turn off
TurnLightsIntent turn off the light please
TurnLightsIntent please turn off the light
TurnLightsIntent turn off the light
TurnLightsIntent set off to the light

AMAZON.StopIntent good bye
AMAZON.StopIntent bye bye
AMAZON.StopIntent bye
AMAZON.StopIntent thank you bye
AMAZON.StopIntent okay good bye
AMAZON.StopIntent okay bye

```

----

# ポイント４つ
* Built in Intentを拡張する
* Built in Slotsを拡張する
* AMAZON.DATE, AMAZON.DURATIONの活用
* AMAZON.YesIntent, AMAZON.NoIntent, AMAZON.StopIntent

----

## Built in Intentを拡張
ASK では、一般的な意図を採取するための Built in Intentが定義できます。

[Built in Intent](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/implementing-the-built-in-intents)

このBuilt in Intent は、Utteranceで拡張できます。

[Extending Built in Intents](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/implementing-the-built-in-intents#extending)

**AMAZON.StopIntentを拡張する例**

<img width="855" alt="スクリーンショット 2017-02-22 15.36.46.png" src="https://qiita-image-store.s3.amazonaws.com/0/49986/cfa9225f-f137-5661-2ed0-20813def2fb8.png">

特定の言葉で特定のIntentに入って欲しい時は、Built in Intentsの拡張を検討します。

----

## Built in Slotsを拡張する
Built In Intents 同様 Slotsにも Built in されたものがあり、こちらも拡張できます。
ただし、Built in Slots には、拡張できるものとできないものがあります。

[拡張可能なスロットタイプ](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/built-in-intent-ref/slot-type-reference#h2_extend_types)

[Slot Type Reference](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/built-in-intent-ref/slot-type-reference)

**AMAZON.EUROPE_CITYを拡張する例**

<img width="855" alt="スクリーンショット 2017-02-22 15.42.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/49986/899e510a-92ac-2420-67a2-15c118ff6907.png">

----

## AMAZON.DATE と AMAZON.DURATION Slot Type.

### 例
AMAZON.DATE と AMAZON.DURATION を利用して、ユーザーの旅行への出発日、滞在期間、目的地、目的を採取するフレーズを考えます。

英語だと以下のようになるでしょう。

> I will go to Washington.D.C for business trip on this weekend for 6 days.

このときの Intent はこのようになります。

> DestinationIntent I will go to {City} for {Event} on {Date} for {Duration}

それぞれのスロットの意味です。

- {City} に目的地
- {Event} に目的
- {Date}に出発日
- {Duration}に滞在期間

IntentSchema に、どのスロットにどんなワードが入ってほしいかを定義します。

```json
:
    {
      "intent": "DestinationIntent",
      "slots":[
        {
          "name":"Event",
          "type":"AMAZON.EventType"
        },
        {
          "name":"CustomEvent",
          "type":"CUSTOM_EVENTS"
        },
        {
          "name":"City",
          "type":"AMAZON.US_CITY"
        },
        {
          "name":"Date",
          "type":"AMAZON.DATE"
        },
        {
          "name":"Duration",
          "type":"AMAZON.DURATION"
        }
      ]
    },
:
```

----

### AMAZON.DATE
AMAZON.DATE は、検知した日付を[ISO-8601 date format. ](https://en.wikipedia.org/wiki/ISO_8601#Dates) の形に変換してスキルに伝えます。

例文の "this weekend" は "2015-W48-WE" という形でスキル側に伝えられます。
**2015年 第４８週目の週末** という意味です。


例えば、こんな会話のパターンがあったとします。

> ユーザー: "I will go to Washington.D.C for business trip on this weekend for 6 days."
>
> Alexa : "OK. I have scheduled and register to your calendar. on 2017.02.22. for 6 days. you will go to Washington.D.C for business trip."


このとき、スキル側でやることは２つ。

1. 2017-W48-WE"を再変換して、発話の形に修正する。
2017-W48-WE ー＞ 2017-１２-0１

2. SSML で 日付を話させる。
"<say-as interpret-as=\"date\">2017-12-01</say-as>."

Alexa は、**December.1, 2017** と話してくれます。

**余談**
1.の変換にpythonが持つ日付のライブラリが対応してないので、自前で頑張しかありません。
（よい方法をおしえてください。）

[AMAZON.DATE](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/built-in-intent-ref/slot-type-reference#date)

----

### AMAZON.DURATION
さきほどのAMAZON.DATEの例で、"for 6 days"と alexaが応答することを考えます。

AMAZON.DURATION は、検知した日付を[ISO-8601 date format. ](https://en.wikipedia.org/wiki/ISO_8601#Durations) の形に変換してスキルに伝えます。

例文の "for 6 days" は "P6D" という形でスキル側に伝えられます。
**P は接頭辞。6D が６日間という意味です。 **

こちらも、AMAZON.DATEと同様に、変換して話させます。

1. 2017-W48-WE"を再変換して、発話の形に修正する。
P６D ー＞ for 6 days

2. 日付を話させる。
今の場合は、for 6 days とそのまま発話させることができるので、SSMLでなくてもかまいません。

[AMAZON.DURATION](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/built-in-intent-ref/slot-type-reference#duration)

----

## AMAZON.YesIntent, AMAZON.NoIntent, AMAZON.StopIntent

### 例
こちらも、こんな会話設計を考えます。

> user  : Ask booking
> alexa : Hi. where you go? 
> user  : Boston.
> alexa : OK. There are many hotels in Boston. Would you select highest one?
> user  : Yes, please.
> alexa : OK. Are you pay now?
> user  : Yes, please.


ここで、５行目と７行目の "Yes, please" は意味が異なります。

５行目の意図： 「一番高いホテルを選ぶよ」
７行目の意図： 「今ホテル代支払っとくよ」

この場合、スキル側では、以下のようにIntentを作ります。

```json
:
    {
      "intent": "SearchHotelIntent",
      "slots":[
        {
          "name":"City",
          "type":"AMAZON.US_CITY"
        }
      ]
    },
    {
      "intent": "PayNowIntent"
    },
    {
      "intent": "AMAZON.YesIntent"
    },

:
```

Built in intentには、Yes, No, No thanks, のような発話に反応する **AMAZON.YesIntent** があります。
ユーザーの "Yes,Please"は YesIntent として、Alexaに伝わります。

----

### session_attributes変数に今何を期待している状態かを保持する。

ユーザーがalexaのスキルに入ると、スキルから抜ける間は、session_attributesに値を保持できます。
このsession_attributesに会話の状況を保持して、YesIntentがどのIntentのYesかを判断します。

コード例を示します。

```py
:
def search_hotel(intent, session):
    session_attributes = session.get('attributes', {})
    session_attributes["state"] = "SearchHotelIntent"
      :
    return build_response(session_attributes, build_speechlet_response(
            intent['name'], speech_output, reprompt_text, should_end_session))


def pay_now(intent, session):
    session_attributes = session.get('attributes', {})
    session_attributes["state"] = "PayNowIntent"
      :
    return build_response(session_attributes, build_speechlet_response(
            intent['name'], speech_output, reprompt_text, should_end_session))


def yes(intent, session):
    card_title = intent['name']
    session_attributes = session.get('attributes', {})
    speech_output = ""
    reprompt_text = ""
    should_end_session = False
    if session_attributes.has_key('state'):
        if attributes['state'] == 'SearchHotelIntent':
            return search_hotel(intent, session)
        elif attributes['state'] == 'PayNowIntent':
            return pay_now(intent, session)

    return build_response(session_attributes, build_speechlet_response(
            intent['name'], speech_output, reprompt_text, should_end_session))


def on_intent(intent_request, session):
    print("on_intent requestId=" + intent_request['requestId'] +
          ", sessionId=" + session['sessionId'])

    intent = intent_request['intent']
    intent_name = intent_request['intent']['name']
    print(intent_name)

    # Dispatch to your skill's intent handlers
    if intent_name == "SearchHotelIntent":
        return search_hotel(intent, session)
    elif intent_name == "PayNowIntent":
        return pay_now(intent, session)
    elif intent_name == "AMAZON.YesIntent":
        return yes(intent, session)
    elif intent_name == "AMAZON.CancelIntent" or intent_name == "AMAZON.StopIntent":
        return handle_session_end_request()
    else:
        raise ValueError("Invalid intent")
:
```