Function Callingで遊戯王APIを呼び出す

Function Callingで遊戯王APIを呼び出す

OpenAI APIのFunction Callingを使って遊戯王APIからカード情報を取得する

OpenAI APIにはFunction Callingという機能があって会話内容に応じて関数を使ってくれるらしい。
公式サンプルの天気予報APIでは気乗りしないので遊戯王APIを使って実験する。

1. システムフロー

Function Callingを使用したシステムは以下の図のように動作する。
青色の項目がOpenAI APIを使用している部分。



Function Callingは

  • 使用する関数
  • 使用する引数

を用意してくれるが関数を実行するわけではない。
関数を実行するところは別途作成する必要がある。
「Function Calling」ではなく「Function Selecting」とか名前を変えた方が良いと思う。

また、せっかくChatAPIを使うなら結果を会話形式で返して欲しい。
その場合は関数の実行結果をOpenAI APIに渡せば良い。

したがって、一度の処理の中で

  • 関数の選択
  • 会話の生成

の2回、OpenAI APIを呼び出すことになる。

2. 実験

コードは最後にまとめて書く。

2.1. 関数が1つだけのパターン

「カード名」を渡すと遊戯王APIから情報を引き出す関数get_card_info_by_nameを作った。
ChatAPIを呼び出す際に引数functionsに上記関数の説明を渡すと会話の内容に応じて関数を使うべきか判断してくれる。

  • 実験1
1
2
[SYSTEM MESSAGE]テキストを入力してください: DARK MAGICIANの攻撃力を教えて!
[AI]DARK MAGICIANの攻撃力は2500です。

「DARK MAGICIAN」は「ブラックマジシャン」の英語名。
遊戯王APIは英語名しか対応していないため数少ないつづりを知ってるやつを入力。
正しく動いていることが分かる。

上記例では、1度目のOpenAI API実行時に下記変数に関数と変数を返してくれる。

1
2
response_message["function_call"]["name"] # 関数:ここでは「get_card_info_by_name」
response_message["function_call"]["arguments"] #引数:ここでは「DARK MAGICIAN」

続いて、

  • 選択された関数を実行して「DARK MAGICIAN」の情報を取得。

  • 「DARK MAGICIAN」の情報を2度目のOpenAI API実行時に渡す。

と手順を踏んだ結果、「DARK MAGICIANの攻撃力は2500です。」という回答が返ってくる。

  • 実験2
1
2
3
4
5
6
7
8
9
10
[SYSTEM MESSAGE]テキストを入力してください: Jinzoのステータスを表にまとめて!
[AI]キャラ名: Jinzo
タイプ: 効果モンスター
効果: フィールド上の罠カード、およびその効果が発動できない。フィールド上のすべての罠の効果を無効にする。
攻撃力: 2400
守備力: 1500
レベル: 6
種族: 機械族
属性: 闇属性
シリーズ: Jinzo

ChatAPIらしく出力を整形してもらうこともできる。
これくらいメジャーだと普通のChatGPTでもできそうだがマイナーカードの英語名が分からない。

  • 実験3
1
2
[SYSTEM MESSAGE]テキストを入力してください: 明日の天気を教えて!
[SYSTEM MESSAGE]関数は呼び出されませんでした。

ふさわしい関数が何も選択されなかった場合の分岐も可能。
今回はprintして終了したが会話をつなげることも当然できる。

2.2. 関数が2つあるパターン

下記2つの関数を用意。

  • 「カード名」を渡すと遊戯王APIから情報を引き出す関数get_card_info_by_name
  • 「カードID」を渡すと遊戯王APIから情報を引き出す関数get_card_info_by_id

会話に応じてふさわしい関数を選んでくれるはず。

  • 実験1
1
2
3
4
5
[SYSTEM MESSAGE]テキストを入力してください: DARK MAGICIANのidを教えて!
[AI]DARK MAGICIANのidは46986421です。

[SYSTEM MESSAGE]テキストを入力してください: Jinzoのidは?
[AI]Jinzoのカードのidは77585513です。

カード名から情報を取得した。
get_card_info_by_nameが呼ばれたことが分かる。

  • 実験2
1
2
3
4
5
6
7
8
9
10
11
12
[SYSTEM MESSAGE]テキストを入力してください: 77585513番のカード情報を表にまとめて!
[AI]| カードID | 77585513 |
|---|---|
| タイプ | 効果モンスター |
| フレームタイプ | 効果 |
| 説明 | フィールド上の罠カード及びその効果の発動を無効化する。フィールド上の全ての罠の効果を無効化する。 |
| 攻撃力 | 2400 |
| 守備力 | 1500 |
| レベル | 6 |
| 種族 | マシン |
| 属性 | 闇 |
| アーキタイプ | Jinzo |

