教學課程:在 .NET 聊天機器人中使用個人化工具

重要

從 2023 年 9 月 20 日起,您將無法建立新的個人化工具資源。 個人化工具服務將于 2026 年 10 月 1 日淘汰。

使用 C# .NET 聊天機器人搭配個人化工具迴圈,為使用者提供正確的內容。 此聊天機器人會向使用者建議特定的咖啡或茶。 使用者可以接受或拒絕該建議。 這可提供個人化工具資訊,以協助更適當地提出下一個建議。

在本教學課程中,您將瞭解如何:

  • 設定 Azure 資源
  • 設定和執行 Bot
  • 使用 Bot Framework 模擬器與 Bot 互動
  • 瞭解 Bot 使用個人化工具的位置和方式

聊天機器人的運作方式為何?

聊天機器人通常是與使用者的來回交談。 此特定聊天機器人會使用個人化工具來選取最佳動作(咖啡或茶),為使用者提供。 個人化工具會使用增強式學習來進行該選擇。

聊天機器人必須管理交談中的回合。 聊天機器人會使用 Bot Framework 來管理聊天機器人的架構和對話,並使用 Azure AI Language Understanding (LUIS) 來了解使用者所說的自然語言所代表的意圖。

聊天機器人是一個網站,具有可回應要求的特定路由。 http://localhost:3978/api/messages 您可以在本機開發 Bot 時,使用 Bot Framework 模擬器,以視覺化方式與執行中的聊天機器人互動。

使用者與 Bot 的互動

這是一個簡單的聊天機器人,可讓您輸入文字查詢。

使用者輸入文字 Bot 會以文字回應 Bot 用來判斷回應文字所採取動作的描述
未輸入任何文字 - Bot 會開始交談。 This is a simple chatbot example that illustrates how to use Personalizer. The bot learns what coffee or tea order is preferred by customers given some context information (such as weather, temperature, and day of the week) and information about the user.
To use the bot, just follow the prompts. To try out a new imaginary context, type “Reset” and a new one will be randomly generated.
Welcome to the coffee bot, please tell me if you want to see the menu or get a coffee or tea suggestion for today. Once I’ve given you a suggestion, you can reply with ‘like’ or ‘don’t like’. It’s Tuesday today and the weather is Snowy.
Bot 會使用指示文字開始對話,並讓您知道內容是什麼: TuesdaySnowy
Show menu Here is our menu: Coffee: Cappuccino Espresso Latte Macchiato Mocha Tea: GreenTea Rooibos 使用 LUIS 判斷查詢意圖,然後顯示咖啡和茶專案的功能表選擇。 動作的功能為
What do you suggest How about Latte? 使用 LUIS 判斷查詢的意圖,然後呼叫 排名 API ,並將最佳選擇顯示為問題 How about {response.RewardActionId}? 。 也顯示 JSON 呼叫和回應,以供說明之用。
I like it That’s great! I’ll keep learning your preferences over time.
Would you like to get a new suggestion or reset the simulated context to a new day?
使用 LUIS 判斷查詢意圖,然後使用 的獎勵 1 呼叫 Reward API ,以顯示 JSON 呼叫和回應,以供說明之用。
I don't like it Oh well, maybe I’ll guess better next time.
Would you like to get a new suggestion or reset the simulated context to a new day?
使用 LUIS 判斷查詢意圖,然後使用 的獎勵 0 呼叫 Reward API ,以顯示 JSON 呼叫和回應,以供說明之用。
Reset 傳回指示文字。 使用 LUIS 判斷查詢的意圖,然後顯示指示文字並重設內容。

此 Bot 中的個人化工具

此聊天機器人會使用個人化工具,根據動作清單 (某些內容類型)和內容功能來選取最上層動作 (特定咖啡或茶)。

Bot 會將動作清單以及內容功能傳送至個人化工具迴圈。 個人化工具會將單一最佳動作傳回 Bot,您的 Bot 會顯示該動作。

在本教學課程中 ,動作 是咖啡和茶的類型:

咖啡
卡布奇諾
Espresso
Latte (拿鐵)
Mocha
GreenTea
羅伊博斯

排名 API: 為了協助個人化工具瞭解您的動作,Bot 會使用每個排名 API 要求傳送下列專案:

  • 具有功能的動作
  • 內容功能

模型的一項 功能 是可跨聊天機器人使用者群成員匯總(群組)之動作或內容的相關資訊。 功能 並非 個別特定(例如使用者識別碼)或高度特定(例如確切的一天時間)。

