Google Chat、Vertex AI、Apps Script を使用してインシデントに対応する

このチュートリアルでは、https://www.googleapis.com/auth/chat.app.* で始まる認可スコープを使用します。これはデベロッパー プレビューの一部として利用できます。Chat 用アプリでは、1 回限りの管理者による承認が必要です。

このチュートリアルでは、インシデントにリアルタイムで応答する Google Chat 用アプリを作成する方法について説明します。インシデントに対応する際、アプリは Chat スペースを作成して入力し、メッセージ、スラッシュ コマンド、ダイアログを使用してインシデントの解決を促進します。また、AI を使用して、インシデント対応の概要を Google ドキュメントにまとめます。

インシデントとは、解決するためにチームのメンバーが直ちに対応する必要があるイベントです。インシデントの例:

  • 顧客管理(CRM)プラットフォームで緊急性の高いケースが作成され、サービスチームが解決に向けて連携する必要があります。
  • システムがオフラインになり、サイト信頼性エンジニア(SRE)のグループにアラートが送信されます。SRE は協力してシステムをオンラインに戻します。
  • 大規模な地震が発生し、緊急対応要員が対応を調整する必要がある。

このチュートリアルでは、ウェブページからボタンをクリックしてインシデントが報告されたときにインシデント アラートが開始されることを前提としています。このウェブページでは、ユーザーにインシデントの基本情報(タイトル、説明、対応者のメールアドレス)の入力を求めることで、インシデントをシミュレートします。

インシデント管理用 Chat 用アプリの動作をご覧ください。

  • インシデントを開始するウェブサイト。
    図 1: インシデントを報告できるウェブサイト。
  • インシデントの Chat スペースが作成されたことを知らせる通知。
    図 2. インシデントの Chat スペースが作成されたことを知らせる通知。
  • インシデント対応の Chat スペース。
    図 3. インシデント対応の Chat スペース。
  • スラッシュ コマンドを使用してインシデントを解決する。
    図 4. スラッシュ コマンドを使用してインシデントを解決する。
  • インシデント解決ダイアログ。
    図 5. インシデント解決ダイアログ。
  • インシデント解決に関する Google ドキュメントがスペースで共有されました。
    図 6. インシデント解決に関する Google ドキュメントがスペースで共有されました。
  • AI 要約インシデント解決の Google ドキュメント。
    図 7. AI 要約インシデント解決の Google ドキュメント。

前提条件

組織でこれらの前提条件を有効にする必要がある場合は、Google Workspace 管理者に依頼して有効にしてください。

  • Google Chat へのアクセス権を持つ Business または Enterprise の Google Workspace アカウント。
  • Google Workspace でディレクトリ(連絡先の共有)が有効になっていること。インシデント アプリは、ディレクトリを使用して、インシデント対応者の名前やメールアドレスなどの連絡先情報を検索します。インシデント対応者は、Google Workspace 組織の Google Chat アカウントを持つユーザーである必要があります。

目標

  • インシデントに対応する Chat 用アプリを構築します。
  • 次の手順に沿って、ユーザーがインシデントに対応できるようにします。
    • インシデント対応スペースの作成。
    • インシデントと対応を要約したメッセージを投稿する。
    • インタラクティブな Chat 用アプリ機能によるコラボレーションをサポートします。
  • Vertex AI を使用して会話と解決策を要約します。

アーキテクチャ

次の図は、インシデント対応の Google Chat アプリで使用される Google Workspace と Google Cloud リソースのアーキテクチャを示しています。

インシデント対応 Google Chat アプリのアーキテクチャ

このアーキテクチャは、インシデント対応の Google Chat 用アプリがインシデントと解決策を処理する方法を示しています。

  1. ユーザーが Apps Script でホストされている外部ウェブサイトからインシデントを開始します。

  2. ウェブサイトは、Apps Script でホストされている Google Chat 用アプリに非同期 HTTP リクエストを送信します。

  3. インシデント対応の Google Chat アプリがリクエストを処理します。

    1. Apps Script Admin SDK サービスは、ユーザー ID やメールアドレスなどのチームメンバー情報を取得します。

    2. Apps Script の高度な Chat サービスを使用して Chat API に一連の HTTP リクエストを送信すると、インシデント対応の Google Chat 用アプリがインシデントの Chat スペースを作成し、チームメンバーを追加して、スペースにメッセージを送信します。

  4. チームメンバーが Chat スペースでインシデントについて話し合います。

  5. チームメンバーがスラッシュ コマンドを呼び出して、インシデントの解決を通知します。

    1. Apps Script の高度な Chat サービスを使用して Chat API に HTTP 呼び出しを行うと、Chat スペースのすべてのメッセージが一覧表示されます。

    2. Vertex AI は、リストされたメッセージを受信し、要約を生成します。

    3. Apps Script の DocumentApp サービスは、ドキュメントを作成し、Vertex AI の要約をドキュメントに追加します。

    4. インシデント対応の Google Chat アプリは、Chat API を呼び出して、概要のドキュメントへのリンクを共有するメッセージを送信します。

環境を準備する

このセクションでは、Chat 用アプリの Google Cloud プロジェクトを作成して構成する方法について説明します。

Google Cloud プロジェクトを作成する

Google Cloud コンソール

  1. Google Cloud コンソールで、メニュー > [IAM と管理] > [プロジェクトを作成] に移動します。

    [Create a Project] に移動

  2. [プロジェクト名] フィールドに、プロジェクトのわかりやすい名前を入力します。

    省略可: プロジェクト ID を編集するには、[編集] をクリックします。プロジェクトの作成後にプロジェクト ID を変更することはできないため、プロジェクトのライフタイムを考慮してニーズに合った ID を指定してください。

  3. [ロケーション] フィールドで、[参照] をクリックして、プロジェクトの候補となるロケーションを表示します。[選択] をクリックします。
  4. [作成] をクリックします。Google Cloud コンソールが [ダッシュボード] ページに移動し、数分以内にプロジェクトが作成されます。

