🧑‍💻

LINE BotでChatGPTと会話する(ソースコード編)

2024/05/20に公開

概要

LINE BotでChatGPTと会話する(セットアップ編)のソースコードの解説記事です。
一通り確認はしていますが、ChatGPTの力を借りて解説を書いています。時々ミスがあるかもしれません。
シーケンス図で下記のようなものです。

関数ごとの解説

この章では関数毎の役割を解説しています。全体の理解にお役立てください

doPost(e)

役割

この関数は、エントリーポイント(LINEのWebhookが初めにアクセスする場所)になっています。
LINEのWebhookからのPOSTリクエストを処理し、ユーザーのメッセージに対する応答を生成して返信する役割を担っています。

詳細

e.postData.contentsをJSON形式に解析し、POSTリクエストの内容を取得します。
解析したデータから、LINEのリプライトークン(replyToken)とユーザーのメッセージ(userMessage)を抽出します。
replyTokenが未定義の場合、エラーをスローします。
ユーザーのメッセージに対するChatGPTの応答(replyMessage)を取得します。
replyMessageを使ってLINEに応答を送信します。
処理が正常に完了した場合は、"post ok"というJSONレスポンスを返します。エラーが発生した場合は、エラーメッセージをログに記録し、"error"というJSONレスポンスを返します。

getChatGptResponse(message)

役割

この関数は、ユーザーから受け取ったメッセージをOpenAIのChatGPT APIに送信し、生成された応答を取得する役割を担っています。

詳細

ユーザーのメッセージを含むメッセージオブジェクト(messages)を作成します。
OpenAI APIにリクエストを送信するためのヘッダー(headers)とオプション(options)を設定します。
設定したオプションを用いて、ChatGPT APIにPOSTリクエストを送信します。
APIからの応答をJSON形式で解析し、応答メッセージを返します。
エラーが発生した場合、エラーメッセージをログに記録し、デフォルトのエラーメッセージを返します。

sendLineReply(replyToken, message)

役割

この関数は、指定されたリプライトークンとメッセージを使ってLINEに応答を送信する役割を担っています。

詳細

返信メッセージのペイロード(payload)を作成します。
LINE APIにリクエストを送信するためのヘッダー(headers)とオプション(options)を設定します。
設定したオプションを用いて、LINE APIにPOSTリクエストを送信します。
エラーが発生した場合、エラーメッセージをログに記録します。
それぞれの関数が連携して、LINEのメッセージを処理し、ChatGPTからの応答を生成してユーザーに返信する一連の流れを実現しています。

1行ごとの解説

ソースコード1行毎の細かなコメントです。それぞれのソースコードが何を意味しているのかを理解するためにお役立てください。

// スクリプトのプロパティサービスからLINEチャンネルのアクセストークンを取得
const LINE_CHANNEL_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty('LINE_CHANNEL_ACCESS_TOKEN');

// スクリプトのプロパティサービスからOpenAIのアクセストークンを取得
const OPENAI_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty('OPENAI_ACCESS_TOKEN');

// LINEのメッセージ返信エンドポイントURLを設定
const LINE_ENDPOINT = 'https://api.line.me/v2/bot/message/reply';

// OpenAIのChatGPT APIエンドポイントURLを設定
const OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions';

// LINEからのPOSTリクエストを処理する関数
function doPost(e) {
  try {
    // POSTリクエストの内容をJSONとして解析
    const json = JSON.parse(e.postData.contents);

    // LINEのリプライトークンを取得
    const replyToken = json.events[0].replyToken;

    // ユーザーからのメッセージを取得
    const userMessage = json.events[0].message.text;

    // リプライトークンが未定義の場合はエラーをスロー
    if (typeof replyToken === 'undefined') {
      throw new Error('Reply token is undefined');
    }

    // ChatGPTの応答を取得
    const replyMessage = getChatGptResponse(userMessage);

    // LINEに応答を送信
    sendLineReply(replyToken, replyMessage);

    // 正常に処理されたことを示すJSONレスポンスを作成して返す
    return ContentService.createTextOutput(JSON.stringify({'content': 'post ok'})).setMimeType(ContentService.MimeType.JSON);
  } catch (error) {
    // エラーが発生した場合、エラーメッセージをログに記録し、エラーを示すJSONレスポンスを返す
    Logger.log('Error in doPost: ' + error.message);
    return ContentService.createTextOutput(JSON.stringify({'content': 'error'})).setMimeType(ContentService.MimeType.JSON);
  }
}

// OpenAIのChatGPTにメッセージを送信し、応答を取得する関数
function getChatGptResponse(message) {
  // ユーザーのメッセージを含むメッセージオブジェクトを作成
  const messages = [{'role': 'user', 'content': message}];

  // リクエストヘッダーを設定
  const headers = {
    'Authorization': 'Bearer ' + OPENAI_ACCESS_TOKEN,
    'Content-type': 'application/json'
  };

  // リクエストオプションを設定
  const options = {
    'muteHttpExceptions': true,
    'headers': headers,
    'method': 'POST',
    'payload': JSON.stringify({
      'model': 'gpt-3.5-turbo',  // 使用するモデルを指定
      'max_tokens': 2048,        // 応答の最大トークン数を設定
      'temperature': 0.9,        // 応答の多様性を設定
      'messages': messages       // 送信するメッセージを設定
    })
  };

  try {
    // OpenAI APIにリクエストを送信
    const response = UrlFetchApp.fetch(OPENAI_API_URL, options);

    // APIからの応答をJSONとして解析
    const jsonResponse = JSON.parse(response.getContentText());

    // 応答メッセージを返す
    return jsonResponse.choices[0].message.content;
  } catch (error) {
    // エラーが発生した場合、エラーメッセージをログに記録し、エラーメッセージを返す
    Logger.log('Error in getChatGptResponse: ' + error.message);
    return 'Sorry, I couldn\'t process your request.';
  }
}

// LINEにメッセージを返信する関数
function sendLineReply(replyToken, message) {
  // 返信メッセージのペイロードを設定
  const payload = {
    'replyToken': replyToken,
    'messages': [{
      'type': 'text',
      'text': message
    }]
  };

  // リクエストヘッダーを設定
  const headers = {
    'Content-Type': 'application/json; charset=UTF-8',
    'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN
  };

  // リクエストオプションを設定
  const options = {
    'method': 'POST',
    'headers': headers,
    'payload': JSON.stringify(payload)
  };

  try {
    // LINE APIにリクエストを送信
    UrlFetchApp.fetch(LINE_ENDPOINT, options);
  } catch (error) {
    // エラーが発生した場合、エラーメッセージをログに記録
    Logger.log('Error in sendLineReply: ' + error.message);
  }
}

Discussion