【0からGASを学ぶ】Gemini Pro API をGASから呼び出して、回答を得よう！

# はじめに
本シリーズでは、GASの始め方や便利な使い方、ビジネス活用まで幅広く解説します。シリーズをひと通り読んでいただければ、あなたもきっとGASマスターになれるはずです。

# シリーズの対象者
* そもそもGASってなんだか**わからない**方
* GASを学びたいけど**何から始めればいいか**わからない方
* GASはわかり始めたけど、**もっと活用ができないか**と模索している方
* とにかく**Googleが好き！** という方

# 前回記事
https://qiita.com/gas-suke/items/887d45dabb2df3c5767a

# Gemini Pro API をGASからアクセスする
では早速始めていきましょう。【0からGASを学ぶ】シリーズの第14回は「Gemini Pro API をGASから呼び出して、回答を得よう！」です。前回までは、Googleフォームを用いた自動化や、`dayjs`ライブラリの使い方を行いました。今回はちょっと流れを変えて、2023年12月13日に満を持して登場した**Gemini Pro API**を使ってみましょう。ちょっと難しそうに感じるかもしれませんが、APIの利用は、<a href="https://qiita.com/gas-suke/items/a1783b68fac098c8c3ae#createpdf" target="_blank">GoogleドキュメントやGoogleスプレッドシートをPDF変換</a>する際に学びました。そのため、実はその**FetchするURLやパラメタをいじればできちゃう**んです。…と聞くと、何となく**楽勝**な感じがしますよね！

```javascript:createPdf（抜粋）
// PDF変換するためのベースURLを作成する
let wUrl = `https://docs.google.com/document/d/${docId}/export?exportFormat=pdf`;

