スキル内でダイアログを使用する

この記事の対象: SDK v4

この記事では、複数のアクションをサポートするスキルを作成する方法について説明します。 これらのアクションは、ダイアログを使用してサポートされます。 メイン ダイアログでは、スキル コンシューマーから最初の入力を受信すると、適切なアクションが開始されます。 関連するサンプル コードのスキル コンシューマーを実装する方法の詳細については、ダイアログを使用してスキルを利用する方法を参照してください。

この記事では、スキルの作成に既に精通していることを前提としています。 スキル ボットの一般的な作成方法については、スキルを実装する方法を参照してください。

Note

Bot Framework JavaScript SDK、C#、Python SDK は引き続きサポートされますが、Java SDK については、最終的な長期サポートは 2023 年 11 月に終了する予定です。 このリポジトリ内の重要なセキュリティとバグの修正のみが行われます。

Java SDK を使用して構築された既存のボットは引き続き機能します。

新しいボットの構築については、Power Virtual Agents の使用を検討し、適切なチャットボット ソリューションの選択についてお読みください。

詳細については、「The future of bot building」をご覧ください。

前提条件

Note

Language Understanding (LUIS) は、2025 年 10 月 1 日に廃止されます。 2023 年 4 月 1 日以降は、新しい LUIS リソースを作成することはできません。 より新しいバージョンの言語理解が、現在、Azure AI Language の一部として提供されています。

Azure AI Language の機能である会話言語理解 (CLU) は、LUIS の更新バージョンです。 Bot Framework SDK での言語理解のサポートの詳細については、「自然言語の理解」を参照してください。

このサンプルについて

skills skillDialog サンプルには、次の 2 つのボットのプロジェクトが含まれています。

  • "ダイアログ ルート ボット"。"スキル ダイアログ" クラスを使用してスキルを利用します。
  • "ダイアログ スキル ボット"。ダイアログを使用して、スキル コンシューマーからのアクティビティを処理します。 このスキルは、コア ボット サンプルを適応させたものです (コア ボットの詳細については、「ボットに自然言語の理解を追加する」を参照してください)。

この記事では、スキル ボット内でダイアログを使用して複数のアクションを管理する方法について説明します。

スキル コンシューマー ボットの詳細については、ダイアログを使用してスキルを利用する方法を参照してください。

リソース

デプロイ済みのボットの場合、ボット間認証では、参加している各ボットが有効な ID を保持している必要があります。 ただし、ID 情報なしで Bot Framework Emulator を使用して、ローカルでスキルとスキル コンシューマーをテストできます。

ユーザー向けボットでスキルを使用できるようにするには、スキルを Azure に登録します。 詳細については、Azure AI Bot Service にボットを登録する方法に関する記事を参照してください。

必要に応じて、スキル ボットではフライト予約 LUIS モデルを使用できます。 このモデルを使用するには、CognitiveModels/FlightBooking.json ファイルを使用して、LUIS モデルを作成し、トレーニングし、発行します。

アプリケーション構成

  1. 必要に応じて、スキルの ID 情報をスキルの構成ファイルに追加します。 (スキルまたはスキル コンシューマーのいずれかで ID が指定されている場合は、両方で指定が必要です)。

  2. LUIS モデルを使用する場合は、LUIS アプリ ID、API キー、および API ホスト名を追加します。

DialogSkillBot\appsettings.json

{
  "MicrosoftAppType": "",
  "MicrosoftAppId": "",
  "MicrosoftAppPassword": "",
  "MicrosoftAppTenantId": "",
  "ConnectionName": "",

  "LuisAppId": "",
  "LuisAPIKey": "",
  "LuisAPIHostName": "",

  // This is a comma separate list with the App IDs that will have access to the skill.
  // This setting is used in AllowedCallersClaimsValidator.
  // Examples: 
  //    [ "*" ] allows all callers.
  //    [ "AppId1", "AppId2" ] only allows access to parent bots with "AppId1" and "AppId2".
  "AllowedCallers": [ "*" ]
}

