はじめに
どうも、たにあんです。今年もどうぞよろしくお願いします。
昨年のアドベントカレンダーに引き続き、makeを使ったお話です。ちょうど作る機会があったので、AIチャットボットを作ってみました。
なお、今回の内容はSlack APIやiPaaS製品、その他APIの使い方に関して、チョットワカル人向けの内容になっています。要点だけ掻い摘んで説明していきます。実際に実装してみたいけど…やmakeが気になる!など、詳細についてご興味のある方はお問い合わせフォームよりご連絡ください。
3行解説
そこそこの分量になってしまったので、なんとなく知りたいというそんなあなたに。
- 難しいことを考えず、Slackイベントのパターンに応じてmakeで楽々処理
- Perplexity APIを利用してインターネットの情報検索に対応
- 会話の履歴をストレージにストックして、AIとユーザーの会話として処理
make 用語解説
makeの用語だけ先に説明しておきます。
- Scenario:どのような処理を自動化するか定義されたもの。ZapierのZaps、Power Automateのフローに相当します。
- Module:Scenario内で利用できる操作そのもの。ZapierやPower Automateのアクション、コネクタに相当します。
- Router:特定のModuleから分岐させるときに使用します。条件分岐も可能。
- Data Store:makeのデータストレージで、任意の列を持ったデータベースにレコードを保存できます。
前提
環境
以下の環境は揃っているものとします。
- Slack(無料でOK)
- make(無料でOK)
- Perplexity API(試すだけであれば$5課金すれば十分遊べます)
API関連
SlackやPerplexity APIの設定方法については触れませんので、以下を参考にしていただければと思います。
- Slack API
- Slack – Events API
- app_mention
- message.channels
- Perplexity – Initial Setup
- Perplexity – API Reference
概要
Scenario自体は以下の通りです。初見だと「うっ…」となるかもしれません。
BotにメンションするとPerplexity AIの回答を受領でき、さらにメンションしたメッセージのスレッド内でやり取りを継続することで会話を続けられるような仕組みになっています。
実際に動いている画面を見ていただいた方が良さそうなのでGIFをのっけておきます。すでにメンションでやり取りが開始されているスレッド内で適当にメッセージを投稿した際の動きです。
ここから各Sectionごとに要点を掻い摘んで説明していきます。
Section1:Webhookの受信とイベントに応じた分岐
概要に記載した画像から配置を変えていますが同じものです。
SlackのEvent SubscriptionsをトリガーにWebhookを受信しています。makeの「Custom Webhook」Moduleから発行されるURLをEvent Subscriptionsに登録しています。
Webhook受信から「Bot以外のユーザー」であればRouter以降の条件分岐に進みます。条件に合致した場合は1〜4の分岐いずれかが実行されることになります。条件さえ合致すれば2ndと3rdといった形で複数の分岐が実行されることもあります。
1st:Event SubscriptionsのChallenge Response用
2nd:Event Subscriptionsの通常Response用
3rd:Slackのスレッド内のやり取りであれば取得
4th:Botへのメンションの処理
3rdと4thがPerplexity APIからの回答を受け取る分岐になっています。
Point1. Bot以外のユーザーイベントのみ反応させましょう
最終的にBotがPerplexity APIの返答を投稿するので、Botが投稿者のメッセージに反応しないようにFilterを設定しましょう。BotにBotが反応してどえらいことになります。実際に私はやらかしました。
Point2. 「2nd:Event Subscriptionsの初動Response」は必須
SlackのEvents APIの仕様で、Webhookが送信されたら3秒以内にResponseを返さないと再度Webhookが送信されてしまいます。Perplexity APIの回答作成に時間がかかる(15秒前後)ため、必ず設定しましょう。Webhook Response自体は適当に200返しておけば良いです。
参考:Slack – Events API > Error Handling
Point3. プライベートチャンネルは拾える?
Event Subscriptionsで「message.groups」を追加してください。私は試していないですができるはず。
Section2:データ加工・処理
オレンジ枠の部分がSection2です。前段のWebhooksとRouterはSection1の範囲ですが、3rdと4thから繋がっていることが分かりやすいよう以下の画像にも含めています。
Section2では以下のような処理が実行されます。
3rdの続き:SlackメッセージをData Storeに保存 → Data Storeからスレッドのやり取りを検索して配列に集約
4thの続き:Text ParserでSlackのメッセージを処理し、Data Storeに保存
Point1. AIに会話履歴をプロンプトとして渡しましょう
「SlackメッセージをData Storeに保存」という処理を実行しています。Perplexity APIやその他の生成AIでは、ユーザーとAIの会話履歴を送信することができます。以下のようにuserとassistantが交互になっている感じのデータを渡してやればOK。
[ { "role": "user", "content": "hoge-nanoda" }, { "role": "assistant", "content": "fuga-nanoda" }, { "role": "user", "content": "hoge-fuga-nodada" }, ]
SlackからAIとユーザーのやり取りを逐一取得するのはデバッグも大変なので、ユーザーから送信されたメッセージを保存しておき、timestampからスレッドのやり取りを取得できるようにしています。
Point2. Data Storeに保存するときはKeyを設定しましょう
Data Storeにレコードを保存する際、Keyを指定できます。入力しなくてもエラーにはなりませんが、Keyを入力しておかないと実質使えないModuleが存在します。今回のScenarioはそのようなModuleを使用していませんが、困ることがあるので設定しましょう。
今回はSlackからWebhookで受信した情報を元に、{{channel ID}}-{{timestamp}}
という形式で保存しています。当然ながらKeyはData Store内で一意でないといけません。
Section3:ユーザーへの応答とデータ処理
自分で区切っておきながらアレですが、激オモSectionきました。
Section1で説明した3rdと4thの分岐後に繋がっています。3rdと4thそれぞれ全く同じModuleを利用しており、処理内容も同じため、「3rd:Slackのスレッド内のやり取りであれば取得」を前提に説明します。
以下のような処理がなされています。文章だけで説明するのは難しいのでMermaidで。
Point1. ユーザー側に通知しましょう
Section3の最初に「回答を作成中です…」という通知をmakeからSlackに送信しています。別の仕組みで動いている既存の社内チャットボットがそのような親切設計になっています。先人の知恵です。感謝します。
また、処理に何らかのエラーが発生した場合も、ユーザーはどのような状態なのかSlack上で分からないため、エラー通知の更新も実装できると丁寧だと思います。
Point2. Perplexity AIの回答を加工しましょう
細かい話ですが、Perplexity AIの回答はネット上の情報を検索して、URLと共に回答してくれます。それらのURLはcitations[]
として返されます。Citationsにも番号を振ってあげる必要があるため、Iterator + Text Aggregatorで番号を振っています。
Point3. Perplexity AIに投げるためのデータ(Messages)処理
Search recordsに続くArray Aggregatorで処理しています。Target Structure typeにてPerplexity APIへのMessagesの構造を自動で取得してくれるので指定してあげます。ただし、Scenarioを1度実行してからでないと、構造を判定してくれないと思うので、一度実行した上で指定してください。
Array AggregatorなどのFlow ControlsやJSON関連のModuleは一癖あって、感覚を掴むまでに少し時間がかかりそうです。当初、モジュールだけではデータ整形が難しいという判断からArray Aggregatorの代わりにCustom Functionsを使って処理していたのですが、使わなくなったので供養しておきます。
Custom Function
function extractTextAndRole(input) { // 入力が文字列の場合はJSONとしてパース if (typeof input === 'string') { input = JSON.parse(input); } // システムメッセージを含む結果を格納する配列 const messages = []; // 各要素からTextとroleを抽出して変換 input.forEach(item => { if (item.data && item.data.text && item.data.role) { messages.push({ "role": item.data.role, "content": item.data.text }); } }); // 最後の要素のcontentに応答要件を追加 if (messages.length > 1) { // システムメッセージ以外の要素が存在する場合 const lastMessage = messages[messages.length - 1]; lastMessage.content += "\n\nPerplexityの出力結果をSlackに投稿するので、以下の点を考慮して返答を作成してください。\n1. Markdownの記述はSlackに適した形式にしてください。\n2. 太字は使用しないでください。\n3. 日本語で回答してください。\n4. 情報が不足している場合は、推測であることを明記してください。"; } // messagesを配列として返す return messages; }
応用
問い合わせ対応の効率化
Slackで届いた問い合わせなどに、この仕組みを利用することで効率化できそうです。実際に社内でも使ってみようかと検討中です。
Slack上で特定のスタンプが押されたら、スタンプが押されたメッセージを元にPerplexity AIを使って回答を作成し、回答サンプルとして使うこともできそうです。Perplexityの回答をそのまま問い合わせの回答として用いるところまでは難しいかもしれませんが、サンプルとしては参考にできる部分があると思います。
Anthropic Claudeを用いたPDF要約
ベータ版でAnthropic ClaudeからAPIでPDFをアップロードして中身を読み取ってくれる機能が2024年11月頃にリリースされています(12月下旬には正式リリースされたっぽいがソース見つからず)。Slackに添付されたファイルを取得して、そのままAnthropic APIに投げてあげることで、PDFを要約できたりと便利っちゃあ便利。
こちらもぜひお試しあれ。
おわりに
iPaaSならではのスピード感で作成することができました。メンテナンスやデバッグ自体もmakeの基本的な機能の範囲内で完結でき、iPaaSは便利やなぁと思った次第です。
ちなみに、AIチャットbotをiPaaSではなくTypeScriptを使ったコードで作ってみたので、そのうちアップするかもです。makeと比較してどうかーみたいな話もかけたらと思っています(書くとは言ってない)。