kun432's blog

Alexaなどスマートスピーカーの話題中心に、Voiceflowの日本語情報を発信してます。たまにAWSやkubernetesなど。

Voiceflowにおける会話のコンテキストを考える ③プライムコンテキスト

f:id:kun432:20200724191613p:plain

Voiceflowにおける会話のコンテキストを考える」シリーズの第3回です。今回は「プライムコンテキスト」です。

第1回はこちら。

第2回はこちら。

目次

プライムコンテキストとは?

前回ご紹介した「コンテキストのスイッチ」をもう一度振り返ってみましょう。

いらっしゃいませ。ご注文は何になさいますか?

コーヒーを一つ。

砂糖とミルクはおつけしますか?

あ、そうだ、営業時間は何時まで?

営業時間は9:00〜22:00です。ところで砂糖とミルクはおつけしますか?

お願いします

途中で会話のコンテキストが「注文を受け付ける」から「営業時間を聞く」に変わります。これが「コンテキストの一時的なスイッチ」でしたね。そして、最後に元の「注文を受け付ける」に戻ってきていますよね。このときのコンテキストの関係性はこうなります。

  • メイン:注文を受け付ける
  • サブ :営業時間を聞く

つまり、コンテキストにも重要度・優先度があるわけです。このとき、より重要・優先度が高いコンテキストを「プライムコンテキスト」と呼んでいます。一度変更したコンテキストから元のコンテキストに戻るかどうかは、この重要度・優先度が大きな要因になります。

では、以下のような場合はどうなるでしょうか?

いらっしゃいませ。ご注文は何になさいますか?

コーヒーを一つ。

砂糖とミルクはおつけしますか?

あ、急に胸が・・・苦しい・・・き、救急車をっ・・・

わ、わかりました!今、救急車を呼びました!

コンテキストはこの2つです。

  • 注文を受け付ける
  • 救急車を呼ぶ

途中で「注文を受け付ける」から「救急車を呼ぶ」に切り替わっています。通常、「救急車を呼ぶ」はそうそう起きることではないので、あくまでも切り替わる前は「注文を受け付ける」がプライムコンテキストです。ただし、もし起こったら重要度は「救急車を呼ぶ」のほうが圧倒的に高い上、この会話の流れで「注文を受け付ける」に戻る必要はないですよね。

つまり、「プライムとなるコンテキスト」が別のコンテキストに「恒久的」にスイッチした、ということになります。

Intent Block

Voiceflowで「恒久的」な「プライムコンテキスト」のスイッチを行うには、"Intent Block”を使います。"Intent Block"はフローのどこからでもインテントを呼び出せるブロックです。つまりフローの流れに沿う必要はありません。早速やってみましょう。

前回までのフローはこんな感じになっていました。いろいろな「注文を受け付ける」フローがプライムコンテキスト、「営業時間を聞く」のは「コマンド」を使ったサブコンテキストになっています。

f:id:kun432:20200921215822p:plain

ここに"Intent Block"を追加します。

f:id:kun432:20200921220016p:plain

"Intent Block"にインテントを追加します。"emergency_intent"としました。

f:id:kun432:20200921220321p:plain

こんな感じでサンプル発話を入力します。

f:id:kun432:20200921220436p:plain

Speak BlockをつなげてAlexaの発話を入力します。

f:id:kun432:20200921220726p:plain

これで終了です。

ではアップロードしてテストしてみましょう。

f:id:kun432:20200921221028p:plain

「注文を受け付ける」コンテキストのフローのどこからでも「救急車を呼ぶ」コンテキストにスイッチして、元のコンテキストに戻らずにそのまま「救急車を呼ぶ」コンテキストのフローに流れていっていますね。これが「プライムコンテキストのスイッチ」です。図にするとこういう感じになります。

f:id:kun432:20200921222243p:plain

うまくいきました!

おまけ

どこからでも呼び出せるということは、スキルの起動と同時に呼び出すこともできます。これが「ワンショット発話」になります。

f:id:kun432:20200921222458p:plain

まとめ

「Voiceflowにおける会話のコンテキスト」シリーズの第3回、プライムコンテキストの「恒久的」なスイッチについてご紹介しました。次回は少し複雑になりますが、コンテキストに応じたインテントを使い分けるというのをやってみたいと思います。