あえて「ID」という言葉を使わずに「77585513番」といじわるなことを言ったが意図を読み取ってくれた。
素晴らしい。

3. まとめ

  1. 思った以上に優秀

ChatGPTでありがちなハルシネーション(自信満々に嘘つく現象)に対して、関数部分を自作で用意することで正確さを保証できる点が素晴らしい。

会話から意図を読み取る力が優れていて、関数を選択するのも凄いが、引数を用意してくれるのがさらにすごい。
これを従来の自然言語処理で実装しようと思ったらかなり大変。
最初は「GPT-4」を使っていたが「GPT-3.5」でも問題なく動く。

  1. 英語名だけだとつまらないので日本語で検索できるようにしたい

遊戯王カードの日本語名とカードIDの対応表があれば実装できる。
上記は以前の実験の副産物として所有しているので実装できる。

↓実装した結果

4. コード

実験に使用したコードを記す。

python3.11
インストールしたライブラリはopenaiのみ。

4.1. 関数が1つだけのパターン

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import openai
import os
import json
import requests


# APIキーを取得
openai.api_key = os.environ["OPENAI_API_KEY"]

# 定数
MODEL = "gpt-3.5-turbo-0613"

# YU-GI-OH APIにカード名を渡してカード情報を取得する関数
def get_card_info_by_name(arguments):
card_name = arguments.get("cardname")
url = f"https://db.ygoprodeck.com/api/v7/cardinfo.php?name={card_name}"
response = requests.get(url)
if response.status_code == 200:
# APIから取得した情報をJSON→辞書→文字列に変換
data = response.json()
# 'data'キーの最初の要素から指定されたキーの情報を取得
card_info = data["data"][0]
keys_to_extract = ["id", "name", "type", "frameType", "desc", "atk", "def", "level", "race", "attribute", "archetype"]
extracted_data = {key: card_info.get(key, None) for key in keys_to_extract} # keyが存在しない項目はNoneを返す(魔法罠のatkなど)
# 辞書を文字列に変換
result_string = ", ".join([f"'{key}': {value!r}" for key, value in extracted_data.items()])
return result_string
else:
return "カードが見つかりませんでした"

# Functions Callingで使用する関数
FUNCTIONS = [
{
"name": "get_card_info_by_name",
"description": "YU-Gi-OH APIによってカード名からカード情報を取得",
"parameters": {
"type": "object",
"properties": {
"cardname": {
"type": "string",
"description": "カード名のみを文字列で入力"
}
},
"required": ["cardname"]
}
},
]

# Functions Callingで使用する関数の辞書
FUNCTIONS_DICT = {
"get_card_info_by_name": get_card_info_by_name,
}


# メインの会話処理
def conversation(user_message):
# 【ステップ1】: OpenAI APIにユーザークエリと利用可能な関数を送信して、使う関数を選んでもらう
message_01 = {"role": "user",
"content": user_message}

response_01 = openai.ChatCompletion.create(
model=MODEL,
messages=[
message_01, # ユーザークエリ
],
functions=FUNCTIONS, # 使用可能な関数
function_call="auto" # auto:APIに関数の選択を任せる
)
response_message = response_01["choices"][0]["message"]

if response_message.get("function_call") is None: # 関数が呼び出されなかった場合
print(f"[SYSTEM MESSAGE]関数は呼び出されませんでした。")

else: # 関数が呼び出された場合
# 【ステップ2】: OpenAI APIの選んだ関数をOpenAI APIの選んだ引数で実行
function_name = response_message["function_call"]["name"]
arguments = json.loads(response_message["function_call"]["arguments"])
function_to_call = FUNCTIONS_DICT[function_name]
function_result = function_to_call(arguments)

# 【ステップ3】: 関数の実行結果をOpenAI APIに送信
message_02 = {
"role": "function",
"name": function_name,
"content": function_result,
}
response_02 = openai.ChatCompletion.create(
model=MODEL,
messages=[
message_01, # ユーザークエリ
message_02, # 関数の実行結果
],
)
response_message_02 = response_02["choices"][0]["message"]
print(f"[AI]{response_message_02['content']}")

# メッセージ入力
user_message = input(f"[SYSTEM MESSAGE]テキストを入力してください: ")
conversation(user_message)

4.2. 関数が2つあるパターン

関数を増やしただけ。
同様に3つ、4つと増やせる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import openai
import os
import json
import requests