gcloud CLI

次のいずれかの開発環境で、Google Cloud CLI(gcloud)にアクセスします。

  • Cloud Shell: gcloud CLI がすでに設定されているオンライン ターミナルを使用するには、Cloud Shell をアクティブにします。
    Cloud Shell をアクティブにする
  • ローカルシェル: ローカル開発環境を使用するには、gcloud CLI をインストールして初期化します。
    Cloud プロジェクトを作成するには、gcloud projects create コマンドを使用します。
    gcloud projects create PROJECT_ID
    作成するプロジェクトの ID を設定して、PROJECT_ID を置き換えます。

Cloud プロジェクトに対する課金を有効にする

Google Cloud コンソール

  1. Google Cloud コンソールで、[お支払い] に移動します。[メニュー] > [お支払い] > [マイ プロジェクト] をクリックします。

    [マイ プロジェクトの課金] に移動

  2. [組織を選択] で、Google Cloud プロジェクトに関連付けられている組織を選択します。
  3. プロジェクトの行で、[アクション] メニュー()を開き、[お支払い情報を変更] をクリックして、Cloud 請求先アカウントを選択します。
  4. [アカウントを設定] をクリックします。

gcloud CLI

  1. 使用可能な請求先アカウントを一覧表示するには、次のコマンドを実行します。
    gcloud billing accounts list
  2. 請求先アカウントを Google Cloud プロジェクトにリンクします。
    gcloud billing projects link PROJECT_ID --billing-account=BILLING_ACCOUNT_ID

    次のように置き換えます。

    • PROJECT_ID は、課金を有効にする Cloud プロジェクトのプロジェクト ID です。
    • BILLING_ACCOUNT_ID は、Google Cloud プロジェクトにリンクする請求先アカウント ID です。

API を有効にする

Google Cloud コンソール

  1. Google Cloud コンソールで、Google Chat API、Google ドキュメント API、Admin SDK API、Google Workspace Marketplace SDK、Vertex AI API を有効にします。

    API を有効にする

  2. 正しい Cloud プロジェクトで API を有効にしていることを確認し、[次へ] をクリックします。

  3. 正しい API を有効にしていることを確認し、[有効にする] をクリックします。

gcloud CLI

  1. 必要に応じて、現在の Cloud プロジェクトを gcloud config set project コマンドで作成したプロジェクトに設定します。

    gcloud config set project PROJECT_ID

    PROJECT_ID は、作成した Cloud プロジェクトのプロジェクト ID に置き換えます。

  2. gcloud services enable コマンドを使用して、Google Chat API、Google ドキュメント API、Admin SDK API、Google Workspace Marketplace SDK、Vertex AI API を有効にします。

    gcloud services enable chat.googleapis.com docs.googleapis.com admin.googleapis.com aiplatform.googleapis.com appsmarket-component.googleapis.com

認証と権限付与の設定

Chat 用アプリは、Chat 用アプリの認証情報を使用して Google Chat API にアクセスします。アプリは、ユーザー認証情報を使用して Admin SDK API と Google ドキュメント API にアクセスします。

ユーザーの認証と認可を設定する

認証と認可により、Chat 用アプリは Google Workspace と Google Cloud のリソースにアクセスしてインシデント対応を処理できます。具体的には、ユーザー認証は Google ドキュメント API と Admin SDK API の呼び出しに使用されます。

このチュートリアルでは、アプリを Workspace ドメインに内部公開するため、プレースホルダ情報を使用しても問題ありません。アプリを外部に公開する前に、同意画面のプレースホルダ情報を実際の情報に置き換えます。

  1. Google Cloud コンソールで、メニュー > > [ブランディング] に移動します。

    [ブランディング] に移動

  2. をすでに構成している場合は、[ブランディング]、[対象ユーザー]、[データアクセス] で次の OAuth 同意画面の設定を構成できます。[ まだ設定されていません] というメッセージが表示された場合は、[使ってみる] をクリックします。

    1. [アプリ情報] の [アプリ名] に「Incident Management」と入力します。
    2. [ユーザー サポートメール] で、メールアドレスまたは適切な Google グループを選択します。
    3. [次へ] をクリックします。
    4. [対象] で [内部] を選択します。[内部] を選択できない場合は、[外部] を選択します。
    5. [次へ] をクリックします。
    6. [連絡先情報] で、プロジェクトに対する変更の通知を受け取るメールアドレスを入力します。
    7. [次へ] をクリックします。
    8. [完了] で、Google API サービスのユーザーデータに関するポリシーを確認し、同意する場合は [Google API サービス: ユーザーデータに関するポリシーに同意します] を選択します。
    9. [続行] をクリックします。
    10. [作成] をクリックします。
    11. ユーザータイプとして [外部] を選択した場合は、テストユーザーを追加します。
      1. [オーディエンス] をクリックします。
      2. [テストユーザー] で [ユーザーを追加] をクリックします。
      3. メールアドレスと他の承認済みテストユーザーを入力し、[保存] をクリックします。
  3. [データ アクセス] > [スコープを追加または削除] をクリックします。パネルが表示され、Google Cloud プロジェクトで有効にした各 API のスコープのリストが表示されます。

    1. [Manually add scopes] に、次のスコープを貼り付けます。

      • https://www.googleapis.com/auth/documents
      • https://www.googleapis.com/auth/admin.directory.user.readonly
      • https://www.googleapis.com/auth/script.external_request
      • https://www.googleapis.com/auth/userinfo.email
      • https://www.googleapis.com/auth/cloud-platform
    2. [Add to Table] をクリックします。

    3. [更新] をクリックします。

    4. アプリに必要なスコープを選択したら、[データアクセス] ページで [保存] をクリックします。

