Alexa カスタムタスクの実装と動作確認方法 - Custom Task -


# はじめに

いつの間にか SMAPIに `get-task`、`search-task`などのタスクを操作するコマンドが追加されていたので使ってみようと思った。現状では、一般開発者がカスタムタスクを作っても、特に何かができるというわけではないので、今回は単純にスキルにタスクを追加してSMAPIで操作してみたというもの。

カスタムタスクでできること。

|No.|できること|概要|
|:--|:--|:--|
|1.|Alexa定型アクション|予め指定した値を使ってスキルを起動することができる。|
|2.|Direct Skill Connections|他のスキルと連携する。|
|3.|Quick Links for Alexa|スキル起動時に外部から値を渡すことができる。|
|4.|Timer API（LAUNCH_TASK）|タイマーが鳴った後にスキルを再開する。|

# 前提

- ASK CLI v2（ASK CLI v1ではデプロイできない）
- <font color="OrangeRed">スキルの公開</font>（カスタムタスクの動作をEchoデバイスで確認する場合）

現在、ASK CLI v1を使用している方は、[Alexa ASK CLI を v2 にバージョンアップして、スキルプロジェクトを v2 用にマイグレーション](https://qiita.com/takara1121/items/a90a5576807d73d8e752) する必要があります。

# 手順

1. `ask new` する。
2. `skill-package/`フォルダ配下に`tasks/`フォルダを手動で作成する。
3. タスク定義ファイルを手動で作成する。
4. `skill.json` にタスク情報を追加する。
5. Lambda関数にカスタムタスクを判別するコードを追加する。
6. `ask deploy`する。
7. カスタムタスクの動作を確認する。
8. SMAPIコマンドでタスクを確認する。

# ask new する

まずスキルを作成する。
任意のフォルダを作成して、`ask new`する。

```
ask new
```

# tasksフォルダを作成する

作成したスキルの `skill-package/` フォルダ配下に移動し、`tasks`（※固定）という名称のフォルダを手動で新規作成をする。

```:スキルプロジェクト
CustomTask:
│  ask-resources.json
│  
├─.ask
│      ask-states.json
│                      
├─lambda
│      index.js
│      package.json
│      util.js
│      
└─skill-package
    │  skill.json
    │  
    ├─assets
    │      ja-JP_largeIcon.png
    │      ja-JP_smallIcon.png
    │      
    ├─interactionModels
    │  └─custom
    │          ja-JP.json
    │          
    └─tasks                   ←‐‐‐  フォルダを作成する
            CountDown.1.json  ←‐‐‐  タスク定義ファイルを作成する
```

# タスク定義ファイルを作成する

`tasks/`フォルダ配下に`タスク定義ファイル`を手動で新規作成する。
※タスクの定義ファイルは`OpenAPI V3`仕様に準拠

#### 制限事項（抜粋）

- タスク定義ファイルのファイル名は `{TaskName}.{TaskVersion}.json` の形式となる。
- タスク名がパスとなる。例）タスク名が`CountDown`の場合、`/CountDown` になる。
- １タスク定義ファイルにつき１タスクとなる。

詳しくは、[公式ドキュメント](https://developer.amazon.com/ja-JP/docs/alexa/custom-skills/implement-custom-tasks-in-your-skill.html#create-a-task-definition-file)を参照

#### タスク定義ファイル

`CountDown` というタスク名称で、バージョンが `1` の場合、ファイル名は `CountDown.1.json` となる。 以下のJSONをコピーするだけ。

```:Amazonマニュアルより転載
{
   "openapi": "3.0.0",
   "info": {
     "title": "Task to perform a count down",
     "version": "1",
     "x-amzn-alexa-access-scope": "vendor-private"
   },
   "tags": [{
     "name": "count down"
   }],
   "paths": {
     "/CountDown": {
       "summary": "Count Down",
       "description": "To start a count down",
       "post": {
         "requestBody": {
           "content": {
             "application/json": {
               "schema": {
                 "$ref": "#/components/schemas/Input"
               }
             }
           }
         },
         "responses": {
           "200": {
             "description": "When the count down finishes successfully",
             "content": {
               "application/json": {
                 "schema": {
                   "$ref": "#/components/schemas/SuccessfulResponse"
                 }
               }
             }
           },
           "400": {
             "description": "When the given parameters fail validations - e.g. lowerLimit cannot be higher than upperLimit"
           },
           "500": {
             "description": "When the count down fails"
           }
         }
       }
     }
   },
   "components": {
     "schemas": {
       "Input": {
         "type": "object",
         "properties": {
           "upperLimit": {
             "type": "number",
             "maximum": 100,
             "minimum": 1
           },
           "lowerLimit": {
             "type": "number",
             "maximum": 100,
             "minimum": 1
           }
         }
       },
       "SuccessfulResponse": {
         "type": "object",
         "properties": {
           "endTime": {
             "type": "string",
             "format": "date-time"
           }
         }
       }
     }
   }
}
```

# skill.json にタスク情報を追加する

タスクを実装するスキルを作成するには、以下のように`skill.json`にタスク情報を追加する必要がある。

```
{
  ...
  "apis": {
    "custom": {
      ...
      "tasks": [{
        "name": "CountDown",
        "version": "1"
      }]
    }
  }
}
```

# Lambda関数にカスタムタスクを判別するコードを追加する

カスタムタスクから起動された場合 `handlerInput.requestEnvelope.request.task` に値が格納される。
タスク名で判別させて目的の処理を実行させる。タスク起動でない場合、task は `undefined` となる。

```JavaScript:index.js
    task = handlerInput.requestEnvelope.request.task;
    if (task.name === 'amzn1.ask.skill.f40f1525-e285-481a-xxxx-4a4665be4fb1.CountDown') {
      let campaignsCode = task.input.CampaignsCode;
      console.log(`LaunhcRequestHandler campaignsCode: ${campaignsCode}`);
    }
```

# ask deploy する

ASK CLI v2で、ask deployコマンドを実行する。

```
ask deploy 
```


# カスタムタスクの動作を確認する

カスタムタスクはスキルを公開していないとEchoデバイスで確認することができない。
そのため、`invoke-skill-end-point` を使用して動作確認をする。

```:invoke-skill-end-point
ask smapi invoke-skill-end-point -s amzn1.ask.skill.f40f1525-e285-481a-xxxx-4a4665be4fb1 --stage development --skill-request-body file:./input.json --endpoint-region FE
```
※skill.jsonのregionsにエンドポイントが設定されていないとエラーになる。


```JSON:skill.json
        "regions": {
          "FE": {
            "endpoint": {
              "uri": "arn:aws:lambda:us-east-1:023972248338:function:ask-custom-SosuDice-default"
            }
          }
        },
```


[input.json のサンプル](https://developer.amazon.com/ja-JP/docs/alexa/custom-skills/implement-custom-tasks-in-your-skill.html#to-invoke-your-task-handler) を参考に input.json を作成してください。自分のスキルを実行したときに CloudWatchログに表示されるRequsetを使っても <font color="OrangeRed">"message": "Session object of the skill request body does not contain a skill id that matches that of the request path."</font>というメッセージが表示され、実行できなかった。なぜか公式のinput.jsonサンプルだと動作する。なんで？

```JOSN:input.json

{
    "version": "1.0",
    "session": {
        "new": true,
        "sessionId": "amzn1.echo-api.session.aaf7b112-434c-11e7-2563-6bbd1672c748",
        "application": {
            "applicationId": "{YourProviderSkillId}"
        },
        "user": {
            "userId": "amzn1.ask.account.12345ABCDEFGH"
    	}
    },
    "context": {
        "System": {
            "application": {
                "applicationId": "{YourProviderSkillId}"
            },
            "user": {
                "userId": "amzn1.ask.account.12345ABCDEFGH"
            }
        }
    },
    "request": {
        "type": "LaunchRequest",
        "requestId": "amzn1.echo-api.request.da528275-5aa6-4f69-8038-06efc94d1923",
        "timestamp": "2020-01-29T23:11:48Z",
        "locale": "ja-JP",
    "task": {
        "name": "{YourProviderSkillId}.{taskName}",
        "version": "1",
        "input": {
            "upperLimit": 10,
            "lowerLimit": 2
        }
    }
}
```

結果が戻ってくればＯＫ。

```JSON:RESPONSE
      "invocationResponse": {
        "body": {
          "version": "1.0",
          "response": {
            "outputSpeech": {
              "type": "SSML",
              "ssml": "<speak>タスク起動です。</speak>"
            }
          },
          "userAgent": "ask-node/2.10.1 Node/v12.18.4",
          "sessionAttributes": {}
        }
      },
```

# SMAPIコマンドでタスクを確認する

スキルが公開されていることが前提。
ASK CLI が v2 になったばかりの頃は、SMAPIにタスクを確認するコマンドはなかったので、v2でタスクを作成して、v1で確認するという、わけのわからない状況だったが、やっと改善され `get-task`、`search-task`という２つのコマンドが追加された。

**<font color="OrangeRed">（注意）スキルの公開前に、`search-task` コマンドや `get-task` コマンドを使っても、そのタスク情報は返却されない。</font>**

#### get-task
指定された`タスク名とバージョン`の組合せでタスク定義の詳細を取得する。
`--task-name` は`CountDown`だけでは検索できず、以下のようにamzn1....で始まる名前を指定する必要がある。`--task-name`は`search-task`コマンドを実行することで確認できる。
`get-task`コマンドを実行すると、上記で作成したタスクが画面に表示される。

```:get-task
ask smapi get-task --skill-id amzn1.ask.skill.f40f1525-e285-481a-xxxx-4a4665be4fb1 --task-name amzn1.ask.skill.f40f1525-e285-481a-xxxx-4a4665be4fb1.CountDown --task-version 1 
```

#### search-task
スキルのタスク情報を取得する。キーワードとプロバイダースキルIDで検索できる。キーワードとプロバイダースキルIDの両方を省略すると、そのスキルのすべてのタスクが返されます。

```:search-task
ask smapi search-task --skill-id amzn1.ask.skill.f40f1525-e285-481a-xxxx-4a4665be4fb1
```

```:Response
{
  "nextToken": "AAIAAUQUjdUvlTskvOjFe6MRnJzD5jfhxC2aN3cuMXVhZq_WiANmN4f8x4a45EF4Bbp5sRfdOUm3T===",
  "taskSummaryList": [
    :
    :
    {
      "description": "To start a count down",
      "name": "amzn1.ask.skill.f40f1525-e285-481a-xxxx-4a4665be4fb1.CountDown",
      "version": "1"
    },
    :
    :
  ],
  "totalCount": 47
}
```

# おわりに

現在、一般開発者は、カスタムタスクを作成したとしても何もできない状態であるため、はやく`Skill Connections` などタスクを使える仕組みを一般開発者にも解放して欲しいと思う。

※記事を書いてから３年ぐらい経過した。カスタムタスクでできることが増えてスキル開発の幅も広がってきたと思う。個人的には、タイマー鳴ったあとにもう一度スキルを起動できる仕組みが気に入っている。


# 参考
- [ASK CLIコマンドリファレンス（v2）（Amazon公式サイト）](https://developer.amazon.com/ja-JP/docs/alexa/smapi/ask-cli-command-reference.html)
- [スキルマニフェストのスキーマ（Amazon公式サイト）](https://developer.amazon.com/ja-JP/docs/alexa/smapi/skill-manifest.html)
- [Implement Custom Tasks in Your Skill（Amazon公式サイト）](https://developer.amazon.com/en-US/docs/alexa/custom-skills/implement-custom-tasks-in-your-skill.html#how-can-skill-developers-see-my-task)
- [Understand Skill Connections（Amazon公式サイト）](https://developer.amazon.com/ja-JP/docs/alexa/custom-skills/understand-skill-connections.html)