ChatGPT / OpenAI APIをGodot Engineのゲームから呼び出す実装の備忘録です。
はじめに
ゲーム分野は、昨今のLLM/生成AIの発展に大きな恩恵を受ける分野の一つだと思います。 ChatGPTを利用してどんなゲームができるだろうか...と、考えるだけでも楽しいですね。
今回は、Godot EngineからChatGPT / OpenAI APIを呼び出すシンプルな実装をやってみます。
ChatGPTをGodot Engineから呼び出す:その3.
— BioErrorLog (@bioerrorlog) May 29, 2023
Godot EngineからPythonを呼び出すのではなく、HttpRequest Nodeを使って直接OpenAI APIを叩く.
LangChainとか使って複雑なことをしたい設計にしなければ、全然これで事足りる. https://t.co/lWWezgv7p9 pic.twitter.com/it5I8d0MWn
# 作業環境 Godot Engine v3.5.2.stable.official.170ba337a
ChatGPTをGodot Engineから呼び出す
最終的なプロジェクトのコードはこちらに配置しています。
概要
構成はシンプルです。
- プレイヤーはOpenAI API Keyを入力する
-> API Keyがローカルに保存される - プレイヤーはメッセージを入力して"Talk"ボタンを押下
-> OpenAI APIがcallされる - 返答が画面に反映される
順を追ってポイントを説明していきます。
詳細はソースコードを参照ください。
API Keyの保存とロード
まずは、プレーヤーがOpenAI API Keyを入力し、それをセーブ&ロードできるようにしておきます。
extends Node var save_file: ConfigFile = ConfigFile.new() func save_api(key: String) -> void: print("Start saving api key") save_file.set_value("credentials", "api_key", key) save_file.save("user://credentials.cfg") print("Save api key finished") func load_api() -> String: print("Start loading api key") var error = save_file.load("user://credentials.cfg") if error == OK: var api_key = save_file.get_value("credentials", "api_key") print("Load api key finished") return api_key else: print("Error: Loading api key failed") return ""
call-gpt-godot/SaveManager.gd at main · bioerrorlog/call-gpt-godot · GitHub
ConfigFileを使って、API Keyをローカルに保存、およびロードする機能を用意しておきます。
ちなみに保存ディレクトリを指定するときに使っているuser://
が指し示している場所については、別途記事にまとめたのでこちらを参照ください。
`user://`はどこを指しているのか | Godot Engine - BioErrorLog Tech Blog
あとはこれを画面上のユーザー入力/ボタンと接続して、API Keyをゲーム画面から保存できるようにします。
# 一部抜粋 extends Node2D onready var save_manager = preload("res://src/SaveManager.gd").new() func _on_SaveButton_pressed(): save_manager.save_api(save_input.text)
call-gpt-godot/Main.gd at main · bioerrorlog/call-gpt-godot · GitHub
OpenAI APIを呼び出す
OpenAI APIを呼び出す部分の実装は、HTTPRequestを使って実装しています。
extends HTTPRequest signal response_received(response) var openai_url = "https://api.openai.com/v1/chat/completions" func _init(): connect("request_completed", self, "_on_request_completed") func call_api(api_key: String, message: String, max_tokens: int = 100): var headers = [ "Authorization: Bearer " + api_key, "Content-Type: application/json", ] var postData = { "model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": message}], "max_tokens": max_tokens } var error = self.request(openai_url, headers, false, HTTPClient.METHOD_POST, JSON.print(postData)) if error != OK: print("HTTP request failed with error: ", error) func _on_request_completed(result, response_code, headers, body): var response = JSON.parse(body.get_string_from_utf8()) if "choices" in response.result and response.result["choices"].size() > 0: emit_signal("response_received", response.result.choices[0].message.content.split("\n")) else: emit_signal("response_received", [])
call-gpt-godot/OpenAIApi.gd at main · bioerrorlog/call-gpt-godot · GitHub
基本的にOpenAI APIの仕様に従ってAPI callを行っているだけです。
この実装では、ユーザーの入力をuser roleとしてChatCompletionのAPIにPOSTしています。 よりプロンプトを工夫する場合は、この部分のプロンプト設計を練っていきます。
APIからの返答を処理した後にemit_signal
することで、外部からこのcall_api
を呼び出して結果を受け取れるようにしています。
返答を画面に反映させる
あとは、APIの返答を受け取って画面に反映させます。
ここまでに挙げたセーブ機能SaveManager.gd
やOpenAI API call機能$OpenAIApi
を呼び出すMainスクリプトがこちらです。
extends Node2D var dialogues = [] var dialogue_index = 0 onready var dialogue_label = $CanvasLayer/Dialogue onready var user_input = $CanvasLayer/UserInput onready var save_input = $CanvasLayer/SaveInput onready var openai_api = $OpenAIApi onready var save_manager = preload("res://src/SaveManager.gd").new() func _ready(): openai_api.connect("response_received", self, "_on_response_received") display_dialogue() func display_dialogue(): if dialogue_index < dialogues.size(): dialogue_label.text = dialogues[dialogue_index] else: print("End of the dialogue") func talk(user_text: String): var api_key = save_manager.load_api() openai_api.call_api(api_key, user_text) func _on_TalkButton_pressed(): talk(user_input.text) func _on_SaveButton_pressed(): save_manager.save_api(save_input.text) func _on_response_received(response: Array): dialogues = response dialogue_index = 0 display_dialogue()
call-gpt-godot/Main.gd at main · bioerrorlog/call-gpt-godot · GitHub
_on_response_received
でAPIからの返答を受け取り、ダイアログに反映させます。
ダイアログに文字列を表示するあたりの実装は割と適当に(ChatGPTとペアプロしながら)サクッと書いただけなので、よりちゃんとしたゲームにする場合はちゃんとした設計が必要になるでしょう。
おわりに
以上、Godot EngineからChatGPTを呼び出す最小限のやり方をメモしました。
もともとはLangChainとかを使いたかったので、Godot EngineからPythonコードを呼び出すやり方を実験していました:
GitHub - bioerrorlog/langchain-godot: Call LangChain from the Godot Engine project
ただこの場合、ExportにPython仮想環境を丸っと含めて...とか色々と面倒なステップが挟まるので、可能ならシンプルにGDScriptからHTTP callしたほうが良さそうだな、と思うに至ります。
既存ライブラリのパワーをフル活用したい、とかでなければ、GDScriptで頑張るやり方でも何かしら面白い試みができるんじゃなかろうか、と思っています。
[関連記事]
参考
GitHub - bioerrorlog/call-gpt-godot: Call OpenAI API from Godot Engine
HTTPRequest — Godot Engine (stable) documentation in English
ConfigFile — Godot Engine (stable) documentation in English
`user://`はどこを指しているのか | Godot Engine - BioErrorLog Tech Blog
GitHub - bioerrorlog/langchain-godot: Call LangChain from the Godot Engine project