Microsoft Bot Framework で ChatGPT を使えるようにする
はじめに
世間では ChatGPT の話題で持ちきりですが、マイクロソフトには以前から Azure 上でボットを作成するための Azure Bot Service およびその実装のための Microsoft Bot Framework があります。Microsoft Bot Framework では、キーワードの抽出は、手動または LUIS (現在は Azure Cognitive Service for Language) を使用していましたが、ここを ChatGPT (OpenAI) に置き換えることで、より自然な対話をすることができるようになります。
Teams のカスタム ボット、メッセージ拡張、会議アプリなどの機能も Azure Bot Service が利用されており、この点においても ChatGPT (OpenAI) の活用が期待できます。すでに Microsoft 365 Copilot の拡張としてプラグインが開発できることがアナウンスされており、Teams AI ライブラリがプレビュー公開されています。
とはいえいきなり Teams AI ライブラリを使って開発するのは難易度が高いところでもあります。まずは、コアの部分である、Microsoft Bot Framework において ChatGPT をどのように利用するかを見ていきたいと思います。
サンプル コード
実行手順 (基本)
プロジェクトの作成
今回は EchoBot のテンプレートをベースに作成します。
まずは 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 は非推奨になっているためこちらを使用する必要があります。
パッケージの追加
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