アプリの認証と認可を設定する

アプリ認証は、Google Chat API の呼び出しに使用されます。

Google Cloud コンソールでサービス アカウントを作成する

サービス アカウントを作成するには、次の操作を行います。

Google Cloud コンソール

  1. Google Cloud コンソールで、メニュー > [IAM と管理] > [サービス アカウント] に移動します。

    [サービス アカウント] に移動

  2. [サービス アカウントを作成] をクリックします。
  3. サービス アカウントの詳細を入力し、[作成して続行] をクリックします。
  4. 省略可: サービス アカウントにロールを割り当て、Google Cloud プロジェクトのリソースへのアクセス権を付与します。詳細については、リソースへのアクセス権の付与、変更、取り消しをご覧ください。
  5. [続行] をクリックします。
  6. 省略可: このサービス アカウントを管理してアクションを実行できるユーザーまたはグループを入力します。詳細については、サービス アカウントの権限借用の管理をご覧ください。
  7. [完了] をクリックします。サービス アカウントのメールアドレスをメモします。

gcloud CLI

  1. サービス アカウントを作成します。
    gcloud iam service-accounts create SERVICE_ACCOUNT_NAME \
      --display-name="SERVICE_ACCOUNT_NAME"
  2. 省略可: サービス アカウントにロールを割り当て、Google Cloud プロジェクトのリソースへのアクセス権を付与します。詳細については、リソースへのアクセス権の付与、変更、取り消しをご覧ください。

サービス アカウントが [サービス アカウント] ページに表示されます。次に、サービス アカウントの秘密鍵を作成します。

秘密鍵を作成する

サービス アカウントの秘密鍵を作成してダウンロードする手順は次のとおりです。

  1. Google Cloud コンソールで、メニュー > [IAM と管理] > [サービス アカウント] に移動します。

    [サービス アカウント] に移動

  2. サービス アカウントを選択します。
  3. [] > [鍵を追加] > [新しい鍵を作成] をクリックします。
  4. [JSON] を選択し、[作成] をクリックします。

    新しい公開鍵と秘密鍵のペアが生成され、新しいファイルとしてパソコンにダウンロードされます。ダウンロードした JSON ファイルを作業ディレクトリに credentials.json として保存します。このファイルはこの鍵の唯一のコピーです。キーを安全に保存する方法については、サービス アカウント キーの管理をご覧ください。

  5. [閉じる] をクリックします。

サービス アカウントの詳細については、Google Cloud IAM ドキュメントのサービス アカウントをご覧ください。

Google Workspace Marketplace 対応の OAuth クライアントを作成する

Google Workspace Marketplace 対応の OAuth クライアントを作成する手順は次のとおりです。

  1. Google Cloud コンソールで、メニュー アイコン > [IAM と管理] > [サービス アカウント] に移動します。

    [サービス アカウント] に移動

  2. Chat 用に作成したサービス アカウントをクリックします。

  3. [詳細設定] をクリックします。

  4. [Google Workspace Marketplace 対応の OAuth クライアントを作成] をクリックします。

  5. [続行] をクリックします。

Google Workspace Marketplace 対応の OAuth クライアントが作成されたことを示す確認メッセージが表示されます。

Chat 用アプリを作成してデプロイする

次のセクションでは、Chat 用アプリに必要なすべてのアプリケーション コードを含む Apps Script プロジェクト全体をコピーして更新するため、各ファイルをコピーして貼り付ける必要はありません。

一部の関数には、ChatApp.gsprocessSlashCommand_() のように、名前の末尾にアンダースコアが含まれています。アンダースコアは、ブラウザで開いたときにインシデントの初期化ウェブページから関数を非表示にします。詳細については、プライベート関数をご覧ください。

Apps Script は、.gs スクリプトと .html ファイルの 2 種類のファイル形式をサポートしています。このサポートに準拠するため、アプリのクライアントサイド JavaScript は <script /> タグ内に、CSS は HTML ファイル内の <style /> タグ内に含まれています。

必要に応じて、GitHub でプロジェクト全体を確認できます。

GitHub で表示

各ファイルの概要は次のとおりです。

Consts.gs

Cloud プロジェクト ID、Vertex AI ロケーション ID、サービス アカウントのアプリ認証情報、インシデントをクローズするためのスラッシュ コマンド ID など、他のコードファイルで参照される定数を定義します。

Consts.gs コードを表示する

apps-script/incident-response-app-auth/Consts.gs
const PROJECT_ID = 'replace-with-your-project-id';
const CLOSE_INCIDENT_COMMAND_ID = 1;
const APP_CREDENTIALS = 'replace-with-your-app-credentials';
const APP_CREDENTIALS_SCOPES = 'https://www.googleapis.com/auth/chat.bot https://www.googleapis.com/auth/chat.app.memberships https://www.googleapis.com/auth/chat.app.spaces.create';
const VERTEX_AI_LOCATION_ID = 'us-central1';
const MODEL_ID = 'gemini-1.5-flash-002';
ChatApp.gs

メッセージ、カードのクリック数、スラッシュ コマンド、ダイアログなどの Chat 操作イベントを処理します。/closeIncident スラッシュ コマンドに応答して、インシデント解決の詳細情報を収集するダイアログを開きます。Chat API の spaces.messages.list メソッドを呼び出して、スペース内のメッセージを読み取ります。Apps Script の Admin SDK Directory サービスを使用してユーザー ID を取得します。

