リンクアンドモチベーションのデータユニット・RevOpsチームの みく / Ryo Koizumi (@slpwalks) です。
リンクアンドモチベーションのRevOpsチームでは、単なる社内データ分析やデータの入出力の管理を行うだけでなく、現場コンサルタントチームのカスタマーサクセスやアポイントメントの行動改善までサポートする、かなり幅の広い活動を行なっています。
現在データユニットではポジションを絶賛募集中なので、ご興味ある方は一度カジュアル面談でお話ししましょう!
概要
- Google Workspace には、アドオンという形でホストアプリの機能を拡張するアプリ(Google Workspace Addon)をインストールすることが出来ます。
- アドオンは Google Apps Script の仕組みで作成することができ、さらに社内ドメイン向けに公開し、社内の方々にインストールしてもらう形で使ってもらうことが出来ます。
- スプレッドシートを拡張してアプリを作成する方法に比べて、組織内の大量のユーザーに同時にリリース出来る点など多くのメリットがあります。
Google Workspace Addon とは?
社内では Google Workspace Addon をどのように利用しているか?
社内では Google Workspace Addon の仕組みを用いた以下のようなツールが展開されています。
- アポイントメントログ収集ツール(アポログ簡単入力くん)
- RevOps チームでは、現場コンサルタント向けに Google カレンダーを拡張したアプリを提供し、アポイントメントログをより効率的に収集するツールを運用しています。
- 収集したアポイントメントログはヘルススコアなどに利用しています。Google Workspace Addon の仕組みを提供することで、アポイントメントログの入力工数が1/5ほどになり、入力率も3倍に改善することが出来ました。
- 現在、LLMと組み合わせ、アポイントメントログの収集だけではなく個人の行動改善を行えるようにする機能(ヘルススコアAIアドバイザー)を開発中です。
- また、RevOps 以外のチームでも同様の仕組みでアプリケーションを提供し、社内のさまざまな業務を効率化しています。
- 先日弊社の大塚が公開した Zoom録画データの文字起こし・要約をする機能 も Google Workspace Addon を用いて実現されています。詳しくは上記記事をご覧ください。
どのようなことが実現できる?
- ホストアプリを開いた時に、特定のUIを表示することが出来ます。(ホームページ)
- Gmailを開いた時や、メッセージを作成する時、カレンダーを開いた時など特定のイベントに反応し、イベントに応じたUIを表示することが出来ます。(コンテキストインタフェース)
- ボタンやドロップダウンメニューを作成し、特定の関数を実行するアクションを設定することが出来ます。
vs. エディタアドオン
- Google Workspace アプリを拡張する仕組みとしては、下記の2種類の仕組みが用意されています。
- Google Workspace Addon (今回の仕組み)
- エディタアドオン(レガシーな仕組み)
- 2つの主な違いは以下になります。
- より詳しい違いは公式ドキュメントを参照してください。基本的にはより新しい Google Workspace Addon の仕組みの方が楽にアドオンを拡張できるようになっています。
社内ツールとしてのスプレッドシートと比べてのメリット・デメリット
社内ツールとして、Google Apps Script を用いて拡張したスプレッドシートを社内展開するようなユースケースが多いと思います。そのような場合と比べての Google Workspace Addon のメリット・デメリットをまとめます。
メリット
- 普通のWebアプリっぽいUIを簡単に作成することが出来る
- ボタン・ドロップダウンメニュー・インプットフォームなど、かなり限定的ながらそれっぽいUIを作成出来ます。
- オートコンプリートなども実現できるため、スプレッドシートのようにコピペで崩れたり、入力規則の設定漏れなどを起こさない安定したUIを提供出来ます。
- 提供出来るUIについては カードベースのインターフェース | Apps Script | Google Developers を参照してください。
- ログインユーザーの情報を取得し、UIを出し分けることができる。
- 単一のデプロイで多くの人数に対して利用させることができ、さらに複数のアプリにまたがってデプロイできる。
デメリット
- マニフェストの設定やGCPの設定がいるため、提供するまでの手順が多い
- アイコンの設定など展開のための準備が必要になります。
- アプリとしての描画範囲がサイドバーに限定される
- カードベースのインタフェースはやや癖があり独自の学習コストが必要になる
実装のハンズオン
UI の作成
- UI については Card Builder で作成できます。
- 基本的には Card という全体の中に複数の Section が存在し、Section の中に複数の Widget(ボタンやインプットフォームなど)が存在します。
- UIを作成すると、右に JSON と JSON を出力するための Apps Script のコードを出力してくれます。後々に作成するスクリプトにコードをコピーすることでUIを構成できるようになります。
アプリの作成
- GAS のトップページ に行き、新しいプロジェクトを作成してください。
マニフェストファイルの編集
{ "timeZone": "Asia/Tokyo", "exceptionLogging": "STACKDRIVER", "runtimeVersion": "V8", "addOns": { "common": { "homepageTrigger": { "runFunction": "onDefaultHomePageOpen", "enabled": true }, "layoutProperties": { "primaryColor": "#673ab7", "secondaryColor": "#9c27b0" }, "logoUrl": "https://ssl.gstatic.com/docs/script/images/logo/script-64.png", "name": "GPT問い合わせくん", "universalActions": [ { "label": "Open Help URL", "openLink": "https://www.example.com/help" } ], "useLocaleFromApp": true }, "calendar": {}, "drive": {}, "gmail": {}, "docs": {}, "sheets": {}, "slides": {} }, "oauthScopes": [ "https://www.googleapis.com/auth/calendar.addons.execute", "https://www.googleapis.com/auth/drive.addons.metadata.readonly", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/script.locale", "https://www.googleapis.com/auth/script.scriptapp", "https://www.googleapis.com/auth/drive.file", "https://www.googleapis.com/auth/documents.currentonly", "https://www.googleapis.com/auth/spreadsheets.currentonly", "https://www.googleapis.com/auth/presentations.currentonly" ], "urlFetchWhitelist": [ "https://api.openai.com/" ] }
addOns
以下がアドオンのマニフェストになります。addons.common
に名前やロゴのURL(必須)を設定します。homepageTrigger.runFunction
にアプリを開いた時に実行する関数を指定します。universalActions
はアプリメニューを開いた時に実行する関数を指定できます。calendar
以下は各アプリの固有の設定を書くことができます。キーを追加することでそのアプリでアドオンが有効になります。今回は共通のホームページを利用し、アプリ固有のイベントには反応しないため中身は書いていません。
oauthScopes
にはそのアプリが利用するスコープを記述します。urlFetchWhiteList
にはアプリが外部接続するURLのドメインリストを記述しておきます。
UI の表示とテスト
- 先ほどのマニフェストでホームページに
onDefaultHomePageOpen
という関数を指定したので、その関数を作成します。 - 先ほどの UI の作成で
buildCard
という関数が作成されたので、そのまま return します。
function buildCard() { let cardSection1TextInput1 = CardService.newTextInput() .setFieldName('message') .setTitle('GPTに聞く内容') .setMultiline(true); let cardSection1ButtonList1Button1Action1 = CardService.newAction() .setFunctionName('send') .setParameters({}); let cardSection1ButtonList1Button1 = CardService.newTextButton() .setText('送信する') .setTextButtonStyle(CardService.TextButtonStyle.TEXT) .setOnClickAction(cardSection1ButtonList1Button1Action1); let cardSection1ButtonList1 = CardService.newButtonSet() .addButton(cardSection1ButtonList1Button1); let cardSection1DecoratedText1 = CardService.newDecoratedText() .setTopLabel('GPTからの返答') .setText('hogehoge'); let cardSection1 = CardService.newCardSection() .addWidget(cardSection1TextInput1) .addWidget(cardSection1ButtonList1) .addWidget(cardSection1DecoratedText1); let card = CardService.newCardBuilder() .addSection(cardSection1) .build(); return card; } function onDefaultHomePageOpen() { return buildCard() }
- テストは右上のデプロイをクリックし、「デプロイをテスト」を選択します。
- デプロイを「Test latest code」にしたまま、下のインストールを選択すると自分のアカウントで利用できるようになります。
- 適当なアプリを開くとデプロイできています。
処理を実装する
buildCard() の修正
- まず、GPTからの返答を受け取って修正するために、
buildCard
に改修を加え、レスポンスを表示できるようにします。
// 引数にレスポンス内容を入れられるようにする(デフォルトは空) function buildCard(response = '') { let cardSection1TextInput1 = CardService.newTextInput() .setFieldName('message') .setTitle('GPTに聞く内容') .setMultiline(true); let cardSection1ButtonList1Button1Action1 = CardService.newAction() .setFunctionName('send') .setParameters({}); let cardSection1ButtonList1Button1 = CardService.newTextButton() .setText('送信する') .setTextButtonStyle(CardService.TextButtonStyle.TEXT) .setOnClickAction(cardSection1ButtonList1Button1Action1); let cardSection1ButtonList1 = CardService.newButtonSet() .addButton(cardSection1ButtonList1Button1); let cardSection1DecoratedText1 = CardService.newDecoratedText() .setTopLabel('GPTからの返答') .setText(response); // <- ここにレスポンスをセットする let cardSection1 = CardService.newCardSection() .addWidget(cardSection1TextInput1) .addWidget(cardSection1ButtonList1) .addWidget(cardSection1DecoratedText1); let card = CardService.newCardBuilder() .addSection(cardSection1) .build(); return card; }
OpenAI の API KEY を登録する
コールバック関数の作成
- 先ほどのUIを出力するコードの下記で send というコールバックを指定しました。これの中身を作成していきます。
- send 関数はイベントオブジェクトを一つ取り、その中にフォームの中身などが入っています。今回は fieldName に
message
を指定したので以下のようになります。- レスポンスは
newActionResponseBuilder()
にbuildCard()
を渡してアップデートさせています。
- レスポンスは
// OpenAI の /chat/completions を叩く関数 function getCompletions_(message) { // API KEY の取得 const properties = PropertiesService.getScriptProperties(); const apiKey = properties.getProperty('OPENAI_API_KEY'); const path = `/chat/completions`; const payload = { model: 'gpt-3.5-turbo', messages: [{ role: 'user', content: message }], max_tokens: 256, temperature: 0.7, }; const response = UrlFetchApp.fetch(`https://api.openai.com/v1${path}`, { method: 'post', headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, payload: JSON.stringify(payload), }); const json = JSON.parse(response.getContentText()); return json.choices[0].message.content; } function send(e) { // フォームの取得 const message = e.commonEventObject.formInputs.message.stringInputs.value[0]; // APIリクエストの実行 const content = getCompletions_(message); // レスポンスの構築 return CardService.newActionResponseBuilder() .setNavigation(CardService.newNavigation().updateCard(buildCard(content))) .setStateChanged(true) .build(); }
- アプリをリロードした上で、適当な文字列を入れて送信するをクリックすると返答が表示されます。
公開する
- 公開するには、下記の手順を踏む必要があります。
デプロイを作成する
- 紐つけた後はデプロイが行えるので再度デプロイを行い、デプロイIDをコピーします。
ストアへの公開設定
- さらに、「ストアの掲載情報」を入力します。アイコンの設定などがめんどくさいですが必須です。
- 公開を押すと、内部アプリとして公開され、ユーザーが自由にインストールできるようになります。
まとめ
- Google Workspace Addon の内部向けの作成・公開方法をざっくり解説しました。
- コンテキストインタフェースを使うとさらにイベントに応じたUIを出したりできるので、ぜひ社内DXの選択肢に入れてみてください。