LoginSignup
2
3

OpenAIとDeepLのAPIキーを利用して、文章生成AI「GPT-3.5Turbo」と画像生成AI「DALL-E3」、更にDeepLの翻訳機能(英訳)を同時に使用できるLINE BotのベースコードをGASで作りました。

英語への翻訳は、GPT-3.5Turbo経由でもできますが、トークンを使用する為、DeepLの無料アカウントで使用できるAPIキーを使用して、その部分は無料化することにしました。

下図のように、文章生成AIと画像生成AIとDeepLを一つのアカウントで同時に使用することが出来ます。
タイトルなし.png

ベースコードでは、「こんにちはGPT」という文字が「含まれる(部分一致)」する入力があった場合に、GPT-3.5Turboと会話でき、

「絵を描いて」という文字が「含まれる(部分一致)する入力があった場合に、DALL-E3が絵を描いてくれ、

「英訳して」という文字が「含まれる(部分一致)する入力があった場合に、DeepLが英訳してくれるコード構成にしています。

それ以外の入力に対しては、単に「A」というテキストが返されるIF文構成になっています。

トリガーとなるキーワードや、仮に「A」としてある返しテキストに関しては、ご自身の環境に合わせて、自由に編集してください。

まず始めに、下のリンクからGSSファイルを開き、メニューファイルから「Make a copy」を選択して、ご自身のGoogle Driveにコピーしてください。
https://docs.google.com/spreadsheets/d/1MI0NPYix3bPwz9CF97xp2wFbYom5MKZgE3Tf4rp-QMg/edit?usp=sharing

GSSファイルをコピーしたら、下図のようにスクリプトを開いてください。
タイトルなし.png

スクリプトエディタを開いたら、所定の位置にLINEのアクセストークンとOpenAIのAPIキー、DeepLのAPIキーを入力してください。
タイトルなし1.png

それだけで、後はデプロイと初回認証を行い、LINE developersのWebhookにリンクさせるだけで使用できます。

DeepLのキーを入力しなくても、英訳機能以外のLINE Bot自体は動作します。
(GPT-3.5Turboとの会話機能の中でも、英訳自体は可能です。トークンは消費しますが)

尚、参考までにmain.gsファイルのコードは下のように作成しております。

main.gs
// LINE Bot 設定
const CHANNEL_ACCESS_TOKEN = 'ここに入力する'; 

// AI設定
const openAIApiKey = "ここに入力する";

//英語翻訳機能を使う為のDEEL API Key
const DEEPL_API_KEY = 'ここに入力する';

const logSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('log');
const DALLE_API = "https://api.openai.com/v1/images/generations";
var replyToken, json;

function doPost(e) {
  json = JSON.parse(e.postData.contents);
  replyToken = json.events[0].replyToken;
  if (typeof replyToken === 'undefined') {
    return;
  }

  var userMessage = json.events[0].message.text;
  var messages;

  if (userMessage.includes("こんにちはGPT")) {
    var replyText; // AIからの応答を保存する変数

    if (/こんにちはGPT、.*駅発、.*駅着/.test(userMessage)) {
    // 交通手段と費用に関するプロンプトを作成
    var prompt = userMessage + "前述の合理的な交通手段と費用を算出して1000字以内で出力してください。読みやすいように箇条書きにして、改行してください";
    replyText = getAIChatAnswer(prompt); // 関数を呼び出し、promptを渡す

    }else if (/.*、レシピ/.test(userMessage)) {
    // 交通手段と費用に関するプロンプトを作成
    var prompt = userMessage + "前述の合理的なレシピと予想される費用を概算で良いので算出し、1000字以内で出力してください。読みやすいように箇条書きにして、改行してください";
    replyText = getAIChatAnswer(prompt); // 関数を呼び出し、promptを渡す

    } else {
      // 通常の応答
      replyText = getAIChatAnswer(userMessage); // 関数を呼び出し
    }

    messages = [{'type': 'text', 'text': replyText}]; // LINEメッセージオブジェクトを作成
    
  } else if (userMessage.startsWith("絵を描いて")) {
    // 絵を描くリクエストに対して画像を生成
    messages = getAIImageAnswer(userMessage);
    
  } else if (userMessage.includes("英訳して")) {
    var textToTranslate = userMessage.replace("英訳して", "");
    var translatedText = translateTextWithDeepL(textToTranslate, "EN");
    messages = [{'type': 'text', 'text': translatedText}];

  } else {
    // それ以外のメッセージには「A」と返信
    messages = [{'type': 'text', 'text': 'A'}];
  }

  // LINEに返信する処理を呼び出す
  const linebotClient = new LineBotSDK.Client({ channelAccessToken: CHANNEL_ACCESS_TOKEN });
  try {
    linebotClient.replyMessage(replyToken, messages);
  } catch (e) {
    log_to_sheet("A", e);
  }

  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}