Voiceflow Updates: AlexaとGoogleでプロジェクトが完全に分かれるようになりました

f:id:kun432:20200918173639p:plain

VoiceflowはAlexaスキル/Googleアクションの両方に対応しています。この切り替えは、これまでは画面上部で変更できていました。

f:id:kun432:20200620194738p:plain

ただ、これにはいくつかの問題がありました。

そもそもAlexaとGoogleで提供可能な機能が違う

例えばAlexaで作ったプロジェクトをGoogle向けに切り替えただけで動く、というわけではなく、使えない機能などが出てきてしまいます。例えば、ブロックにあるメニューのうち、”Channel"というのがプラットフォームに特化したものになります。見てみましょう。

Alexaの場合(すいません、βテストに参加しているので、普通は見れないブロックも見えてます)

f:id:kun432:20200918174536p:plain

Googleの場合

f:id:kun432:20200918174644p:plain

全然違いますね。Alexa向けに作ったプロジェクトで、Googleで使えないブロックが含まれていた場合、それらは使えないので、修正する必要が出てきてしまいます。

AlexaとGoogleで共通な機能でも中身が違う

また、Alexa/Google共通のブロックでも使い方が変わってくるものもあります。例えば、インテント。ビルトインで使えるインテントもこれだけ違います。

Alexaの場合(ちょっと画像編集して一覧で見れるようにしてます)

f:id:kun432:20200918180123p:plain

Googleの場合。実はGoogleの場合はビルトインインテントがないのですね・・・(Dialogflowにはもちろんありますので、Voiceflowで実装されていないということだと思います。)

f:id:kun432:20200918180908p:plain

つまり、現状のVoiceflowだと、Googleの場合は「Yes」や「No」みたいなものも、カスタムインテントですべて作らないといけないということになりますね。それはしょうがないとしても、逆にGoogleからAlexaに戻した場合、Alexaには「Yes」「No」のビルトインインテントがあるので、おそらくバッティングすることになります・・・

じゃあどうするか?(これまで)

じゃあ、同じプロジェクトを、AlexaスキルとGoogleアクションの両方に対応させたい、となった場合、これまではどうするのがよかったか?というと、プロジェクトを分けることだったと思います。例えば、

  • まずAlexaスキル向けに作る
  • そのプロジェクトをコピーして、Googleアクションに切り替え
  • Googleアクション向けに修正する
  • 同じ機能を追加したければ、両方のプロジェクトに追加する

という感じです。個人的には、切り替えてサラッと動くわけではないし、都度変更していく手間を考えると、1つのプロジェクトで切り替えるよりは、最初からプロジェクトを分けてしまったほうが良い、と感じていました。

今回のアップデート

ということで、今回のアップデートですが、この切替機能がなくなり、プロジェクト作成時にどちらかを選ぶことになります。プロジェクトを作成してみましょう。"Create Project"をクリックします。

f:id:kun432:20200919011040p:plain

プロジェクト名、アイコン画像を設定して、"Continue"をクリックします。

f:id:kun432:20200919011132p:plain

はい、ここですね。ここでAlexaスキル向けか、Googleアクション向けを決めることになります。今回はAlexaを選択します。

f:id:kun432:20200919011541p:plain

最後に、呼び出し名と言語を設定して"Create Project"をクリックします。

f:id:kun432:20200919011655p:plain

Alexaスキル用プロジェクトができました。画面上部の切り替えスイッチはありませんね。一旦プロジェクト一覧画面に戻ってみましょう。

f:id:kun432:20200919011929p:plain

作成したプロジェクトをコピーしてみます。

f:id:kun432:20200919011956p:plain

Alexaスキル用プロジェクトとしてコピーされます。つまり、プロジェクトのコピーなどでも切り替えができないということです。

f:id:kun432:20200919012025p:plain

ではプロジェクトをAlexa/Googleごとに1から作らないといけないのか、というと、一応逃げ道はあるようです。VoiceflowのChangeLogに記載されている内容を抜粋して拙訳します。

  • 異なるチャンネル(Alexa or Google)のプロジェクト間でブロックをコピー+ペーストすることは可能です。コピー元のプロジェクトでチャンネル固有のブロックが含まれていた場合、コピー先のチャンネルでは機能しません。