アクティビティルーティング ロジック

このスキルでは、いくつかの異なる機能がサポートされています。 フライトを予約したり、都市の天気を取得したりできます。 また、これらのいずれかのコンテキストの外部でメッセージを受信した場合は、LUIS を使用してメッセージの解釈を試みることができます。 スキルのマニフェストには、これらのアクション、その入力パラメーターと出力パラメーター、スキルのエンドポイントが記述されています。 注目すべき点として、このスキルでは "BookFlight" イベントや "GetWeather" イベントを処理できます。 また、メッセージ アクティビティを処理することもできます。

このスキルでは、アクティビティルーティング ダイアログが定義されており、スキル コンシューマーからの最初の受信アクティビティに基づいて、どのアクションを開始するかを選択するために使用されます。 LUIS モデルが提供されている場合は、それによって、最初のメッセージでフライトの予約の意図や天気の取得の意図が認識されます。

フライト予約アクションは、個別のダイアログとして実装された、複数ステップ プロセスです。 このアクションが開始されると、そのダイアログによって受信アクティビティが処理されます。 天気取得アクションには、完全に実装されたボットで置き換えられるプレースホルダー ロジックがあります。

アクティビティルーティング ダイアログには、次のことを行うコードが含まれています。

スキルで使用されるダイアログは、コンポーネント ダイアログ クラスから継承されたものです。 コンポーネント ダイアログの詳細については、ダイアログの複雑さを管理する方法を参照してください。

ダイアログを初期化する

アクティビティルーティング ダイアログには、フライトを予約するための子ダイアログが含まれています。 メイン ウォーターフォール ダイアログには、受信した最初のアクティビティに基づいてアクションを開始する 1 つのステップがあります。

また、LUIS 認識エンジンも受け入れます。 この認識エンジンが初期化されている場合、ダイアログではそれを使用して最初のメッセージ アクティビティの意図が解釈されます。

DialogSkillBot\Dialogs\ActivityRouterDialog.cs

private readonly DialogSkillBotRecognizer _luisRecognizer;

public ActivityRouterDialog(DialogSkillBotRecognizer luisRecognizer)
    : base(nameof(ActivityRouterDialog))
{
    _luisRecognizer = luisRecognizer;

    AddDialog(new BookingDialog());
    AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[] { ProcessActivityAsync }));

    // The initial child Dialog to run.
    InitialDialogId = nameof(WaterfallDialog);
}

最初のアクティビティを処理する

メイン ウォーターフォール ダイアログの最初の (および唯一の) ステップでは、受信アクティビティの種類がスキルによって確認されます。

  • イベント アクティビティは、イベントの名前に基づいて適切なアクションを開始する on event activity ハンドラーに転送されます。
  • メッセージ アクティビティは、何をすべきかを決定する前に追加の処理を実行する on message activity ハンドラーに転送されます。

受信アクティビティの種類またはイベントの名前を認識できない場合、スキルはエラー メッセージを送信して終了します。

DialogSkillBot\Dialogs\ActivityRouterDialog.cs

private async Task<DialogTurnResult> ProcessActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    // A skill can send trace activities, if needed.
    await stepContext.Context.TraceActivityAsync($"{GetType().Name}.ProcessActivityAsync()", label: $"Got ActivityType: {stepContext.Context.Activity.Type}", cancellationToken: cancellationToken);

    switch (stepContext.Context.Activity.Type)
    {
        case ActivityTypes.Event:
            return await OnEventActivityAsync(stepContext, cancellationToken);

        case ActivityTypes.Message:
            return await OnMessageActivityAsync(stepContext, cancellationToken);

        default:
            // We didn't get an activity type we can handle.
            await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Unrecognized ActivityType: \"{stepContext.Context.Activity.Type}\".", inputHint: InputHints.IgnoringInput), cancellationToken);
            return new DialogTurnResult(DialogTurnStatus.Complete);
    }
}
// This method performs different tasks based on the event name.
private async Task<DialogTurnResult> OnEventActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var activity = stepContext.Context.Activity;
    await stepContext.Context.TraceActivityAsync($"{GetType().Name}.OnEventActivityAsync()", label: $"Name: {activity.Name}. Value: {GetObjectAsJsonString(activity.Value)}", cancellationToken: cancellationToken);

    // Resolve what to execute based on the event name.
    switch (activity.Name)
    {
        case "BookFlight":
            return await BeginBookFlight(stepContext, cancellationToken);

        case "GetWeather":
            return await BeginGetWeather(stepContext, cancellationToken);

        default:
            // We didn't get an event name we can handle.
            await stepContext.Context.SendActivityAsync(MessageFactory.Text($"Unrecognized EventName: \"{activity.Name}\".", inputHint: InputHints.IgnoringInput), cancellationToken);
            return new DialogTurnResult(DialogTurnStatus.Complete);
    }
}

