生成 AI ライブラリを作成して GAS・AppSheet アプリに Gemini を簡単導入

Gemini_in_GAS_AppSheet

Gemini API を簡単に利用できるライブラリで Google Apps Script や AppSheet で作成した簡易内製アプリが一気にパワーアップする方法を紹介します。

Gemini for Google Workspace が Google Workspace(以下 GWS)の全てのエディションに標準搭載され、GWS での作業が Gemini によって効率よくこなせるようになりました。Google Apps Script(以下 GAS)を利用して業務に合わせてカスタマイズされたアプリを作成し業務効率を上げているユーザーも多いと思います。そんな内製アプリに生成 AI の機能が搭載されたら、より高度な業務効率化が期待できます。またノーコードアプリ開発ツールである AppSheet を利用して非エンジニアの方も簡単に業務アプリが作成できるように生成 AI 機能による開発サポートの機能が搭載されましたが、まだ生成 AI のアプリ内利用は限定的です。今回は GAS や AppSheet から簡単に生成 AI 機能を利用することができるようになるライブラリを紹介し、MBS での活用事例をご紹介します。

Gemini API を呼び出すライブラリの作成

Vertex AI 管理画面での操作

GAS や AppSheet のアプリから簡単に呼び出せるように、 Gemini API を呼び出すライブラリを GAS で作成します。
Gemini API を呼び出すには Google AI Studio で API キーを発行する方法もありますが、今回は Google Cloud の Vertex AI から利用します。

Google Cloud のコンソール画面で必要な API を有効化させて Vertex AI にアクセスします。(プロジェクト作成直後であれば Vertex AI のダッシュボード画面にアクセスしたときに推奨される API 群をまとめて有効化できますね。)Vertex AI Studio > 自由形式 へアクセスすると一般的な生成 AI チャットのような対話型のインターフェースが表示されます。ここでは、モデル選択や各種パラメータ設定を通じて、プロンプトに対する応答を調整できます。Gemini 以外にも、Anthropic の Claude や Meta の Llama など、様々なモデルを選択可能です。

今回は生成 AI モデルに本記事執筆時最新の Gemini 2.0 Flash(gemini-2.0-flash-001)を選択しました。パラメータはデフォルト値とし、テキストフォーマットは「書式なしテキスト」を選択しました。また業務利用を目的としているので安全フィルタを以下のように設定しました。日本語の表現と英語の表記で少しニュアンスに差異がありそうですので英語での表記も併記します。

項目 設定
悪意のある表現(HATE SPEECH) ほとんどをブロック(BLOCK LOW AND ABOVE)
危険なコンテンツ(DANGEROUS CONTENTS) 一部をブロック(BLOCK MEDIUM AND ABOVE)
性的描写が露骨なコンテンツ(SEXUALLY EXPLICIT) ほとんどをブロック(BLOCK LOW AND ABOVE)
ハラスメントコンテンツ(HARASSMENT) ほとんどをブロック(BLOCK LOW AND ABOVE)

任意のプロンプトを入力して適切な応答が得られたら、画面右上の「 <> コードを取得」をクリックします。展開された画面の右上にある「 cURL 」ボタンをクリックすると、Google Cloud SDK環境のターミナルからcurlコマンドでAPIを呼び出すコードが表示されます。このコードを若干修正することで、GAS から Gemini API を呼び出すことが可能になります。

GAS ライブラリの作成

前項で取得したコードを元に、UrlFetchApp を用いて、Gemini APIエンドポイントにプロンプトを送信するコードを記述します。今回は以下のようなコードを作成しました。
Google Cloud プロジェクト ID、API エンドポイント、モデル ID、ロケーション ID はスクリプトプロパティから取得するように記述していますので、GAS の設定画面より適宜プロパティに必要な設定情報を登録してください。


gemini.gs


/**
 * Google Gemini APIを使用してテキストを生成する関数。
 *
 * @param {string} prompt 生成するテキストのプロンプト。必須。
 * @param {Object} [config] 生成オプションの設定オブジェクト。省略可能。
 * @param {number} [config.temperature=1] 生成テキストの多様性を制御する温度パラメータ。0〜1の値。高いほど多様になります。
 * @param {number} [config.maxOutputTokens=8192] 生成される最大トークン数。トークンは単語や句読点のようなテキストの単位です。
 * @param {number} [config.topP=0.95] 生成テキストの選択確率を制御するTop-pパラメータ。低いほど、より予測可能なテキストが生成されます。
 * @param {number} [config.topK=32] 生成テキストの選択確率を制御するTop-kパラメータ。上位K個のトークンのみが考慮されます。
 *
 * @return {string} 生成されたテキスト。
 * @throws {Error} APIリクエストが失敗した場合、エラーをスローします。
 */
