💻

Microsoft Bot Framework で ChatGPT を使えるようにする

2023/06/30に公開

はじめに

世間では ChatGPT の話題で持ちきりですが、マイクロソフトには以前から Azure 上でボットを作成するための Azure Bot Service およびその実装のための Microsoft Bot Framework があります。Microsoft Bot Framework では、キーワードの抽出は、手動または LUIS (現在は Azure Cognitive Service for Language) を使用していましたが、ここを ChatGPT (OpenAI) に置き換えることで、より自然な対話をすることができるようになります。

https://learn.microsoft.com/ja-jp/azure/bot-service/bot-builder-howto-v4-luis?WT.mc_id=M365-MVP-5002941

Teams のカスタム ボット、メッセージ拡張、会議アプリなどの機能も Azure Bot Service が利用されており、この点においても ChatGPT (OpenAI) の活用が期待できます。すでに Microsoft 365 Copilot の拡張としてプラグインが開発できることがアナウンスされており、Teams AI ライブラリがプレビュー公開されています。

https://learn.microsoft.com/ja-jp/microsoftteams/platform/bots/how-to/teams conversational ai/how-conversation-ai-get-started?WT.mc_id=M365-MVP-5002941

とはいえいきなり Teams AI ライブラリを使って開発するのは難易度が高いところでもあります。まずは、コアの部分である、Microsoft Bot Framework において ChatGPT をどのように利用するかを見ていきたいと思います。

サンプル コード

https://github.com/karamem0/samples/tree/main/chatgpt-on-bot-framework

実装方法 (基本)

プロジェクトの作成

今回は EchoBot のテンプレートをベースに作成します。

https://learn.microsoft.com/ja-jp/azure/bot-service/bot-service-quickstart-create-bot?WT.mc_id=M365-MVP-5002941

まずは EchoBot のテンプレートをダウンロードします。

dotnet new -i Microsoft.Bot.Framework.CSharp.EchoBot

プロジェクトを作成します。なお現時点では .NET 7 SDK が入っている場合は動作しないようなので .NET 6 SDK を使用するようにしてください。

dotnet new echobot -n Karamem0.SampleApplication

パッケージの追加

OpenAI クライアント ライブラリを追加します。

dotnet add package Azure.AI.OpenAI --prerelease

コードの修正

appsettings.json

OpenAI のサイトから API キーを取得して設定します。

  {
    "MicrosoftAppType": "",
    "MicrosoftAppId": "",
    "MicrosoftAppPassword": "",
    "MicrosoftAppTenantId": "",
+   "OpenAIApiKey": "{{openai-api-key}}"
  }

EchoBot.cs

コンストラクタを追加して OpenAIClient のインスタンスを作成します。API キーは appsettings.json で設定したものです。

private readonly OpenAIClient chatClient;

public EchoBot(IConfiguration configuration)
{
    this.chatClient = new OpenAIClient(configuration.GetValue<string>("OpenAIApiKey"));
}

OnMessageActivityAsync メソッドを修正します。

protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
    var chatCompletionsOptions = new ChatCompletionsOptions();
    chatCompletionsOptions.Messages.Add(new ChatMessage(
        ChatRole.System,
        "あなたは Microsoft Bot Framework から呼び出されるアシスタントです。ユーザーからの質問に回答してください。"
    ));
    chatCompletionsOptions.Messages.Add(new ChatMessage(
        ChatRole.User,
        turnContext.Activity.Text
    ));
    var chatCompletion = await this.chatClient.GetChatCompletionsAsync(
        "gpt-3.5-turbo",
        chatCompletionsOptions,
        cancellationToken
    );
    var replyText = chatCompletion.Value.Choices[0].Message.Content;
    await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
}

エミュレータでの確認

Bot Framework Emulator を使って動作を確認してみます。ちゃんと回答できていますね。

実装方法 (応用)

ただ、この状態だと 1 問 1 答となるので ChatGPT の特長である文脈の理解ができません。なので会話履歴を保持しておくようにします。Microsoft Bot Framework ではユーザーごとの状態管理の機能があるので、こちらを使用することにします。状態はインメモリでも管理できますが、今回は Azure Blob Storage に格納します。

Azurite のインストール

Azurite は Azure Storage Account のエミュレーターです。これまでの Azure Storage Emulator は非推奨になっているためこちらを使用する必要があります。