つまり、プロジェクトを開いて、ブロックをコピー、もう一つのプロジェクトをペーストすればいけるということですね。例えば、Alexaスキル向けプロジェクトをGoogle向けプロジェクトにコピペする場合だと、まずこんな感じで範囲選択して、

f:id:kun432:20200919022648p:plain

Windowsの場合はCtrl+C、Macの場合はCommand+Cでコピーします。以下のように表示されればコピーされています。

f:id:kun432:20200919022804p:plain

でAlexa向けプロジェクトから抜けて、Goole向けプロジェクトを開いてペーストすればOKです。ただし、本来は対応していないブロックなどもコピーされてしまいます。例えば、以下の例だとDisplay BlockはAlexaのみなので、Googleの場合は削除しないと正しく動作しないようです。

f:id:kun432:20200919023134p:plain

インテントやスロットなどもほぼ引き継がれるようですが、ビルトインのインテントやスロットの場合はうまく引き継がれない場合がありますので、修正が必要です。

f:id:kun432:20200919023300p:plain

両方アップロードしていたらどうなるの?

これもChangeLogに載っています。

  • 過去にAlexaとGoogleの両方にアップロードしていたプロジェクトは、プロジェクトのダッシュボードに、各チャンネルごとにそれぞれ別のプロジェクトとして表示されます。

つまり、2つのプロジェクトになっているということですね。どちらか片方だけしか使えなくなるというわけではなさそうです。安心ですね。

まとめ

今回の変更の背景は、以下のChangeLogに載っています。

DeepLで日本語翻訳したものに少し拙訳を入れたものを以下に掲載します。

なぜこれを構築したのか?

私たちが最初にVoiceflowを構築したとき、私たちの意図は「一度構築したら、どこでも公開できる」プラットフォームを作ることでした。この2年間で、AlexaとGoogleアシスタントは提供するサービスを拡大し続けており、それぞれのチャンネルは違う方向に向かっています。もし私たちが「すべてのチャンネルに共通の単一のプロジェクト」を続けるとしたら、それは両方のチャンネルで共通の機能しか提供できないことを意味します。

私たちの目標は、各チャンネルが提供しているすべての機能に対応できるようにすることであり、これを実現するためにはそれぞれのチャンネル用にプロジェクトを独立させざるを得ませんでした。現時点では構造的な変更に過ぎませんが、AlexaやGoogle アシスタント、そして今後のロードマップで紹介することになる新しいチャンネルに、より素晴らしい機能を提供できるための扉が開かれました。

つまり、当初はAlexa/Googleの違いをVoiceflowが吸収するという方針だったのが、それぞれが別々のやり方でどんどん変化していく中で、徐々に厳しくなってきた、ということかなぁと思います。

ユーザの立場としては、できるだけ同じやり方でやりたいという思いもあるのですが、個人的には今回のアップデートはむしろ歓迎したいですね。というのも、現在のVoiceflowのGoogle対応は、冒頭でご紹介した通り、

  • Alexa固有の機能は多数対応しているけど、Google固有の機能は全く対応していない
  • ビルトのインインテントは微妙に異なるが、Googleにもないわけではない。が、現状はすべてカスタムインテントとして実装する必要がある

といった点に加え、

  • Alexaだと開発者アカウントとリンクしてアップロードするだけだが、Googleの場合は手動で多数の事前準備が必要、かつ、プロジェクトごとに行う必要があり、非常に手間
  • 共通のブロックできちんと動かないものがある(Stream Blockなどは少し不具合があると認識しています)

など、Alexa対応と比較すると、非常に弱いんですね。これがプラットフォームごとに独立することで影響しあわなくなれば、一気にGoogle対応が進むことを期待できるのではないかと。まあ個人の勝手な意見ではありますが・・・

ということで、マルチプラットフォーム向けにVoiceflowを使っている/使おうと思っている場合は少し注意が必要ですよ、という話でした。今回のアップデートも含めて、ぜひぜひGoogle対応も進めていってほしいですね!

TalkyJSを触ってみた

f:id:kun432:20200913175615p:plain

昨日の JAWS SONIC 2020 & MIDNIGHT JAWS 2020、Alexa周りのセッションだけ見てました。感想は別途まとめるとして、Alexa Champion伊東さんのセッションで紹介されていた「TalkyJS」を試してみたのでメモ。予めお伝えしておくと、JSもTSも全然詳しくないので深いことは書けません。その点は予めご了承ください。