メッセージ アクティビティを処理する

LUIS 認識エンジンが構成されている場合、スキルでは LUIS が呼び出され、次に意図に基づいてアクションが開始されます。 LUIS 認識エンジンが構成されていない場合、または意図がサポートされていない場合、スキルはエラー メッセージを送信して終了します。

DialogSkillBot\Dialogs\ActivityRouterDialog.cs

// This method just gets a message activity and runs it through LUIS. 
private async Task<DialogTurnResult> OnMessageActivityAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var activity = stepContext.Context.Activity;
    await stepContext.Context.TraceActivityAsync($"{GetType().Name}.OnMessageActivityAsync()", label: $"Text: \"{activity.Text}\". Value: {GetObjectAsJsonString(activity.Value)}", cancellationToken: cancellationToken);

    if (!_luisRecognizer.IsConfigured)
    {
        await stepContext.Context.SendActivityAsync(MessageFactory.Text("NOTE: LUIS is not configured. To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' to the appsettings.json file.", inputHint: InputHints.IgnoringInput), cancellationToken);
    }
    else
    {
        // Call LUIS with the utterance.
        var luisResult = await _luisRecognizer.RecognizeAsync<FlightBooking>(stepContext.Context, cancellationToken);

        // Create a message showing the LUIS results.
        var sb = new StringBuilder();
        sb.AppendLine($"LUIS results for \"{activity.Text}\":");
        var (intent, intentScore) = luisResult.Intents.FirstOrDefault(x => x.Value.Equals(luisResult.Intents.Values.Max()));
        sb.AppendLine($"Intent: \"{intent}\" Score: {intentScore.Score}");

        await stepContext.Context.SendActivityAsync(MessageFactory.Text(sb.ToString(), inputHint: InputHints.IgnoringInput), cancellationToken);

        // Start a dialog if we recognize the intent.
        switch (luisResult.TopIntent().intent)
        {
            case FlightBooking.Intent.BookFlight:
                return await BeginBookFlight(stepContext, cancellationToken);

            case FlightBooking.Intent.GetWeather:
                return await BeginGetWeather(stepContext, cancellationToken);

            default:
                // Catch all for unhandled intents.
                var didntUnderstandMessageText = $"Sorry, I didn't get that. Please try asking in a different way (intent was {luisResult.TopIntent().intent})";
                var didntUnderstandMessage = MessageFactory.Text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
                await stepContext.Context.SendActivityAsync(didntUnderstandMessage, cancellationToken);
                break;
        }
    }

    return new DialogTurnResult(DialogTurnStatus.Complete);
}

複数ステップのアクションを開始する

フライト予約アクションでは、ユーザーから予約の詳細を取得するために、複数ステップ ダイアログが開始されます。

天気取得アクションは実装されていません。 現時点では、プレースホルダー メッセージが送信され、終了します。

DialogSkillBot\Dialogs\ActivityRouterDialog.cs

