白猫のメモ帳

C#とかJavaとかJavaScriptとかHTMLとか機械学習とか。

ChatGPTのAPIに追加されたFunction callingで遊んでみる

こんばんは。

ChatGPTのAPI使ってますか?
Function callingという機能がしれっと追加されたみたいなのでちょっと遊んでみます。
何をしてくれるか一見すると謎いですが、プラグインを自分の環境で動かせるみたいな感じです。

今回はNode.js(TypeScript)にしてみます。
なんかC#のライブラリはあんまり最新じゃなさそうなので…。

先にやっておくこと

  • OpenAIでAPIを使えるように支払い情報とAPI Tokenの発行をしておく
  • Node.jsとnpmを使えるようにしておく
  • VSCodeとかNode.jsの開発環境を整えておく

今回はこの辺は割愛します。

Node.jsのアプリケーションを作る

とりあえずサクッと作ります。
お好きなディレクトリでプロジェクトを初期化します。

npm init -y

必要なものをインストールします。

npm install typescript @types/node ts-node openai

package.jsonのscriptsを編集して起動構成を足します。

{
  "name": "chat",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "ts-node index.ts"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@types/node": "^20.3.1",
    "openai": "^3.3.0",
    "ts-node": "^10.9.1",
    "typescript": "^5.1.3"
  }
}

index.tsを作ります。

console.log("hello");

実行してみます。

npm run start

> chat@1.0.0 start
> ts-node index.ts

hello

大丈夫そうですね。

とりあえずチャットできるかお試し

import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";

// APIの設定
const configuration = new Configuration({
    apiKey: "ここにトークン入れる…環境変数とか使う方がいいとは思う"
});
const openai = new OpenAIApi(configuration);

(async () => {

    // ユーザプロンプト
    const userMessage: ChatCompletionRequestMessage = { 
        role: "user", 
        content: "こんにちは" 
    };

    // 問い合わせ
    const chat1 = await openai.createChatCompletion({
        model: "gpt-3.5-turbo-0613",
        messages: [userMessage],
        max_tokens: 100
    });

    // レスポンス
    const assistantMessage = chat1.data.choices[0].message;
    console.log(assistantMessage);
})();

実行。

npm run start
{ role: 'assistant', content: 'こんにちは!どのようにお手伝いできますか?' }

大丈夫そうですね。

Function callingしてみる

関数の定義の仕方などはChatGPTプラグインとほとんど同じですね。
説明はたぶん英語で書いたほうが精度がいいですが、日本語でも大丈夫なのかも…。
OpenAPIのリファレンスとか見るとわかりやすいかな。

1ファイルに纏めちゃったのでちょっと長いけどこんな感じ。
ポイントは呼び出せる関数の定義をちゃんと教えてあげることと、関数の結果を更に「function」というロールで投げ直すことですかね。

import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";

// APIの設定
const configuration = new Configuration({
    apiKey: "ここにトークン入れる…環境変数とか使う方がいいとは思う"
});
const openai = new OpenAIApi(configuration);

// 呼び出す関数の定義
const functionSetting = [{
    name: "getSchedule",
    description: "Get schedules between start to end.",
    parameters: {
      type: "object",
      properties: {
        start: {
          type: "string",
          format: "date-time",
          description: "a start time"
        },
        end: {
            type: "string",
            format: "date-time",
          description: "a end time"
        }
      },
      required: ["start", "end"]
    }
  }
];

// 例えばタスク管理のサービスのAPIをつつくとか
// とりあえず固定の結果を返しておく
function getSchedule(start: Date, end: Date) {
    return {
        "schedules": [{
            time: "2023/06/17 09:00",
            event: "朝ごはんを食べる"
        }, {
            time: "2023/06/17 10:00",
            event: "部屋の掃除をする"
        }, {
            time: "2023/06/17 11:00",
            event: "買い物に出かける"
        }]
    };
}

(async () => {

    // ユーザプロンプト
    const userMessage: ChatCompletionRequestMessage = { 
        role: "user", 
        content: "明日の午前中の予定を教えて" 
    };

    // 1回目の問い合わせ
    const chat1 = await openai.createChatCompletion({
        model: "gpt-3.5-turbo-0613",
        messages: [userMessage],
        max_tokens: 100,
        function_call: "auto",
        functions: functionSetting
    });

    // 1回目のレスポンス
    const assistantMessage = chat1.data.choices[0].message;
    console.log(assistantMessage);

    // 関数を使ってくれなそうだったら諦める(今回は関数1つなので手抜き)
    if (assistantMessage?.function_call?.name != "getSchedule") {
        return;
    }

    // 返却してもらった関数呼び出しの定義に従って関数呼び出し
    const args = JSON.parse(assistantMessage?.function_call?.arguments || "{}");
    const schedule = getSchedule(new Date(args.start), new Date(args.end));

    // 関数の実行結果を設定
    const functionMessage: ChatCompletionRequestMessage = { 
        role: "function", 
        content: JSON.stringify(schedule), 
        name: "getSchedule"
    };

    // 2回目の呼び出し
    const chat2 = await openai.createChatCompletion({
        model: "gpt-3.5-turbo-0613",
        messages: [userMessage, assistantMessage, functionMessage],
        max_tokens: 100,
    });

    // 2回目のレスポンス
    console.log(chat2.data.choices[0].message);

})();

実行。

npm run start
{
  role: 'assistant',
  content: null,
  function_call: {
    name: 'getSchedule',
    arguments: '{\n  "start": "2022-02-22T09:00:00",\n  "end": "2022-02-22T12:00:00"\n}'
}
{
  role: 'assistant',
  content: '明日の午前中の予定は以下の通りです:\n' +
    '\n' +
    '- 9:00: 朝ごはんを食べる\n' +
    '- 10:00: 部屋の掃除をする\n' +
    '- 11:00: 買い物に出かける\n' +
    '\n' +
    '以上が予定です。'
}

おおー…なんか渡してる引数が明日じゃないけどね…。
時が止まってるから日付関係は苦手なのかな?

まとめ

結構簡単にできてえらい。
(TypeScriptまだよくわからない)