目次

TalkyJSとは

https://talkyjs.dev/

Alexa Champion岡本さん謹製のrailsライクなフレームワークだそうです。サイトを見ると、以下のような特徴があります。

  • ASK-SDK互換
  • CLIからテンプレートを作成
  • SSMLをJSX(TSX)で書ける
  • 主要なハンドラやインターセプターをビルトイン
  • シチュエーショナルデザインフレンドリー

サンプルコードを見るととてもスッキリ書けるようです。では早速試してみましょう。

cliのインストール

talkyjsのcliをインストールしておきます。

$ npm install -g @talkyjs/cli

プロジェクトの新規作成

なにはともあれ https://talkyjs.dev/docs/get-started/introduction に従ってやってみたいと思います。

HelloWorldなテンプレートが用意されているのでこれを使ってask newします。

$ ask new  --template-url https://github.com/talkyjs/talkyjs-alexa-skill-template-helloworld.git

バックエンドはまずは "AWS Lambda"で。"Alexa-hosted"はそもそもask cliでテンプレートが使えないのでご注意。

? Choose a method to host your skill's backend resources:  AWS Lambda

テンプレートが公式以外の場合は以下のような警告が出ます。"y"で進めます。

[Warn]: CLI is about to download the skill template from unofficial template https://github.com/talkyjs/talkyjs-alexa-skill-template-helloworld.git. Please make sure you understand the source code to best protect yourself from malicious usage.
? Would you like to continue download the skill template?  (y/N) y

スキル名はとりあえずデフォルトで。

? Please type in your skill name:  (talkyjs-alexa-skill-template-helloworld)

プロジェクトのディレクトリ名もとりあえずデフォルトで。

? Please type in your folder name for the skill project (alphanumeric):  (talkyjs-alexa-skill-template-helloworld)

以下のように表示されればOKです。

Project for skill "talkyjs-alexa-skill-template-helloworld" is successfully created at /...(snip).../talkyjs-alexa-skill-template-helloworld

Project initialized with deploy delegate "@ask-cli/cfn-deployer" successfully.

ディレクトリに移動してみましょう。

$ cd talkyjs-alexa-skill-template-helloworld

ディレクトリの構成はこんな感じです。

$ tree -a
.
├── .ask
│   └── ask-states.json
├── .github
│   └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── LICENSE.txt
├── README.md
├── ask-resources.json
├── hooks
│   └── build.sh
├── infrastructure
│   └── cfn-deployer
│       └── skill-stack.yaml
├── lambda
│   ├── .npmignore
│   ├── README.md
│   ├── package.json
│   ├── src
│   │   ├── HelpIntent
│   │   │   ├── HelpIntent.router.ts
│   │   │   ├── HelpIntent.speech.tsx
│   │   │   └── tests
│   │   │       ├── HelpIntent.router.spec.ts
│   │   │       └── HelpIntent.speech.spec.tsx
│   │   ├── LaunchRequest
│   │   │   ├── LaunchRequest.router.ts
│   │   │   ├── LaunchRequest.speech.tsx
│   │   │   └── tests
│   │   │       ├── LaunchRequest.router.spec.ts
│   │   │       └── LaunchRequest.speech.spec.tsx
│   │   ├── StopAndCancelAndNoIntent
│   │   │   ├── StopAndCancelAndNoIntent.router.ts
│   │   │   ├── StopAndCancelAndNoIntent.speech.tsx
│   │   │   └── tests
│   │   │       ├── StopAndCancelAndNoIntent.router.spec.ts
│   │   │       └── StopAndCancelAndNoIntent.speech.spec.tsx
│   │   ├── index.ts
│   │   └── tests
│   │       └── index.spec.ts
│   ├── tsconfig.json
│   └── webpack.config.ts
└── skill-package
    ├── assets
    │   ├── en-US_largeIcon.png
    │   └── en-US_smallIcon.png
    ├── interactionModels
    │   └── custom
    │       └── en-US.json
    └── skill.json

18 directories, 31 files

中身はちょっと置いといて、一旦進めましょう。

デプロイ

lambdaディレクトリに移動して、パッケージをインストールしておきます。