// headersにアクセストークンを格納する
let wOtions = {
  headers: {
    'Authorization': `Bearer ${ScriptApp.getOAuthToken()}`
  }
}; 
// PDFを作成する
let wBlob = UrlFetchApp.fetch(wUrl, wOtions).getBlob().setName(fileName + '.pdf');
```

## 今回やること
1. Gemini Pro APIのAPIキーを取得
1. GASからGemini Pro APIの呼び出し
1. 呼び出した結果から回答を抽出

:::note info
今回はあくまでもGASからGemini pro APIの呼び出しまでとします。次回以降、さらにそれを**LINEのチャットボットに組み込み**を行い、**自分だけの相談相手**を作っていきましょう。
:::

## 事前準備
### STEP.1　Gemini Pro APIのAPIキーを取得
※以下はGoogleの**フリーアカウント向けの手順**となります。GoogleWorkspaceをご利用の方は、事前に1つ確認しておくことがあるため、**STEP.0** を先にご確認ください。

<table>
  <tr>
    <th>№</td>
    <th>手順</td>
    <th>イメージ</td>
  </tr>
  <tr>
    <td>1</td>
    <td>以下のURLにアクセスして、「Get API key in Google AI Stuio」をクリックします。<br/><a href="https://ai.google.dev/" target="_blank">https://ai.google.dev/</a></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/99d1121b-6c57-a159-a909-58abcdcf0666.png" width="600"></td>
  </tr>
  <tr>
    <td>2</td>
    <td>同意項目にチェックを入れ、右下の「Continue」をクリックします。なお、同意項目は3つ全てに同意する必要はありません。1番上のみがチェックされていれば進めますが、以下を参考にしてください。
      <ul>
        <li>利用規約に関する同意</li>
        <li>アップデート情報等の受け取り有無</li>
        <li>AI調査研究への参加有無</li>
      </ul>
    </td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/2146ee99-acc6-e93c-78d6-65c24a5cabb5.png" width="600"></td>
  </tr>
  <tr>
    <td>3</td>
    <td>「Get API key」をクリックします。</td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/0a26f5fa-d6b0-0b96-b5a8-d8b936343f10.png" width="600"></td>
  </tr>
  <tr>
    <td>4</td>
    <td>「Create API key in new project」をクリックします。</td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/1314e2d9-7e45-f832-4bf9-5f984778e2b0.png" width="600"></td>
  </tr>
  <tr>
    <td>5</td>
    <td>しばらくするとAPI keyが作成されるため「Copy」をクリックします。<br/>※ここで取得したキーを後ほど利用します。</td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/7c13ce7f-4852-3a36-2bbf-16a481dc6b2f.png" width="600"></td>
  </tr>
</table>

:::note info
### STEP.0　早期アクセスアプリの有効化設定
GoogleWorkspaceをご利用の方は、上記手順に従ってNo.1の「Get API key in Google AI Stuio」をクリックすると、**正しく遷移しない場合**があります。その場合は、**早期アクセスアプリの有効化設定**がされていない可能性が高いです。以下を参考に設定を行ったうえで**STEP.1**を実施してください。
<table>
  <tr>
    <th>№</td>
    <th>手順</td>
    <th>イメージ</td>
  </tr>
  <tr>
    <td>1</td>
    <td>以下のURLにアクセスして、「早期アクセスアプリ」をクリックします。<br/><a href="https://admin.google.com/ac/appslist/additional?hl=ja" target="_blank">https://admin.google.com/ac/appslist/additional?hl=ja</a></td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/224733bf-9591-62f2-7fca-22f09d4c66c9.png" width="600"></td>
  </tr>
  <tr>
    <td>2</td>
    <td>「サービスのステータス」エリアをクリックします。</td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/f6bac6cc-c73b-d5ef-a0cb-f613a7f6309b.png" width="600"></td>
  </tr>
  <tr>
    <td>3</td>
    <td>サービスのステータスをオンにし「保存」をクリックします。</td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/7a3d9ee5-fd6c-724c-6f27-482a7d79d5e5.png" width="600"></td>
  </tr>
  <tr>
    <td>4</td>
    <td>1つ前の画面に戻り、今度は「Core Data Access Permissions」エリアをクリックします。</td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/bf184bd9-2995-40d6-a76a-ace8c71f8691.png" width="600"></td>
  </tr>
  <tr>
    <td>5</td>
    <td>チェック状態にし「保存」をクリックします。</td>
    <td><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/33582965-2128-6028-889d-c260d50a9dbe.png" width="600"></td>
  </tr>
</table>
:::

## いざ開発！
### STEP.2　GASからGemini Pro APIの呼び出し
今回はGemini Pro APIをとにかくGASから呼び出すことだけを目的としているため、スタンドアロン型でGASを記述していきます。<a href='https://qiita.com/gas-suke/items/5cf7bd4c6c3f15a25838#%E3%82%B9%E3%82%BF%E3%83%B3%E3%83%89%E3%82%A2%E3%83%AD%E3%83%B3%E5%9E%8B' target='_blank'>こちら</a>を参考にGASエディタを起動してください。では、どんどんいきますよ、ついてきてください。

#### まずは仕様を確認
Gemini ProのAPIをPythonやNode.js等から呼び出す場合には、すでに**GoogleGenerativeAI**のライブラリがあるため、**比較的簡単に実装が可能**です。しかしながら、GASから呼び出す場合は、**Fetch先のURLやそれに付随するパラメータを構成**し、返却値である**HTTPResponseから回答を抽出**する必要があります。とはいっても、Google大先生がすでに情報はまとめてくれてますので、ご安心を。

https://ai.google.dev/tutorials/rest_quickstart#gemini_and_content_based_apis

```bat:curlによる呼び出し
curl https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=$API_KEY \
    -H 'Content-Type: application/json' \
    -X POST \
    -d '{
      "contents": [{
        "parts":[{
          "text": "Write a story about a magic backpack."}]}]}' 2> /dev/null
```

これを見ると、以下のことがわかります。
| 項目 | 値 |
|:--|:--|
| Fetch先のURL | https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=$API_KEY |
| method種別 | POST |
| データ種別 | application/json |
| データ本体 | { <br/>&nbsp;&nbsp;"contents": [{<br/>&nbsp;&nbsp;&nbsp;&nbsp;"parts":[{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"text": "Write a story about a magic backpack."<br/>&nbsp;&nbsp;&nbsp;&nbsp;}]<br/>&nbsp;&nbsp;}]<br/>} |

#### ではこれをGASで表現する

```javascript:Qiita014.gs
const GEMINI_API = '**STEP.1のNo.5で取得したAPI keyを記載する**';

