💻

Microsoft Teams のカスタム ボットで OpenAI を使ってコマンドに応答できるようにする

2024/03/17に公開

はじめに

Microsoft Teams のカスタム ボットでは、チャットで会話できる機能のほかに、コマンドを指定して任意の操作を実行させる機能があります。

https://learn.microsoft.com/ja-jp/microsoftteams/platform/bots/how-to/create-a-bot-commands-menu?WT.mc_id=M365-MVP-5002941

例えば、予約ボットを作成したときは、ユーザーは開発者によって定義された予約に関するコマンドを呼び出すことが可能です。開発者はボット アプリでコマンドを受け取ることで、指定されたコマンドに対しての操作を行います。

実装例としては以下のような感じになります。コマンドはチャットの文字列として渡ってきます。

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    var command = turnContext.Activity.RemoveRecipientMention();
    var message = command switch
    {
        "予約作成" => "予約を作成しました。",
        "予約変更" => "予約を変更しました。",
        "予約削除" => "予約を削除しました。",
        _ => "認識できないコマンドです。"
    };
    await turnContext.SendActivityAsync(MessageFactory.Text(message, message), cancellationToken);
}

ただこの方法では味気ないので、Azure OpenAI Service を使ってもう少しスマートにコマンドに応答させたいと思います。

サンプル コード

https://github.com/karamem0/samples/blob/main/microsoft-teams-bot-commands-with-azure-openai/

コマンドの解析には OpenAI の Function Calling を使用します。Function Calling は JSON スキーマを指定することで関数呼び出し用の JSON データを生成する機能です。ひとつの OpenAI 呼び出しに対して複数の Function を指定できるため、今回はコマンドごとに Function を定義します。

public async Task<ReservationParameter?> GetReservationParameter(string text, CancellationToken cancellationToken = default)
{
    var options = new ChatCompletionsOptions(
        this.deploymentName,
        [
            new ChatRequestUserMessage(text)
        ])
    {
        FunctionCall = FunctionDefinition.Auto
    };
    options.Functions.Add(new FunctionDefinition()
    {
        Name = "CreateReservation",
        Description = "予約を作成します。",
        Parameters = BinaryData.FromString(CreateReservationPatameters)
    });
    options.Functions.Add(new FunctionDefinition()
    {
        Name = "UpdateReservation",
        Description = "予約を変更します。",
        Parameters = BinaryData.FromString(UpdateReservationPatameters)
    });
    options.Functions.Add(new FunctionDefinition()
    {
        Name = "RemoveReservation",
        Description = "予約を削除します。",
        Parameters = BinaryData.FromString(RemoveReservationPatameters)
    });
    var response = await this.client.GetChatCompletionsAsync(options, cancellationToken);
    var arguments = response.Value.Choices[0]?.Message?.FunctionCall?.Arguments;
    return arguments is null ? null : JsonSerializer.Deserialize<ReservationParameter>(arguments);
}

コマンド応答の部分を上記のメソッドを呼び出すように修正します。

  protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
  {
      var command = turnContext.Activity.RemoveRecipientMention();
-     var message = command switch
-     {
-         "予約作成" => "予約を作成しました。",
-         "予約変更" => "予約を変更しました。",
-         "予約削除" => "予約を削除しました。",
-         _ => "認識できないコマンドです。"
-     };
+     var parameter = await this.openAIService.GetReservationParameter(command, cancellationToken: cancellationToken);
+     var message = parameter?.Command switch
+     {
+         "予約作成" => $"{parameter?.Location ?? "<不明な場所>"}の予約を作成しました。",
+         "予約変更" => $"{parameter?.Location ?? "<不明な場所>"}の予約を変更しました。",
+         "予約削除" => $"{parameter?.Location ?? "<不明な場所>"}の予約を削除しました。",
+         _ => "認識できないコマンドです。"
+     };
      await turnContext.SendActivityAsync(MessageFactory.Text(message, message), cancellationToken);
  }

実行してみると、通常のコマンド呼び出しだけではなく、文章に対しても応答できていることが確認できます。さらにパラメーター (今回の場合は場所) を文章から抽出してコマンドを実行できます。

なお OpenAI の応答は完璧ではありません。OpenAI は入力補助であり、必要に応じてダイアログ (タスク モジュール) や Adaptive Cards も活用することが重要です。

おわりに

今回の方法を使用すると、Copilot Plugin と同じことを簡単に実現できるので、ぜひお試しいただければと思います。

https://blogs.partner.microsoft.com/ja/partner/unlock-productivity-with-jira-cloud-plugin-for-microsoft-copilot-for-microsoft-365/

Discussion