功能可用來將動作對齊模型中的目前內容。 此模型代表個人化工具過去對動作、內容及其功能的知識,可讓其做出受過教育的決策。

模型,包括功能,會根據 Azure 入口網站中的模型更新頻率 設定,依排程更新。

您應該使用相同的規劃和設計來選取功能,以套用至技術架構中的任何架構或模型。 特徵值可以使用商務邏輯或協力廠商系統來設定。

警告

此應用程式中的功能適用于示範,而且不一定是 Web 應用程式中最適合用於使用案例的功能。

動作功能

每個動作(內容專案)都有有助於區分咖啡或茶專案的功能。

這些功能不會在Azure 入口網站中設定為迴圈設定的一部分。 相反地,它們會以 JSON 物件的形式傳送至每個排名 API 呼叫。 這可讓動作及其功能彈性隨著時間成長、變更和縮減,讓個人化工具能夠遵循趨勢。

咖啡和茶的功能包括:

  • 咖啡豆的起源位置,如肯雅和巴西
  • 咖啡或茶是有機的嗎?
  • 咖啡的淺色或深烤

雖然咖啡在上述清單中有三個功能,但茶只有一個。 只將功能傳遞至對動作有意義的個人化工具。 如果功能不適用於動作,請勿傳入空白值。

內容功能

內容功能可協助個人化工具瞭解環境的內容,例如顯示裝置、使用者、位置和與您使用案例相關的其他功能。

此聊天機器人的內容包括:

  • 天氣類型(雪、雨、晴朗)
  • 一周中的一天

在此聊天機器人中隨機選取功能。 在實際的 Bot 中,針對內容功能使用實際資料。

此 Bot 的設計考慮

關於此交談,有一些注意事項:

  • Bot 互動 :對話非常簡單,因為它在簡單的使用案例中示範排名和獎勵。 它不會示範 Bot Framework SDK 或模擬器的完整功能。
  • 個人化工具 :會隨機選取功能以模擬使用方式。 請勿在生產個人化工具案例中隨機化功能。
  • Language Understanding (LUIS) :LUIS 模型的少數範例語句僅供此範例使用。 請勿在生產 LUIS 應用程式中使用如此少的範例語句。

安裝必要的軟體

  • Visual Studio 2019。 如果您想要使用 .NET Core CLI,可下載的範例存放庫會包含指示。
  • Microsoft Bot Framework 模擬器 是桌面應用程式,可讓 Bot 開發人員在 localhost 上測試和偵錯其 Bot,或透過通道從遠端執行。

下載聊天機器人的範例程式碼

聊天機器人可在個人化工具範例存放庫中取得。 複製或 下載 存放庫,然後使用 Visual Studio 2019 開啟目錄中的 /samples/ChatbotExample 範例。

若要複製存放庫,請在 Bash 殼層 (終端機) 中使用下列 Git 命令。

git clone https://github.com/Azure-Samples/cognitive-services-personalizer-samples.git

建立和設定個人化工具與 LUIS 資源

建立 Azure 資源

若要使用此聊天機器人,您必須建立個人化工具與 Language Understanding (LUIS) 的 Azure 資源。

建立 LUIS 應用程式

如果您不熟悉 LUIS,您必須 登入 並立即移轉您的帳戶。 您不需要建立新的資源,而是選取您在本教學課程上一節中建立的資源。

  1. 若要建立新的 LUIS 應用程式,請在 LUIS 入口網站 中選取您的訂用帳戶和撰寫資源。
  2. 然後,仍然在同一個頁面上,選取 [+ 新增應用程式進行交談 ],然後 選取 [匯入為 JSON ]。
  3. 在快顯對話方塊中,選取 [ 選擇檔案 ],然後選取檔案 /samples/ChatbotExample/CognitiveModels/coffeebot.json 。 輸入名稱 Personalizer Coffee bot
  4. 選取 LUIS 入口網站右上方導覽中的 [ 訓練 ] 按鈕。
  5. 選取 [ 發佈] 按鈕,將應用程式發佈至預測執行時間的生產 位置
  6. 選取 [ 管理 ],然後 設定 。 複製應用程式識別碼 的值 。 您必須在 .NET 專案的檔案中 appsettings.json 設定此值。
  7. 仍在 [管理] 段中,選取 [Azure 資源 ]。 這會在應用程式上顯示相關聯的資源。
  8. 選取 [ 新增預測資源 ]。 在快顯對話方塊中,選取您的訂用帳戶,以及本教學課程上一節中建立的預測資源,然後選取 [ 完成 ]。
  9. 複製 [主要金鑰 ] 和 [端點 URL ] 的值 。 您必須在 .NET 專案的檔案中 appsettings.json 設定這些值。

