はじめに
外部からTeamsボットを介して「DM」(Teamsの用語では「プロアクティブメッセージ」)を送る方法について調べていくと、Microsoftのドキュメントに、「アダプタ」「会話リファレンス」「Webフック」など、様々な異なるキーワードが出てきて、実装の方法がいまひとつ分かりにくいように感じます。
そこで、今回、コードサンプルを作成し、「ユーザーのチャット画面にプロアクティブメッセージを表示させるためのトリガを、外部からTeamsボットに向けて送る方法」について説明できるようにしてみました。コードサンプルは、こちらからご参照ください。また、Azureへの登録やアプリのインストールの方法については、次のドキュメントをダウンロードしてください。
下の動画では、このコードサンプルについて、デモを交えて詳しく解説しています。
本稿はその要約です。動画は約20分×2本と若干長めになってしまっていますので、要点だけ確認していただく場合は、本稿を先にお読みいただくことをお勧めします。
要点だけをお知りになりたい方は、本稿をお読みください。順序立てた説明をお聞きになりたい方は、動画もあわせてご覧いただければと思います。さらに、処理や動作のより詳細な内容については、コードサンプルもあわせてご覧ください。
プロアクティブメッセージの実現方法
外部からプロアクティブメッセージの送信(≒「会話」の再開)のトリガを送る方法を一言で表すならば、
パースが通る「会話リファレンス」のJSONオブジェクトを、/api/notifyエンドポイントを介して、BotFrameworkAdapterクラスのcontinueConversationメソッドの第1引数に渡す
「会話リファレンス」というのは、チャット上の会話スレッドを識別するためのオブジェクトです。対象となっている発話(アクティビティで表現される)が属する「会話」全体を特定するためのプロパティの集まりとして表現されます。
それらのプロパティには、会話ID、アクティビティの応答を処理するサーバーのURL、会話に参加しているユーザーのリスト、会話のタイプ(個人チャットなのかグループチャットなのか等)、会話に使用される言語、が含まれます。
ということになります。具体的には、下に示す【ステップ①】【ステップ②】のようになります。
このほかに、Microsoftのドキュメントでは、プロアクティブメッセージを送信する方法として、以下のような方法も紹介されています。
- 「会話リファレンス」をローカルに取得・保存しアプリ内部でトリガさせる方法(Send proactive messages – Teams)
- コネクタサービスを直接利用する方法(Proactive messaging for bots – Teams)
- Webフックやノーコード・ローコード系の方法
しかし、これらの方法は「外部からトリガできない」「最新のBot Framework Ver.4の方法でない」「本格的な実装には向かない」といった点で応用がきかないので、本稿では、広く応用できる方法として、/api/notifyエンドポイントを実装する方法(Send proactive notifications to users – Azure Bot Service – Bot Service)を紹介しています。
/api/notifyエンドポイントに会話リファレンスを渡す
【ステップ①】要求本文(req.body)に「会話リファレンス」を含めて、外部システム側からボット側の/api/notify宛てにPOST HTTPSリクエストを送付します。
次に、ボット側の/api/notify内(ルーティング先のコントローラ内)で、その要求本文から「会話リファレンス」を抽出し、continueConversationメソッドの第1引数(reference)に渡します。
【ステップ②】continueConversationメソッド内でreferenceがパースされます。referenceのパースが通った(referenceと合致する会話スレッドがフレームワーク側で見つかった)ならば、その会話スレッドで動作するTurnContextオブジェクトがアダプタから渡されて、コールバック関数(callback=「logic handler」と呼ばれる)が実行され、ターン(「会話」の一単位)が開始(=「会話」が再開)されます。
要するに、会話を再開するには、パースが通る「会話リファレンス」(reference)が必要になります。つまり、前もって、ユーザーとの会話スレッド上で取得した「会話リファレンス」のJSONオブジェクト(stringifyしたもの)を、そのユーザーに紐付けした形でデータベースに保存しておく必要があります(Retrieve and store the conversation reference)。
/api/messagesエンドポイントとの類似性と違い
BotFrameworkAdapterに関するドキュメント(BotFrameworkAdapter class | Microsoft Docs)には、この
「① referenceのパースが通った」ならば「② アダプタからTurnContextが渡されて(ミドルウェアを経て)最後にcallbackが実行される」
という動作は、Bot Frameworkが/api/messagesエンドポイント経由でprocessActivityメソッド(Bot FrameworkからActivityとTurnContextを受け取ってボットを動作させる)を実行する際の
「① activityのパースが通った」ならば「② アダプタからTurnContextが渡されて(ミドルウェアを経て)最後にcallbackが実行される」
という動作と「類似している」(This method is similar to the processActivity method. The adapter creates a TurnContext and routes it through its middleware before calling the logic handler. )という記述があります。
/api/notifyエンドポイント、continueConversationメソッド、および「会話リファレンス」オブジェクトの間の関係性が、/api/messagesエンドポイント、processActivityメソッド、および「アクティビティ」オブジェクトの間の関係性とよく似ていることに注目すれば、/api/notifyエンドポイントについての理解が容易になります。
ただし、これら2つには本質的な違いもあります。
Bot Frameworkが提供するのはステートフルなボットであるため、「アクティビティ」は状態遷移しており、その状態はフレームワークが管理しています。外部においてその状態と合致するActivityオブジェクトを作成(ないしはその都度保存して加工)することは一般的に言って困難です。Activityオブジェクトを外部で作成するには、フレームワークとは別に状態管理の仕組みを外部に自前で作成する必要があります。
それに対して、「会話リファレンス」については、一般的には固定値とみなせるため、外部にConversationReferenceオブジェクトを保存して再利用すること、ひいては本稿のような方法で、Bot Frameworkから独立した外部システムからプロアクティブメッセージ(のトリガ)を送信することが可能となっています。
会話リファレンス
「会話リファレンス」のJSONデータは、TurnContextクラスのgetConversationReferenceメソッドを使用すると取得できます(Activityオブジェクトを引数とする)。
「プロアクティブメッセージの送信」を扱ったMicrosoftのドキュメントにあるコードサンプルでは、単純に、この「会話リファレンス」の全体を保存しておき、DMを送る際にcontinueConversationメソッドの第1引数に渡しています(Send proactive notifications to users、Send proactive messages)。
一方で、ドキュメントの記述の中には、「プロアクティブメッセージの送信に必要となる情報はconversationIDとserviceUrlだけである」と示唆するものも見られます(Get the conversation ID and send the message、Service URL may change)。
このドキュメントの箇所には、「serviceUrlはまれに変更になる場合があり、変更になった場合は前の会話リファレンスは使えなくなるので、その際は改めて会話リファレンスを取得し直す」とあります。
実際に試してみても、「conversationIDとserviceUrlだけ」を渡す方法でも、continueConversationメソッドのパースが通り、プロアクティブメッセージが送信できました。
基本となるのは、公式の方法である「会話リファレンス全体」を渡す方法なので、実際の実装においてはこちらの方法を使用するほうが無難と思われます。
本コードサンプルでは、continueConversationメソッドの第1引数に渡す値として、公式の方法である「会話リファレンス全体」を渡す方法と、「conversationIDとserviceUrlだけ」を渡す方法の両方を試すことができるようにしました。
デモの概要
本コードサンプルを構成する「デモボット」と「デモアプリ」は、MongoDBや、NodeJSのExpressモジュール、Axiosモジュールなどのポピュラーな方法を使用して実装されているので、容易にコードを辿っていくことができます。
以下に、概略図を示します。詳しい内容については動画をご参照ください。
デモの構成
デモアプリは「外部サービス」の見本となる
デモボット側の3つのステップ
デモアプリ側の1つのステップ
まとめ
プロアクティブメッセージの送信は、パースが通る「会話リファレンス」のJSONオブジェクトを、/api/notifyエンドポイントを介して、BotFrameworkAdapterクラスのcontinueConversationメソッドの第1引数に渡すことにより、実現できました。
コメント