# APIキーを取得
openai.api_key = os.environ["OPENAI_API_KEY"]

# 定数
MODEL = "gpt-3.5-turbo-0613"

# YU-GI-OH APIにカード名を渡してカード情報を取得する関数
def get_card_info_by_name(arguments):
card_name = arguments.get("cardname")
url = f"https://db.ygoprodeck.com/api/v7/cardinfo.php?name={card_name}"
response = requests.get(url)
if response.status_code == 200:
# APIから取得した情報をJSON→辞書→文字列に変換
data = response.json()
# 'data'キーの最初の要素から指定されたキーの情報を取得
card_info = data["data"][0]
keys_to_extract = ["id", "name", "type", "frameType", "desc", "atk", "def", "level", "race", "attribute", "archetype"]
extracted_data = {key: card_info.get(key, None) for key in keys_to_extract} # keyが存在しない項目はNoneを返す(魔法罠のatkなど)
# 辞書を文字列に変換
result_string = ", ".join([f"'{key}': {value!r}" for key, value in extracted_data.items()])
return result_string
else:
return "カードが見つかりませんでした"

# YU-GI-OH APIにカード名を渡してカード情報を取得する関数
def get_card_info_by_id(arguments):
card_id = arguments.get("cardname")
url = f"https://db.ygoprodeck.com/api/v7/cardinfo.php?id={card_id}"
response = requests.get(url)
if response.status_code == 200:
# APIから取得した情報をJSON→辞書→文字列に変換
data = response.json()
# 'data'キーの最初の要素から指定されたキーの情報を取得
card_info = data["data"][0]
keys_to_extract = ["id", "name", "type", "frameType", "desc", "atk", "def", "level", "race", "attribute", "archetype"]
extracted_data = {key: card_info.get(key, None) for key in keys_to_extract} # keyが存在しない項目はNoneを返す(魔法罠のatkなど)
# 辞書を文字列に変換
result_string = ", ".join([f"'{key}': {value!r}" for key, value in extracted_data.items()])
return result_string
else:
return "カードが見つかりませんでした"

# Functions Callingで使用する関数
FUNCTIONS = [
{
"name": "get_card_info_by_name",
"description": "YU-Gi-OH APIによってカード名からカード情報を取得",
"parameters": {
"type": "object",
"properties": {
"cardname": {
"type": "string",
"description": "カード名のみを文字列で入力"
}
},
"required": ["cardname"]
}
},
{
"name": "get_card_info_by_id",
"description": "YU-Gi-OH APIによってカードIDからカード情報を取得",
"parameters": {
"type": "object",
"properties": {
"cardname": {
"type": "string",
"description": "カードIDのみを文字列で入力"
}
},
"required": ["cardname"]
}
},
]

# Functions Callingで使用する関数の辞書
FUNCTIONS_DICT = {
"get_card_info_by_name": get_card_info_by_name,
"get_card_info_by_id": get_card_info_by_id,
}


# メインの会話処理
def conversation(user_message):
# 【ステップ1】: OpenAI APIにユーザークエリと利用可能な関数を送信して、使う関数を選んでもらう
message_01 = {"role": "user",
"content": user_message}

response_01 = openai.ChatCompletion.create(
model=MODEL,
messages=[
message_01, # ユーザークエリ
],
functions=FUNCTIONS, # 使用可能な関数
function_call="auto" # auto:APIに関数の選択を任せる
)
response_message = response_01["choices"][0]["message"]

if response_message.get("function_call") is None: # 関数が呼び出されなかった場合
print(f"[SYSTEM MESSAGE]関数は呼び出されませんでした。")

else: # 関数が呼び出された場合
# 【ステップ2】: OpenAI APIの選んだ関数をOpenAI APIの選んだ引数で実行
function_name = response_message["function_call"]["name"]
arguments = json.loads(response_message["function_call"]["arguments"])
function_to_call = FUNCTIONS_DICT[function_name]
function_result = function_to_call(arguments)

# 【ステップ3】: 関数の実行結果をOpenAI APIに送信
message_02 = {
"role": "function",
"name": function_name,
"content": function_result,
}
response_02 = openai.ChatCompletion.create(
model=MODEL,
messages=[
message_01, # ユーザークエリ
message_02, # 関数の実行結果
],
)
response_message_02 = response_02["choices"][0]["message"]
print(f"[AI]{response_message_02['content']}")

# メッセージ入力
user_message = input(f"[SYSTEM MESSAGE]テキストを入力してください: ")
conversation(user_message)

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×