Help us understand the problem. What is going on with this article?

C# で Clova スキルを開発する

More than 1 year has passed since last update.

本記事では CEK C# SDK で Clova スキルのサーバー部分を開発する方法を紹介します。

この記事では紹介しないこと

  • CEK とは何か
  • 対話スキルについて

これらのことは #cek タグで調べれば、多くの分かりやすい記事があるため、説明は割愛します。

  • Web API のホスティング詳細
  • dotnet core Web API の詳細

開発したサーバー側コードは、パブリックにアクセスできる場所に配置します。普段は Azure を使っていますが、dotnet core が動作すればどこでもいいです。

前提条件

基礎となるプロジェクトの準備

1. ターミナルより新しく dotnet のプロジェクトを作成。

dotnet new webapi -n FirstCEK

2. SDK の読込み。

cd FirstCEK
dotnet add package CEK.CSharp
dotnet build

3. VS Code でプロジェクトを開く。

code .

4. Controllers 配下の ValuesController.cs を ClovaController.cs にリネームして、以下のコードに差し替え。参照した C# の SDK と ClovaClient を作成。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using CEK.CSharp;
using CEK.CSharp.Models;

namespace FirstCEK.Controllers
{
    [Route("api/[controller]")]
    public class ClovaController : Controller
    {
        private ClovaClient client;
        public ClovaController()
        {
            client = new ClovaClient();
        }

        // POST api/clova
        [HttpPost]
        public async Task<IActionResult> Post()
        {
            return new OkObjectResult(true);
        }
    }
}

以上でまずはサーバー側の準備が整いました。

クローバーからの通信を処理

メッセージの確認とパース

まずクローバーから送られてきた通信に対して、以下の処理を行います。

  • 署名の確認
  • 受け取ったデータのパース

SDK を使うと以下のコードでこれらの処理が出来ます。

CEKRequest request = await client.GetRequest(
    Request.Headers["SignatureCEK"], 
    Request.Body
);

リクエストタイプごとのハンドリング

次にどのようなリクエストが送られてきたかを確認します。CEK のリクエストタイプは全部で 4 種類あります。

LaunchRequest : ユーザーが特定のExtensionの使用を開始したことを示す。
IntentRequest : 解析されたユーザーのリクエストを渡す。
SessionEndedRequest : ユーザーの特定のスキルの使用が終了したことを示す。
EventRequest : クライアントの状態の変化や、それに伴うリクエストをExtensionに渡すために使用される。

※EventRequest はオーディオコンテンツ系の処理で使うようなので、今回は割愛します。

イベントのタイプはパースされた request オブジェクトの Request.Type プロパティで取得できます。

switch (request.Request.Type)
{
    case RequestType.LaunchRequest:
        break;
    case RequestType.SessionEndedRequest:
        break;
    case RequestType.IntentRequest:
        switch (request.Request.Intent.Name)
        {
            case "Clova.YesIntent":
                break;
            case "Clova.NoIntent":
                break;
            case "Clova.GuideIntent":
                break;
        }
        break;
}

セッション情報の取得

ユーザーとのマルチターン対話を行う際にセッションに格納した値は GetSessionAttribute メソッドで取得可能です。既定値を渡すことでデータがない場合の値を指定します。

var mySessionValue = request.GetSessionAttribute(
    "mySessionKey", 
    "defaultValue"
);

クローバーへの返信

受信内容を確認したら、次に返信を作成してクローバーに返します。

CEKResponse オブジェクト

クローバーに対しての返信は CEKResponse オブジェクトを作成して OKObjectResult に渡します。

CEKResponse response = new CEKResponse();
...
return new OkObjectResult(response);

音声テキストまたは音声ファイルの指定

クローバーに発話させる内容として、テキストまたは音声ファイルを指定できます。テキストの場合は発話する言語が指定可能です。言語指定を省略すると日本語になります。
また複数のテキストや URL を指定できます。

response.AddText("スキルへようこそ", Lang.Ja);
response.AddUrl("https://clova-common.line-scdn.net/dice/rolling_dice_sound.mp3");

単純なテキストではなく、要約音声情報と詳細音声情報を渡したい場合は、要約は AddBriefText/AddBriefUrl、詳細は AddVerboseText/AddVerboseUrl メソッドを使います。

response.AddBriefText("天気予報です。");
response.AddVerboseText("週末まで全国に梅雨…猛暑和らぐ。");
response.AddVerboseText("明日全国的に梅雨…ところによって局地的に激しい雨に注意。");                            

タイムアウト時の追加メッセージ

ユーザーインプットがないまま一定時間が経過した場合は、AddRepromptText/AddRepromptUrl で追加のメッセージを発話できます。

response.AddRepromptText("何か言ってください。");                            

セッション情報の格納

ユーザーとのマルチターン対話を行う際にセッションに情報を格納する場合、AddSession メソッドを使います。

response.AddSession("mySessionKey", "mySessionValue");

マルチターン会話の指定

会話が引き続きユーザー入力を期待する場合、response.ShouldEndSession プロパティを true に指定します。

response.ShouldEndSession = true;

サイコロサンプルを実装してみる

サンプルサイコロスキル は Node.js で書かれた CEK サンプルですが、ここでは C# で書き直してみます。動くかどうかは、実際に試してください。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using CEK.CSharp;
using CEK.CSharp.Models;

namespace FirstCEK.Controllers
{
    [Route("api/[controller]")]
    public class ClovaController : Controller
    {
        private ClovaClient client;
        public ClovaController()
        {
            client = new ClovaClient();
        }

        // POST api/clova
        [HttpPost]
        public async Task<IActionResult> Post()
        {
            CEKRequest request = await client.GetRequest(
                Request.Headers["SignatureCEK"],
                Request.Body
            );

            CEKResponse response = new CEKResponse();
            switch (request.Request.Type)
            {
                case RequestType.LaunchRequest:
                    response.AddText("いくつのサイコロを投げますか?");
                    response.AddSession("intent", "ThrowDiceIntent");
                    response.ShouldEndSession = false;
                    break;
                case RequestType.SessionEndedRequest:
                    response.AddText("サイコロを終了します。");
                    break;
                case RequestType.IntentRequest:
                    switch (request.Request.Intent.Name)
                    {
                        case "ThrowDiceIntent":
                            var diceCount = request.Request.Intent.Slots.ContainsKey("diceCount") ?
                                int.Parse(request.Request.Intent.Slots["diceCount"].ToString()) : 1;
                            response.AddText($"サイコロを{diceCount}個投げます。");
                            response.AddUrl("https://clova-common.line-scdn.net/dice/rolling_dice_sound.mp3");
                            var throwResult = ThrowDice(diceCount);
                            response.AddText($"{diceCount}個のサイコロの合計は{throwResult}です。");
                            break;
                        case "Clova.YesIntent":
                            break;
                        case "Clova.NoIntent":
                            break;
                        case "Clova.GuideIntent":
                            response.AddText("サイコロを1個投げて、と言ってみてください。");
                            response.ShouldEndSession = false;
                            break;
                    }
                    break;
            }
            return new OkObjectResult(response);
        }

        private int ThrowDice(int diceCount)
        {
            var totalCount = 0;
            for (int i = 0; i < diceCount; i++)
            {
                totalCount += (new Random()).Next(1, 6);
            }

            return totalCount;
        }
    }
}

まとめ

CEK のサーバー側開発は結構シンプルですが、内部ロジックをどうするかによって面白いスキルも開発できるはずですので、是非色々なアイデアを試してください。

参照

CEK ドキュメント
CEK C# SDK
CEK サンプルサイコロスキル

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away