見出し画像

【記事修正】Googleスプレッドシート用テキスト生成関数(GPT、Claude、Gemini、Groq等マルチAI対応)

こんにちは!
ノーリーです。ClaudeやChatGPT使ってますか?

生成AIを表計算で使いませんか?
Claudeだけでなく、GPTも、Geminiも、そして話題のGroqも使えるようになりました。
GroqやGeminiは現在無料で利用可能です。

生成AIによる回答の比較が簡単

できるだけコストかからないように、キャッシュ機能付きです。

インストール方法として、ライブラリ機能を使う方法を紹介しましたが、問題があることが判明しました。
改めて、スプレッドシートファイルを公開してそのコピーを作る方式に変更することにしました。
お詫びして訂正いたします。

この記事は、大阪のIT専門学校「清風情報工科学院」の校長・平岡憲人(ノーリー)がお送りします。
清風情報工科学院では、情報処理系の講師を急募しております。
ご興味のある方はこちらの記事を御覧ください。


1.「generateText関数」でできること

プロンプを与えて、生成AIからの回答を引き出し表示すること。
AIベンダー、モデル、回答の長さなどを変更可能。

そのまま使いたいかたはライブラリ登録で使えます。
ソースコードを公開いたしますので、自分のGoogleスプレッドシートのスクリプトエディタに張り付けても使えます。

必要なもの

・各生成AIのAPIキー

※ソフトは無料ですが、各AIの利用料がかかります。
 現在GroqとGeminiは無料で利用可能です。

処理内容

generateText関数からプロンプトを各AIベンダーのAPIに送り、生成される回答を表示する。
その際、AIベンダー名、モデル名、最長トークン数等を指定する。
プロンプトはテキストのみの前提。

リクエスト制限にかかりにくい様に、また、APIのコスト上昇を抑えるため、一度リクエストしたプロンプトとは10日間キャッシュに保管するようにしている。
キャッシュにあるリクエストなら、API呼び出しはせずキャッシュの値を返す。

お断り

このソフトは無保証です。
問題があれば、この記事のコメントに書いて下さい。
対処するかもしれませんが、しないかもしれません。
また、APIの利用によって利用料金が発生します。
当方は、この利用料についても一切責任を持ちません。

2.インストール方法

インストール方法を改めます。
ライブラリを利用する方法ではうまく行かないことが判明しました。
もっと簡単な方法がわかりました。

(1)Googleスプレッドシートを開く

次のGoogleスプレッドシートを開いて下さい。

すると「閲覧のみ」で開きます。

ファイルメニューから「コピーを作成」をクリック。

ファイルの名前と保存先が聞かれますので、好きな名前をつけて下さい。
そして、「コピーを保存」をクリック。

自分のGoogleドライブにコピーが保存されます。
コピーが自動的に開きます。

(2)各AIベンダーのAPIキーを取得する

コピーが開くと、APIキーがないというエラー表示が出ます。

そこで、各AIベンダーのAPIキーを取得して、APIキーをスクリプトプロパティに設定します。
お使いになるモデルのAPIキーだけ設定すればよいです。
4つとも設定する必要はありません。
4つ切り替えて使いたいときは4つとも設定して下さい。

なお、現在、
 ・groq
 ・Gemini
については、無料で利用可能です。
現在、おすすめは「groq」です。

APIキーは、メモ帳などに記録しておいて下さい。

ClaudeのAPIキーは次のURLから。

GROQのAPIキーは次のURLから。

GeminiのAPIキーは、Google AI Studioの「Get API key」ボタンから。
次の記事に、Google AI Studioを通じてAPIキーを取得する方法を説明してあります。
この記事の2.と4.を読んで下さい。

あるいは、次の記事を御覧ください。
npakaさんの記事は簡潔でわかりやすい。

OpenAIのAPIキーは、次のリンクから。

https://platform.openai.com/api-keys

(3)各AIベンダーのAPIキーを設定する

次に、このAPIキーを、Apps Scriptに設定します。
まずApps Scriptの開発画面を開きます。
メニューの拡張機能>Apps Scriptを開く。

開くとこのような画面になります。