https://learn.microsoft.com/ja-jp/azure/storage/common/storage-use-azurite?WT.mc_id=M365-MVP-5002941

パッケージの追加

Azure Blob Storage を扱うためのパッケージを追加します。

dotnet add package Microsoft.Bot.Builder.Azure.Blobs

コードの修正

appsettings.json

Azure Blob Storage (Azurite) への接続情報を追加します。

  {
    "MicrosoftAppType": "",
    "MicrosoftAppId": "",
    "MicrosoftAppPassword": "",
    "MicrosoftAppTenantId": "",
    "OpenAIApiKey": "{{openai-api-key}}",
+   "AzureBlobStorageConnectionString": "UseDevelopmentStorage=true",
+   "AzureBlobStorageContainerName": "bot-states"
  }

Startup.cs

状態管理のための構成情報を追加します。

  public void ConfigureServices(IServiceCollection services)
  {
      services.AddHttpClient().AddControllers().AddNewtonsoftJson(options =>
      {
          options.SerializerSettings.MaxDepth = HttpHelper.BotMessageSerializerSettings.MaxDepth;
      });
      services.AddSingleton<BotFrameworkAuthentication, ConfigurationBotFrameworkAuthentication>();
      services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
      services.AddTransient<IBot, Bots.EchoBot>();
+     services.AddSingleton<IStorage>(new BlobsStorage(
+         this.Configuration.GetValue<string>("AzureBlobStorageConnectionString"),
+         this.Configuration.GetValue<string>("AzureBlobStorageContainerName")));
+     services.AddSingleton<ConversationState>();
  }

EchoBot.cs

コンストラクタで ConversationState を受け取るようにします。

  private readonly OpenAIClient chatClient;

+ private readonly ConversationState conversationState;

  public EchoBot(IConfiguration configuration, ConversationState conversationState)
  {
      this.chatClient = new OpenAIClient(configuration.GetValue<string>("OpenAIApiKey"));
+     this.conversationState = conversationState;
  }

OnMessageActivityAsync メソッドを書き換えます。過去の会話は ConversationState を使って保持しておき、OpenAI にリクエストするときに入れなおすようにします。トークン長の制限があるので古い会話は切り捨てます。

  protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
  {
      var accessor = this.conversationState.CreateProperty<List<ChatMessage>>(nameof(ChatMessage));
      var messages = await accessor.GetAsync(turnContext, () => new(), cancellationToken);
      while (messages.Count > 8)
      {
          messages.RemoveAt(0);
      }
      var chatCompletionsOptions = new ChatCompletionsOptions();
      chatCompletionsOptions.Messages.Add(new ChatMessage(
          ChatRole.System,
          "あなたは Microsoft Bot Framework から呼び出されるアシスタントです。ユーザーからの質問に回答してください。"
      ));
      foreach (var message in messages)
      {
          chatCompletionsOptions.Messages.Add(message);
      }
      chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.User, turnContext.Activity.Text));
      var chatCompletion = await this.chatClient.GetChatCompletionsAsync(
          "gpt-3.5-turbo",
          chatCompletionsOptions,
          cancellationToken
      );
      var replyText = chatCompletion.Value.Choices[0].Message.Content;
      await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);
      messages.Add(new ChatMessage(ChatRole.User, turnContext.Activity.Text));
      messages.Add(new ChatMessage(ChatRole.Assistant, replyText));
      await accessor.SetAsync(turnContext, messages, cancellationToken);
      await this.conversationState.SaveChangesAsync(turnContext, cancellationToken: cancellationToken);
  }

エミュレータでの確認

こちらも Bot Framework Emulator で確認します。前の会話の内容を理解して回答できていますね。

おわりに

ここまでできれば、あとは Azure Bot Service を使って Microsoft Teams、Slack、LINE などのさまざまな外部サービスと連携できます。今回は OpenAI の API を使用していますが、Azure OpenAI Service を使う場合も OpenAIClient の初期化の方法を少し変えるだけです。このサンプルでは単に ChatGPT と会話できるようになるだけですが、プロンプトをうまく調整すれば、ユーザーの入力内容から呼び出すロジックを推論させたり、結果として返す内容を生成させたりということができるようになります。ぜひ皆さんもいろいろ試してみていただければと思います。

Discussion