共用方式為


使用技能內的對話框

適用於: SDK v4

本文示範如何建立支援多個動作的技能。 它支援使用對話框的這些動作。 主要對話框會接收技能取用者的初始輸入,然後啟動適當的動作。 如需實作相關範例程式代碼技能取用者的相關信息,請參閱如何使用對話框用技能。

本文假設您已熟悉建立技能。 如需如何一般建立技能 Bot,請參閱如何 實作技能

注意

Bot Framework JavaScript、C# 和 Python SDK 將會繼續受到支援,不過,Java SDK 即將淘汰,最終長期支援將於 2023 年 11 月結束。

使用 Java SDK 建置的現有 Bot 將繼續運作。

針對新的 Bot 建置,請考慮使用 Microsoft Copilot Studio ,並閱讀 選擇正確的 Copilot 解決方案

如需詳細資訊,請參閱 Bot 建置的未來。

必要條件

注意

Language Understanding (LUIS) 將於 2025 年 10 月 1 日淘汰。 從 2023 年 4 月 1 日起,您將無法建立新的 LUIS 資源。 新版的語言理解現在已提供作為 Azure AI 語言的一部分。

對話式語言理解(CLU)是 Azure AI 語言的一項功能,是 LUIS 的更新版本。 如需 Bot Framework SDK 中語言理解支援的詳細資訊,請參閱 自然語言理解

關於此範例

技能技能Dialog 範例包含兩個 Bot 的專案:

  • 對話根 Bot,它會使用技能對話類別來取用技能。
  • 對話 技能 Bot,它會使用對話來處理來自技能取用者的活動。 此技能是核心 Bot 範例的改編。 (如需核心 Bot 的詳細資訊,請參閱如何將 自然語言理解新增至 Bot

本文著重於如何使用技能 Bot 內的對話框來管理多個動作。

如需技能取用者 Bot 的相關信息,請參閱如何使用對話框用技能。

資源

對於已部署的 Bot,Bot 對 Bot 驗證會要求每個參與的 Bot 都有有效的身分識別。 不過,您可以使用 Bot Framework 模擬器在本機測試技能和技能取用者,而不需要身分識別資訊。

若要讓技能可供使用者面向的 Bot 使用,請向 Azure 註冊技能。 如需詳細資訊,請參閱如何使用 Azure AI Bot Service 註冊 Bot。

或者,技能 Bot 可以使用正式發行前小眾測試版 LUIS 模型。 若要使用此模型,請使用 CognitiveModels/FlightBooking.json 檔案來建立、定型及發佈 LUIS 模型。

應用程式設定

  1. 或者,將技能的身分識別資訊新增至技能的組態檔。 (如果技能或技能取用者指定身分識別,則兩者都必須。

  2. 如果您使用 LUIS 模型,請新增 LUIS 應用程式識別碼、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 模型可以在初始訊息中辨識書籍正式發行前小眾測試版和取得天氣意圖。

書籍正式發行前小眾測試版動作是一個多步驟程式,會實作為個別對話。 動作開始后,該對話框會處理傳入的活動。 get-weather 動作具有將在完全實作的 Bot 中取代的佔位符邏輯。

活動路由對話框包含下列項目的程式代碼:

技能中使用的對話會繼承自 元件對話 類別。 如需元件對話的詳細資訊,請參閱如何 管理對話複雜度

初始化對話框

活動路由對話框包含用於預訂航班的子對話。 主要瀑布式對話有一個步驟,會根據收到的初始活動啟動動作。

它也接受 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);
}

處理初始活動

在主要瀑布對話的第一個步驟中,技能會檢查傳入的活動類型。

  • 事件活動會轉送至 事件活動 處理程式上的 ,此處理程式會根據事件的名稱啟動適當的動作。
  • 訊息活動會轉送至 訊息活動 處理程式上的 ,該處理程式會在決定該怎麼做之前執行其他處理。

如果技能無法辨識傳入活動的類型或事件的名稱,則會傳送錯誤訊息並結束。

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);
}

開始多步驟動作

預訂正式發行前小眾測試版動作會啟動多步驟對話框,以從使用者取得預訂詳細數據。

未實作 get-weather 動作。 目前,它會傳送佔位元元訊息,然後結束。

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);
}

傳回結果

技能會啟動預訂對話框,以進行預訂班機動作。 由於活動路由對話只有一個步驟,所以當預約對話結束時,活動路由對話框也會結束,而預約對話中的對話框結果會變成活動路由對話框的對話結果。

get-weather 動作只會結束而不設定傳回值。

取消多步驟動作

預約對話方塊及其子日期解析程式對話框都衍生自基底取消與說明對話框,其會檢查來自使用者的訊息。

  • 在 [說明] 或 [?“] 上,它會顯示說明訊息,然後在下列回合繼續對話流程。
  • 在 [取消] 或 [結束] 上,它會取消所有結束技能的對話框。

如需詳細資訊,請參閱如何處理 用戶中斷

服務註冊

此技能所需的服務與一般技能 Bot 所需的服務相同。 如需必要服務的討論,請參閱如何 實作技能

技能指令清單

技能指令清單是 JSON 檔案,描述技能可執行的活動、其輸入和輸出參數,以及技能的端點。 指令清單包含從另一個 Bot 存取技能所需的資訊。

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

您可以使用技能取用者在模擬器中測試技能。 若要這樣做,您必須同時執行技能與技能取用者 Bot。 如需如何設定技能的相關信息,請參閱如何使用 對話框來取用技能

下載並安裝最新的 Bot Framework 模擬器

  1. 在本機計算機上執行對話技能 Bot 和對話根 Bot。 如果您需要指示,請參閱範例的 READMEC#JavaScriptJava Python 檔案。
  2. 使用模擬器來測試 Bot。
    • 當您第一次加入交談時,Bot 會顯示歡迎訊息,並詢問您想要呼叫的技能。 此範例的技能 Bot 只有一個技能。
    • 選取 DialogSkillBot
  3. 接下來,Bot 會要求您選擇技能的動作。 選擇 [BookFlight]。
    1. 技能開始其預訂飛行動作:回答提示。
    2. 技能完成時,根 Bot 會顯示預約詳細數據,然後再次提示您想要呼叫的技能。
  4. 再次選取 DialogSkillBot 和 “BookFlight”。
    1. 回答第一個提示,然後輸入 「cancel」 以取消動作。
    2. 技能 Bot 會在不完成動作的情況下結束,而取用者會提示您輸入您想要呼叫的技能。

深入瞭解偵錯

由於技能與技能取用者之間的流量已經過驗證,因此偵錯這類 Bot 時會有額外的步驟。

  • 技能取用者及其直接或間接取用的所有技能都必須執行。
  • 如果 Bot 在本機執行,且任何 Bot 都有應用程式識別碼和密碼,則所有 Bot 都必須具有有效的標識碼和密碼。
  • 如果 Bot 全部部署,請參閱如何使用 devtunnel 從任何通道偵錯 Bot。
  • 如果某些 Bot 在本機執行,且有些 Bot 已部署,請參閱如何偵錯技能或技能取用

否則,您可以偵錯技能取用者或技能,就像偵錯其他 Bot 一樣。 如需詳細資訊,請參閱使用 Bot Framework 模擬器進行 Bot 偵錯和偵錯。

其他資訊