画面の左側のメニューの一番下の歯車アイコンをクリック。

プロジェクト設定画面になる。
一番下までスクロール。

一番下までスクロールすると、「スクリプトプロパティ」が出てくる。

「スクリプトプロパティを追加」をクリック。

プロパティに「CLAUDE_API_KEY」、値にClaudeのAPIキーの値を入力。

その後は、「スクリプトプロパティを追加」をクリックして、プロパティと値を順々に入れていってください。
4つ入れ終わるとこのようになります。

APIキー

最後に、「スクリプトプロパティの保存」ボタンをクリックして下さい。

プロパティと値を入力したら「スクリプトプロパティを保存」をクリック

これで、設定完了。
「Apps Script」画面は閉じてよいです。

3.使い方


Claude先生

Googleスプレッドシート上での使い方は以下の通りです。

構文


=generateText(プロンプト, [AIベンダー名], [キャッシュ機能], [モデル名], [最長トークン数], [温度], [top_p])

プロンプト: 生成AIに渡すプロンプト(テキスト)
AIベンダー名: AIベンダーの名前(省略時「GROQ」)
  例  CLAUDE  ・・・ AnthropicのClaudeシリーズ
     GROQ   ・・・ groqシリーズ
     GEMINI   ・・・ GoogleのGeminiシリーズ
     GPT    ・・・ OpenAIのGPTシリーズ
キャッシュ機能: キャッシュ機能を使うかどうか(省略時「true」)
  例  ture   ・・・ 機能を使う
     false   ・・・ 機能を使わない
モデル名: 大規模言語モデル名、AIベンダー毎に異なる
  例  CLAUDE  claude-3-opus-20240229
           claude-3-sonnet-20240229
           claude-3-haiku-20240307 (省略時)
     GROQ   Llama3-70B-8192 (省略時)
           Llama3-8B-8192
           Mixtral-8x7b0-32678
           Llama2-70b-4092
           Gemma-7b-lt
     GEMINI   gemini-1.5-pro-latest (省略時)
           gemini-pro
     GPT    gpt-4-turbo (省略時)
           gpt-4
           gpt-4-32k
           gpt-3.5-turbo
最長トークン数: 回答の最大の長さ(数字、単位トークン)
  省略時 300
温度: 回答の自由度(0以上の数字、数字が大きいほうが自由度高い)
  省略時 モデルにより異なる
top_p: 回答の自由度(0から1の数字、数字が大きいほうが自由度高い)
  省略時 モデルにより異なる

温度(temperature)については次の記事を御覧ください。

top_pについては次の記事を御覧ください。  

実施例

実施例

律儀に働いてくれます。

4.ソースコード

このプログラムは、自由に使うことができます。
改変も自由です。
CC BY-SA 表示 - 継承 4.0 国際 に従って下さい。

言語は、JavaScriptです。

// generateText関数
/**
 * 指定されたプロンプトとパラメータを使用して、選択されたベンダーのAPIを呼び出し、テキストを生成する関数
 *       2024/4/22  Version 1.0  HIRAOKA Norito  CC BY-SA  表示 - 継承 4.0 国際
 * 
 * @param {string} prompt - 生成するテキストのプロンプト
 * @param {string} vendor - 使用するベンダー(デフォルトは"CLAUDE")
 * @param {boolean} cache_enable - キャッシュを使用するかどうか(デフォルトはtrue)
 * @param {string|null} model - 使用するモデル(デフォルトはnull)
 * @param {number|null} max_tokens - 生成するテキストの最大トークン数(デフォルトはnull)
 * @param {number|null} temperature - 生成時の温度設定(デフォルトはnull)
 * @param {number|null} top_p - 生成時のトップp設定(デフォルトはnull)
 * @return {string} - 生成されたテキスト
 */
