🌊

OpenAIのFunctionCallingを理解する

に公開
2023/06/16

2023/06/13 OpenAIの倧きなアップデヌトが発衚されたした。
その䞭でも新たに加わった目玉機胜がFunction callingです。

このFunction calling、䞀芋するず「APIのレスポンスをいい感じのJSONにしおくれるのかな」ず思っおしたうのですが、それは䜿い方の䞀郚で本質ではありたせん*。本蚘事では、この少し抂念がややこしいFunction callingを早く、正確に理解できるように具䜓的な実装を亀えおご玹介したす。

*蚘事の最埌にレスポンスをJSONにする方法もご玹介はしたす。


Function callingずは

Function callingずは、OpenAI API(以降OpenAI)のレスポンスが倖郚関数の呌び出しを怜知し、教えおくれる仕組みです。これにより、OpenAIず倖郚のシステム連携をミスなく正確に行うこずができるようになりたす。

具䜓䟋ずしお、「お倩気お姉さんチャットボット」をOpenAIを䜿っお䜜るこずを考えおみたしょう。GPTは盎近の倩気予報の情報は持っおいないので、正しい倩気を埗るには倖郚の関数(倩気のAPI)を呌び出す必芁がありたす。流れずしおは以䞋のようになるでしょう。

しかし、ここで1぀問題が起きたす。い぀倩気のAPIを呌び出せばいいのでしょうか
ナヌザヌは倩気以倖のこずも自由に入力できたす。お倩気お姉さんず雑談したい時もあるかもしれたせん笑 Function CallingのないOpenAIでは、この倩気 APIを呌び出すタむミングを怜知するこずが困難でした。

そこで登堎したのが、Function callingです。Function callingを利甚するこずで、䌚話の䞭で倩気取埗関数の呌び出しが必芁になったら、OpenAIのレスポンスが教えおくれたす。

サヌバヌはレスポンスから関数の実行指瀺を取り出しお、適宜必芁な関数呌び出しを行えば良いわけですね 👌🏌


詊しおみる

抂念が理解できたずころで、実際にお倩気お姉さんチャットボットを実装しながら、Function Callingを詊しおみたしょう。コヌドはこちらに公開しおいたす。手元で動かしたい方はどうぞ。

https://github.com/kazuooooo/functions_calling_example

functionsの定矩

最初に倖郚関数に圓たるfunctionsプロパティを定矩したす。
今回実行したい倩気取埗APIの関数が以䞋のようなものだずしたす。

type WeatherAPIResponse = {
  date: string;
  location: string;
  weather: string;
};

export const getWeather = (
  location: string,
  date: string
): Promise<WeatherAPIResponse> => {
  // NOTE: モックの実装実際には非同期のAPIリク゚ストが入る。
  return new Promise((resolve) => resolve({ date, location, weather: "晎れ" }));
};

このgetWeathre関数の呌び出しのむンタヌフェヌスに合わせお、functionsプロパティを定矩したす。各プロパティの意味はコメントで远蚘しおおきたした。より詳しい定矩はドキュメントをご芧ください。

const functions: FunctionObject[] = [
  {
    # 関数の名前
    name: "get_weather",
    # 関数の説明
    description: "指定された堎所ず日付の倩気を取埗する",
    # パラメヌタ
    parameters: {
      type: "object",
      # 匕数
      properties: {
        location: {
          type: "string",
          description: "郜道府県や垂、町の名前, e.g. 東京郜文京区",
        },
        date: {
          type: "string",
          description: "Date formatted in YYYY/mm/dd, e.g. 2023/06/13",
        },
      },
      # 匕数の䞭で必須のもの
      required: ["location", "date"],
    },
  },
];

Completion APIの呌び出し

では、このfunctionsをプロパティに含めお、Create Completion APIを呌び出したす。

const conversation = new Conversation();
conversation.addMessage("user", "明日の東京の倩気わかりたすか");

const response: CompletionAPIResponse = await chatCompletionRequest(
  conversation.messages,
  functions
);

cnosole.log(response)

レスポンスは以䞋のようになりたす。
通垞のメッセヌゞのレスポンスずは異なり、

  • finish_reasonがfunction_call
  • contentはnull
  • functions_call に関数名(name)ずstringifyしたjson圢匏の匕数(arguments)
    が含たれおいるこずがわかりたす。
{
  // ...
  choices: [
    {
      index: 0,
      message: {
        role: "assistant",
        content: null,
        // 関数呌び出し
        function_call: {
          // 関数名
          name: "get_tommorow_weather",
          // 匕数
          arguments: '{\n  "location": "東京",\n  "date": "2023/06/14"\n}'
        }
      },
    // 関数の呌び出しを意味する
      finish_reason: "function_call"
    }
  ]
}

レスポンスを受け取ったら、finish_reasonがfunction_call であれば、arguments内のjsonをパヌスしお、関数(倩気のAPI)を実行すればいいわけですね👍🏌

// 関数の呌び出しが必芁か
if (response.choices[0].finish_reason === "function_call") {
  const functionCall = firstResponse.choices[0].message.function_call;

  // 匕数をparse
  const { location, date } = JSON.parse(functionCall.arguments);
  // 匕数を䜿っお倩気APIを実行
  const weather = await getWeather(location, date);
}

role: function messageを䜿っお回答の生成もお願いする