private async Task<DialogTurnResult> BeginBookFlight(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var activity = stepContext.Context.Activity;
    var bookingDetails = new BookingDetails();
    if (activity.Value != null)
    {
        bookingDetails = JsonConvert.DeserializeObject<BookingDetails>(JsonConvert.SerializeObject(activity.Value));
    }

    // Start the booking dialog.
    var bookingDialog = FindDialog(nameof(BookingDialog));
    return await stepContext.BeginDialogAsync(bookingDialog.Id, bookingDetails, cancellationToken);
}
private static async Task<DialogTurnResult> BeginGetWeather(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
    var activity = stepContext.Context.Activity;
    var location = new Location();
    if (activity.Value != null)
    {
        location = JsonConvert.DeserializeObject<Location>(JsonConvert.SerializeObject(activity.Value));
    }

    // We haven't implemented the GetWeatherDialog so we just display a TODO message.
    var getWeatherMessageText = $"TODO: get weather for here (lat: {location.Latitude}, long: {location.Longitude}";
    var getWeatherMessage = MessageFactory.Text(getWeatherMessageText, getWeatherMessageText, InputHints.IgnoringInput);
    await stepContext.Context.SendActivityAsync(getWeatherMessage, cancellationToken);
    return new DialogTurnResult(DialogTurnStatus.Complete);
}

結果を返す

このスキルでは、フライト予約アクションの予約ダイアログが開始されます。 アクティビティルーティング ダイアログにはステップが 1 つしかないため、予約ダイアログが終了すると、アクティビティルーティング ダイアログも終了し、予約ダイアログからのダイアログの結果がアクティビティルーティング ダイアログのダイアログの結果になります。

天気取得アクションは、戻り値を設定することなくそのまま終了します。

複数ステップ アクションを取り消す

予約ダイアログとその子の日付リゾルバー ダイアログは両方とも、ユーザーからのメッセージをチェックする基本的なキャンセルおよびヘルプ ダイアログから派生します。

  • "ヘルプ" または "?" の場合は、ヘルプ メッセージが表示され、次のターンで会話フローが続行されます。
  • "キャンセル" または "終了" の場合は、すべてのダイアログがキャンセルされ、それによってスキルが終了します。

詳細については、ユーザーによる割り込みを処理する方法を参照してください。

サービス登録

このスキルに必要なサービスは、一般的に、スキル ボットに必要なものと同じです。 必要なサービスについては、スキルを実装する方法を参照してください。

スキル マニフェスト

"スキル マニフェスト" は、スキルが実行できるアクティビティ、その入力パラメーターと出力パラメーター、およびスキルのエンドポイントが記述された JSON ファイルです。 マニフェストには、別のボットからスキルにアクセスするために必要な情報が含まれています。

DialogSkillBot\wwwroot\manifest\dialogchildbot-manifest-1.0.json

{
  "$schema": "https://schemas.botframework.com/schemas/skills/skill-manifest-2.0.0.json",
  "$id": "DialogSkillBot",
  "name": "Skill bot with dialogs",
  "version": "1.0",
  "description": "This is a sample skill definition for multiple activity types.",
  "publisherName": "Microsoft",
  "privacyUrl": "https://dialogskillbot.contoso.com/privacy.html",
  "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.",
  "license": "",
  "iconUrl": "https://dialogskillbot.contoso.com/icon.png",
  "tags": [
    "sample",
    "travel",
    "weather",
    "luis"
  ],
  "endpoints": [
    {
      "name": "default",
      "protocol": "BotFrameworkV3",
      "description": "Default endpoint for the skill.",
      "endpointUrl": "https://dialogskillbot.contoso.com/api/messages",
      "msAppId": "00000000-0000-0000-0000-000000000000"
    }
  ],
  "activities": {
    "bookFlight": {
      "description": "Books a flight (multi turn).",
      "type": "event",
      "name": "BookFlight",
      "value": {
        "$ref": "#/definitions/bookingInfo"
      },
      "resultValue": {
        "$ref": "#/definitions/bookingInfo"
      }
    },
    "getWeather": {
      "description": "Retrieves and returns the weather for the user's location.",
      "type": "event",
      "name": "GetWeather",
      "value": {
        "$ref": "#/definitions/location"
      },
      "resultValue": {
        "$ref": "#/definitions/weatherReport"
      }
    },
    "passthroughMessage": {
      "type": "message",
      "description": "Receives the user's utterance and attempts to resolve it using the skill's LUIS models.",
      "value": {
        "type": "object"
      }
    }
  },
  "definitions": {
    "bookingInfo": {
      "type": "object",
      "required": [
        "origin"
      ],
      "properties": {
        "origin": {
          "type": "string",
          "description": "This is the origin city for the flight."
        },
        "destination": {
          "type": "string",
          "description": "This is the destination city for the flight."
        },
        "travelDate": {
          "type": "string",
          "description": "The date for the flight in YYYY-MM-DD format."
        }
      }
    },
    "weatherReport": {
      "type": "array",
      "description": "Array of forecasts for the next week.",
      "items": [
        {
          "type": "string"
        }
      ]
    },
    "location": {
      "type": "object",
      "description": "Location metadata.",
      "properties": {
        "latitude": {
          "type": "number",
          "title": "Latitude"
        },
        "longitude": {
          "type": "number",
          "title": "Longitude"
        },
        "postalCode": {
          "type": "string",
          "title": "Postal code"
        }
      }
    }
  }
}

