BioErrorLog Tech Blog

試行錯誤の記録

ChatGPTをGodot Engineから呼び出す

ChatGPT / OpenAI APIをGodot Engineのゲームから呼び出す実装の備忘録です。

はじめに

ゲーム分野は、昨今のLLM/生成AIの発展に大きな恩恵を受ける分野の一つだと思います。 ChatGPTを利用してどんなゲームができるだろうか...と、考えるだけでも楽しいですね。

今回は、Godot EngineからChatGPT / OpenAI APIを呼び出すシンプルな実装をやってみます。

# 作業環境
Godot Engine v3.5.2.stable.official.170ba337a

ChatGPTをGodot Engineから呼び出す

最終的なプロジェクトのコードはこちらに配置しています。

github.com

概要

ゲーム画面

構成はシンプルです。

  1. プレイヤーはOpenAI API Keyを入力する
    -> API Keyがローカルに保存される
  2. プレイヤーはメッセージを入力して"Talk"ボタンを押下
    -> OpenAI APIがcallされる
  3. 返答が画面に反映される

順を追ってポイントを説明していきます。

詳細はソースコードを参照ください。

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で頑張るやり方でも何かしら面白い試みができるんじゃなかろうか、と思っています。

[関連記事]

www.bioerrorlog.work

www.bioerrorlog.work

www.bioerrorlog.work

参考

GitHub - bioerrorlog/call-gpt-godot: Call OpenAI API from Godot Engine

HTTPRequest — Godot Engine (stable) documentation in English

OpenAI API

ConfigFile — Godot Engine (stable) documentation in English

`user://`はどこを指しているのか | Godot Engine - BioErrorLog Tech Blog

OpenAI API

GitHub - bioerrorlog/langchain-godot: Call LangChain from the Godot Engine project