14
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

新規開発や新技術の検証、導入にまつわる記事を投稿しよう!

ChatGPT APIのFunction callingを使って頑張らずにチャットをアクションに繋げる

Last updated at Posted at 2023-06-20

はじめに

2023/6/13のOpenAIのアップデートでFunction callingという機能が追加されました。

触ってみたところ今まで気合いと若干の運頼みでどうにかしていたところを綺麗にできる機能なのが分かったので試してみました。

個人的にはFunction callingという名前がちょっと紛らわしかったです。Function selectingみたいな感じじゃないかと思いました。

やってくれることは、雑にいうとユーザーの入力メッセージに対して使えそうな関数を選んでレスポンスしてくれるというものです。これだけだと有り難みが分かりにくいので説明して行きます。

ChatGPT APIのレスポンスを何らかのアクションに繋げるには何が必要だったのか

この機能ができるまでChatGPT APIのレスポンスから何かしらアクションを行わせたい時、以下の実装が必要でした。

1. レスポンス内容の指定
2. レスポンスのパース
3. パース結果を関数に渡す

まず、ChatGPT APIに何か質問したとして返ってくるのはテキストメッセージです。(ChatGPTなので当たり前ですが)

なので例えばJSON形式など、その後の工程で使いやすい形で返すよう、ChatGPT APIに指示を出していました。
一例ですが、こんな感じのプロンプトを書いたりしてました。

role = "
Strictly adhere to the following restrictions.
- You are a pharmacist.
- You are professional, but also kind, friendly, and respectful.
- You reply in the user's language.
- You can reply only in the JSON format including the \"message\" and \"medicines\" properties.
- If medicine is recomended, include each in this property in array format. Array elements must be names only, not sentences.
- if medicine is not recomended, set \"medicines\" value to null.
- Again, you must in any case respond in JSON format which has the message and medicines parameters.
"

トークン節約のため英語にしてますが、内容としては薬剤師という設定で、

  1. 絶対にJSONで答えろ
  2. そのJSONにはmessagemedicinesというプロパティを含めろ
  3. オススメの薬があったらmedicinesに配列で含めろ
  4. オススメの薬がないならmedicinesはnullにしろ
  5. 繰り返す、絶対JSONで返せ!

みたいなことが書いてあります。細かいことは置いておいて、要はレスポンス内容を指定しようとしているということです。

ChatGPT APIからのレスポンスをその後に別の処理で使いたければ、パースしやすいようにしてどんな値を返すかまで指示しないといけなかったということですね。そうしないとただのテキストで返ってくるので正規表現で頑張る手もあるかもですが中々大変かなと思います。

ちなみにgpt-3.5-turboではここまで書いてもJSONで返ってこないことが結構な確率でありました。
なのでこの後にパース失敗時やパースできても想定してるプロパティが入ってない時などに対応できるような処理を書き、ユーザーにはそれっぽく答えをするようにしてました。運が絡むものでした。

Fanction callingで何ができるようになったのか

プログラムの例があったほうが早いので書いてみます。
例なので単純なものにしています。また、より汎用的にするためこの例ではOpenAIのモジュールを使っていません。

全体像は以下ですが、下に部分ごとに解説を書きます。

import requests
import json

def search_medicine(medicine):
    url = "https://www.amazon.co.jp/s?k=" + medicine
    return url

def ask_chat_gpt():
    headers = {
        "Authorization": "Bearer your-token"
    }
    messages = [
        {
            "role": "system",
            "content": "あなたは薬剤師です。ユーザーの症状に対してオススメの薬の名前を具体的に教えてください。"
        },
        {
            "role": "user",
            "content": "微熱があるみたい"
        }
    ]
    functions = [
        {
            "name": "search_medicine",
            "description": "症状に合わせた推奨される医薬品の候補を返す",
            "parameters": {
                "type": "object",
                "properties": {
                    "medicine": {
                        "type": "string",
                        "description": "薬の名前"
                    }
                },
                "required": ["medicine"]
            }
        }    
    ]
    function_call = "auto"
    body = {
        "model": "gpt-3.5-turbo-0613",
        "messages": messages,
        "functions": functions,
        "function_call": function_call
    }
    response = requests.post("https://api.openai.com/v1/chat/completions", headers=headers, json=body)
    return response.json()

