ChatGPTのFunction Calling使ってみた

こんにちは、あるいはこんばんは
新卒3年目の野地です。最近は生成AIの波にのまれています。

aiチャット

OpenAIのChatGPTがプログラミングの新たなパラダイムを形成しています。
特にGoogle Apps Script(GAS)のような手軽な環境と組み合わせることで非エンジニアでも様々な業務効率化ができ、新たな可能性が広がっています。

今回はその一例として、OpenAIのChat API の機能、functionCallを使用したフリーアドレス管理について試したものを紹介します。

フリーアドレスの管理とChatGPT

マネックスでは、システム開発部署の座席配置はフリーアドレス形式を採用しています。
具体的には、Googleスプレッドシートに名前を入力し、座席を指定する方式です。
(それは果たして「フリー」アドレスなのか…?)

この方法をさらに便利にするために、ChatGPTとGASを利用したチャット形式の座席予約を試してみました。

functionCallの使用方法

functionCallは、ChatGPTが適切な関数を呼び出すことを可能にする新機能です。

以下は、ChatGPTからAPI応答を受け取るコードの一例です。

// ChatGPT API関連定数
const SECRET_KEY = "***";
const MAX_TOKENS = 2000;
const MODEL_NAME = "gpt-3.5-turbo"const MODEL_TEMP = 0.5;
const URL = "https://api.openai.com/v1/chat/completions";

function GPT(prompt) {
  
  const payload = {
    model: MODEL_NAME,
    messages: prompt,
    temperature: MODEL_TEMP,
    max_tokens: MAX_TOKENS,
    functions: FUNCTIONS,
  };

  const options = {
    contentType: "application/json",
    headers: { Authorization: "Bearer " + SECRET_KEY },
    payload: JSON.stringify(payload),
    muteHttpExceptions : true,
  };

  const res = JSON.parse(UrlFetchApp.fetch(URL, options).getContentText());
}

このコードでは、Chat APIの呼び出しに使用するfunctionCallパラメータが重要なポイントとなります。
以下のように、複数の関数の定義と説明を設定します。

// function callingの対象.適宜加える
const FUNCTIONS =  [
  {
    name: "get_vacant_seats",
    description: "Get the current vacant seats",
    parameters: {
      type: "object",
      properties: {},
    }
  },
  {
    name: "reserve_seat",
    description: "reserve the specified seat by name",
    parameters: {
      type: "object",
      properties: {
        seat: {
          type: "string",
          description: "the seat which function caller wants to reserve e.g. 'D17' "
        },
        name: {
          type: "string",
          description: "reservation name"
        },
      },
      required: ["seat", "name"]
    }
  },
]

これによりChatGPTはユーザーの入力から適切な関数を判断し、「この関数をこの引数で呼び出してください」と指示してくれます。
このif文の条件で判断しているように、responseのmessageの中にfunction_callという項目があります。

 } else if (!!res.choices[0] && !!res.choices[0].message.function_call) {

呼び出すべき関数がない場合、function_callの項目は無い状態で応答が返されます。

ChatGPTに「このFunction呼んでください」と言われたら、素直に実行します。

let result = ''; 
  if (!!res.error) {
    result = res.error.message;
    return result;
  } else if (!!res.choices[0] && !!res.choices[0].message.function_call) {

    const function_name = res.choices[0].message.function_call.name;
    const function_args = JSON.parse(res.choices[0].message.function_call.arguments);

    const f_res = globalThis[FUNCTION_MAP[function_name]](function_args);

    prompt.push({role: "function", name: function_name, content: f_res});
    // function_callが無くなるまで呼び出し
    result = GPT(prompt);
  } else {
    result = res.choices[0].message.content;
  }

GAS上での関数名とChat APIに渡すfunction名をマッピングして、globalThisで関数名の文字列から実行しています。
(同名にしてしまえばマッピングは不要ですね...)

// 各function_nameとスクリプト上の関数名定義との紐づけ
const FUNCTION_MAP = {
  'get_vacant_seats': 'getSeats',
  'reserve_seat': 'reserveSeat',
}

対応するgetSeats()など、実際の関数は別で実装してください。今回は割愛します。

そして、その結果は再度ChatGPTへの入力にセットされ、次のステップへと進みます。
このステップは、ChatGPTが呼び出すべき関数がないと判断するまで繰り返されます。

最終的なコード全体は以下の通りです。

最終的なコード

// ChatGPT API関連定数
const SECRET_KEY = "****";
const MAX_TOKENS = 2000;
const MODEL_NAME = "gpt-3.5-turbo"; 
const MODEL_TEMP = 0.5;
const URL = "https://api.openai.com/v1/chat/completions";

// function callingの対象.適宜加える
const FUNCTIONS =  [
  {
    name: "get_vacant_seats",
    description: "Get the current vacant seats",
    parameters: {
      type: "object",
      properties: {},
    }
  },
  {
    name: "reserve_seat",
    description: "reserve the specified seat by name",
    parameters: {
      type: "object",
      properties: {
        seat: {
          type: "string",
          description: "the seat which function caller wants to reserve e.g. 'D17' "
        },
        name: {
          type: "string",
          description: "reservation name"
        },
      },
      required: ["seat", "name"]
    }
  },
];
// 各function_nameとスクリプト上の関数名定義との紐づけ
const FUNCTION_MAP = {
  'get_vacant_seats': 'getSeats',
  'reserve_seat': 'reserveSeat',
}

function GPT(prompt) {
  
  const payload = {
    model: MODEL_NAME,
    messages: prompt,
    temperature: MODEL_TEMP,
    max_tokens: MAX_TOKENS,
    functions: FUNCTIONS,
  };

  const options = {
    contentType: "application/json",
    headers: { Authorization: "Bearer " + SECRET_KEY },
    payload: JSON.stringify(payload),
    muteHttpExceptions : true,
  };

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

  let result = ''; 
  if (!!res.error) {
    result = res.error.message;
    return result;
  } else if (!!res.choices[0] && !!res.choices[0].message.function_call) {

    const function_name = res.choices[0].message.function_call.name;
    const function_args = JSON.parse(res.choices[0].message.function_call.arguments);

    const f_res = globalThis[FUNCTION_MAP[function_name]](function_args);
    prompt.push({role: "function", name: function_name, content: f_res});
    // function_callが無くなるまで呼び出し
    result = GPT(prompt);
  } else {
    result = res.choices[0].message.content;
  }
  
  return result;
}

ChatGPTとGASによる業務効率化

このコードで、通常は複数ステップを要する座席の予約作業を、単一の指示で完了させることが可能となります。
「適当な席を○○(名前)で予約して」という単一の指示により、ChatGPTとGASは空席の検索から予約までを自動で行い、「席××が「○○」で予約されました」と返信してくれます。 夢が広がりますね。

時代の変わり目

当社マネックスでは、お客様向け・社内向けと両面でChatGPT等生成AIの活用を積極的に検討し、実験しています。
自由で創造的な環境を提供するマネックスで、一緒に新しい可能性を追求してみませんか? 求人情報は以下をご覧ください!!

open.talentio.com