//DALL-E3に関する関数
function getAIImageAnswer(text) {
  var imageURL = generateImageURL(text);
  var messages = [
    {'type':'text', 'text': '画像ができました!'},
    {'type':'image', 'originalContentUrl': imageURL, 'previewImageUrl': imageURL}
  ];
  return messages;
}

function generateImageURL(text) {
  var options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + openAIApiKey
    },
    "payload": JSON.stringify({
      "prompt": text,
      "model": "dall-e-3",
      "response_format": "url"
    })
  };
  var response = UrlFetchApp.fetch(DALLE_API, options);
  var data = JSON.parse(response.getContentText());
  return data.data[0].url;
}

//GPT-3.5Turboに関する関数
function getAIChatAnswer(prompt, userId) {
  var requestOptions = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + openAIApiKey
    },
    "payload": JSON.stringify({
      "model": "gpt-3.5-turbo",
      "messages": [
         {"role": "user", "content": prompt}
      ]
    })
  };
  var response = UrlFetchApp.fetch("https://api.openai.com/v1/chat/completions", requestOptions);
  var responseText = response.getContentText();
  var json = JSON.parse(responseText);
  return json.choices[0].message.content.trim();
}

function log_to_sheet(column, text) {
  var lastRow;
  if(logSheet.getRange(column + "1").getValue() == ""){
    lastRow = 0;
  } else if(logSheet.getRange(column + "2").getValue() == ""){
    lastRow = 1;
  } else {
    lastRow = logSheet.getRange(column + "1").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
    if(lastRow >= 1000){
      logSheet.getRange(column + "1:" + column + "1000").clearContent();
      lastRow = 0;
    }
  }
  var putRange = column + String(lastRow + 1);
  logSheet.getRange(putRange).setValue(text);
}

DeepL機能を動作させる為の関数は、別途deepl.gsファイルとして下のように作成しています。

deepl.gs
//自動英語翻訳機能を成立させる為の追加関数
function translateTextWithDeepL(text, targetLang) {
  const deepLUrl = `https://api-free.deepl.com/v2/translate?auth_key=${DEEPL_API_KEY}&text=${encodeURIComponent(text)}&target_lang=${targetLang}`;
  const response = UrlFetchApp.fetch(deepLUrl, {
    'method': 'post'
  });
  const jsonResponse = JSON.parse(response.getContentText());
  if (jsonResponse.translations && jsonResponse.translations.length > 0) {
    return jsonResponse.translations[0].text;
  } else {
    return "翻訳に失敗しました。";
  }
}

また、GPT-3.5Turboに対する応用的な用途として、ユーザーの入力に対して、プロンプトを追加してGPT-3.5 Turboに送信するコードも2つ、実装しています。

それぞれ、ご自身の用途に合わせてプロンプトやトリガー条件を工夫してみてください。

その2つのコードに関しては上のコードからご確認いただけますが、

まず1つめは、こんにちはGPT、〇〇駅発、〇〇駅着という形式の入力があった時に、「前述の合理的な交通手段と費用を算出して1000字以内で出力してください。読みやすいように箇条書きにして、改行してください」というプロンプトを追加して送信するものです。

実際に動作させると、下図の様な応答になります。
タイトルなし1.png

2つめは、こんにちはGPT、〇〇(料理名)、レシピという形式の入力があった時に、「前述の合理的なレシピと予想される費用を概算で良いので算出し、1000字以内で出力してください。読みやすいように箇条書きにして、改行してください」というプロンプトを追加して送信するものです。

実際に動作させると、下図の様な応答になります。
タイトルなし1.png

これらのコードは、「ユーザーの入力に対して、管理者がプロンプトを追加してGPT-3.5Turboに送信する」形を構築する為のサンプルとして実装しています。

...

2024年5月13日、更に、改良を加えたGPT-4omniへの切り替え機能を持つベースコードも合わせて作成しました。

OpenAIによれば、GPT4ominはGPT-4Turboと同等の精度を持ち、かつトークン代はGPT-4Turboの更に半額に抑えられているモデルです。

改良版については、下のリンクからGSSファイルを開き、メニューファイルから「Make a copy」を選択して、ご自身のGoogle Driveにコピーしてください。
https://docs.google.com/spreadsheets/d/1EhpTnlfhI7sYf8Z2ixFGGmxtsY7d3gMOZi0kQ_rwuPg/edit?usp=sharing

その後の設定に関しては、上記解説した通常版と同じです。

参考までに、改良版のmain.gsファイルのコードは下のように作成しております。

main.gs
// LINE Bot 設定
const CHANNEL_ACCESS_TOKEN = 'ここに入力する'; 

// AI設定
const openAIApiKey = "ここに入力する";

//英語翻訳機能を使う為のDEEL API Key
const DEEPL_API_KEY = 'ここに入力する';

const logSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('log');
const DALLE_API = "https://api.openai.com/v1/images/generations";
var replyToken, json;