function generateText(prompt, vendor = "GROQ", cache_enable = true, model = null, max_tokens = null, temperature = null, top_p = null) {
  try {
    // 設定情報を取得する
    const config = getConfig(vendor);
    // APIキーを取得する
    const apiKey = getApiKey(vendor);
    //Logger.log(apiKey);
    // モデルを決定する    
    const selectedModel = getModel(config, model);
    // 最大トークン数を決定する
    const selectedMaxTokens = getMaxTokens(config, max_tokens);
    // 温度を決定する
    const selectedTemperature = getTemperature(config, temperature);
    // top_pを決定する
    const selectedTopP = getTopP(config, top_p);

    // キャッシュキーを生成する
    const cacheKey = generateCacheKey(prompt, vendor, selectedModel, selectedMaxTokens, selectedTemperature, selectedTopP);
    // cache_enableがTRUEの場合、キャッシュからデータを取得する
    if (cache_enable) {
      const cachedResult = getCachedResult(cacheKey);
      if (cachedResult) {
        return cachedResult;
      }
    }

    // APIリクエストのペイロードを作成する
    const payload = createPayload(prompt, config, selectedModel, selectedMaxTokens, selectedTemperature, selectedTopP);
    //Logger.log(payload);
    // APIリクエストのオプションを設定する
    const options = createOptions(apiKey, config, payload);
    //Logger.log(options);
    // APIのURLを取得する
    const apiUrl = getApiUrl(config, selectedModel, apiKey);
    //Logger.log(apiUrl);

    // APIリクエストを送信し、レスポンスを取得する
    const response = sendRequest(apiUrl, options);
    //Logger.log(response);
    // APIレスポンスから結果を取得する
    const result = getResult(response, config);
    
    // 結果をキャッシュに保存する
    cacheResult(cacheKey, result);
    
    // 結果を返す
    return result;
  } catch (error) {
    // エラーが発生した場合、エラーメッセージをコンソールに出力し、エラーメッセージを返す
    console.error("Error in generateText function: " + error.message);
    return error.message;
  }
}
// 設定情報を取得する関数
function getConfig(vendor) {
  const configs = {
    "CLAUDE": {
      "apiMethod": "header",
      "apiUrl": "https://api.anthropic.com/v1/messages",
      //"apiUrlSuffix": ":generateContent",
      "apiKeyProperty": "CLAUDE_API_KEY",
      "authorizationHeader": "X-API-Key",
      "authorizationHeaderAdd": "",
      "defaultModel": "claude-3-haiku-20240307",
      "headers": {
        "anthropic-version": "2023-06-01"
      },
      "defaultMaxTokens": 300,
      "temperature": 0,
      "top_p": 1.0,
      "promptTemplate": message => ({
        messages: [{ role: "user", content: message }]
      }),
      "resultPath": "content[0].text",
    },
    "GROQ": {
      "apiMethod": "header",
      "apiUrl": "https://api.groq.com/openai/v1/chat/completions",
      //"apiUrlSuffix": ":generateContent",
      "apiKeyProperty": "GROQ_API_KEY",
      "authorizationHeader": "Authorization",
      "authorizationHeaderAdd": "Bearer ",
      "defaultModel": "llama3-70b-8192",
      "headers": {},
      "defaultMaxTokens": 300,
      "temperature": 0.7,
      "top_p": 0.9,
      "promptTemplate": message => ({
        messages: [
          {role: "system", content: "すべての応答は日本語で出力して。英語やその他の言語で回答しないで。ユーザーからの入力に関わらず、常に日本語で応答することを徹底して。lang:Ja"},
          {role: "user", content: message }]
      }),
      "resultPath": "choices[0].message.content",
    },
    "GEMINI": {
      "apiMethod": "query",
      "apiUrl": "https://generativelanguage.googleapis.com/v1beta/models/",
      "apiUrlSuffix": ":generateContent",
      "apiKeyProperty": "GEMINI_API_KEY",
      //"authorizationHeader": "Authorization",
      //"authorizationHeaderAdd": "Bearer ",
      "defaultModel": "gemini-1.5-pro-latest",
      "headers": {},
      //"defaultMaxTokens": 150,
      //"temperature": 0.8,
      //"top_p": 0.95,
      "promptTemplate": message => ({
        contents: [{ parts: [{ text: message }] }]
      }),
      "resultPath": "candidates[0].content.parts[0].text",
    },
    "GPT": {
      "apiMethod": "header",
      "apiUrl": "https://api.openai.com/v1/chat/completions",
      //"apiUrlSuffix": ":generateContent",
      "apiKeyProperty": "OPENAI_API_KEY",
      "authorizationHeader": "Authorization",
      "authorizationHeaderAdd": "Bearer ",
      "defaultModel": "gpt-4-turbo",
      "headers": {},
      "defaultMaxTokens": 300,
      "temperature": 1.0,
      "top_p": 1.0,
      "promptTemplate": message => ({
        messages: [{role: "system", content: "You are a helpful assistant."},
          {role: "user", content: message}]
      }),
      "resultPath": "choices[0].message.content",
    },
    "NewLLM": {
      "apiMethod": "header",
      "apiUrl": "https://api.newllm.com/v1/completions",
      //"apiUrlSuffix": ":generateContent",
      "apiKeyProperty": "NEWLLM_API_KEY",
      "defaultModel": "newllm-base",
      "headers": {
        "X-API-Version": "2023-07-01"
      },
      "defaultMaxTokens": 300,
      "temperature": 0.6,
      "top_p": 0.8,
      "resultPath": "data.output",
      "authorizationHeader": "Authorization",
      "authorizationHeaderAdd": "Bearer ",
      "promptTemplate": message => ({
        messages: [{ role: "user", content: message }]
      }),
    }
  };
  
  return configs[vendor];
}