この部分は本来はask deployの中でやってくれるみたいなのですが、私の環境ではask deploy時にビルドに必要なコマンド等へのパスが通っていない、等でコケたので、手動でインストールしてます。

$ cd lambda
$ npm install

ではデプロイします。プロジェクトのトップディレクトリに戻ってask deployします。

$ ask deploy

...snip...

Skill is enabled successfully.

enabled successfullyと表示されればOKです。これで、Alexa側にスキルが作成され、AWS側にLambdaの関数が作成されていればOKです。確認してみましょう。

Alexa開発者コンソールにスキルが作成されています。

f:id:kun432:20200916235612p:plain

インテントもできていますね。ビルトインインテントとHelloWorldIntentがあるのが見えます。

f:id:kun432:20200917000331p:plain

そしてLambdaの方にも作成されています。

f:id:kun432:20200917000814p:plain

実際に動かしてみましょう。Alexa開発者コンソールのテストシミュレータで"hello world"スキルを起動してみてください。

f:id:kun432:20200917001205p:plain

動いてますね!LaunchRequest/HelpIntent/StopIntentを実行してみました。HelloWorldIntentはこのあと追加しますのでまだありません。

routerの追加

routerとはなんぞや?ということなんですが、雑な説明をすると、よりシンプルに書けるリクエストハンドラーのようです。似たようなcanHandle/Handleみたいなのを毎回書くの、めんどくさいですよね。それをシンプルにしてくれます。

talkyjsでは、コマンドラインでrouterを追加することができます。lambdaディレクトリに移動して、以下を実行します。

$ talky g router HelloWorldIntent

対話形式で設定していきます。まずリクエストタイプ。"IntentRequest"を選択します。

? What type do you handle the request? (IntentRequest)

SSMLを何で書くか、を指定できます。通常のSSMLか、JSX(TSX)を選択できます。JSXというのは、Reactで採用されている、JS/TSのコードの中にHTML/XMLを埋め込んで書ける一種のテンプレート、といったもののようで(語弊があったらすみません)そういえば先日のAlexa Live 2020でAPLがJSXで書けるという話もありました。React全く知らないのですが、どういうものか見てみたいので、今回は "tsx" を選択します。

? Which type do you want to write SSML? (Use arrow keys)
  default
❯ tsx

以下のようにファイルの雛形が一通り生成されます。

CREATE src/HelloWorldIntent/HelloWorldIntent.router.ts (500 bytes)
CREATE src/HelloWorldIntent/HelloWorldIntent.speech.tsx (449 bytes)
CREATE src/HelloWorldIntent/tests/HelloWorldIntent.router.spec.ts (1242 bytes)
CREATE src/HelloWorldIntent/tests/HelloWorldIntent.speech.spec.tsx (518 bytes)

少しファイルの中身を覗いていく前に、普通にask-sdkで作った場合のハンドラーはこんな感じです。

const HelloWorldIntentHandler = {
    canHandle(handlerInput) {
        return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
            && Alexa.getIntentName(handlerInput.requestEnvelope) === 'HelloWorldIntent';
    },
    handle(handlerInput) {
        const speakOutput = 'Hello World!';

        return handlerInput.responseBuilder
            .speak(speakOutput)
            //.reprompt('add a reprompt if you want to keep the session open for the user to respond')
            .getResponse();
    }
};

talkyjsのrouterだとこんな感じになります。HelloWorldIntent.router.ts

import { Router } from "@talkyjs/core";

import { HelloWorldIntentScript } from './HelloWorldIntent.speech'


export const HelloWorldIntentRouter: Router = {
    requestType: "IntentRequest",
    intentName: "HelloWorldIntent",
    handler: async (handlerInput) => {


        const script = new HelloWorldIntentScript(handlerInput)
        return script
            .createResponseBuilder()
            .getResponse();


    }
}

canHandleのところがrequestTypeとintentNameだけで表現できるし、メインのロジックを書くhandleのところは今までとそれほど違うわけではありません。すっきりしていてわかりやすいし、以降もしやすそうです。

実際の発話部分はHelloWorldIntent.speech.tsxから読み込まれます。そちらを見てみましょう。

/** @jsx ssml */
import {
    ssml,
    SpeechScriptJSX,
} from '@ask-utils/speech-script'

