こんにちは、CCCMKホールディングス TECH LABの三浦です。
近所の街路樹が紅葉していて「秋だなぁ」と思ったのですが、もうすぐ12月なんですよね。例年だったらもっと早く紅葉している気がします。なんだか今年は色々、いつもと違うリズムの一年になったように感じています。
さて、先日Bot Framework Composerに入門した話をご紹介しました。
その後も色々試しながらBot Framework Composerについて調べており、最近ようやく少しずつですが仕組みが理解出来てきました。そこで今回はMicrosoft Teamsから送ったメッセージをAzure BotからAzure OpenAI ServiceにAPIで送信し、返ってきたレスポンスをAzure Botから応答メッセージとしてMicrosoft Teamsに送る、という一連の流れをBot Framework Composerを使って実装する方法についてご紹介したいと思います。
Botの構成
Bot Framework ComposerはMicrosoftが提供するBot開発フレームワーク、"Bot Framework"をGUIを通じて利用し、Botアプリケーションを開発することが出来る開発環境です。Bot FrameworkにおけるBotアプリケーション開発では"Dialog"という概念が中心になります。また、会話を通じて変化するBotの状態を保持する仕組みとして"Memory"があります。
Dialogの構成要素
DialogはRecognizer,Trigger, Action, Generatorの4つの機能を持っています。それぞれについて、簡単にまとめてみます。この内容は以下を参考にしています。
Recognizer
ユーザーのBotへの入力を受け取り、ユーザーが意図することを解釈する役割を持っています。抽出されたユーザーの意図は"Intent"としてDialogのTriggerとして利用することが出来ます。今回は意識して使用しなかったのですが、これを利用することで例えば一般的な会話をするBotから特定領域の専門知識について回答してくれるBotにモード切替をする、といったことに活用出来ると思います。Trigger
なんらかのイベントの発生をキャッチして、それに応じた処理を実行する役目を持っています。勘違いしやすいのですが、1つのイベントにTriggerが紐づけられる処理は1つだけのようです。もし1つのイベントを起点に複数の処理を発火させたい場合はその処理の中でなんらかの発火イベントを起こす必要があるようでした。Action
ActionはBot Framework Composerにおける処理の1単位で、Triggerによって実行される処理はActionを組み合わせて実装します。Actionにはたとえばユーザーの入力を受け取るものや、HTTPリクエストを送ったり、ユーザーにメッセージを送信するものがあります。Generator
ユーザーへの応答メッセージを、登録済みのTemplateや変数を使って生成する機能です。
Memory
Botの状態、たとえばこれまでの会話の履歴や現在のユーザーの情報などを記録する仕組みが"Memory"です。Memoryはその値を保持する期間を定めた"Scope"を決めることが出来ます。Memoryに保存されたデータは"property"と呼び、propertyへの値の格納はActionを通じて行うことが出来ます。propertyへのアクセスはScopeと名前を組み合わせた"scope.name"という文字列で行うことが出来ます。
MemoryのScopeの種類は以下を参考にしています。
今回特に意識したのは以下のScopeです。
- Settings
Botアプリケーション全体の設定が保存されます。読み取り専用です。 - Conversation
現在の会話が継続している間、保持されます。 - Dialog
Dialogが起動中に保持されます。 - Turn
ユーザー-Bot間の1回のやり取りの間、保持されます。
今回作成したBotの構成
今回作成したBotは、1つのDialogに対し、2つのTriggerを持つシンプルな構成にしています。2つのTriggerについてまとめると以下の様になります。
Greeting(ConversationUpdate activity)
会話に新しいユーザーが追加されると実行されます。会話に参加したユーザーにメッセージを送信するActionを実行します。Message received(Message received activity)
Botがユーザーから何かメッセージを受け取ると実行されます。今回のアプリケーションのメインの処理を行い、この中でAzure OpenAIへのHTTPリクエストを送信するActionも実行します。
Botの実装
ここからは具体的な実装について、説明していきます。
Bot SettingsにAzure OpenAIとStorage accountへの接続情報を追記する
Botアプリケーションの設定情報は、アプリケーションのプロジェクトフォルダ内のsettingsフォルダに格納されたappsettings.jsonに記載されています。今回のBotアプリケーションはAzure OpenAIとStorage accountへの接続を行うため、それらの接続に必要となる情報を設定ファイルに書き込んでおきます。
Bot Framework Composerからappsettings.jsonを編集することが出来ます。Bot Settingsを開き、"Advanced Settings View(json)"のスライドをオンにすると、appsettings.jsonを編集することが出来るようになります。
Azure OpenAIとStorage accountへの接続情報を、以下の様に入力しておきます。
{ ... "azopenai": { "endpoint": "https://xxxx.openai.azure.com/openai/deployments/xxxxx", "token": "token" }, "logstorage": { "connection": "DefaultEndpointsProtocol=https;AccountName=xxxx;AccountKey=xxxx;EndpointSuffix=core.windows.net", "table": "log" }, ... }
Storage account tableにログを書き込むためのPackageのインストール
Bot Framework Composerでは外部Packageをアプリケーションにインストールして機能追加することが出来ます。今回Azure OpenAIへのリクエストとレスポンスの履歴をStorage accountのTableに書き込むようにするため、Storage account Tableへのアクセスが容易になる以下のPackageを追加しました。
Triggerの実装
Greeting(ConversationUpdate activity)
新しいBotを作る時、Templateを選択することが出来ます。今回はC#の"Empty Bot"をTemplateとして選択しました。"Empty Bot"で作るとGreeting(ConversationUpdate activity)Triggerは自動的に生成され、その処理内容は以下の様になります。そのままでも動作しますが、"Send a response"のActionを編集して日本語の挨拶を返すように編集しました。
Message received(Message received activity)
ユーザーからメッセージを受け取ると実行される処理で、このアプリケーションのメインの処理になります。こちらは実行しているActionについて、1つずつ内容を紹介します。
Azure OpenAIのパラメータの設定
まずAzure OpenAIを利用する際のモデルのパラメータを設定するActionです。DialogをScopeとするpropertyに各パラメータをセットしています。
System Messageの設定
今回使用したモデルは"gpt-35-turbo-16k"です。このモデルにはその動作の大枠を指示するSystem Messageを与えることが出来ます。以下のActionでSystem MessageをDialogをScopeとするpropertyに格納しています。
ユーザー入力メッセージを取得する
ユーザーがBotに入力したメッセージを取得し、Azure OpenAIに入力する形式に変換してpropertyに保存します。Botに入力されたメッセージはturn.activity.text
というpropertyに自動的に格納されるため、その値を参照しています。
会話の履歴を保持する配列への追加
ユーザーの入力とAzure OpenAIの回答は配列としてpropertyに格納していきます。
配列型のpropertyを編集するためのActionがBot Framework Composerには用意されており、Actionの設定は以下の様にしています。会話の履歴は会話を通じて保持できるConversation Scopeのpropertyに持たせるようにしました。
Azure OpenAIへのHTTPリクエストの送信
Azure OpenAIにHTTPリクエストを送信するActionです。
こちらがActionの設定内容です。
UrlとHeadersの"api-key"はappsettings.jsonに記載した値を取得するため、それぞれSettings Scopeのpropertyを参照しています。ややこしいのがリクエストボディに当たるBodyの設定で、以下の様にBot Framework Composerの組み込み関数を駆使して設定しています。
={ "messages":flatten(createArray(dialog.system_message,conversation.chat_history)), "max_tokens": dialog.max_tokens, "temperature": dialog.temperature, "frequency_penalty": dialog.frequency_penalty, "presence_penalty": dialog.presence_penalty, "top_p":dialog.top_p, "stop":dialog.stop }
"messages"の値のところが特に複雑で、まずSystem Messageを格納したdialog.system_message
と会話の履歴を格納した配列conversation.chat_history
を結合して配列の配列を作ります。そのあと組み込み関数flatten
を使って一次元の配列に変換する、ということを行っています。
このActionで取得されたAzure OpenAIのレスポンスはturn.results
propertyに格納しています。
Azure OpenAIのレスポンスをユーザーに送信する
前のActionで取得したAzure OpenAIのレスポンスをパースし、応答テキスト部分だけをユーザーに送信します。応答テキストはturn.results.content.choices[0].message.content
に格納されています。
会話の履歴を保持する配列への追加
この部分はユーザーの入力を会話の履歴conversation.chat_history
の配列に追加するのと同様です。
ログの出力
ユーザーの入力と、それに対するAzure OpenAIの応答をログファイルに出力します。出力先はStorage accountのTableです。このActionは最初に追加したPackageをインストールすることで使用可能になります。
Actionの設定は以下の様になっており、出力先のConnection StringとTable Nameを指定する必要があります。これらはappsettings.jsonに記載した値を参照させています。
書き出すデータの形式はJson形式で指定するのですが、こちらもBot Framework Composerの組み込み関数を駆使して指定しました。やっていることは、動的にテキストの内容が変わる部分を文字列結合の組み込み関数concat
で繋げ、最後にjson
関数でJsonに変換する、というかなり力技な内容です。
=json( concat( '{"partitionKey":"teams-gpt", "RowKey":"',string(turn.activity.timestamp),'","UserName":"',turn.activity.from.name,'", "HumanInput":"',dialog.last_input.content,'","AIOutput":"',dialog.last_output.content,'"}'))
過去の会話の履歴を消す
最後はこれまでの会話の履歴の長さをチェックし、長さが10を超えた場合は一番古いユーザーの入力とAzure OpenAIの応答を配列conversation.chat_history
から消す、という処理です。これによりAzure OpenAIに入力するテキストが長くなりすぎない様に制御します。過去の履歴を消すために、配列の先頭から要素を消す組み込み関数skip
を使用しました。
以上がMessage received(Message received activity)Triggerで実行される処理です。
Botのテスト
Bot Framework ComposerでBotのテストをすることが出来ます。Botを起動し、Bot Framework ComposerのOpen Web Chatを開くと、以下の様にBotを試すことが出来ます。
ちゃんと応答してくれています!
まとめ
ということで、今回はBot Framework Composerを使ったAzure OpenAIと接続したBotアプリケーションの作り方について、これまで試してきたことをご紹介しました。Botアプリケーションは1から作ろうとすると考えなければならないことが多々ありますが、Bot Framework Composerを使えばそれらの大部分を気にせずBotアプリケーションの開発に取り掛かることが出来る手軽さがあります。一方で細かい調整を行おうとすると少し苦労するところもあったので、もう少し慣れが必要だな、と思いました。
今回はDialogが1つだけのシンプルな構成のBotアプリケーションでしたが、複数のDialogを使った複雑なアプリケーションの開発も今後試してみたいと思います。