// APIキーを取得する関数
function getApiKey(vendor) {
  // 指定されたベンダーの設定情報を取得
  const config = getConfig(vendor);
  // スクリプトプロパティからAPIキーを取得
  const apiKey = PropertiesService.getScriptProperties().getProperty(config.apiKeyProperty);
  
  // APIキーが設定されていない場合はエラーをスロー
  if (!apiKey) {
    throw new Error(
      `APIキーが設定されていません。以下の手順に従って、${vendor}のAPIキーを設定してください。\n\n` +
      `1. ${vendor}の開発者ウェブサイトにアクセスし、アカウントを作成またはログインしてください。\n` +
      `2. APIキーの取得コーナーに行き、新しいAPIキーを取得してください。\n` +
      `3. Google Apps Scriptエディタで、左のメニューの歯車アイコンの「プロジェクトの設定」を選択してください。\n` +
      `4. スクロールし「スクリプトプロパティ」コーナーに移動してください。\n` +
      `5. (2つ目以降は「スクリプトプロパティを編集」をクリック後)「スクリプトプロパティを追加」をクリックして下さい。\n` +
      `6. プロパティ欄に「${config.apiKeyProperty}」を、値欄に取得したAPIキーを設定してください。\n` +
      `7. 「スクリプトプロパティを保存」をクリックしてプロパティを保存してください。\n\n` +
      `APIキーを設定後、再度スクリプトを実行してください。`
    );
  }
  
  // 取得したAPIキーを返す
  return apiKey;
}

// モデル名を取得する関数
function getModel(config, model) {
  // 指定されたモデルがある場合はそれを使用し、ない場合はデフォルトのモデルを使用
  return model || config.defaultModel;
}

// 最大トークン数を取得する関数
function getMaxTokens(config, max_tokens) {
  return max_tokens || config.defaultMaxTokens;
}

// 温度を取得する関数 
function getTemperature(config, temperature) {
  return temperature || config.temperature;
}

// top_pを取得する関数
function getTopP(config, top_p) {
  return top_p || config.top_p;
}

// キャッシュキーを生成する関数
function generateCacheKey(prompt, vendor, model, max_tokens, temperature, top_p) {
  return prompt + "_" + vendor + "_" + model + "_" + max_tokens + "_" + temperature + "_" + top_p;
}

// キャッシュからデータを取得する関数
function getCachedResult(cacheKey) {
  // スクリプトキャッシュを取得
  const cache = CacheService.getScriptCache();
  // 指定されたキャッシュキーに対応するデータを取得して返す
  return cache.get(cacheKey);
}

