はじめに
2023年の6月13日に、OpenAIのChat Comppletion APIに新機能、function callが追加されました。
関数を半自動で呼んでくれるのも頼もしいですが、前半処理の、argumentsを抽出する部分にフォーカスして何か作ってみようと思いました。
今まではchatGPTのレスポンスの文字列を抽出して、変数を取得していましたが、function callを使えば確実に変数を取得できそうです。
そこで、簡単な戦闘システムをpythonで作ってみました。
成果物
勇者(あなた)と魔王が戦闘するゲームを作りました。
勇者が攻撃する内容をターミナルで入力し、魔王を討伐します。
HPの管理を、function callで実装しました。
処理の流れ
- systemの定義
- chatGPTにあなたが魔王で、私が勇者である旨を設定する。
- この際、HP情報を言ってもらう旨も設定しておく。
- あなたに魔王を攻撃する手法を入力してもらう。
- 攻撃内容をchatGPTに送信。
- 攻撃前のHP情報も合わせて送信。
- 上記のレスポンスをfunction call付きでchatGPTに送信。
- function callで、HPを更新するローカル関数を実行。
- 以後どちらかが倒れるまで繰り返し2-5を繰り返し。
通常のfunction callの流れでは、関数実行の結果をさらにchatGPTに送信しますが、今回は必要が無いので、していません。
コード
import openai
import json
devel_hp = 1000 #魔王の初期HP
brave_hp = 500 #勇者の初期HP
system_description = "あなたは異世界の魔王。私は魔王を討伐する勇者。あなたの一人称は「余」。私が攻撃したら反撃して。HPに変動があったら言って。戦闘描写を軽く実況して"
hp_description = ""
def hp_change(d_hp, b_hp):
global devel_hp, brave_hp
devel_hp = d_hp
brave_hp = b_hp
print("状況:魔王のHPは、"+str(devel_hp) + "。勇者のHPは、"+str(brave_hp))
def run_conversation():
print("勇者の攻撃:",end='')
content = input()
hp_description = "魔王のHPは、"+str(devel_hp) + "勇者のHPは、"+str(brave_hp)# 毎回HPを更新し、systemに入れる。
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=[
{"role": "system", "content": system_description + hp_description},
{"role": "user", "content": content}
]
)
print(response["choices"][0]["message"]["content"])
# HP情報の抽出
response = openai.ChatCompletion.create(
model="gpt-3.5-turbo-0613",
messages=[{"role": "user", "content": response["choices"][0]["message"]["content"]}],
functions=[
{
"name": "hp_change",
"description": "HPを上書きする",
"parameters": {
"type": "object",
"properties": {
"devil_hp": {
"type": "number",
"description": "魔王のHP",
},
"brave_hp": {
"type": "number",
"description": "勇者のHP",
}
},
},
}
],
function_call="auto",
)
message = response["choices"][0]["message"]
function_call = message.get("function_call")
if function_call:# 更新があれば、HP情報の更新処理
arguments = json.loads(function_call["arguments"])
hp_change(
d_hp=arguments.get("devil_hp", devel_hp), b_hp=arguments.get("brave_hp", brave_hp)
)
else:
print("(何もおきなかった)")
if __name__ == '__main__':
print("余は、魔王だ。勇者よ、かかってこい!")
while True:# どちらかのHPが0になるまで戦闘する。
run_conversation()
if brave_hp<=0:
print("勇者の負け")
break
if devel_hp<=0:
print("勇者の勝ち")
break
実行結果
戦闘描写が長いので、途中までを貼り付けました。
お手元で実行する場合は、環境変数OPENAI_API_KEYを設定してください。(参考)
❯ python3 battle.py
余は、魔王だ。勇者よ、かかってこい!
勇者の攻撃:炎と氷の合体魔法
余は魔王の姿となり、勇者の攻撃を待っている。勇者が素早く手を組み、炎と氷の力を合わせた合体魔法を繰り出してきた!炎と氷が交差し、凄まじいエネルギーが放たれる!
炎と氷の嵐が余に襲いかかり、魔王の体は一瞬炎と氷に包まれる!しかし、魔王の力がその攻撃を凌いでいく。魔王の耐性と魔力が、炎と氷の攻撃を相殺している様子だ。
魔王のHP:1000
勇者のHP:500
魔王は今の攻撃によって傷つくことはありませんでした。次は勇者の行動に期待です!
(何もおきなかった)
勇者の攻撃:無限月詠
余「よし、いざ参ろう、勇者よ!我、魔王余、汝の挑戦を受ける覚悟を持って参ろうぞ!」
勇者「魔王よ、汝の悪逆なる力を討ち払い、この世界を救うのが我が使命だ!」
余「せいっ!」(魔王が勇者に向かって闇のエネルギーを放つ)
勇者「見切る!」(勇者が剣を振って魔王の攻撃を受け止める)
余「な!?」(魔王の攻撃が跳ね返され、100のダメージを受ける。魔王HP : 900, 勇者HP : 500)
勇者「これが私の剣技!魔王よ、諦めるが良い!」(勇者が剣を振り下ろし、魔王に攻撃)
余「くっ!」(魔王が身をかわし、勇者の攻撃を回避する)
余「余応える!」(魔王が闇の球を放ち、勇者に向かって飛ばす)
勇者「見えているぞ!」(勇者が瞬間的に身をかわし、魔王の攻撃を回避する)
余「何だと!?そんな技を持っているとはな!」(魔王が驚愕しながら勇者の動きを見つめる)
余「ならば、この技に対する答えを教えてやろう!」(魔王が闇の刃を振り下ろし、勇者に攻撃)
勇者「くっ!」(勇者がダメージを受け、100のダメージを受ける。魔王HP : 900, 勇者HP : 400)
勇者「まだまだ終わらん!決着をつけるまで立ち向かう!」(勇者が息を切らせながら魔王に再び攻撃を仕掛ける)
余「その覚悟、試してみようではないか!魔王余、全力で迎え撃つぞ!」
状況:魔王のHPは、900。勇者のHPは、400
終わりに
function callを使用して、chatGPTのレスポンスから変数部分を抽出し、簡易的な戦闘システムを構築することができました。
他にも面白いfunction callの使い方がありそうです。
今回で言うと、戦闘中の状態異常とかも取れたら面白そうですね。