export class HelloWorldIntentScript extends SpeechScriptJSX {
    speech() {
        return (
            <speak>
                <p>Hello! It's a nice development. How are you?</p>
            </speak>
        )
    }

    reprompt() {
        return (
            <speak>
                <p>How are you?</p>
            </speak>
        )
    }

}

色々書いてありますが、speech()とrepromptのreturnの中身のところです。ここSSMLをベタッと書けるようですね。なんていうかヒアドキュメントっぽい。これまでは普通に文字列として書くパターンが多いと思うのですが、クォートミスってコケるとかありがちですし、そもそもまず見にくい、ということで、スッキリ書けて見れるのはとても良いですね。値を渡して埋め込んだものを発話させるというのももちろんできるようです。

追加したルータ(ハンドラー)は、ask-sdkの場合と同じく、skillBuilderハンドラにリクエストハンドラとして追加する必要があります。ここもTalkyjsがラップしてくれていて、src/index.tsにこれまでと同じような形で追加するだけです。また、src/index.tsでは、永続アトリビュートに使用するデータベースの設定ができたり、development/productionなど環境によってデバッグ機能の有効・無効を制御できたりするようですが、今回は割愛します。

以下の2箇所に追加します。

import { SkillFactory, TalkyJSSkillConfig, SkillHandler } from '@talkyjs/core'
import { CustomSkillBuilder } from 'ask-sdk-core'
import { LaunchRequestRouter } from './LaunchRequest/LaunchRequest.router'
import { HelpIntentRouter } from './HelpIntent/HelpIntent.router'
import { StopAndCancelAndNoIntentRouter } from './StopAndCancelAndNoIntent/StopAndCancelAndNoIntent.router'
import { HelloWorldIntentRouter } from './HelloWorldIntent/HelloWorldIntent.router'           // ★追加

...snip...

/**
 * Skill Factory (Added preset handler)
 */
export const skillFactory = SkillFactory.launch(config)
.addRequestRouters([
    LaunchRequestRouter,
    HelpIntentRouter,
    StopAndCancelAndNoIntentRouter,
    HelloWorldIntentRouter,      // ★追加
])

...snip...

最後にちょっとおまけで、HelloWorldスキルっぽく全体的な発話を修正していきましょう。

src/HelloWorldIntent/HelloWorldIntent.speech.tsx

export class HelloWorldIntentScript extends SpeechScriptJSX {
    speech() {
        return (
            <speak>
                <p>Hello World. It's a nice development. You can say hello to me.</p>
            </speak>
        )
    }

    reprompt() {
        return (
            <speak>
                <p>Say hello to me.</p>
            </speak>
        )
    }

}

src/LaunchRequest/LaunchRequest.speech.tsx

/** @jsx ssml */
import {
    ssml,
    SpeechScriptJSX,
} from '@ask-utils/speech-script'

export class LaunchRequestScript extends SpeechScriptJSX {
    speech() {
        return (
            <speak>
                <p>Welcome to Hello World. you can say hello to me.</p>
            </speak>
        )
    }

    reprompt() {
        return (
            <speak>
                <p>Say hello to me.</p>
            </speak>
        )
    }

}

src/HelpIntent/HelpIntent.speech.tsx

/** @jsx ssml */
import {
    ssml,
    SpeechScriptJSX,
} from '@ask-utils/speech-script'

export class HelpIntentScript extends SpeechScriptJSX {
    speech() {
        return (
            <speak>
                <p>In this skill, when you say hello, I will say hello. Say hello to me.</p>
            </speak>
        )
    }

    reprompt() {
        return (
            <speak>
                <p>Say hello to me.</p>
            </speak>
        )
    }

}

では、再度デプロイします。

$ ask deploy

テストします。

f:id:kun432:20200917023747p:plain

うまくいきました!

まとめ

まずはかんたんに動かしてみるところまでやってみました。まだ全然さわりの部分しか見えてないですが、シンプルに書けてCLIでサクッとハンドラ追加できたりして良さそうだなぁという印象です。ReactもTSも全然知らないレベルなので、以前作ったシンプルなスキルをお試しで移行しつつ、色々触ってみようかなと思ってます。とりあえず日本語のHelloWorldテンプレート作ろうかな〜。

他にもたくさん便利な機能があるようなので、詳細はドキュメントを御覧ください。