function Qiita014_GeminiPro() {
  const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${GEMINI_API}`
        , payload = {
            'contents': [
              {
                'parts': [{
                  'text': 'Write a story about a magic backpack.'
                }]
              }
            ]
          }
        , options = {
            'method': 'post',
            'contentType': 'application/json',
            'payload': JSON.stringify(payload)
          };

  const res = UrlFetchApp.fetch(url, options)
        , resJson = JSON.parse(res.getContentText());

  console.log(JSON.stringify(resJson));
}
```
いかがでしょうか。Google大先生が示してくれた**curlコマンドを素直にGASで表現しただけ**です。非常に明瞭ですよね。
こちらを実行するとこのような実行結果が得られます。
※見やすいように一部**JSONの整形**だけしておきます。

```:実行結果
17:24:52	お知らせ	実行開始
17:25:00	情報
{
  "candidates": [
    {
      "content": {
        "parts": [
          {
            "text": "In the quaint town of Brightville, nestled amidst rolling hills and whispering woods, lived a young boy named Oliver. …"
          }
        ],
        "role": "model"
      },
      "finishReason": "STOP",
      "index": 0,
      "safetyRatings": [
        {
          "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HATE_SPEECH",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_HARASSMENT",
          "probability": "NEGLIGIBLE"
        },
        {
          "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
          "probability": "NEGLIGIBLE"
        }
      ]
    }
  ],
  "promptFeedback": {
    "safetyRatings": [
      {
        "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
        "probability": "NEGLIGIBLE"
      },
      {
        "category": "HARM_CATEGORY_HATE_SPEECH",
        "probability": "NEGLIGIBLE"
      },
      {
        "category": "HARM_CATEGORY_HARASSMENT",
        "probability": "NEGLIGIBLE"
      },
      {
        "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
        "probability": "NEGLIGIBLE"
      }
    ]
  }
}
17:25:00	お知らせ	実行完了
```

### STEP.3　呼び出した結果から回答を抽出
#### 回答文がどこにあるかを確認
上記の実行結果を見れば一目瞭然ですが、以下の位置にGemini Pro APIからの回答があります。
```
{
  "candidates": [
    {
      "content": {
        "parts": [
          {
            "text": "In the quaint town of Brightville, nestled amidst rolling hills and whispering woods, lived a young boy named Oliver. …"
          }
```

:::note info
返却されたJSONには、**様々な要素**がありますが、以下の4つのパラメタに関して設定を行うことで、**Gemini Pro APIからの回答を制御**することができます。※<a href="https://cloud.google.com/vertex-ai/docs/generative-ai/model-reference/gemini?hl=ja#request_body" target="_blank">詳細はこちら</a>
* **HARM_CATEGORY_SEXUALLY_EXPLICIT**
性的行為やその他のわいせつなコンテンツに関して
* **HARM_CATEGORY_HATE_SPEECH**
否定的または有害なコメントに関して
* **HARM_CATEGORY_HARASSMENT**
悪意のある、威圧的、いじめ、または虐待的なコメントに関して
* **HARM_CATEGORY_DANGEROUS_CONTENT**
有害な商品、サービス、活動などのコンテンツに関して

これらについて、以下の**閾値**を設定することができます。※<a href="https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/configure-safety-attributes#safety_setting_thresholds" target="_blank">詳細はこちら</a>
| 概要 | 閾値設定値 | 閾値説明 |
|:--|:--|:--|
| ブロックしない | BLOCK_NONE | 安全でないコンテンツの可能性に関係なく、常に表示します。 |
| ブロックが少ない  | BLOCK_ONLY_HIGH | 安全でないコンテンツの可能性が高い場合はブロックします。 |
| 一部をブロックする | BLOCK_MEDIUM_AND_ABOVE  | 安全でないコンテンツの可能性が中程度または高い場合にブロックします。 |
| ほとんどをブロックする | BLOCK_LOW_AND_ABOVE | 安全でないコンテンツの可能性が中程度または高い場合にブロックします。 |
|   | HARM_BLOCK_THRESHOLD_UNSPECIFIED | 閾値は指定されていません。デフォルトの閾値を使用してブロックします。 |
:::

#### 回答文をGASで抽出
```javascript:Qiita014.gs
const GEMINI_API = '**STEP.1のNo.5で取得したAPI keyを記載する**';

function Qiita014_GeminiPro() {
  const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${GEMINI_API}`
        , payload = {
            'contents': [
              {
                'parts': [{
                  'text': 'Google Apps Scriptについて教えてください。'
                }]
              }
            ]
          }
        , options = {
            'method': 'post',
            'contentType': 'application/json',
            'payload': JSON.stringify(payload)
          };

  const res = UrlFetchApp.fetch(url, options)
        , resJson = JSON.parse(res.getContentText());

  // responseがちゃんと返ってきていることを確認
  if (resJson && resJson.candidates && resJson.candidates.length > 0) {
    /** 以下の位置を取得
    {
      "candidates": [
      {
        "content": {
        "parts": [
        {
          "text": 
    */
    console.log(resJson.candidates[0].content.parts[0].text);
  } else {
    // HARM_CATEGORYによって、無回答の場合も多々あり
    console.log('回答が返されませんでした。');
  }
}
```

```実行結果
17:28:56	お知らせ	実行開始
17:29:05	情報	Google Apps Scriptとは、Google Workspaceのアプリケーション上で動作するスクリプティング言語です。JavaScriptに基づいており、Google Sheets、Google Docs、Google Slides、Google FormsなどのGoogle Workspaceアプリケーションと連携して、自動化やカスタマイズを実現することができます。