使用 appsettings.json 檔案設定 Bot

  1. 使用 Visual Studio 2019 開啟聊天機器人解決方案檔案 ChatbotSamples.sln

  2. 在專案的根目錄中開啟 appsettings.json

  3. 設定本教學課程上一節複製的所有五個設定。

    {
      "PersonalizerChatbot": {
        "LuisAppId": "",
        "LuisAPIKey": "",
        "LuisServiceEndpoint": "",
        "PersonalizerServiceEndpoint": "",
        "PersonalizerAPIKey": ""
      }
    }
    

建置並執行 Bot

appsettings.json設定 之後,您就可以建置並執行聊天機器人。 當您這麼做時,瀏覽器會開啟至執行中的網站 http://localhost:3978

Screenshot of browser displaying chat bot web site.

讓網站保持執行,因為本教學課程說明 Bot 正在執行的動作,因此您可以與 Bot 互動。

設定 Bot Framework 模擬器

  1. 開啟 Bot Framework 模擬器,然後選取 [ 開啟 Bot ]。

    Screenshot of Bot Framework Emulator startup screen.

  2. 使用下列 Bot URL 設定 Bot,然後選取 [連線

    http://localhost:3978/api/messages

    Screenshot of Bot Framework Emulator open bot settings.

    模擬器會連線到聊天機器人,並顯示指示文字,以及記錄和偵錯資訊,有助於本機開發。

    Screenshot of Bot Framework Emulator in first turn of conversation.

在 Bot Framework 模擬器中使用 Bot

  1. 輸入 I would like to see the menu 來要求查看功能表。 聊天機器人會顯示專案。

  2. 讓 Bot 輸入 Please suggest a drink for me. 來建議專案:模擬器會在聊天視窗中顯示排名要求和回應,讓您可以看到完整的 JSON。 Bot 會提出建議,例如 How about Latte?

  3. 您想要的答案,這表示您接受個人化工具的最高排名選取專案, I like it. 模擬器會在聊天視窗中顯示獎勵分數為 1 和回應的 Reward 要求,讓您可以看到完整的 JSON。 Bot 會回應 That’s great! I’ll keep learning your preferences over time.Would you like to get a new suggestion or reset the simulated context to a new day?

    如果您回應 no 選取範圍,則會將獎勵分數 0 傳送給個人化工具。

使用個人化工具瞭解 .NET 程式碼

.NET 解決方案是簡單的 Bot 架構聊天機器人。 與個人化工具相關的程式碼位於下列資料夾中:

  • /samples/ChatbotExample/Bots
    • PersonalizerChatbot.cs Bot 與個人化工具之間互動的檔案
  • /samples/ChatbotExample/ReinforcementLearning - 管理個人化工具模型的動作和功能
  • /samples/ChatbotExample/Model - 個人化工具動作和功能的檔案,以及 LUIS 意圖的檔案

PersonalizerChatbot.cs - 使用個人化工具

類別 PersonalizerChatbot 衍生自 Microsoft.Bot.Builder.ActivityHandler 。 它有三個屬性和方法來管理交談流程。

警告

請勿從本教學課程複製程式碼。 使用個人化工具範例存放庫中 範例程式碼。

public class PersonalizerChatbot : ActivityHandler
{

    private readonly LuisRecognizer _luisRecognizer;
    private readonly PersonalizerClient _personalizerClient;

    private readonly RLContextManager _rlFeaturesManager;

    public PersonalizerChatbot(LuisRecognizer luisRecognizer, RLContextManager rlContextManager, PersonalizerClient personalizerClient)
            {
                _luisRecognizer = luisRecognizer;
                _rlFeaturesManager = rlContextManager;
                _personalizerClient = personalizerClient;
            }
    }

    public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
    {
        await base.OnTurnAsync(turnContext, cancellationToken);

        if (turnContext.Activity.Type == ActivityTypes.Message)
        {
            // Check LUIS model
            var recognizerResult = await _luisRecognizer.RecognizeAsync(turnContext, cancellationToken);
            var topIntent = recognizerResult?.GetTopScoringIntent();
            if (topIntent != null && topIntent.HasValue && topIntent.Value.intent != "None")
            {
                Intents intent = (Intents)Enum.Parse(typeof(Intents), topIntent.Value.intent);
                switch (intent)
                {
                    case Intents.ShowMenu:
                        await turnContext.SendActivityAsync($"Here is our menu: \n Coffee: {CoffeesMethods.DisplayCoffees()}\n Tea: {TeaMethods.DisplayTeas()}", cancellationToken: cancellationToken);
                        break;
                    case Intents.ChooseRank:
                        // Here we generate the event ID for this Rank.
                        var response = await ChooseRankAsync(turnContext, _rlFeaturesManager.GenerateEventId(), cancellationToken);
                        _rlFeaturesManager.CurrentPreference = response.Ranking;
                        await turnContext.SendActivityAsync($"How about {response.RewardActionId}?", cancellationToken: cancellationToken);
                        break;
                    case Intents.RewardLike:
                        if (!string.IsNullOrEmpty(_rlFeaturesManager.CurrentEventId))
                        {
                            await RewardAsync(turnContext, _rlFeaturesManager.CurrentEventId, 1, cancellationToken);
                            await turnContext.SendActivityAsync($"That's great! I'll keep learning your preferences over time.", cancellationToken: cancellationToken);
                            await SendByebyeMessageAsync(turnContext, cancellationToken);
                        }
                        else
                        {
                            await turnContext.SendActivityAsync($"Not sure what you like. Did you ask for a suggestion?", cancellationToken: cancellationToken);
                        }

                        break;
                    case Intents.RewardDislike:
                        if (!string.IsNullOrEmpty(_rlFeaturesManager.CurrentEventId))
                        {
                            await RewardAsync(turnContext, _rlFeaturesManager.CurrentEventId, 0, cancellationToken);
                            await turnContext.SendActivityAsync($"Oh well, maybe I'll guess better next time.", cancellationToken: cancellationToken);
                            await SendByebyeMessageAsync(turnContext, cancellationToken);
                        }
                        else
                        {
                            await turnContext.SendActivityAsync($"Not sure what you dislike. Did you ask for a suggestion?", cancellationToken: cancellationToken);
                        }

                        break;
                    case Intents.Reset:
                        _rlFeaturesManager.GenerateRLFeatures();
                        await SendResetMessageAsync(turnContext, cancellationToken);
                        break;
                    default:
                        break;
                }
            }
            else
            {
                var msg = @"Could not match your message with any of the following LUIS intents:
                        'ShowMenu'
                        'ChooseRank'
                        'RewardLike'
                        'RewardDislike'.
                        Try typing 'Show me the menu','What do you suggest','I like it','I don't like it'.";
                await turnContext.SendActivityAsync(msg);
            }
        }
        else if (turnContext.Activity.Type == ActivityTypes.ConversationUpdate)
        {
            // Generate a new weekday and weather condition
            // These will act as the context features when we call rank with Personalizer
            _rlFeaturesManager.GenerateRLFeatures();

            // Send a welcome message to the user and tell them what actions they may perform to use this bot
            await SendWelcomeMessageAsync(turnContext, cancellationToken);
        }
        else
        {
            await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected", cancellationToken: cancellationToken);
        }
    }

    // code removed for brevity, full sample code available for download
    private async Task SendWelcomeMessageAsync(ITurnContext turnContext, CancellationToken cancellationToken)
    private async Task SendResetMessageAsync(ITurnContext turnContext, CancellationToken cancellationToken)
    private async Task SendByebyeMessageAsync(ITurnContext turnContext, CancellationToken cancellationToken)
    private async Task<RankResponse> ChooseRankAsync(ITurnContext turnContext, string eventId, CancellationToken cancellationToken)
    private async Task RewardAsync(ITurnContext turnContext, string eventId, double reward, CancellationToken cancellationToken)
}