ChatApp.gs コードを表示する

apps-script/incident-response-app-auth/ChatApp.gs
/**
 * Responds to a MESSAGE event in Google Chat.
 *
 * This app only responds to a slash command with the ID 1 ("/closeIncident").
 * It will respond to any other message with a simple "Hello" text message.
 *
 * @param {Object} event the event object from Google Chat
 */
function onMessage(event) {
  if (event.message.slashCommand) {
    return processSlashCommand_(event);
  }
  return { "text": "Hello from Incident Response app!" };
}

/**
 * Responds to a CARD_CLICKED event in Google Chat.
 *
 * This app only responds to one kind of dialog (Close Incident).
 *
 * @param {Object} event the event object from Google Chat
 */
function onCardClick(event) {
  if (event.isDialogEvent) {
    if (event.dialogEventType == 'SUBMIT_DIALOG') {
      return processSubmitDialog_(event);
    }
    return {
      actionResponse: {
        type: "DIALOG",
        dialogAction: {
          actionStatus: "OK"
        }
      }
    };
  }
}

/**
 * Responds to a MESSAGE event with a Slash command in Google Chat.
 *
 * This app only responds to a slash command with the ID 1 ("/closeIncident")
 * by returning a Dialog.
 *
 * @param {Object} event the event object from Google Chat
 */