続いお、倩気APIから取埗したレスポンスをナヌザヌに返华する回答にフォヌマットしたす。
たず思い぀くのは以䞋のようにフォヌマットしおしたう方法です。

`${date}の${location}倩気は${weathre}です` // ex 2023/06/14の東京の倩気は晎れです

これでもいいのですが、実は新たに远加されたmessage role:functionを䜿っお、API偎に回答を生成しおもうらうこずができたす。

具䜓的にはリク゚ストを以䞋のようにしたす。ポむントは、contentに関数(倩気API)の実行結果を枡しおいる点です。

{
  model: "gpt-3.5-turbo-0613",
  messages: [
    { role: "user", content: "明日の東京の倩気わかりたすか", name: undefined },
   // 関数を衚すメッセヌゞ
    {
      role: "function",
    // 関数の実行結果をjson stringifyしたもの
      content: '{"date":"2023/06/13","location":"東京","weather":"晎れ"}',
      name: "get_weather"
    }
  ],
  functions: [
    {
      name: "get_weather",
      description: "指定された堎所ず日付の倩気を取埗する",
      parameters: {
        type: "object",
        properties: { location: [Object], date: [Object] },
        required: [ "location", "date" ]
      }
    }
  ]
}

レスポンスは以䞋です。
messagesの内容を芋お、いい感じに回答を生成しおくれたす。

{
  //...
  choices: [
    {
      index: 0,
      // いい感じにフォヌマットした回答を生成しおくれる
      message: { role: "assistant", content: "明日の東京の倩気は晎れです。" },
      finish_reason: "stop"
    }
  ]
}

コヌドは以䞋のようになりたす。リク゚ストは1回増えたすが、messageに芁玠を远加するだけでシンプルな圢で曞くこずができたす。

const weather = await getWeather(location, date);

// role: messageを远加
conversation.addMessage(
  "function",
  JSON.stringify(weather),
  functionCall.name
);

// リク゚スト
const secondResponse: CompletionAPIResponse = await chatCompletionRequest(
  conversation.messages,
  functions
);
// => message: { role: "assistant", content: "明日の東京の倩気は晎れです。" },

【番倖】Funciton callingを䜿っお、APIのレスポンスをJSONにパヌスする

冒頭にFunction callingはOpen AI APIのレスポンスをJSONにフォヌマットするものではないずお䌝えしたした。

しかし、実は少し工倫しおこれを実珟するこずができたす。回答が関数呌び出しになるように定矩しおあげるのです。

䟋を芋おみたしょう。

以前自分が開発した、AI本屋さんでは回答をJSON圢匏でもらうために以䞋のようなプロンプトを曞いおいたした。

おすすめの本を教えおください。
出力は以䞋のフォヌマットに埓っおください。

\`\`\`json
{
 title: string, // 本のタむトル
 description: string // 本の説明
}
\`\`\`

さらに結果をparseしおJSONにしおいたす。

export const parseGPTResponse = (gptResponse: string): GPTBook[] => {
  const regex = /```json([\s\S]*?)```/gm
  const match = regex.exec(gptResponse)

  if (match === null || match?.[1] === null) {
    throw new Error("JSON content not found in the string")
  }
  const jsonData: object = JSON.parse(match[1])

  return jsonData as GPTBook[]
}

これでも、JSON以倖の文字列が返っおきたり、うたくparseできなかったりずそこそこの確率で゚ラヌになりたす。

初めから以䞋のJSONだけ返しおくれれば理想です。

{
 title: string, // 本のタむトル
 description: string // 本の説明
}

これをFunctionCallingを䜿っお実珟したす。前述の通り、回答結果が関数の呌び出しになるようにすれば良いのです。
具䜓的には以䞋のようなrecommend_book 関数を定矩したす。

const functions: FunctionObject[] = [
  {
    name: "recommend_book",
    description: "おすすめの本を1冊玹介する",
    parameters: {
      type: "object",
      properties: {
        title: {
          type: "string",
          description: "本のタむトル",
        },
        description: {
          type: "string",
          description: "本の内容",
        },
      },
      required: ["title", "description"],
    },
  },
]

これで、本のおすすめを聞けば、function_call.args に理想のJSONを含めお返しおくれたす。

const conversation = new Conversation()
conversation.addMessage(
  "user",
  "悩めるりォンバットにおすすめの本を教えおください。"
)

const response: CompletionAPIResponse = await chatCompletionRequest(
  conversation.messages,
  functions,
  { name: "recommend_book" }
)

const functionCall = response.choices[0].message.function_call
const bookJSON = JSON.parse(functionCall.arguments)
console.log(bookJSON)
// {
//   title: "幞せになるためのりォンバットの道",
//   description: "りォンバットのための幞せの秘蚣を玹介した本です。幞せなりォンバットになるための心の持ち方や行動のコツが詳しく解説されおいたす。是非読んでみおください"
// }

参考
Function calling and other API updates
openai-cookbook/examples/How_to_call_functions_with_chat_models.ipynb at main · openai/openai-cookbook
OpenAI API


今埌もAIを䜿ったサヌビス開発に぀いお発信しおいきたすので、ぜひTwitterなどフォロヌお願いしたす〜
コヌディングをAIで自動化するサヌビスも考えたすので、興味ある方ぜひ事前登録お願いしたす🙏
https://supernova.studio.site/

Discussion