前面加上 Send 管理與 Bot 和 LUIS 交談的方法。 方法和 ChooseRankAsyncRewardAsync 與個人化工具互動。

呼叫排名 API 並顯示結果

方法 ChooseRankAsync 會藉由收集具有功能和內容功能的動作,來建置 JSON 資料以傳送至個人化工具排名 API。

private async Task<RankResponse> ChooseRankAsync(ITurnContext turnContext, string eventId, CancellationToken cancellationToken)
{
    IList<object> contextFeature = new List<object>
    {
        new { weather = _rlFeaturesManager.RLFeatures.Weather.ToString() },
        new { dayofweek = _rlFeaturesManager.RLFeatures.DayOfWeek.ToString() },
    };

    Random rand = new Random(DateTime.UtcNow.Millisecond);
    IList<RankableAction> actions = new List<RankableAction>();
    var coffees = Enum.GetValues(typeof(Coffees));
    var beansOrigin = Enum.GetValues(typeof(CoffeeBeansOrigin));
    var organic = Enum.GetValues(typeof(Organic));
    var roast = Enum.GetValues(typeof(CoffeeRoast));
    var teas = Enum.GetValues(typeof(Teas));

    foreach (var coffee in coffees)
    {
        actions.Add(new RankableAction
        {
            Id = coffee.ToString(),
            Features =
            new List<object>()
            {
                new { BeansOrigin = beansOrigin.GetValue(rand.Next(0, beansOrigin.Length)).ToString() },
                new { Organic = organic.GetValue(rand.Next(0, organic.Length)).ToString() },
                new { Roast = roast.GetValue(rand.Next(0, roast.Length)).ToString() },
            },
        });
    }

    foreach (var tea in teas)
    {
        actions.Add(new RankableAction
        {
            Id = tea.ToString(),
            Features =
            new List<object>()
            {
                new { Organic = organic.GetValue(rand.Next(0, organic.Length)).ToString() },
            },
        });
    }

    // Sending a rank request to Personalizer
    // Here we are asking Personalizer to decide which drink the user is most likely to want
    // based on the current context features (weather, day of the week generated in RLContextManager)
    // and the features of the drinks themselves
    var request = new RankRequest(actions, contextFeature, null, eventId);
    await turnContext.SendActivityAsync(
        "===== DEBUG MESSAGE CALL TO RANK =====\n" +
        "This is what is getting sent to Rank:\n" +
        $"{JsonConvert.SerializeObject(request, Formatting.Indented)}\n",
        cancellationToken: cancellationToken);
    var response = await _personalizerClient.RankAsync(request, cancellationToken);
    await turnContext.SendActivityAsync(
        $"===== DEBUG MESSAGE RETURN FROM RANK =====\n" +
        "This is what Rank returned:\n" +
        $"{JsonConvert.SerializeObject(response, Formatting.Indented)}\n",
        cancellationToken: cancellationToken);
    return response;
}