function gemini(prompt, config = {}) {
    // デフォルトの生成設定。必要に応じて変更可能です。
    const DEFAULT_GEN_CONFIG = {
        responseModalities: ["TEXT"], // 応答形式はテキストのみ
        maxOutputTokens: 8192,          // 生成される最大トークン数
        temperature: 1,                // 生成テキストの多様性(0〜1)
        topP: 0.95,                   // Top-p パラメータ
        topK: 32,                     // Top-k パラメータ
    };

    // 提供された設定でデフォルト設定を上書き
    const genConfig = { ...DEFAULT_GEN_CONFIG, ...config };

    // APIリクエストのボディ
    const request = {
        contents: [{ role: "user", parts: [{ text: prompt }] }], // プロンプトをユーザー入力として設定
        generationConfig: genConfig,                           // 生成設定
        safetySettings: [                                      // 安全設定
            { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_LOW_AND_ABOVE" },        // ヘイトスピーチ検出
            { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_MEDIUM_AND_ABOVE" }, // 危険なコンテンツ検出
            { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_LOW_AND_ABOVE" },  // 性的に露骨なコンテンツ検出
            { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_LOW_AND_ABOVE" },         // ハラスメント検出
        ],
    };

    // Google Cloud Projectの設定
    const API_ENDPOINT = PropertiesService.getScriptProperties().getProperty("API_ENDPOINT");   // APIエンドポイント
    const PROJECT_ID = PropertiesService.getScriptProperties().getProperty("PROJECT_ID");       // プロジェクトID
    const MODEL_ID = PropertiesService.getScriptProperties().getProperty("MODEL_ID");           // 使用するモデルID
    const LOCATION_ID = PropertiesService.getScriptProperties().getProperty("LOCATION_ID");     // ロケーションID
    const GENERATE_CONTENT_API = "streamGenerateContent";    // API名

    // APIリクエストURLの構築
    const url = `https://${API_ENDPOINT}/v1/projects/${PROJECT_ID}/locations/${LOCATION_ID}/publishers/google/models/${MODEL_ID}:${GENERATE_CONTENT_API}`;

    // OAuth 2.0 トークンの取得
    const token = ScriptApp.getOAuthToken();

    // APIリクエストのオプション
    const options = {
        method: "POST",
        headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json",
        },
        payload: JSON.stringify(request),
        muteHttpExceptions: false,                       // HTTP例外をキャッチするためにfalseを設定
    };

    try {
        // APIリクエストの実行
        const response = UrlFetchApp.fetch(url, options);

        // HTTPステータスコードの確認
        if (response.getResponseCode() !== 200) {
            const error = JSON.parse(response.getContentText());
            const errorMessage = `Gemini API Error: ${response.getResponseCode()} ${error.error.message}`;
            Logger.log(errorMessage);                             
            throw new Error(errorMessage);                   
        }

        // レスポンスデータの解析
        const responseData = JSON.parse(response.getContentText());
        let returnText = "";

        // 複数チャンクのレスポンスを結合
        if (responseData && Array.isArray(responseData)) {
            for (const data of responseData) {
                // 安全なプロパティアクセス
                if (data?.candidates?.[0]?.content?.parts?.[0]?.text) {
                    returnText += data.candidates[0].content.parts[0].text;
                } else {
                    Logger.log(`Invalid response data format: ${JSON.stringify(data)}`); 
                }
            }
        } else {
            Logger.log(`Invalid response data: ${JSON.stringify(responseData)}`); 
        }

        return returnText; // 生成されたテキストを 返す
    } catch (e) {
        Logger.log(`An error occurred: ${e.message}`);        
        throw e;                                            
    }
}



上記コードの実行にはスコープの追加が必要になります。
設定画面からマニフェストファイル appscript.json を表示するにチェックを入れ、下記のように oauthScopes を追記します。


appscript.json


{
  "timeZone": "Asia/Tokyo",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/cloud-platform.read-only",
    "https://www.googleapis.com/auth/script.external_request"
  ]
}

期待どおりの応答が得られたらライブラリ用のコードは完成です。

function testGemini() {
  var res = gemini("空はなぜ青い?");
  console.log(res);
}

Gemini ライブラリの活用(GAS 編)

GAS アプリから利用できるようにライブラリ化する

[ デプロイ ▼ ] よりライブラリとしてデプロイします。
「種類」に ライブラリ を選択します。

デプロイしたライブラリを他の GAS プロジェクトで利用する時に必要になるスクリプト ID は歯車アイコンの設定画面から取得できます。

GAS アプリからライブラリを利用する

[ライブラリを利用する GAS プロジェクトで [ライブラリ + ] をクリックしてコピーしたスクリプト ID を入力して登録します。

下記のようなテストコードで、それらしい回答が返ってきたらライブラリの利用ができる状態になっています。

function testLibrary () {
  var response = [ライブラリのファイル名].gemini(“雲が白いのはなぜ?”);
  console.log(response);
}

MBS の GAS アプリでの Gemini 活用事例

MBS では2024年の GWS 事例コンテストにて最優秀賞をいただいた FAX の自動分析などでこのライブラリを利用しています。
詳細はこちらのブログ記事をぜひご覧ください。

enter image description here

Gemini ライブラリの活用(AppSheet 編)

AppSheet でのライブラリ利用

AppSheet で GAS の関数を利用するには Automation オートメーションを利用します。
今回は「旅行先」「種類(おかし、置物など)」「渡す相手」を入力すると、旅先で購入するおみやげをリコメンドしてくれるアプリを作成する想定で説明します。

❶プロンプトの作成

Data にプロンプト用のバーチャルカラムを作成する
    プロンプトはバーチャルカラムに記述すると扱いが便利です。

バーチャルカラムの FROMULA にプロンプトを記述する
    プロンプトにはデータの情報を含めることができます。データは列名を「 [列名] 」の形式で記述し、プロンプトの文言と「 & 」でつなぎます。

FORMULA の入力例

"旅行先で買うお土産を提案してください。"
& [エリア] & "に出かけます。"
& [おみやげ種類] & "を買う予定です。"
& [渡す相手] & "に渡します。回答はお土産に最適なものの例をひとつ具体的な名称で挙げてください。"

❷Gemini に投げる

AppSheet で GAS の関数を利用するオートメーションは下図のような流れになります。

Eventの作成
    データが追加または編集された時にオートメーションが実行されるように、
    Data change typeAddsUpdates を選択。

    Condition には追加・編集前後でデータが変更されたときのみオートメーションが走るように設定します。
    編集前後でデータが変更されたことを示すのはこのように書くのが定番です。

[_THISROW_BEFORE].[ 列名 ] <> [_THISROW_AFTER].[ 列名 ]

Condition の入力例

OR(
  [_THISROW_BEFORE].[エリア] <> [_THISROW_AFTER].[エリア],
  [_THISROW_BEFORE].[おみやげ種類] <> [_THISROW_AFTER].[おみやげ種類],
  [_THISROW_BEFORE].[渡す相手] <> [_THISROW_AFTER].[渡す相手]
)

GAS にデータを投げる(step1)

    Process を追加し、名称を「step1」として、「Run a task」を選択。
    「Call a script」を選択し、 Apps Script Project で作成したライブラリの GAS プロジェクトを選択します。
    Function name でライブラリ内の関数名を選択します。今回のサンプルでは gemini を選択。
    Function Parameters で prompt の欄に バーチャルカラムの [ プロンプト ] を選択。
    Return Value をオンにして、type は String を選択。

GAS からデータを取得(step2)

    Process にステップ を追加。名称を「step2」とし、Run a data action を選択。
    Set row values を選択。
    Set these column で 「おみやげ」に step1 で Gemini に投げたプロンプトの返り値を入れる。
    「おみやげ」列を選択し、値として [step1].[Output] を設定する。

「旅行先」「種類(おかし、置物など)」「渡す相手」を入力して保存すると(しばらく待って、リロードボタンを押下)、「おみやげ」の列におすすめのおみやげが提案されていれば成功です。

MBS の AppSheet アプリでの Gemini 活用事例

MBSでは毎年エンジニア、非エンジニアを問わず参加できる社内ハッカソンを開催していますが、2025年2月に開催したハッカソンでは「生成 AI × AppSheet」と銘打ってこのライブラリを利用したノーコードアプリ開発に取り組みました。プログラミングの経験がない社員も簡単に生成 AI を活用したアプリケーションが簡単につくれるとあって、さまざまなアイデアが飛び交い非常に盛り上がりました。
詳細はこちらのブログをぜひご覧ください。

ライブラリの拡張

実際に MBS で利用しているライブラリでは画像のドライブファイル ID を指定することでマルチモーダル対応を可能にしたり、複数の回答をJSON形式で返すようにしています。業務にあったスタイルになるようにライブラリや呼び出し元のアプリケーションを工夫してみてください。

まとめ

今回は GAS や AppSheet で作成した簡易内製アプリで簡単に生成 AI を利用できるライブラリの作成を紹介しました。ライブラリを社内で共有しておくことで、社内アプリの高度化や市民開発のモチベーションアップにつながることを期待します!

Next Post Previous Post