if __name__ == '__main__':
    response = ask_chat_gpt()
    print(response)

ChatGPT APIへのリクエストはこんな形のBodyになります。

body = {
    "model": "gpt-3.5-turbo-0613", # モデル
    "messages": messages, # 入力するメッセージ
    "functions": functions, # ChatGPT APIに伝える関数群
    "function_call": "auto" # autoにしておくと、functionsで伝えた関数から使えそうなものを選ぶ
}

次に、大事なのがこの部分です。search_medicine1つだけしか書いてないですが、ここにAPI側に伝える関数を書いて行きます。関数名、その説明、引数ですね。

functions = [
    {
        "name": "search_medicine",
        "description": "症状に合わせた推奨される医薬品の候補を返す",
        "parameters": {
            "type": "object",
            "properties": {
                "medicine": {
                    "type": "string",
                    "description": "薬の名前"
                }
            },
            "required": ["medicine"]
        }
    }    
]

実行してみると以下のような内容になります。

{
  "id": "chatcmpl-7SsfdsfTDGE6TSd02XWL",
  "object": "chat.completion",
  "created": 1687022283,
  "model": "gpt-3.5-turbo-0613",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "function_call": {
          "name": "search_medicine",
          "arguments": {
            "medicine": "解熱剤"
          }
        }
      },
      "finish_reason": "function_call"
    }
  ],
  "usage": {
    "prompt_tokens": 139,
    "completion_tokens": 21,
    "total_tokens": 160
  }
}

messageの中にfunction_callとあり、search_medicineが選択されてます。また、argumentsに引数でmedicine解熱剤と入ってます。入力メッセージと関数の説明から、この関数を選択したわけですね。

{
  "index": 0,
  "message": {
    "role": "assistant",
    "content": null,
    "function_call": {
      "name": "search_medicine",
      "arguments": {
        "medicine": "解熱剤"
      }
    }
  },
  "finish_reason": "function_call"
}

あとは、この結果から好きに処理を書けば良いです。

function = message["function_call"]["name"]
if function == "search_medicine":
    medicine = json.loads(message["function_call"]["arguments"])["medicine"]
    medicine_url = search_medicine(medicine)
    print(medicine_url)
    ..その他好きな処理..

実際には必ずしも症状を含むコメントではない入力がされることもあるので、

if message.get("function_call"):
    function = message["function_call"]["name"]
    if function == "search_medicine":
        medicine = json.loads(message["function_call"]["arguments"])["medicine"]
        medicine_url = search_medicine(medicine)
        print(medicine_url)
else:
    print(message["content"])

↑こんな感じにすればそれにも対応できます。ChatGPT APIに伝えている関数に、該当するものがなかった場合の処理です。

単にこんにちはとやってみると、

    messages = [
        {
            "role": "system",
            "content": "あなたは薬剤師です。プロフェッショナルな薬剤師として振る舞ってください。"
        },
        {
            "role": "user",
            # "content": "微熱があるみたい"
            "content": "こんにちは"
        }
    ]

普通にコメントが返ってきます。

こんにちは!どのようなお困りごとがありますか?お手伝いできることがあれば教えてください。

まとめ

Function callingを使えば、ユーザーのメッセージに対してアクションに繋げるプログラムを簡単に作れます。
今までもできなくはなかったですが、かなりやりやすくなったなと思います。頑張ってプロンプトでレスポンスの形式を指定しなくて良くなりましたし、複数の関数を伝えておけばユーザー入力に対して最適な次の処理を選んでくれます。

だいぶ応用しやすくなったと感じます。

参考

  • Function calling

14
18
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
14
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?