// APIリクエストのペイロードを作成する関数
function createPayload(prompt, config, model, max_tokens, temperature, top_p) {
  // ベースとなるペイロードオブジェクトを作成
  const basePayload = {
    "model": model,
  };
  if (max_tokens) {
    basePayload["max_tokens"] = max_tokens
  }
  if (temperature) {
    basePayload["temperature"] = temperature
  }
  if (top_p) {
    basePayload["top_p"] = top_p
  }
  //Logger.log(basePayload);
  //Logger.log(config.promptTemplate)
  // プロンプトテンプレートを使用してプロンプトペイロードを作成
  const promptPayload = config.promptTemplate(prompt);
  Logger.log(promptPayload);
  // ベースペイロードとプロンプトペイロードを組み合わせて最終的なペイロードを作成
  return {
    ...basePayload,
    ...promptPayload,
  };
}

// APIリクエストのオプションを設定する関数
function createOptions(apiKey, config, payload) {
  // ヘッダーオブジェクトを作成し、Content-Typeを設定
  const headers = {
    "Content-Type": "application/json",
    ...config.headers
  };
  // APIキーの指定方法がヘッダーの場合
  if (config.apiMethod === "header") {
    // 認証ヘッダーが指定されている場合
    if (config.authorizationHeader) {
      // 認証ヘッダーにAPIキーを設定
      headers[config.authorizationHeader] = `${config.authorizationHeaderAdd || ""}${apiKey}`;
    }
  };
  // オプションオブジェクトを作成し、メソッド、ヘッダー、ペイロードを設定
  const options = {
    "method": "POST",
    "headers": headers,
    "payload": JSON.stringify(payload)
  };
  // 作成したオプションオブジェクトを返す
  return options;
}

// APIのURLを設定する関数
function getApiUrl(config, model, apiKey) {
  // APIのベースURLを取得
  let apiUrl = config.apiUrl;
  
  // APIキーの指定方法がクエリパラメータの場合
  if (config.apiMethod === "query") {
    // モデル名とAPIのURLサフィックスを追加
    apiUrl += model + (config.apiUrlSuffix || "");
    // APIキーをクエリパラメータとして追加
    apiUrl += `?key=${apiKey}`;
  }
  
  // 最終的なAPIのURLを返す
  return apiUrl;
}

// APIリクエストを送信する関数
function sendRequest(apiUrl, options) {
  // 指定されたURLとオプションを使用してAPIリクエストを送信
  const response = UrlFetchApp.fetch(apiUrl, options);
  
  // レスポンスのステータスコードが200以外の場合はエラーをスロー
  if (response.getResponseCode() !== 200) {
    throw new Error("API returned status code " + response.getResponseCode());
  }
  
  // レスポンスを返す
  return response;
}
  
// APIレスポンスから結果を取得する関数
function getResult(response, config) {
  //Logger.log(response);
  // レスポンスのコンテンツをJSONとしてパース
  const json = JSON.parse(response.getContentText());
  // 設定情報で指定されたパスを使用して結果を取得
  return eval(`json.${config.resultPath}`);
}

// 結果をキャッシュに保存する関数
function cacheResult(cacheKey, result) {
  // スクリプトキャッシュを取得
  const cache = CacheService.getScriptCache();
  // 指定されたキャッシュキーと結果を使用してキャッシュに保存(有効期限は10日)
  cache.put(cacheKey, result, 864000); // 10日間
}

ソースコードには、コメントをつけておきました。
読んでいただけば、処理の流れはわかると思います。

Windows環境で動作確認しています。
Macでも動くと思いますが、確認していません。

動作などに問題がありましたら、コメントにてお知らせ下さい。

5.まとめ

以上、「generateText関数」の簡単な説明でした。
役に立ったら「いいね」お願いします!

本校・清風情報工科学院では、生成AIを活用した教育に取り組んでいます。
昨年度は、IT教育の主にシステム開発のところに使っていました。
今年度は、画像生成や日本語教育でも使っていきます。
ご興味がある方、本校で教鞭をおとりになりませんか?

清風情報工科学院では、情報処理系の講師を急募しております。
こちらの記事を御覧ください。


よろしければサポートお願いします! いただいたサポートはクリエイターとしての活動費に使わせていただきます! (