【LLM】GoogleスプレッドシートでGemini関数を使える環境を構築する話

# はじめに
:::note info
🎄 本記事は [ZOZO Advent Calendar 2024](https://qiita.com/advent-calendar/2024/zozo) シリーズ11の24日目です
:::

[Google Workspace Labs](https://support.google.com/docs/answer/13447104?hl=JA)で利用できるようになった「Gemini in Google スプレッドシート」をご存知でしょうか？

公式のドキュメントを見てみると、サービスの機能として以下が紹介されています。

> - テーブルを作成する。
> - 数式を作成する。
> - データ分析と分析情報を生成する。
> - グラフを作成する。
> - ドライブ内のファイルや Gmail 内のメールの内容を要約する。

確かに便利な機能ではあるのですが、痒いところに手が届かないと感じた方も多いのではないでしょうか？

```python
= gemini(system_content, user_content)
```
私が本当に使いたかったのは、この関数をいつでも実行できるスプレッドシートでした。

公式が提供してくれないなら作れば良い！ということで、本記事では「GoogleスプレッドシートでGemini(LLM)関数を実装するための環境構築方法」についてご紹介します！✨

```
・ 想定読者　　　　：APIの構築方法やGoogleスプレッドシートのカスタム関数について理解していて、社内の業務効率化に取り組みたい方
・ 読んで分かること：スプレッドシートでGemini(LLM)を使うための環境構築方法
```

# システムの全体像
今回ご紹介するシステムは大きく分けて以下の2つのフローで構築しています。
1. Geminiにリクエストできる社内用APIを構築：事前準備として構築、BigQueryでログを管理
2. GAS経由でAPIにリクエストする関数を用意：関数への入力をAPIに渡す役割

![gemini.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/539242/62e59b1c-e4e6-4193-6e87-2b31634972ad.jpeg)

本記事ではAPIの構築方法は省略し、LLM周りの環境を中心にご説明します。

# Geminiにリクエストできる社内用APIを構築する方法
構築方法について以下の手順で説明しています。

Step1：BigQueryでログの入出力環境を構築
Step2：VertexAIのSDKでGeminiを利用する

## 💾 Step1：BigQueryでログの入出力環境を構築
#### 「どうしてログを管理しなければならないの？」
Googleスプレッドシートには再計算という処理があり、関数内で参照している値が変更された際や、一定時間が経過後に再度スプシを開いた際、行や列の変更があった際に関数の計算が再実行されます。参照している値が変更された場合であれば問題ないのですが、それ以外の状況で再計算を行うとLLMを利用するコストが余計に発生します。これを回避するために、「参照している値が変更されたのか？」をチェックするためのテーブルを用意する必要があります。


今回のログ用テーブルは以下の構成で作成しました。

|   フィールド名  | 種類        |
|:--------------|:-----------|
| request_date  | DATE       |
| request_sheet | STRING     |
| system_content| STRING     |
| user_content  | STRING     |
| response      | STRING     |

「request_date, request_sheet」はどのシートから行われたリクエストなのかを特定するために作成したカラムです。カスタム関数を実行したユーザーをGASで取得することができないため、代わりにシート名を取得しています。
「system_contentとuser_content」は関数に入力された引数です。「response」はGeminiからの回答になります。

APIにリクエストがある度に、このテーブルに対して以下のようなクエリを実行し、responseがnullではなかった場合はそのまま値が返されます。

```sql
SELECT
    response
FROM `gemini-log-table`
WHERE 1=1
    AND system_content = "hoge"
    AND user_content = "huga"
```

responseの値がnullだった場合は、新しくgeminiにリクエストを行い、その結果をテーブルに格納します。

```sql
INSERT INTO `gemini-log-table`
SELECT
    CURRENT_DATE() AS request_date
    , "{sheet_name}" AS request_sheet
    , "{system_content}" AS system_content
    , "{user_content}" AS user_content
    , "{response  }" AS response
```

この分岐処理を実装できたら、ログ周りの仕組みが完成です。

※ この方法が正解というわけではないので、おすすめのログ管理方法があればコメントで教えてください 🙏

## 🤖 Step2：VertexAIのSDKでGeminiを利用する方法
VertexAIのSDKを利用することで、簡単にGeminiを利用することができます。

```python
from vertexai.generative_models import GenerativeModel

def gemini(system_content, user_content):
    model = GenerativeModel(
        "gemini-1.5-flash-002",
        system_instruction=[
            system_content,
        ]
    )

    response = model.generate_content(
    user_message,
    generation_config={
        "max_output_tokens": 4000,
        "temperature": 0
    },
    stream=False
    )

    
    print(response.candidates[0].content.parts[0].text)
    answer = response.candidates[0].content.parts[0].text
    
    return answer
```

パラメータは各々の環境に合わせて調整してみてください。

# GAS経由でAPIにリクエストする関数を用意
構築方法について以下の手順で説明しています。

Step1：APIリクエストを行う処理をライブラリ化
Step2：カスタム関数でライブラリを呼び出す

## 🔨 Step1：APIリクエストを行う処理をライブラリ化
GASでは以下のようなコードでAPIにリクエストを行うことができます。

```javascript
const url = 'https://hoge';

const requestBody = {
    "system_content": system_content,
    "user_content": user_content
};

const requestOptions = {
    "method": "POST",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${ScriptApp.getIdentityToken()}`
    },
    "payload": JSON.stringify(requestBody)
};
  
response = UrlFetchApp.fetch(url, requestOptions);
Logger.log(response);
```

先ほどの工程で作成したAPIの環境に合わせてコードを修正し、この処理を関数として作成します。

GASにはスクリプトIDを使って他のファイルの関数を呼び出す機能があるため、ここで作成した関数をライブラリとして利用します。

## 🤖 Step2：カスタム関数でライブラリを呼び出す
Step1で作成したライブラリを使ってカスタム関数を作成します。

```javascript
/**
 * Geminiに質問を行い、回答の文字列を返します。
 *
 * @param {"あなたは優秀な通訳です。文章を英訳してください。"} systemContent AIのシステム設定
 * @param {"こんにちは！"} userContent プロンプト
 * @return {"Hello!"} Geminiから返された答え
 * @customfunction
 */
function gemini(systemContent, userContent) {
  return geminiAPI.gemini(systemContent, userContent);
}
```
カスタム関数内にStep1で書いたような処理を記載しても同じように動作するのですが、カスタム関数はスプレッドシート1つずつに紐づくため、コードを修正する際のメンテナンスが非常に難しくなります。ライブラリを利用する仕様で関数を作成しておけば、APIへのリクエスト処理に修正が必要な際も、参照元のコードを修正するだけで全ての関数に変更が反映されるので管理が容易です。


# おわりに
本記事では、GoogleスプレッドシートでGemini(LLM)を関数として利用する方法についてご紹介しました。system_contentの部分をテンプレート化することで、エージェント化した関数を作ることもできると思うので、皆さんの環境でカスタムしながら是非挑戦してみてください！🎉