呼叫 Reward API 並顯示結果

方法 RewardAsync 會藉由判斷分數來建置 JSON 資料,以傳送至個人化工具獎勵 API。 分數取決於使用者文字中識別的 LUIS 意圖,並從 方法傳送 OnTurnAsync

private async Task RewardAsync(ITurnContext turnContext, string eventId, double reward, CancellationToken cancellationToken)
{
    await turnContext.SendActivityAsync(
        "===== DEBUG MESSAGE CALL REWARD =====\n" +
        "Calling Reward:\n" +
        $"eventId = {eventId}, reward = {reward}\n",
        cancellationToken: cancellationToken);

    // Sending a reward request to Personalizer
    // Here we are responding to the drink ranking Personalizer provided us
    // If the user liked the highest ranked drink, we give a high reward (1)
    // If they did not, we give a low reward (0)
    await _personalizerClient.RewardAsync(eventId, new RewardRequest(reward), cancellationToken);
}

Bot 的設計考慮

此範例旨在示範 Bot 中個人化工具的簡單端對端解決方案。 您的使用案例可能更為複雜。

如果您打算在生產 Bot 中使用個人化工具,請規劃:

  • 每次您需要排名選取專案時 ,即時存取個人化工具 。 排名 API 無法批次處理或快取。 獎勵呼叫可以延遲或卸載至個別的程式,如果您未在定時期間傳回獎勵,則會為事件設定預設獎勵值。
  • 獎勵的使用案例型計算:此範例顯示兩個獎勵為零,其中一個沒有範圍,且分數沒有負值。 您的系統需要更細微的評分。
  • Bot 通道:此範例會使用單一通道,但如果您想要在單一通道上使用多個通道或 Bot 的變化,可能需要視為個人化工具模型內容功能的一部分。

清除資源

當您完成本教學課程時,請清除下列資源:

  • 刪除範例專案目錄。
  • 刪除Azure 入口網站中的個人化工具與 LUIS 資源。

下一步