function doPost(e) {
  json = JSON.parse(e.postData.contents);
  replyToken = json.events[0].replyToken;
  if (typeof replyToken === 'undefined') {
    return;
  }

  var userMessage = json.events[0].message.text;
  var messages;

  if (userMessage.includes("こんにちはGPT")) {
    var replyText; // AIからの応答を保存する変数
    var model = "gpt-3.5-turbo"; // デフォルトモデル

    if (userMessage.includes("こんにちはGPT4")) {
      model = "gpt-4o-2024-05-13"; // GPT-4モデルを指定
    }

    if (/こんにちはGPT[34]、.*駅発、.*駅着/.test(userMessage)) {
      var prompt = userMessage + "前述の合理的な交通手段と費用を算出して1000字以内で出力してください。読みやすいように箇条書きにして、改行してください";
      replyText = getAIChatAnswer(prompt, model); // 関数を呼び出し、promptとモデルを渡す

    } else if (/.*、レシピ/.test(userMessage)) {
      var prompt = userMessage + "前述の合理的なレシピと予想される費用を概算で良いので算出し、1000字以内で出力してください。読みやすいように箇条書きにして、改行してください";
      replyText = getAIChatAnswer(prompt, model); // 関数を呼び出し、promptとモデルを渡す

    } else {
      replyText = getAIChatAnswer(userMessage, model); // 関数を呼び出し
    }

    messages = [{'type': 'text', 'text': replyText}]; // LINEメッセージオブジェクトを作成
    
  } else if (userMessage.startsWith("絵を描いて")) {
    messages = getAIImageAnswer(userMessage);
    
  } else if (userMessage.includes("英訳して")) {
    var textToTranslate = userMessage.replace("英訳して", "");
    var translatedText = translateTextWithDeepL(textToTranslate, "EN");
    messages = [{'type': 'text', 'text': translatedText}];

  } else {
    messages = [{'type': 'text', 'text': 'A'}];
  }

  const linebotClient = new LineBotSDK.Client({ channelAccessToken: CHANNEL_ACCESS_TOKEN });
  try {
    linebotClient.replyMessage(replyToken, messages);
  } catch (e) {
    log_to_sheet("A", e);
  }

  return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
}

//DALL-E3に関する関数
function getAIImageAnswer(text) {
  var imageURL = generateImageURL(text);
  var messages = [
    {'type':'text', 'text': '画像ができました!'},
    {'type':'image', 'originalContentUrl': imageURL, 'previewImageUrl': imageURL}
  ];
  return messages;
}

function generateImageURL(text) {
  var options = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + openAIApiKey
    },
    "payload": JSON.stringify({
      "prompt": text,
      "model": "dall-e-3",
      "response_format": "url"
    })
  };
  var response = UrlFetchApp.fetch(DALLE_API, options);
  var data = JSON.parse(response.getContentText());
  return data.data[0].url;
}

//GPTに関する関数
function getAIChatAnswer(prompt, model) {
  var requestOptions = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + openAIApiKey
    },
    "payload": JSON.stringify({
      "model": model,
      "messages": [
         {"role": "user", "content": prompt}
      ]
    })
  };
  var response = UrlFetchApp.fetch("https://api.openai.com/v1/chat/completions", requestOptions);
  var responseText = response.getContentText();
  var json = JSON.parse(responseText);
  return json.choices[0].message.content.trim();
}

function log_to_sheet(column, text) {
  var lastRow;
  if(logSheet.getRange(column + "1").getValue() == ""){
    lastRow = 0;
  } else if(logSheet.getRange(column + "2").getValue() == ""){
    lastRow = 1;
  } else {
    lastRow = logSheet.getRange(column + "1").getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();
    if(lastRow >= 1000){
      logSheet.getRange(column + "1:" + column + "1000").clearContent();
      lastRow = 0;
    }
  }
  var putRange = column + String(lastRow + 1);
  logSheet.getRange(putRange).setValue(text);
}

この改良版では、下図のように「こんにちはGPT」と部分一致するテキスト入力をした際にはGPT-3.5Turboと会話し、「こんにちはGPT4」と部分一致するテキスト入力をした際にはGPT-4omniと会話する分岐を行います。
タイトルなし.png

ユーザーの入力に対して、プロンプトを追加してGPTに送信する2つのコードにおいても、GPT-3.5TurboとGPT-4omniを切り替えて使用することが出来ます。

まず1つ目の機能では、下図のように「こんにちはGPT3、〇〇駅発、〇〇駅着」と入力した時は、GPT-3.5Turboが回答し、「こんにちはGPT4、〇〇駅発、〇〇駅着」と入力した時は、GPT-4omniが回答します。
タイトルなし.png

料理のレシピを出力させる機能についても下図のように、同様に使用モデルをGPT-3.5TurboとGPT-4Turboを切り替えることが出来ます。
タイトルなし.png

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3