function processSlashCommand_(event) {
  if (event.message.slashCommand.commandId != CLOSE_INCIDENT_COMMAND_ID) {
    return {
      "text": "Command not recognized. Use the command `/closeIncident` to close the incident managed by this space."
    };
  }
  const sections = [
    {
      header: "Close Incident",
      widgets: [
        {
          textInput: {
            label: "Please describe the incident resolution",
            type: "MULTIPLE_LINE",
            name: "description"
          }
        },
        {
          buttonList: {
            buttons: [
              {
                text: "Close Incident",
                onClick: {
                  action: {
                    function: "closeIncident"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  ];
  return {
    actionResponse: {
      type: "DIALOG",
      dialogAction: {
        dialog: {
          body: {
            sections,
          }
        }
      }
    }
  };
}

/**
 * Responds to a CARD_CLICKED event with a Dialog submission in Google Chat.
 *
 * This app only responds to one kind of dialog (Close Incident).
 * It creates a Doc with a summary of the incident information and posts a message
 * to the space with a link to the Doc.
 *
 * @param {Object} event the event object from Google Chat
 */
function processSubmitDialog_(event) {
  const resolution = event.common.formInputs.description[""].stringInputs.value[0];
  const chatHistory = concatenateAllSpaceMessages_(event.space.name);
  const chatSummary = summarizeChatHistory_(chatHistory);
  const docUrl = createDoc_(event.space.displayName, resolution, chatHistory, chatSummary);
  return {
    actionResponse: {
      type: "NEW_MESSAGE",
    },
    text: `Incident closed with the following resolution: ${resolution}\n\nHere is the automatically generated post-mortem:\n${docUrl}`
  };
}

/**
 * Lists all the messages in the Chat space, then concatenate all of them into
 * a single text containing the full Chat history.
 *
 * For simplicity for this demo, it only fetches the first 100 messages.
 *
 * Messages with slash commands are filtered out, so the returned history will
 * contain only the conversations between users and not app command invocations.
 *
 * @return {string} a text containing all the messages in the space in the format:
 *          Sender's name: Message
 */
function concatenateAllSpaceMessages_(spaceName) {
  // Call Chat API method spaces.messages.list
  const response = Chat.Spaces.Messages.list(spaceName, { 'pageSize': 100 });
  const messages = response.messages;
  // Fetch the display names of the message senders and returns a text
  // concatenating all the messages.
  let userMap = new Map();
  return messages
    .filter(message => message.slashCommand === undefined)
    .map(message => `${getUserDisplayName_(userMap, message.sender.name)}: ${message.text}`)
    .join('\n');
}

/**
 * Obtains the display name of a user by using the Admin Directory API.
 *
 * The fetched display name is cached in the provided map, so we only call the API
 * once per user.
 *
 * If the user does not have a display name, then the full name is used.
 *
 * @param {Map} userMap a map containing the display names previously fetched
 * @param {string} userName the resource name of the user
 * @return {string} the user's display name
 */
function getUserDisplayName_(userMap, userName) {
  if (userMap.has(userName)) {
    return userMap.get(userName);
  }
  let displayName = 'Unknown User';
  try {
    const user = AdminDirectory.Users.get(
      userName.replace("users/", ""),
      { projection: 'BASIC', viewType: 'domain_public' });
    displayName = user.name.displayName ? user.name.displayName : user.name.fullName;
  } catch (e) {
    // Ignore error if the API call fails (for example, because it's an
    // out-of-domain user or Chat app)) and just use 'Unknown User'.
  }
  userMap.set(userName, displayName);
  return displayName;
}
ChatSpaceCreator.gs

インシデントの初期化ウェブページでユーザーが入力したフォームデータを受け取り、それを使用して Chat スペースを設定します。具体的には、Chat スペースを作成してデータを入力し、インシデントに関するメッセージを投稿します。

ChatSpaceCreator.gs コードを表示する

apps-script/incident-response-app-auth/ChatSpaceCreator.gs
/**
 * Handles an incident by creating a chat space, adding members, and posting a message.
 * All the actions are done using application credentials.
 *
 * @param {Object} formData - The data submitted by the user. It should contain the fields:
 *                           - title: The display name of the chat space.
 *                           - description: The description of the incident.
 *                           - users: A comma-separated string of user emails to be added to the space.
 * @return {string} The resource name of the new space.
 */
function handleIncident(formData) {
  const users = formData.users.trim().length > 0 ? formData.users.split(',') : [];
  const service = getService_();
  if (!service.hasAccess()) {
    console.error(service.getLastError());
    return;
   }
  const spaceName = createChatSpace_(formData.title, service);
  createHumanMembership_(spaceName, getUserEmail(), service);
  for (const user of users ){
    createHumanMembership_(spaceName, user, service);
  }
  createMessage_(spaceName, formData.description, service);
  return spaceName;
}
/**
 * Creates a chat space with application credentials.
 *
 * @param {string} displayName - The name of the chat space.
 * @param {object} service - The credentials of the service account.
 * @returns {string} The resource name of the new space.
*/
function createChatSpace_(displayName, service) {
  try {
    // For private apps, the alias can be used
    const my_customer_alias = "customers/my_customer";
    // Specify the space to create.
    const space = {
        displayName: displayName,
        spaceType: 'SPACE',                
        customer: my_customer_alias
    };
    // Call Chat API with a service account to create a message.
    const createdSpace = Chat.Spaces.create(
        space,
        {},
        // Authenticate with the service account token.
        {'Authorization': 'Bearer ' + service.getAccessToken()});
    return createdSpace.name;
  } catch (err) {
    // TODO (developer) - Handle exception.
    console.log('Failed to create space with error %s', err.message);
  }
}
/*
 * Creates a chat message with application credentials.
 *
 * @param {string} spaceName - The resource name of the space.
 * @param {string} message - The text to be posted.
 * @param {object} service - The credentials of the service account.
 * @return {string} the resource name of the new space.
 */
function createMessage_(spaceName, message, service) {
  try {
    // Call Chat API with a service account to create a message.
    const result = Chat.Spaces.Messages.create(
        {'text': message},
        spaceName,
        {},
        // Authenticate with the service account token.
        {'Authorization': 'Bearer ' + service.getAccessToken()});

  } catch (err) {
    // TODO (developer) - Handle exception.
    console.log('Failed to create message with error %s', err.message);
  }
}
/**
 * Creates a human membership in a chat space with application credentials.
 *
 * @param {string} spaceName - The resource name of the space.
 * @param {string} email - The email of the user to be added.
 * @param {object} service - The credentials of the service account.
 */
function createHumanMembership_(spaceName, email, service){
  try{
    const membership = {
      member: {
        name: 'users/'+email,
        // User type for the membership
        type: 'HUMAN'
      }
    };
    const result = Chat.Spaces.Members.create(
      membership,
      spaceName,
      {},
      {'Authorization': 'Bearer ' + service.getAccessToken()}
    );
  } catch (err){
    console.log('Failed to create membership with error %s', err.message)
  }

}

 /*
 * Creates a service for the service account.
 * @return {object}  - The credentials of the service account.
 */
function getService_() {
  return OAuth2.createService(APP_CREDENTIALS.client_email)
      .setTokenUrl('https://oauth2.googleapis.com/token')
      .setPrivateKey(APP_CREDENTIALS.private_key)
      .setIssuer(APP_CREDENTIALS.client_email)
      .setSubject(APP_CREDENTIALS.client_email)
      .setScope(APP_CREDENTIALS_SCOPES)
      .setPropertyStore(PropertiesService.getScriptProperties());
}
DocsApi.gs

Google ドキュメント API を呼び出して、ユーザーの Google ドライブに Google ドキュメントを作成し、VertexAiApi.gs で作成されたインシデント情報の概要をドキュメントに書き込みます。

DocsApi.gs コードを表示する

apps-script/incident-response-app-auth/DocsApi.gs
/**
 * Creates a Doc in the user's Google Drive and writes a summary of the incident information to it.
 *
 * @param {string} title The title of the incident
 * @param {string} resolution Incident resolution described by the user
 * @param {string} chatHistory The whole Chat history be included in the document
 * @param {string} chatSummary A summary of the Chat conversation to be included in the document
 * @return {string} the URL of the created Doc
 */
function createDoc_(title, resolution, chatHistory, chatSummary) {
  let doc = DocumentApp.create(title);
  let body = doc.getBody();
  body.appendParagraph(`Post-Mortem: ${title}`).setHeading(DocumentApp.ParagraphHeading.TITLE);
  body.appendParagraph("Resolution").setHeading(DocumentApp.ParagraphHeading.HEADING1);
  body.appendParagraph(resolution);
  body.appendParagraph("Summary of the conversation").setHeading(DocumentApp.ParagraphHeading.HEADING1);
  body.appendParagraph(chatSummary);
  body.appendParagraph("Full Chat history").setHeading(DocumentApp.ParagraphHeading.HEADING1);
  body.appendParagraph(chatHistory);
  return doc.getUrl();
}
VertexAiApi.gs

Vertex AI API を使用して、Chat スペースでの会話を要約します。この要約は、DocsAPI.gs の専用ドキュメントに投稿されます。

VertexAiApi.gs コードを表示する

apps-script/incident-response-app-auth/VertexAiApi.gs
/**
 * Summarizes a Chat conversation using the Vertex AI text prediction API.
 *
 * @param {string} chatHistory The Chat history that will be summarized.
 * @return {string} The content from the text prediction response.
 */


function summarizeChatHistory_(chatHistory) {

  const API_ENDPOINT = `https://${VERTEX_AI_LOCATION_ID}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${VERTEX_AI_LOCATION_ID}/publishers/google/models/${MODEL_ID}:generateContent`;
  const prompt = "Summarize the following conversation between Engineers resolving an incident"
      + " in a few sentences. Use only the information from the conversation.\n\n" + chatHistory;
  // Get the access token.
  const accessToken = ScriptApp.getOAuthToken();

  const headers = {
    'Authorization': 'Bearer ' + accessToken,
    'Content-Type': 'application/json',
  };
  const payload = {
    'contents': {
      'role': 'user',
      'parts' : [
        {
          'text': prompt
        }
      ]
    }
  }
  const options = {
    'method': 'post',
    'headers': headers,
    'payload': JSON.stringify(payload),
    'muteHttpExceptions': true,
  };
  try {
    const response = UrlFetchApp.fetch(API_ENDPOINT, options);
    const responseCode = response.getResponseCode();
    const responseText = response.getContentText();

    if (responseCode === 200) {
      const jsonResponse = JSON.parse(responseText);
      console.log(jsonResponse)
      if (jsonResponse.candidates && jsonResponse.candidates.length > 0) {
        return jsonResponse.candidates[0].content.parts[0].text; // Access the summarized text
      } else {
        return "No summary found in response.";
      }

    } else {
      console.error("Vertex AI API Error:", responseCode, responseText);
      return `Error: ${responseCode} - ${responseText}`;
    }
  } catch (e) {
    console.error("UrlFetchApp Error:", e);
    return "Error: " + e.toString();
  }
}
WebController.gs

インシデントの初期化ウェブサイトを提供します。

WebController.gs コードを表示する

apps-script/incident-response-app-auth/WebController.gs
/**
 * Serves the web page from Index.html.
 */
function doGet() {
  return HtmlService
    .createTemplateFromFile('Index')
    .evaluate();
}

/**
 * Serves the web content from the specified filename.
 */
function include(filename) {
  return HtmlService
    .createHtmlOutputFromFile(filename)
    .getContent();
}

/**
 * Returns the email address of the user running the script.
 */
function getUserEmail() {
  return Session.getActiveUser().getEmail();
}
Index.html

インシデントの初期化ウェブサイトを構成する HTML。

Index.html コードを表示する

apps-script/incident-response-app-auth/Index.html
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet'>
    <?!= include('Stylesheet'); ?>
  </head>
  <body>
    <div class="container">
      <div class="content">
        <h1>Incident Manager</h1>
        <form id="incident-form" onsubmit="handleFormSubmit(this)">
          <div id="form">
            <p>
              <label for="title">Incident title</label><br/>
              <input type="text" name="title" id="title" />
            </p>
            <p>
              <label for="users">Incident responders</label><br/>
              <small>
                Please enter a comma-separated list of email addresses of the users
                that should be added to the space.
                Do not include <?= getUserEmail() ?> as it will be added automatically.
              </small><br/>
              <input type="text" name="users" id="users" />
            </p>
            <p>
              <label for="description">Initial message</label></br>
              <small>This message will be posted after the space is created.</small><br/>
              <textarea name="description" id="description"></textarea>
            </p>
            <p class="text-center">
              <input type="submit" value="CREATE CHAT SPACE" />
            </p>
          </div>
          <div id="output" class="hidden"></div>
          <div id="clear" class="hidden">
            <input type="reset" value="CREATE ANOTHER INCIDENT" onclick="onReset()" />
          </div>
        </form>
      </div>
    </div>
    <?!= include('JavaScript'); ?>
  </body>
</html>
JavaScript.html

インシデントの初期化ウェブサイトの送信、エラー、クリアなどのフォームの動作を処理します。これは、WebController.gs のカスタム include 関数によって Index.html に含まれます。

JavaScript.html コードを表示する

apps-script/incident-response-app-auth/JavaScript.html
<script>
  var formDiv = document.getElementById('form');
  var outputDiv = document.getElementById('output');
  var clearDiv = document.getElementById('clear');

  function handleFormSubmit(formObject) {
    event.preventDefault();
    outputDiv.innerHTML = 'Please wait while we create the space...';
    hide(formDiv);
    show(outputDiv);
    google.script.run
      .withSuccessHandler(updateOutput)
      .withFailureHandler(onFailure)
      .handleIncident(formObject);
  }

  function updateOutput(response) {
    var spaceId = response.replace('spaces/', '');
    outputDiv.innerHTML =
      '<p>Space created!</p><p><a href="https://mail.google.com/chat/#chat/space/'
        + spaceId
        + '" target="_blank">Open space</a></p>';
    show(outputDiv);
    show(clearDiv);
  }

  function onFailure(error) {
    outputDiv.innerHTML = 'ERROR: ' + error.message;
    outputDiv.classList.add('error');
    show(outputDiv);
    show(clearDiv);
  }

  function onReset() {
    outputDiv.innerHTML = '';
    outputDiv.classList.remove('error');
    show(formDiv);
    hide(outputDiv);
    hide(clearDiv);
  }

  function hide(element) {
    element.classList.add('hidden');
  }

  function show(element) {
    element.classList.remove('hidden');
  }
</script>
Stylesheet.html

インシデントの初期化ウェブサイトの CSS。これは、WebController.gs のカスタム include 関数によって Index.html に含まれます。

Stylesheet.html コードを表示する

apps-script/incident-response-app-auth/Stylesheet.html
<style>
  * {
    box-sizing: border-box;
  }
  body {
    font-family: Roboto, Arial, Helvetica, sans-serif;
  }
  div.container {
    display: flex;
    justify-content: center;
    align-items: center;
    position: absolute;
    top: 0; bottom: 0; left: 0; right: 0;
  }
  div.content {
    width: 80%;
    max-width: 1000px;
    padding: 1rem;
    border: 1px solid #999;
    border-radius: 0.25rem;
    box-shadow: 0 2px 2px 0 rgba(66, 66, 66, 0.08), 0 2px 4px 2px rgba(66, 66, 66, 0.16);
  }
  h1 {
    text-align: center;
    padding-bottom: 1rem;
    margin: 0 -1rem 1rem -1rem;
    border-bottom: 1px solid #999;
  }
 #output {
    text-align: center;
    min-height: 250px;
  }
  div#clear {
    text-align: center;
    padding-top: 1rem;
    margin: 1rem -1rem 0 -1rem;
    border-top: 1px solid #999;
  }
  input[type=text], textarea {
    width: 100%;
    padding: 1rem 0.5rem;
    margin: 0.5rem 0;
    border: 0;
    border-bottom: 1px solid #999;
    background-color: #f0f0f0;
  }
  textarea {
    height: 5rem;
  }
  small {
    color: #999;
  }
  input[type=submit], input[type=reset] {
    padding: 1rem;
    border: none;
    background-color: #6200ee;
    color: #fff;
    border-radius: 0.25rem;
    width: 25%;
  }
  .hidden {
    display: none;
  }
  .text-center {
    text-align: center;
  }
  .error {
    color: red;
  }
</style>

Cloud プロジェクトの番号と ID を確認する

  1. Google Cloud コンソールで、Cloud プロジェクトに移動します。

    Google Cloud コンソールに移動

  2. 設定とユーティリティ > [プロジェクトの設定] をクリックします。

  3. [プロジェクト番号] フィールドと [プロジェクト ID] フィールドの値を書き留めます。これらは、以降のセクションで使用します。

Apps Script プロジェクトを作成する

Apps Script プロジェクトを作成して Cloud プロジェクトに接続するには:

  1. 次のボタンをクリックして、Google Chat でインシデントに対応する Apps Script プロジェクトを開きます。
    プロジェクトを開く
  2. [概要] をクリックします。
  3. 概要ページで、コピーを作成するためのアイコン [コピーを作成] をクリックします。
  4. Apps Script プロジェクトのコピーに名前を付けます。

    1. [Copy of Respond to incidents with Google Chat] をクリックします。

    2. [プロジェクトのタイトル] に「Incident Management Chat app」と入力します。

    3. [名前を変更] をクリックします。

  5. Apps Script プロジェクトのコピーで、Consts.gs ファイルに移動し、YOUR_PROJECT_ID を Cloud プロジェクトの ID に置き換えます。

Apps Script プロジェクトの Cloud プロジェクトを設定する

  1. Apps Script プロジェクトで、プロジェクト設定のアイコン [プロジェクトの設定] をクリックします。
  2. [Google Cloud Platform(GCP)プロジェクト] で、[プロジェクトを変更] をクリックします。
  3. [GCP プロジェクト番号] に、Cloud プロジェクトのプロジェクト番号を貼り付けます。
  4. [プロジェクトを設定] をクリックします。これで、Cloud プロジェクトと Apps Script プロジェクトが接続されました。

Apps Script のデプロイを作成する

すべてのコードが配置されたので、Apps Script プロジェクトをデプロイします。デプロイ ID は、Google Cloud で Chat 用アプリを構成するときに使用します。

  1. Apps Script で、インシデント対応アプリのプロジェクトを開きます。

    Apps Script に移動

  2. [Deploy] > [New deployment] をクリックします。

  3. [アドオン] と [ウェブアプリ] がまだ選択されていない場合は、[種類の選択] の横にあるデプロイタイプ プロジェクト設定のアイコン をクリックし、[アドオン] と [ウェブアプリ] を選択します。

  4. [説明] に、このバージョンの説明(例: Complete version of incident management app)を入力します。

  5. [実行ユーザー] で、[ウェブアプリにアクセスするユーザー] を選択します。

  6. [アクセスできるユーザー] で、[Workspace 組織内の全員] を選択します。ここで、「Workspace 組織」は Google Workspace 組織の名前です。

  7. [デプロイ] をクリックします。Apps Script はデプロイの成功を報告し、デプロイ ID とインシデントの初期化ウェブページの URL を提供します。

  8. インシデントを開始するときに後でアクセスできるように、ウェブアプリの URL をメモしておきます。[デプロイ ID] をコピーします。この ID は、Google Cloud コンソールで Chat 用アプリを構成するときに使用します。

  9. [完了] をクリックします。

Google Cloud コンソールで Chat 用アプリを構成する

このセクションでは、Google Cloud コンソールで Google Chat API を構成する方法について説明します。この構成には、Chat 用アプリに関する情報(Apps Script プロジェクトから作成したデプロイの ID など)が含まれます。

  1. Google Cloud コンソールで、メニュー > [その他のプロダクト] > [Google Workspace] > [プロダクト ライブラリ] > [Google Chat API] > [管理] > [構成] をクリックします。

    Chat API の構成に移動

  2. [アプリ名] に「Incident Management」と入力します。

  3. [アバターの URL] に「https://developers.google.com/chat/images/quickstart-app-avatar.png」と入力します。

  4. [説明] に「Responds to incidents.」と入力します。

  5. [インタラクティブ機能を有効にする] 切り替えボタンをクリックしてオンにします。

  6. [機能] で、[スペースとグループの会話に参加する] を選択します。

  7. [接続設定] で [Apps Script] を選択します。

  8. [Deployment ID] に、先ほど Apps Script プロジェクトのデプロイからコピーした Apps Script デプロイ ID を貼り付けます。

  9. 完全に実装された Chat 用アプリが使用するスラッシュ コマンドを登録します。

    1. [コマンド] で、[コマンドを追加] をクリックします。

    2. [コマンド ID] に「1」と入力します。

    3. [説明] に「Closes the incident being discussed in the space.」と入力します。

    4. [コマンドの種類] で [スラッシュ コマンド] を選択します。

    5. [スラッシュ コマンド名] に「/closeIncident」と入力します。

    6. [ダイアログを開く] を選択します。

    7. [完了] をクリックします。スラッシュ コマンドが登録され、一覧表示されます。

  10. [公開設定] で、[Your Workspace Domain 内の特定のユーザーおよびグループにこの Chat 用アプリの利用を許可する] を選択し、メールアドレスを入力します。

  11. [ログ] で、[エラーを Logging にロギング] を選択します。

  12. [保存] をクリックします。[設定が保存されました] というメッセージが表示され、アプリのテストの準備が整ったことを示します。

管理者の承認を得る

管理者の承認を得るには、Google Workspace Marketplace SDK で Chat 用アプリを構成する必要があります。

Google Workspace Marketplace SDK で Chat 用アプリを構成する

Google Workspace Marketplace SDK で Chat 用アプリを構成する手順は次のとおりです。

  1. Google Cloud コンソールで、メニュー > [API とサービス] > [有効な API とサービス] > [Google Workspace Marketplace SDK] > [アプリの構成] に移動します。

    [アプリの構成] に移動

  2. [アプリの構成] ページを完了します。Chat 用アプリの設定方法は、対象ユーザーなどの要因によって異なります。アプリの構成ページの入力については、Google Workspace Marketplace SDK でアプリを構成するをご覧ください。このガイドでは、次の情報を入力します。

    1. [アプリの公開設定] で [限定公開] を選択します。
    2. [インストール設定] で、[管理者によるインストール] を選択します。
    3. [アプリの統合] で [Chat 用アプリ] を選択します。
    4. [OAuth スコープ] に、次のスコープを入力します。
      • https://www.googleapis.com/auth/chat.app.spaces
      • https://www.googleapis.com/auth/chat.app.memberships
    5. [デベロッパー情報] で、デベロッパー名デベロッパー ウェブサイトの URLデベロッパーのメールアドレスを入力します。
    6. [Save draft] をクリックします。

アプリを設定したら、ストアの掲載情報を更新します。

  1. Google Cloud コンソールで、メニュー > [API とサービス] > [有効な API とサービス] > [Google Workspace Marketplace SDK] > [ストアの掲載情報] に移動します。
  2. [App details] で、カテゴリとして [Web Development] を選択します。
  3. [グラフィック アセット] で、リクエストされた形式のアプリ アイコンをアップロードします。
  4. [スクリーンショット] で、アプリケーションのスクリーンショットをアップロードします。
  5. [サポートリンク] で、利用規約の URL、プライバシー ポリシーの URL、サポート URL を入力します。
  6. [Distribution] で、このアプリケーションを利用可能にする地域を選択します。
  7. [公開] をクリックします。

管理者の承認を得る

サービス アカウントが管理者の承認を受け取るように構成されたので、Chat 用アプリの認可を設定するの手順に沿って承認を付与できる Google Workspace 管理者から承認を取得します。

Chat アプリをテストする

インシデント管理 Chat 用アプリをテストするには、ウェブページからインシデントを開始し、Chat 用アプリが想定どおりに動作することを確認します。

  1. Apps Script のデプロイ ウェブアプリの URL に移動します。

  2. Apps Script からデータへのアクセス権限を求められたら、[権限を確認] をクリックし、Google Workspace ドメインの適切な Google アカウントでログインして、[許可] をクリックします。

  3. インシデントの初期化ウェブページが開きます。テスト情報を入力します。

    1. [インシデントのタイトル] に「The First Incident」と入力します。
    2. 必要に応じて、[Incident responders] に、インシデント対応担当者のメールアドレスを入力します。Google Workspace 組織の Google Chat アカウントを持つユーザーである必要があります。そうでない場合、スペースの作成は失敗します。自分のメールアドレスは自動的に含まれるため、入力しないでください。
    3. [初期メッセージ] に「Testing the incident management Chat app.」と入力します。
  4. [Create Chat Space] をクリックします。creating space メッセージが表示されます。

  5. スペースが作成されると、Space created! メッセージが表示されます。[スペースを開く] をクリックします。Chat のスペースが新しいタブで開きます。

  6. 必要に応じて、あなたと他のインシデント対応者はスペースでメッセージを送信できます。アプリは Vertex AI を使用してこれらのメッセージを要約し、振り返りドキュメントを共有します。

  7. インシデント対応を終了して解決プロセスを開始するには、Chat スペースで「/closeIncident」と入力します。インシデント管理ダイアログが開きます。

  8. [インシデントをクローズ] に、インシデントの解決策の説明(Test complete など)を入力します。

  9. [Close Incident](インシデントをクローズ)をクリックします。

インシデント管理アプリは、スペース内のメッセージを一覧表示し、Vertex AI で要約して、Google ドキュメントに要約を貼り付け、スペースでドキュメントを共有します。

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、Cloud プロジェクトを削除することをおすすめします。

  1. Google Cloud コンソールで、[リソースの管理] ページに移動します。メニュー アイコン > [IAM と管理] > [リソースの管理] をクリックします。

    Resource Manager に移動

  2. プロジェクト リストで、削除するプロジェクトを選択し、[削除] をクリックします。
  3. ダイアログでプロジェクト ID を入力し、[シャットダウン] をクリックしてプロジェクトを削除します。