1. はじめに
参考文献
今回のMicrosoft Azure Functions ではじめる Alexaスキル開発チュートリアルは、以下のサイトを参考に、Microsoft Azure Functions環境(Node.js JavaScript)で、Alexa SDK V2(ask-sdk)を用いたAlexaスキル開発ができるよう、2019年1月時点の開発環境にアレンジし、まとめたものとなります。
- AlexaアプリをAWS lambdaではなくAzure functionでつくった話
- RamjiP/alexa-skill-sdk-for-azure-function
- Alexaスキルを Microsoft Azure Functions(Node.js)環境で開発する方法
このスライドを見てできること
- Microsoft Azure Functions(Node.js JavaScript)を用いた、Alexaスキル開発の方法が理解できるようになる。
今回作成するAlexaスキル
- スキル名:宇宙の豆知識
※スキルテンプレートのFACTスキルをベースに、Microsoft Azure Functions環境でNode.js実行できるように変更を加えてあります。
ユーザー:「アレクサ、<宇宙の豆知識>を開いて」
アレクサ:「知ってましたか、XXXXXXXX。」
※従量課金プランでAzure Functionsを利用している場合は、初回実行時に以下の応答となる場合があります。(おそらく、支払いプランを変更すると改善できると思います。)
アレクサ:「スキルからの応答に、予想以上の時間がかかっています。」
前提条件
- 今回の記事は、Windows10 + Visual Studio Code + コマンドプロンプト(cmd.exe)を想定した開発環境となります。
- Visual Studio Codeが利用できる開発環境であれば、環境依存しない想定です。
2. 事前準備・開発環境
事前準備(アカウント)
事前準備(ソフトウェア)
> npm install -g azure-functions-core-tools
必要なソフトウェア
- ブラウザ:Google Chrome(推奨)
- エディタ:Visual Studio Code(推奨)
必要な開発環境
- マイク&スピーカが利用できるパソコン
- インターネット(Wi-Fi)
3. AzureFunctionsプロジェクトの作成
プロジェクトフォルダの新規作成
- 任意のフォルダ内に、プロジェクトフォルダを新規作成します。
- 今回は、C:\Work\Azure配下に、functions-project-alexa-fact-skillフォルダを新規作成しました。
AzureFunctions領域の表示
- Visula Studio Code を起動する。
- アクティビティバーのAzureアイコンを、クリックする。
- Azureアイコンが表示されていない場合は、Visual Studio Code 用 Azure Functions 拡張機能をインストールし、Visual Studio Code を再起動する必要があります。
AzureFunctionsプロジェクトの作成
- 「新しいプロジェクトの作成]アイコンを、クリックする。
- 先ほど新規作成した「プロジェクトフォルダ」を選択し、「Select(選択)」ボタンをクリックする。
プロジェクト言語の選択
- プロジェクト言語の選択から、「JavaScript」をクリックする。
- このタイミングで、プロジェクトフォルダ内にAzureFunctionsで必要なファイルが自動作成されます。
ワークスペースに追加
- 「Add to workspace(ワークスペースに追加)」 をクリックする。
4. HTTPトリガー関数の作成
HTTPトリガー関数作成の対象フォルダ選択
- 「Create Function(関数の作成)]アイコンを、クリックする。
- 先ほど新規作成したプロジェクトフォルダ(任意名:functions-project-alexa-fact-skill)をクリックする。
- うまくこの画面がでずに、次のページの関数作成選択が表示されることがあるかもしれません、その時は、HTTPトリガー関数作成をもう一度試すとよいと思います。
HTTPトリガー関数の作成
- 「HTTP trigger(HTTPトリガー関数テンプレート)]を、クリックする。
HTTPトリガー関数名の決定
- HTTPトリガー名(任意名:HttpTrigger)を入力し、エンターキーを入力する。
HTTPトリガー関数の認証選択
- 「匿名認証(Anonymous)」を選択し、クリックする。
- このタイミングで、プロジェクトフォルダ内に「HttpTrigger」フォルダが作成され、HttpTriggerフォルダ内に、「Node.jsプログラム(index.js)」が作成されます。
- Alexaプログラム本体は、作成されたindex.jsファイルとなります。
HTTPトリガー関数作成直後のindex.js
- 初期状態として、HTTPトリガー関数(index.js)の中身が表示されると思います。
- この状態で、「F5キー」押下またはメニューから「デバッグ→デバッグの開始」を選択すると、HTTPトリガー関数(URLアクセス可能なWebAPI)が実行されます。
- 実行すると、画面下部に、「ターミナル」ウィンドウが表示されますので、ターミナル内に表示されたURLをクリックしてみてください。WebAPIとして機能し、ブラウザ上に文字列が表示されると思います。
- 終了する場合は、Visual Studio Codeの画面上部の「切断」アイコンをクリックしてください。
ログラム本体は、作成されたindex.jsファイルとなります。
Application started. Press Ctrl+C to shut down.
Http Functions:
HttpTrigger: [GET,POST] http://localhost:7071/api/HttpTrigger
Please pass a name on the query string or in the request body
5. Alexaプログラムの作成
index.jsの修正
- 「エクスプローラ」アイコンをクリックし、プロジェクト内のファイルを表示する。
- 「index.js」ファイルをクリックし、表示する。
- Alexaプログラム(index.js)の内容をコピーし、index.jsファイルへペーストする(プログラムの内容をすべて書き換える)。
- 今回、Microsoft Azure Functionsの関数引数パラメータ(res)と、AWS Lambdaの関数引数パラメータ(event)との差分を、イベント関数(event)を用意し用いることで解消しています。
Alexaプログラム(index.js)の内容
/* eslint-disable func-names */
/* eslint-disable no-console */
'use strict';
/**
* モジュール参照
*
*/
const Alexa = require('ask-sdk-core');
/**
* 定数
*
*/
const SKILL_NAME = "宇宙の豆知識";
const GET_FACT_MESSAGE = "知ってましたか?";
const HELP_MESSAGE = "豆知識を聞きたい時は「豆知識」と、終わりたい時は「おしまい」と言ってください。どうしますか?";
const HELP_REPROMPT = "どうしますか?";
const ERROR_MESSAGE = '申し訳ありませんが、エラーが発生しました';
const STOP_MESSAGE = "さようなら";
const data = [
"水星の一年はたった88日です。",
"金星は水星と比べて太陽より遠くにありますが、気温は水星よりも高いです。",
"金星は反時計回りに自転しています。過去に起こった隕石の衝突が原因と言われています。",
"火星上から見ると、太陽の大きさは地球から見た場合の約半分に見えます。",
"木星の<sub alias='いちにち'>1日</sub>は全惑星の中で一番短いです。",
"天の川銀河は約50億年後にアンドロメダ星雲と衝突します。",
"太陽の質量は全太陽系の質量の99.86%を占めます。",
"太陽はほぼ完璧な円形です。",
"皆既日食は一年から二年に一度しか発生しない珍しい出来事です。",
"土星は自身が太陽から受けるエネルギーの2.5倍のエネルギーを宇宙に放出しています。",
"太陽の内部温度は摂氏1500万度にも達します。",
"月は毎年3.8cm地球から離れていっています。"
];
/**
* ローンチリクエストハンドラ
*
* ユーザ発話「アレクサ、<スキル名>を開いて」
* まはた ユーザ発話「何か教えて。」
*/
const GetNewFactHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'LaunchRequest'
|| (request.type === 'IntentRequest'
&& request.intent.name === 'GetNewFactIntent');
},
handle(handlerInput) {
var factArr = data;
var factIndex = Math.floor(Math.random() * factArr.length);
var randomFact = factArr[factIndex];
// スピークアウトプットの設定(Alexa応答設定)
var speakOutput = GET_FACT_MESSAGE + randomFact;
// withSimpleCard(カード付きAlexa応答&終了)
return handlerInput.responseBuilder
.speak(speakOutput)
.withSimpleCard(SKILL_NAME, randomFact)
.withShouldEndSession(true)
.getResponse();
},
};
/**
* ヘルプハンドラ
*
* ユーザ発話「ヘルプ。」
*/
const HelpHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest'
&& request.intent.name === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
// スピークアウトプットの設定(Alexa応答設定)
var speakOutput = HELP_MESSAGE;
// リプロンプトの設定(聞き直し設定)
var reprompt = HELP_REPROMPT;
// ask(Alexa応答+たずねる)
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(reprompt)
.getResponse();
},
};
/**
* キャンセル&ストップハンドラ
*
* ユーザ発話「キャンセル。」
* ユーザ発話「止めて。」
*/
const ExitHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest'
&& (request.intent.name === 'AMAZON.CancelIntent'
|| request.intent.name === 'AMAZON.StopIntent');
},
handle(handlerInput) {
// スピークアウトプットの設定(Alexa応答設定)
var speakOutput = STOP_MESSAGE;
// tell(Alexa応答&終了)
return handlerInput.responseBuilder
.speak(speakOutput)
.withShouldEndSession(true)
.getResponse();
},
};
/**
* 予期せぬ終了
*
* ユーザ発話「終了。」
*/
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
console.log(`Session ended with reason: ${handlerInput.requestEnvelope.request.reason}`);
return handlerInput.responseBuilder
.withShouldEndSession(true)
.getResponse();
},
};
/**
* エラーハンドラ
*
*/
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`Error handled: ${error.message}`);
console.log(`Error stack: ${error.stack}`);
// スピークアウトプットの設定(Alexa応答設定)
var speakOutput = ERROR_MESSAGE;
return handlerInput.responseBuilder
.speak(speakOutput)
.reprompt(speakOutput)
.getResponse();
},
};
// ーーーー 追記ここから AzureFunctions対応 ーーーー
/**
* イベント関数
*
* Azureのreqパラメータを、AWS Lambdaのeventパラメータに変換
*/
let event = function (req) {
let EventEmitter = require('events');
let ee = new EventEmitter();
for (let prop in req.body) {
if(prop){
ee[prop] = req.body[prop];
}
}
return ee;
};
/**
* プログラムの開始位置
*
*/
let skill;
module.exports = async function (context, req) {
if(!skill){
// ハンドラ設定
skill = Alexa.SkillBuilders.custom()
.addRequestHandlers(
GetNewFactHandler,
HelpHandler,
ExitHandler,
// FallbackHandler,
SessionEndedRequestHandler,
)
.addErrorHandlers(ErrorHandler)
.create();
}
// 処理実行&Alexa応答
return skill.invoke(event(req));
};
// ーーーー 追記ここまで AzureFunctions対応 ーーーー
function.jsonの修正
- 「function.json」ファイルをクリックし、表示する。
- function.jsonの内容をコピーし、function.jsonファイルへペーストする(ファイルの内容をすべて書き換える)。
- "res"を"$return"に変更することで、HTTPトリガー関数の実行結果レスポンス内容を、index.jsプログラムのreturnの値(Response JSON)に変更しています。
- もし、Alexaプログラムを実行しても、レスポンス結果が空JOSN{}となる場合は、function.jsonが正しく修正されていない可能性があります。
//修正前
{
"type": "http",
"direction": "out",
"name": "res"
}
// 修正後
{
"type": "http",
"direction": "out",
"name": "$return"
}
function.jsonの内容
{
"disabled": false,
"bindings": [
{
"authLevel": "anonymous",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": [
"get",
"post"
]
},
{
"type": "http",
"direction": "out",
"name": "$return"
}
]
}
package.jsonファイルの作成
- コマンドプロンプト(cmd.exe)を開く。
- cdコマンドで、プロジェクトフォルダ内に移動する。
- npm initコマンドで、package.jsonファイルを新規生成する。
//プロジェクトフォルダ内に移動
> cd C:\Work\Azure\functions-project-alexa-fact-skill
//package.jsonファイルの新規生成
> npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (functions-project-alexa-fact-skill)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to C:\Work\Azure\functions-project-alexa-fact-skill\package.json:
{
"name": "functions-project-alexa-fact-skill",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this OK? (yes) yes
必要Nodeモジュールのインストール
- コマンドプロンプト(cmd.exe)を開く。
- cdコマンドで、プロジェクトフォルダ内に移動する。(package.jsonファイルが同一フォルダ内に存在していること。)
- npm installコマンドで、必要Nodeモジュール(ask-sdk-model、ask-sdk-core)をインストールする。
//ask-sdk-modelモジュールのインストール
> npm install --save ask-sdk-model
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN functions-project-alexa-fact-skill@1.0.0 No description
npm WARN functions-project-alexa-fact-skill@1.0.0 No repository field.
+ ask-sdk-model@1.11.2
added 1 package from 3 contributors and audited 1 package in 1.783s
found 0 vulnerabilities
//ask-sdk-coreモジュールのインストール
> npm install --save ask-sdk-core
npm WARN functions-project-alexa-fact-skill@1.0.0 No description
npm WARN functions-project-alexa-fact-skill@1.0.0 No repository field.
+ ask-sdk-core@2.3.0
added 2 packages from 3 contributors and audited 3 packages in 1.943s
found 0 vulnerabilities
package.jsonファイルの内容
{
"name": "functions-project-alexa-fact-skill",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"ask-sdk-core": "^2.3.0",
"ask-sdk-model": "^1.11.2"
}
}
6. ローカル環境でのテスト実行
Visual Studio Codeを用いたHTTPトリガーの実行
- 「F5キー」押下またはメニューから「デバッグ→デバッグの開始」を選択し、HTTPトリガー関数(URLアクセス可能なWebAPI)を実行する。
- 「ターミナル」に表示される「URL」を、コピーする。
Requestの送信(ヘッダー部設定)
- ローカル環境のURLに対しJSON Request をPOSTできるツールを用意する。(今回は、ChromeブラウザにChrome拡張機能の「Advanced REST client」をインストールし利用する。
- リクエストのMethodに「POST」を選択する。
- リクエストのURLに「先ほどコピーしたHTTPトリガーのURL」を、ペーストする。
- リクエストのHeadersに「Content-Type」、「application/json」を設定する。
Requestの送信(ボディー部設定)
- Request の Body部に、「application/json」、「Rqw input」を選択する。
- Request の Body部に、Alexaスキルに送信したいリクエスト内容をセットする。
- 「SEND」ボタンをクリックする。
テスト用Request JSON(LaunchRequest)
{
"version": "1.0",
"session": {
"new": true,
"sessionId": "session0001",
"application": {
"applicationId": "amzn1.echo-sdk-ams.app.0001"
},
"attributes": {},
"user": {
"userId": null
}
},
"request": {
"type": "LaunchRequest",
"requestId": "request0001",
"timestamp": "2018-08-11T13:04:56+0900"
}
}
Request送信結果の確認
- 画面下部の実行結果を、確認する。
- 実行結果が「200 OK」となっており、Response JSON内容が表示されていれば成功です。
7. Microsoft Azure Function App の作成
Function Appの作成
- Azureにログインし、ポータルに移動する。
- 「リソースの作成」を、クリックする。
- 「Compute」を、クリックする。
Function Appの作成
- 「Function App」を、クリックする。
Function Appの作成
- アプリ名を入力する。(任意ですがURLの一部となるため、ユニークである必要があります。)
- サブスクリプションを選択する。
- リソースグループを選択する。
- ホスティングプランを選択する。
- 場所に、「東日本」を選択する。
- ランタイムスタックに、「JavaScript」を選択する。
- ストレージを選択する。
- Application Insightsを選択する。(有効にするとログがとれるのだと思う。たぶん)
- 「作成」ボタンをクリックする。
- サブスクリプションおよびホスティングプランは、各自の環境に合わせてください。
- ホスティングプランに「従量課金プラン」を選択した場合、HTTPトリガー関数の初回起動に時間がかかる場合があるかもしれません。
Function App を開く
- Function Appの作成まで、3分程度待つ。
- 作成が完了するとポップアップが表示されるので、「リソースに移動」または「Function App」をクリックする。
Function App を開く
- Azure上に、Function Appが作成されていることを確認する。
- この時点ではまだ、関数が作成されていない状態(デプロイされていない状態)となります。
8. HTTPトリガーのデプロイ
Visual Studio Code からHTTPトリガーをデプロイ
- Azureにサインインする。
- 「Azure」アイコンを、クリックする。
- 「Deploy to Function App」アイコンを、クリックする。
- 先ほど作成したAzureの「Function App 名」を、選択する。
Visual Studio Code からHTTPトリガーをデプロイ
- 「Deploy(デプロイ)」ボタンをクリックし、ローカル環境で開発したプロジェクトを、クラウド環境のAzureへ、デプロイする。
- 初回のデプロイには、5分から10分程度時間がかかるかと思います。
9. Azure環境でのテスト実行
HttpTriggerのテスト実行
- デプロイが完了しており、「HttpTrigger」が反映されていることを確認する。
- 「テスト」を、クリックする。
HttpTriggerのテスト実行
- HTTPメソッドに、「POST」を選択する。
- 要求本文に、「テスト用Request JSON(LaunchRequest)」をペーストする。
- 「実行」ボタンを、クリックする。
- 実行結果として、「出力」が画面右下に表示されればOKです。
10. 関数URLの取得
関数URLの取得
- 「関数のURLの取得」から、URLをコピーしておく。
11. Alexaスキルの作成
Amazon Developer Consoleにアクセス
- Amazon Developer Consoleを、開く。
- 「管理者コンソール」を、クリックする。
Amazon Developer Consoleにログイン
- Alexaスキルを作成したいAmazon.co.jpアカウントで、ログインする。
- Alexaスキルは、アマゾンアカウントに紐づき、Alexaスキルが作成されます。
Alexa Skills Kitの選択
- 「Alexa」を、クリックする。
- 「Alexa Skills Kit」を、クリックする。
スキルの作成
- 「スキルの作成」を、クリックする。
スキルの作成
- スキル名(任意名:宇宙の豆知識)を、入力する。
- デフォルトの言語に、「日本語(日本)」を選択する。
- 「カスタムスキル」を、選択する。
- 「スキルを作成」ボタンを、クリックする。
JSONエディターによるスキルの設定
- 「JSONエディター」を、クリックする。
JSONエディターによるスキルの設定
- 「JSONエディター」内に、スキル設定JSONの内容を張り付ける。
- 「モデルを保存」ボタンを、クリックする。
スキル設定JSON
{
"interactionModel": {
"languageModel": {
"invocationName": "宇宙の豆知識",
"intents": [
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "GetNewFactIntent",
"slots": [],
"samples": [
"豆知識",
"教えて",
"話して",
"聞かせて",
"宇宙の豆知識",
"豆知識を教えて",
"宇宙の豆知識を教えて",
"豆知識を聞かせて",
"宇宙の豆知識を聞かせて",
"トリビア",
"宇宙のトリビア",
"トリビアを教えて",
"宇宙のトリビアを教えて",
"何か教えて",
"宇宙について教えて",
"なんか教えて",
"なんか言って"
]
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
}
],
"types": []
}
}
}
モデルをビルド
- 「モデルをビルド」を、クリックする。
- Alexaスキルの構築(学習)が始まり、3分程度待ちます。
エンドポイントの設定
- 「エンドポイント」を、クリックする。
- 「HTTPS」を、クリックする。
- 「デフォルトの地域のURL」に、AzureのHttpTrrigerから取得した「関数のURL」をペーストする。
- 「開発用のエンドポイントは、証明機関が発行したワイルドカード証明書をもつドメインのサブドメインです」を、選択する。
- 「エンドポイントを保存」を、クリックする。
チェックリストの確認
- 「スキルビルダーのチェックリスト」の「1から4」すべてが、オールグリーン(登録済み)であることを確認する。
- 登録漏れがある場合は、手順に漏れ(保存忘れ、ビルド忘れ)がありますので、確認してください。
11. Alexaスキルの作成
ブラウザテスト
- 「テスト」を、クリックする。
- 「開発中」を、選択する。
- マイクアイコンを押したまま「<(任意)スキル呼び出し名>を開いて」と発話するか、もしくはキーボードを利用し「<(任意)宇宙の豆知識>を開いて」と入力後エンターキーを押下する。
ブラウザテスト
- アレクサ応答がスピーカから出力され、ブラウザ画面にも実行結果が表示されることを確認する。
- 開発アカウントと同じAmazon.co.jpアカウントに紐づくEcho実機デバイスからも、同様にテストすることができます。
12. おわりに
おわりに
みなさん、Microsoft Azure Functions ではじめる Alexaスキル開発チュートリアルはいかがでしたか?みなさんの環境でも、うまく実行できていれば幸いです。うまくいけば、4時間もあれば完成するかと思います。
Microsoft Azure Functions環境でのAlexaスキル開発がおこなえたことで、Microsoft Azure開発者にも、Alexaスキルを手軽に開発するきっかけになることを願っております。
2019/01/20 TAKAHIRO NISHIZONO
13. 補足
Alexaスキル開発の基礎を学ぶには
-
Alexaスキル開発の基礎を学ぶための、おすすめサイトをご紹介します。
初回のAzure Functionsの起動が遅い?
- AWS Lambdaとくらべ、Azure Functionsで従量課金プランを選択した場合、初回実行にかかる時間が長いように感じます。
- 個人でAlexaスキルの申請(公開)を考えている場合、Azure Functionsの従量課金プランでは、Alexa仕様の「8秒以内にレスポンスを正常に返す」ことができず、リジェクト(申請否認)される可能性が考えられます。
- Azure Functionsで初回実行を早めるためには、「App Service プラン」の導入等の検討が必要かもしれません。
Azure Functions のコールドスタンバイ対策
参考文献
- スマートスピーカースキルのバックエンドをAzure Functionsで作ろう!~コールドスタート問題と向き合う~
- スマートスピーカーのバックエンドとしての Azure Functions のコールドスタート対策
従量課金プランの問題点
参考文献
認証方式にFunctionを選ぶ場合
参考文献
Googleアシスタント音声アプリを開発するには?
参考文献
- Azure Functions Node.jsでActions on Google Client Libraryを使う方法
- Azure App ServiceでActions on Google Client Libraryを使う方法
- Actions on Google Client Library APIリファレンス
マンガでわかるLINE Clova開発 第4話 Azure Functionsでバックエンドを作ろう
参考文献