"スキル マニフェスト スキーマ" は、スキル マニフェストのスキーマが記述された JSON ファイルです。 最新のスキーマ バージョンは v2.1 です。

スキル ボットをテストする

スキル コンシューマーを使用してエミュレーターでスキルをテストすることができます。 これを行うには、スキル ボットとスキル コンシューマー ボットを両方同時に実行する必要があります。 スキルを構成する方法については、ダイアログを使用してスキルを利用する方法を参照してください。

最新の Bot Framework Emulator をダウンロードしてインストールします。

  1. ダイアログ スキル ボットとダイアログ ルート ボットをご利用のマシンでローカルに実行します。 手順が必要な場合は、サンプルの README ファイルで C#JavaScriptJava、または Python の箇所を参照してください。
  2. エミュレーターを使用してボットをテストします。
    • 最初に会話に参加すると、ボットによってウェルカム メッセージが表示され、呼び出したいスキルをたずねられます。 このサンプルのスキル ボットには、スキルは 1 つしかありません。
    • [DialogSkillBot] を選択します。
  3. 次に、スキルのアクションを選択するようにボットから求められます。 [BookFlight] を選択します。
    1. スキルでは、そのフライト予約アクションが開始されます。プロンプトに応答してください。
    2. スキルが完了すると、ルート ボットによって、予約の詳細が表示され、その後、呼び出したいスキルを入力するよう再度求められます。
  4. もう一度 [DialogSkillBot] を選択し、[BookFlight] を選択します。
    1. 最初のプロンプトに応答し、次に「cancel」と入力してアクションを取り消します。
    2. スキル ボットはアクションを完了せずに終了します。呼び出したいスキルを入力するようコンシューマーから求められます。

デバッグの詳細

スキルとスキル コンシューマーの間のトラフィックは認証されるため、このようなボットをデバッグする際には追加の手順があります。

  • スキル コンシューマーおよびそれが使用するすべてのスキルが、直接または間接的に実行されている必要があります。
  • ボットがローカルで実行されており、いずれかのボットにアプリ ID とパスワードが設定されている場合は、すべてのボットに有効な ID とパスワードが必要です。
  • ボットがすべてデプロイ済みである場合は、ngrok を使用して任意のチャネルからボットをデバッグする方法をご覧ください。
  • 一部のボットがローカルで実行されており、一部がデプロイ済みである場合は、スキルまたはスキル コンシューマーをデバッグする方法に関する記事をご覧ください。

それ以外の場合は、その他のボットをデバッグするのと同様に、スキル コンシューマーまたはスキルをデバッグできます。 詳細については、ボットのデバッグに関する記事、およびBot Framework Emulator を使用したデバッグに関する記事を参照してください。

追加情報