Google Apps Scriptを使う利点は以下の通りです。

* **コードを書く必要がない:** Google Apps Scriptは、コードを書く必要なく、ビジュアルなインターフェースでスクリプトを作成することができます。
* **強力なAPI:** Google Apps Scriptは、Google Workspaceの様々なアプリケーションのAPIにアクセスすることができます。これにより、自動化やカスタマイズを簡単に実現することができます。
* **クロスプラットフォーム:** Google Apps Scriptは、ウェブブラウザ上で動作するため、どのオペレーティングシステムでも使用することができます。
* **無料:** Google Apps Scriptは、無料で使用することができます。

Google Apps Scriptの主なユースケースは以下の通りです。

* **データの自動化:** Google Apps Scriptを使って、スプレッドシートのデータを自動的に更新したり、フォームの回答を自動的に処理したりすることができます。
* **カスタムレポートの作成:** Google Apps Scriptを使って、スプレッドシートのデータを分析してカスタムレポートを作成することができます。
* **ウェブアプリケーションの開発:** Google Apps Scriptを使って、ウェブアプリケーションを開発することができます。
* **アドオンの作成:** Google Apps Scriptを使って、Google Workspaceのアプリケーション用のアドオンを作成することができます。

Google Apps Scriptは、Google Workspaceユーザーにとって強力なツールです。自動化やカスタマイズを実現することで、生産性を向上させ、時間を節約することができます。
17:29:05	お知らせ	実行完了
```
ばっちり取得できましたね。しかも私の記事より詳細にGASを語りやがって、**Gemini Pro 恐るべし**です。

# おわりに
お疲れ様でした。
第14回は「Gemini Pro API をGASから呼び出して、回答を得よう！」ということで、**GASからGemini Proを扱う手順**とともに、**API keyの取得**も含めて、今後Gemini Proを扱う下準備を行いました。次回は、先述の通りですが**LINE Messaging API**と連携させ、自分だけの**相談チャットボット**を作っていきましょう。**GASはなんでもできる！** と述べていたのが少しずつわかってきたのでは！？引き続き、GASを楽しんでいきましょう！！
記事を読んで、「良いな」や「今後に期待できる！」と感じて頂けたら**いいね**や**フォロー**、**コメント**いただけると幸いです。それではまた次回をお楽しみに！

## ブログでより詳しく解説しています！
以下画像をクリックしてブログにアクセス！！
<a href="https://gas-4u.com/gas13/" target="_blank"><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3575702/90912bf8-02fe-68e5-c3e9-652c85c3ed2b.png" width="600"/></a>