General Agent によるテストプレイ
- 環境の構築
- 作業フォルダの作成
- データの事前準備
- マップ画像の用意
- マップスキャンデータの配置
- ゲームバイナリの配置
- 設定ファイルの編集
- ゲーム情報の設定
- Behavior Tree Maker の起動
- ゲーム内情報の設定
- ゲームの基本操作の設定
- 座標変換係数の設定
- オブジェクト情報の設定
- コードの変更
- chat_bot/chat_llm.py の編集
- chat_bot/chat_llm_data.py の編集
- 動作確認
- General Agent の起動
- 移動コマンドの確認
- チャットからのビヘイビアツリー自動生成の確認
環境の構築
Playable! General Agent > 環境設定 を参照して、General Agent 用の Python 仮想環境を構築してください。
作業フォルダの作成
新規ゲームに対応するためには、General Agent のソースコードを適切に書き換える必要があります。 そのための準備として、以下のことを行ってください。
ダウンロードした Playable! ライブラリの中にある playable-general-agent
フォルダを丸ごとコピーし、 playable-general-agent-{新規ゲーム名}
にリネームします。
なお、{新規ゲーム名}
の部分は template
としてください。
データの事前準備
マップ画像の用意
Third Person テンプレートにはマップ画像に相当するものが存在しないため、エディタ上でスクリーンショットを撮ったものをマップ画像として使用します。
- エディタ画面左上の設定で、「並行投影」を「上」、「表示モード」を「ライティングなし」を選択し、エリア全体が画面に収まるように拡大/縮小してからスクリーンショットを撮ってください。図のように x 軸の正方向が右を向くようにしてください。
- 用意した画像のファイル名を
ThirdPersonMap.png
としてください。
マップスキャンデータの配置
これ以降の工程は Map Scanner による地形のスキャンが完了した後でなければ行うことはできません。
Python/data/
フォルダ内にtpt_work_space
フォルダを作成します。- マップスキャンで得られたスキャンデータ類を
Python/data/tpt_work_space
以下に配置します。これらは{Map Scanner のディレクトリ}/Python/map_data
にあります。 最終的に以下のようなファイル構成になっていることを確認してください。 以下に記載されていないファイルが含まれている分には問題ありません。Python
└── data
└── tpt_work_space
├── map_data
│ ├── _map_list.yaml
│ └── ThirdPersonMap.png
└── map_graph
└── ThirdPersonMap
└── graph_data.bin
ゲームバイナリの配置
ゲームプロジェクトの作成と自動テスト対応 > パッケージ化 で作成済みのゲームバイナリを含む WindowsNoEditor
(Unreal Engine 4) 又は Windows
(Unreal Engine 5) フォルダ全体を Python
以下に配置してください。
設定ファイルの編集
Python/settings.yaml
ファイルの game_path
、game_window_title
、workspace_folder
、base_workspace_folder
の各項目の値を環境に合わせて編集します。
# ゲーム情報
game:
# 実際のローカル環境に合わせて変えてください *必須
game_path: ./Windows/MyUEProject52.exe
# 対象とするゲームウィンドウのタイトル(現状固定) *必須
game_window_title: "MyUEProject52 (64-bit Development PCD3D_SM6) "
# オンラインマニュアル(メニュー > ヘルプ > マニュアル)のリンク先
# ※未設定の場合はヘルプメニューが非表示になります
# online_manual: https://mycompany.co.jp/general_agent_manual/
# work_space *必須
base_workspace_folder: ./data/tpt_work_space_base
workspace_folder: ./data/tpt_work_space
# 以下省略
ゲーム情報の設定
Behavior Tree Maker を利用してゲーム固有の「ゲーム情報」を設定します。
ゲーム情報には以下のものが含まれます。
- ゲーム内情報
- ゲームの基本動作
- 座標変換係数
- オブジェクト情報
Behavior Tree Maker の起動
- ゲーム情報の設定には Behavior Tree Maker を使用します。
- Behavior Tree Maker は General Agent 内のメニューから起動します。
- General Agent (Behavior Tree Maker) はゲーム情報設定時のみ「新規ゲーム適用モード」で起動する必要があります。
- 新規ゲーム適用時のオプション (--add-new-game|-ang) とデバッグオプション (--debug|-d) を追加して General Agent を起動します。
$ python general_agent.py -ang -d
- メニューから Behavior Tree Maker を起動します。
- 【メニューバー > ツール > Behavior Tree Maker を起動】
ゲーム内情報の設定
- 通信中のゲームから「ゲーム内情報」を取得します。
- タブを【ビヘイビアツリー作成に必要なデータ / ゲーム内情報】に切り替えて、【現在通信中のゲーム内情報を取得】ボタンをクリックします。
- GamePad や PerformanceInfo に関連する情報の参照を解除し、必要なゲーム内情報のみを残します。
- 左側の【ゲーム内情報】カラムから、
GamePad_
およびPerformanceInfo_
で始まるアイテムを選択し、右側のカラムで【参照】チェックボックスをはずすと、当該のアイテムが中央の【参照しないパラメータ】カラムに移動します。
- 左側の【ゲーム内情報】カラムから、
- LLM を利用して「ゲーム内情報の説明」を生成します。
- 【LLM を利用して説明を記述】ボタンをクリックして、【仕様書からゲーム内情報を生成】ウィンドウを表示します。
- 【LLM に解釈させてゲーム内情報を生成】ボタンをクリックします。
- Third Person テンプレートの場合、情報変数名が十分に適合しているため、【ゲーム内情報の仕様】カラムは空で構いません。
ゲームの基本操作の設定
- LLM を利用して「ゲームの基本操作」を生成します。
- 【ゲームの基本操作】タブに切り替え、【LLM を利用して仕様書から生成】ボタンをクリックして、【ゲームの基本操作を生成】ウィンドウを表示します。
- 左側の【ゲーム操作の仕様】カラムに、以下の内容をペーストします。
左アナログスティック上: プレイヤー 前の移動
左アナログスティック下: プレイヤー 後の移動
左アナログスティック左: プレイヤー 左の移動
左アナログスティック右: プレイヤー 右の移動
右アナログスティック上: カメラ上
右アナログスティック下: カメラ下
右アナログスティック左: カメラ左
右アナログスティック右: カメラ右
A ボタン: ジャンプする
LT + RT + 左スティック押し込み: デバッグカメラの ON/OFF - 【LLM に解釈させて基本操作を生成】ボタンをクリックします。
- 生成された「基本操作」を確認し、登録します。登録した「基本操作」に基づいて、システムは自動的に対応する動作ノードとビヘイビアーツリーを作成します。
- 左側の【未登録の操作】カラムから、任意のアイテムを選択し、右側のカラムで【登録】チェックボックスを ON にすると、当該のアイテムが中央の【登録済みの操作】カラムに移動します。
- 作成されたビヘイビアーツリーを確認します。
- 【ビヘイビアーツリー】タブに切り替え、左側の【ノード一覧】カラムから、任意のアイテムを選択し、右側のカラムで【Start】ボタンをクリックします。
- ビヘイビアツリーが正常に動作していることを確認します。
Third Person テンプレートはデフォルトの EnterGame を使用しています。ゲームによっては、メニュー画面等からテストプレイを実行可能な画面に遷移するまでの操作を EnterGame に実装する必要があります。
座標変換係数の設定
プレイヤーキャラクターのゲーム上の位置とマップ上の位置を合わせます。
- 以下の手順で、プレイヤーキャラクターのゲーム上の位置とマップ上の位置の対応付けを 2 カ所で実施します。
- マップウィンドウ (Game Map Window) をダブルクリックしてを整列させます。
- プレイヤーキャラクターをマップ上の任意の位置に移動させ、 【プレイヤー位置設定】ボタンをクリックし、マップ上の対応する位置をクリックします。(※1 ヶ所目)
- 別の位置で、前述のステップをもう一度実施します。(※2 ヶ所目)
- 任意の位置に移動させて、ゲーム上の位置とマップ上の位置が一致していることを確認します。
オブジェクト情報の設定
ツールを終了して、
Python/data/tpt_work_space/object/
のobject_reference.yaml
とobject_sentences.yaml
にオブジェクト情報を追加します。 内容は以下の通り:object_reference.yamlmap:
- object_id: ThirdPersonMap
map_id: ThirdPersonMap
pos: [900, 1110, 92]
place:
- object_id: PL_CUSTOM_1
pos: [1304, 2115, 292]
rot: [0, 0, 0]
map_id: ThirdPersonMapobject_sentences.yamlmap:
ThirdPersonMap:
- デフォルトマップ
place:
PL_CUSTOM_1:
- 場所1tpt_work_space
フォルダをコピーしてtpt_work_space_base
フォルダを作成します。
コードの変更
本項では主に、Third Person テンプレートでは不要なオブジェクト及びコマンドに関するコードを削除します。
尚、Third Person テンプレートは、オブジェクト及びコマンドについては Alfort のサブセットとなるため、以下のコードを編集せずそのままでも動作します。 他のゲームに適用する際の参考にしてください。
chat_bot/chat_llm.py の編集
ChatLLM は、LLM を利用してチャットでのユーザー入力を解析する機能を提供するクラスです。
ChatLLM.load_data()
ChatLLM クラスのインスタンス内で必要となるデータを準備する関数です。
以下では、Third Person テンプレートでは不要なオブジェクトにするコードを削除します。
# 目的語: エイリアスを含む
map_list = self.target_aliases.get("map", [])
place_list = self.target_aliases.get("place", [])
# Alfort固有部分 以下3行削除
# enemy_list = self.target_aliases.get("enemy", [])
# item_list = self.target_aliases.get("item", [])
# person_list = self.target_aliases.get("person", [])
self.map_list = map_list
self.place_list = place_list
# Alfort固有部分 以下3行削除
# self.enemy_list = enemy_list
# self.item_list = item_list
# self.person_list = person_list
# prompt: get_tasks_oneshot
self.prompt_get_tasks = ChatLLMData.prompt_get_tasks.format(
command_list=command_list,
command_ja_list=command_ja_list + command_ja_list2,
position_list=position_list,
repeat_list=repeat_list,
map_place_list=map_list + place_list,
# Alfort固有部分 以下3行削除
# enemy_list=enemy_list,
# item_list=item_list,
# person_list=person_list,
time_unit_list=time_unit_list,
num_unit_list=num_unit_list,
)
# functions: get_tasks_oneshot_fc
self.functions_get_tasks_fc = ChatLLMData.functions_get_tasks_fc
self.functions_get_tasks_fc[0]["parameters"]["properties"]["sentences"]["items"]["properties"]["command"]["enum"] = command_list
self.functions_get_tasks_fc[0]["parameters"]["properties"]["sentences"]["items"]["properties"]["場所以外の位置関係"]["enum"] = position_list
self.functions_get_tasks_fc[0]["parameters"]["properties"]["sentences"]["items"]["properties"]["数量以外の繰り返し"]["enum"] = repeat_list
self.functions_get_tasks_fc[0]["parameters"]["properties"]["sentences"]["items"]["properties"]["場所"]["enum"] = map_list + place_list
# Alfort固有部分 以下3行削除
# self.functions_get_tasks_fc[0]["parameters"]["properties"]["sentences"]["items"]["properties"]["敵"]["enum"] = enemy_list
# self.functions_get_tasks_fc[0]["parameters"]["properties"]["sentences"]["items"]["properties"]["もの"]["enum"] = item_list
# self.functions_get_tasks_fc[0]["parameters"]["properties"]["sentences"]["items"]["properties"]["ひと"]["enum"] = person_list
self.functions_get_tasks_fc[0]["parameters"]["properties"]["sentences"]["items"]["properties"]["時間単位"]["enum"] = time_unit_list
self.functions_get_tasks_fc[0]["parameters"]["properties"]["sentences"]["items"]["properties"]["数量単位"]["enum"] = num_unit_list
ChatLLM.to_task()
LLM からの応答を ChatLLM クラス内形式である function_call 形式から、システム内形式である task 形式に変換する関数です。
以下では、Third Person テンプレートでは不要なオブジェクトにするコードを削除します。
elif command in self.command_reference and self.command_reference[command]["need_target"][0] == "Yes":
# 稀にtargetが返らない場合がある問題の対応
# Combat、Collectはtargetを明示されなくても動作可能
# Alfort固有部分 以下8行削除
# if command == "Combat":
# task["target"] = ("敵", "")
# elif command == "Collect":
# task["target"] = ("アイテム", "")
# elif command == "Talk":
# task["target"] = ("人", "")
# elif command == "Read":
# task["target"] = ("看板", "")
pass
if "position" in function_call["arguments"] or "repeat" in function_call["arguments"]:
position = function_call["arguments"].get("position", "")
repeat = function_call["arguments"].get("repeat", "")
word_dict = self.word_dict
sub_target = []
for k, v in word_dict.items():
if position in v and task["command"] not in ("MoveTo"): # 念のため
sub_target.append(k)
break
# Alfort固有部分 以下1行変更
# if repeat in v and task["num"] <= 0 and task["command"] not in ("MoveTo", "Talk"): # 念のため
if repeat in v and task["num"] <= 0 and task["command"] not in ("MoveTo"): # 念のため
sub_target.append(k)
break
ChatLLM.convert_get_tasks()
ChatLLM クラス内形式である function_call 形式のデータを変換する関数です。 get_tasks_oneshot() 又は get_tasks_phase() 関数とセットで使用します。
以下では、Third Person テンプレートでは不要なオブジェクトにするコードを削除します。
# キー変換
parts = []
for param in function_args["sentences"]:
part = {}
part["command"] = param.get("コマンド", "")
if part["command"] in (None, ""):
part["command"] = ""
# ~中略~
part["place"] = param.get("場所", "")
if part["place"] in (None, ""):
part["place"] = ""
# Alfort固有部分 以下3行削除
# part["enemy"] = param.get("敵", "")
# if part["enemy"] in (None, ""):
# part["enemy"] = ""
# Alfort固有部分 以下3行削除
# part["item"] = param.get("もの", "")
# if part["item"] in (None, ""):
# part["item"] = ""
# Alfort固有部分 以下3行削除
# part["person"] = param.get("ひと", "")
# if part["person"] in (None, ""):
# part["person"] = ""
part["num"] = param.get("数量", "")
if part["num"] in (None, ""):
part["num"] = 0
# タスク追加分割
parts2 = []
for part in parts:
part2 = part.copy()
flag = False
# 目的語とコマンドが不一致の場合にタスクを追加で分割する
if "place" in part and part["place"]:
if "command" in part and self.command_reference[part["command"]]["kind_of_target"]:
if not "MoveTo" in self.convert_table and part["command"] != "MoveTo":
part3 = part.copy()
part3["command"] = "MoveTo"
part3["target"] = part["place"]
part3["num"] = 0
part3["time"] = -1
parts2.append(part3)
elif "MoveTo" in self.convert_table and part["command"] != self.convert_table["MoveTo"]:
part3 = part.copy()
part3["command"] = self.convert_table["MoveTo"]
part3["target"] = part["place"]
part3["num"] = 0
part3["time"] = -1
parts2.append(part3)
else:
part2["target"] = part["place"]
parts2.append(part2)
flag = True
# Alfort固有部分 以下3行削除
# if "enemy" in part and part["enemy"]:
# if "command" in part and self.command_reference[part["command"]]["kind_of_target"]:
# part2["target"] = part["enemy"]
# Alfort固有部分 以下3行削除
# if "item" in part and part["item"]:
# if "command" in part and self.command_reference[part["command"]]["kind_of_target"]:
# part2["target"] = part["item"]
# Alfort固有部分 以下3行削除
# if "person" in part and part["person"]:
# if "command" in part and self.command_reference[part["command"]]["kind_of_target"]:
# part2["target"] = part["person"]
if not flag:
parts2.append(part2)
# NewFunction判定
function_calls3 = []
function_name = function_calls2[0]["name"]
function_args = function_calls2[0]["arguments"]
if function_name == "split_sentence" and "sentences" in function_args:
for func in function_args["sentences"]:
# logger.info(func)
func_name = func["command"]
new_func_name = ""
new_func_desc = ""
if func_name == "" or type(func_name) is not str:
continue
elif func_name in self.revert_table.keys():
func_name = self.revert_table[func_name]
# elif func_name not in self.command_reference.keys():
elif func_name == "NewFunction":
func_name = "NewFunction"
new_func_name = func["command"]
if new_func_name in self.func_params:
new_func_desc = self.func_params[new_func_name].get("task_description", self.func_params[new_func_name]["description"])
else:
if "new_command_name" in func:
new_func_name = func["new_command_name"]
if "sentence" in func:
new_func_desc = func["sentence"]
if new_func_desc:
is_success2, content2, error2 = self.remove_num_time(new_func_desc)
if is_success2:
new_func_desc = content2
func2 = {
"name": func_name,
"arguments": {}
}
if func_name in self.command_reference:
# if self.command_reference[func_name]["need_target"][0] == "Yes":
if True:
if "position" in func:
if "num" not in func or func["num"] <= 0: # 念のため
if func_name not in ("MoveTo"): # 念のため
func2["arguments"]["position"] = func["position"]
# if self.command_reference[func_name]["need_target"][0] == "Yes":
if True:
if "repeat" in func:
if "num" not in func or func["num"] <= 0: # 念のため
# Alfort固有部分 以下1行変更
# if func_name not in ("MoveTo", "Talk"): # 念のため
if func_name not in ("MoveTo"): # 念のため
func2["arguments"]["repeat"] = func["repeat"]
ChatLLM.convert_get_tasks_fc()
ChatLLM クラス内形式である function_call 形式のデータを変換する関数です。 get_tasks_oneshot_fc() 関数とセットで使用します。
以下では、Third Person テンプレートでは不要なオブジェクトにするコードを削除します。
part["place"] = param.get("場所", "")
if part["place"] in (None, ""):
part["place"] = ""
# Alfort固有部分 以下3行削除
# part["enemy"] = param.get("敵", "")
# if part["enemy"] in (None, ""):
# part["enemy"] = ""
# Alfort固有部分 以下3行削除
# part["item"] = param.get("もの", "")
# if part["item"] in (None, ""):
# part["item"] = ""
# Alfort固有部分 以下3行削除
# part["person"] = param.get("ひと", "")
# if part["person"] in (None, ""):
# part["person"] = ""
part["has_num"] = param.get("数量指定あり", "")
if part["has_num"] in (None, ""):
part["has_num"] = False
# タスク追加分割
parts2 = []
for part in parts:
part2 = part.copy()
flag = False
# 目的語とコマンドが不一致の場合にタスクを追加で分割する
if "place" in part and part["place"]:
if "command" in part and part["command"] != self.convert_table["MoveTo"]:
part3 = part.copy()
part3["command"] = self.convert_table["MoveTo"]
part3["target"] = part["place"]
part3["num"] = 0
part3["time"] = -1
parts2.append(part3)
else:
part2["target"] = part["place"]
parts2.append(part2)
flag = True
# Alfort固有部分 以下10行削除
# if "enemy" in part and part["enemy"]:
# if "command" in part and part["command"] != self.convert_table["Combat"]:
# part3 = part.copy()
# part3["command"] = self.convert_table["Combat"]
# part3["target"] = part["enemy"]
# part3["num"] = 0
# part3["time"] = -1
# parts2.append(part3)
# else:
# part2["target"] = part["enemy"]
# Alfort固有部分 以下10行削除
# if "item" in part and part["item"]:
# if "command" in part and part["command"] != self.convert_table["Collect"]:
# part3 = part.copy()
# part3["command"] = self.convert_table["Collect"]
# part3["target"] = part["item"]
# part3["num"] = 0
# part3["time"] = -1
# parts2.append(part3)
# else:
# part2["target"] = part["item"]
# Alfort固有部分 以下10行削除
# if "person" in part and part["person"]:
# if "command" in part and part["command"] != self.convert_table["Talk"]:
# part3 = part.copy()
# part3["command"] = self.convert_table["Talk"]
# part3["target"] = part["person"]
# part3["num"] = 0
# part3["time"] = -1
# parts2.append(part3)
# else:
# part2["target"] = part["person"]
if not flag:
parts2.append(part2)
ChatLLM.get_tasks_phase.run_sequence()
get_tasks_phase() は、LLM を利用して、ユーザー入力テキストから、システム内形式である task 形式データを取得する関数です。フェーズ方式を使用します。 run_sequence() は、get_tasks_phase() 関数内のローカル関数で、LLM との対話処理シーケンスを遂行する関数です。
以下では、LLM に送信するプロンプトにオブジェクトのリストを埋め込む処理から、Third Person テンプレートでは不要なオブジェクトにするコードを削除します。
# LLM処理シーケンス関数
def run_sequence(input_text: str, prompt1: str, prompt2: str, functions1: List[dict] = None, functions2: List[dict] = None) -> Tuple[bool, List[dict], str]:
"""LLM処理シーケンス(get_tasks_phase)"""
start = time.time()
function_calls3 = []
is_success, function_calls, error = self.run_sequence_get_tasks(input_text, prompt1, functions1)
if is_success:
func3 = {}
func3["name"] = "split_sentence"
func3["arguments"] = {}
sentences = []
for sentence in function_calls[0]["arguments"]["sentences"]:
command = sentence["コマンド"]
f_place_list = []
# Alfort固有部分 以下3行削除
# f_enemy_list = []
# f_item_list = []
# f_person_list = []
if command in self.command_reference and self.command_reference[command]["need_target"][0] == "Yes":
for target in self.command_reference[command]["kind_of_target"]:
if target == "map":
f_place_list += self.map_list
elif target == "place":
f_place_list += self.place_list
# Alfort固有部分 以下3行削除
# elif target == "enemy":
# f_enemy_list += self.enemy_list
# elif target == "item":
# f_item_list += self.item_list
# elif target == "person":
# f_person_list += self.person_list
for target in self.command_reference[command]["sub_target"]:
if target == "map":
f_place_list += self.map_list
elif target == "place":
f_place_list += self.place_list
# Alfort固有部分 以下6行削除
# elif target == "enemy":
# f_enemy_list += self.enemy_list
# elif target == "item":
# f_item_list += self.item_list
# elif target == "person":
# f_person_list += self.person_list
prompt3 = prompt2.format(
command_list=[command],
command_ja_list=self.command_ja_dict[command]+self.command_ja_dict2[command],
position_list=self.position_list,
repeat_list=self.repeat_list,
map_place_list=f_place_list,
# Alfort固有部分 以下3行削除
# enemy_list=f_enemy_list,
# item_list=f_item_list,
# person_list=f_person_list,
time_unit_list=self.time_unit_list,
num_unit_list=self.num_unit_list,
)
input_text2 = sentence["テキスト"]
is_success2, function_calls2, error2 = self.run_sequence_get_tasks(input_text2, prompt3)
if is_success2:
for func2 in function_calls2:
if "arguments" in func2:
if "sentences" in func2["arguments"]:
sentences.extend(func2["arguments"]["sentences"])
else:
sentences.append(func2["arguments"])
else:
is_success = is_success2
error = error2
break
func3["arguments"]["sentences"] = sentences
function_calls3.append(func3)
print(f"##### function_calls3 {json.dumps(function_calls3, indent=2, ensure_ascii=False)}")
elapsed = time.time() - start
print(f"##### elapsed {elapsed:.3f} sec")
return is_success, function_calls3, error
chat_bot/chat_llm_data.py の編集
ChatLLMData は、ChatLLM クラスで利用する静的データを定義するクラスです。
ChatLLMData.func_params
サンプルコマンドに関するデータを定義する辞書です。
以下では、Third Person テンプレートでは不要なコマンドに関するコードを削除します。
# サンプルコマンド
func_params = {
# Alfort固有部分 以下3行削除
# "AttackWhenEnemyApproaches": {"description": "敵が近づいたら攻撃"},
# "EvadeWhenEnemyApproaches": {"description": "敵が近づいたら回避"},
# "CameraWork": {"description": "カメラワーク", "task_description": "無条件でWaitなしでCameraControl"},
}
ChatLLMData.convert_table
システム内コマンド名と LLM に送信するコマンド名との対応関係を定義する辞書です。
以下では、Third Person テンプレートでは不要なコマンドに関するコードを削除します。
# コマンド変換テーブル
# コマンド名がLLMの解釈のバイアスになるケースがあるため
# LLM用にコマンド名を変換するためのテーブル
# ただし現状は実質変換対象はない
convert_table = {
# "MoveTo": "MoveToPlace",
"MoveTo": "MoveTo",
# Alfort固有部分 以下1行削除
# "Combat": "Combat",
# "Collect": "Collect",
# "Talk": "Talk",
}
ChatLLMData.prompt_get_tasks
ChatLLM.get_tasks_oneshot() で利用する LLM 送信用プロンプトです。
Third Person テンプレート用には、以下の内容で置き換えてください。
# Alfort固有部分
# get_tasks_oneshot: prompt40: oneshot, phase2
prompt_get_tasks = """\
あなたはユーザーのゲームプレイのコマンドを受け付けるアシスタントです。
入力テキストを解析してJSON形式の配列にしてください。
以下各JSONキーと使用可能な単語のリストです。
----------
"コマンド": {command_list}
"動詞": {command_ja_list}
"位置関係": {position_list}
"繰り返し": {repeat_list}
"場所": {map_place_list}
"時間単位": {time_unit_list}
"数量単位": {num_unit_list}
以下の例を参考にしてください。
----------
入力テキスト例: 場所nに移動して
レスポンス例:
[
{{
"テキスト": "場所nに移動して",
"場所": "場所n",
"動詞": "移動する",
"コマンド": "MoveTo"
}}
]
----------
入力テキスト例: 場所nでジャンプして
レスポンス例:
[
{{
"テキスト": "場所nでジャンプして",
"場所": "場所n",
"動詞": "ジャンプする",
"コマンド": "Jump"
}}
]
----------
入力テキスト例: 延々とジャンプして
レスポンス例:
[
{{
"テキスト": "延々とジャンプして",
"繰り返し": "延々と",
"動詞": "ジャンプする",
"コマンド": "Jump"
}}
]
----------
入力テキスト例: 場所nに移動してジャンプして
レスポンス例:
[
{{
"テキスト": "場所nに移動して",
"場所": "場所n",
"動詞": "移動する",
"コマンド": "MoveTo"
}},
{{
"テキスト": "ジャンプして",
"動詞": "ジャンプする",
"コマンド": "Jump"
}}
]
----------
入力テキスト例: 乗馬して
レスポンス例:
[
{{
"テキスト": "乗馬して",
"新規動詞": "乗馬する",
"新規コマンド": "RideHorse",
"動詞": "新規コマンドを作成する",
"コマンド": "NewFunction"
}}
]
----------
入力テキスト例: 場所nに2回移動して
レスポンス例:
[
{{
"テキスト": "場所nに2回移動して",
"数量": 2,
"数量単位": "回",
"動詞": "移動する",
"コマンド": "MoveTo"
}}
]
----------
入力テキスト例: 場所nに3分4回移動して
レスポンス例:
[
{{
"テキスト": "場所nに3分4回移動して",
"場所": "場所n",
"時間": 3,
"時間単位": "分",
"数量": 4,
"数量単位": "回",
"動詞": "移動する",
"コマンド": "MoveTo"
}}
]
----------
入力テキスト例: 終了
レスポンス例:
[
{{
"テキスト": "終了",
"動詞": "終了する",
"コマンド": "End"
}}
]
----------
入力テキスト例: 中止
レスポンス例:
[
{{
"テキスト": "中止",
"動詞": "終了する",
"コマンド": "End"
}}
]
----------
入力テキスト例: 中断
レスポンス例:
[
{{
"テキスト": "中断",
"動詞": "停止する",
"コマンド": "Stop"
}}
]
----------
入力テキスト例: 停止
レスポンス例:
[
{{
"テキスト": "停止",
"動詞": "停止する",
"コマンド": "Stop"
}}
]
----------
入力テキスト例: 全て終了
レスポンス例:
[
{{
"テキスト": "全て終了",
"動詞": "全て終了する",
"コマンド": "AllEnd"
}}
]
----------
入力テキスト例: クリア
レスポンス例:
[
{{
"テキスト": "クリア",
"動詞": "全て終了する",
"コマンド": "AllEnd"
}}
]
なお、通常の会話・挨拶には会話・挨拶で応答してください。
その際、応答内容にあなたへの指示内容を含めないでください。
"""
ChatLLMData.prompt_get_tasks_phase1
ChatLLM.get_tasks_phase() で利用する LLM 送信用プロンプトです。
Third Person テンプレート用には、以下の内容で置き換えてください。
# Alfort固有部分
# get_tasks_phase: prompt24: phase1
prompt_get_tasks_phase1 = """\
あなたはユーザーのゲームプレイのコマンドを受け付けるアシスタントです。
入力テキストを解析してJSON形式の配列にしてください。
以下各JSONキーと使用可能な単語のリストです。
----------
"コマンド": {command_list}
"動詞": {command_ja_list}
以下の例を参考にしてください。
----------
入力テキスト例: 場所nに移動して
レスポンス例:
[
{{
"テキスト": "場所nに移動して",
"動詞": "移動する",
"コマンド": "MoveTo"
}}
]
----------
入力テキスト例: 延々とジャンプして
レスポンス例:
[
{{
"テキスト": "延々とジャンプして",
"動詞": "ジャンプする",
"コマンド": "Jump"
}}
]
----------
入力テキスト例: 場所nでジャンプして
レスポンス例:
[
{{
"テキスト": "場所nでジャンプして",
"動詞": "ジャンプする",
"コマンド": "Jump"
}}
]
----------
入力テキスト例: 場所nに移動してジャンプして
レスポンス例:
[
{{
"テキスト": "場所nに移動して",
"動詞": "移動する",
"コマンド": "MoveTo"
}},
{{
"テキスト": "ジャンプして",
"動詞": "ジャンプする",
"コマンド": "Jump"
}}
]
----------
入力テキスト例: 乗馬して
レスポンス例:
[
{{
"テキスト": "乗馬して",
"新規動詞": "乗馬する",
"新規コマンド": "RideHorse",
"動詞": "新規コマンドを作成する",
"コマンド": "NewFunction"
}}
]
----------
入力テキスト例: 場所nに2回移動して
レスポンス例:
[
{{
"テキスト": "場所nに2回移動して",
"動詞": "移動する",
"コマンド": "MoveTo"
}}
]
----------
入力テキスト例: 場所nに3分4回移動して
レスポンス例:
[
{{
"テキスト": "場所nに3分4回移動して",
"動詞": "移動する",
"コマンド": "MoveTo"
}}
]
----------
入力テキスト例: 終了
レスポンス例:
[
{{
"テキスト": "終了",
"動詞": "終了する",
"コマンド": "End"
}}
]
----------
入力テキスト例: 中止
レスポンス例:
[
{{
"テキスト": "中止",
"動詞": "終了する",
"コマンド": "End"
}}
]
----------
入力テキスト例: 中断
レスポンス例:
[
{{
"テキスト": "中断",
"動詞": "停止する",
"コマンド": "Stop"
}}
]
----------
入力テキスト例: 停止
レスポンス例:
[
{{
"テキスト": "停止",
"動詞": "停止する",
"コマンド": "Stop"
}}
]
----------
入力テキスト例: 全て終了
レスポンス例:
[
{{
"テキスト": "全て終了",
"動詞": "全て終了する",
"コマンド": "AllEnd"
}}
]
----------
入力テキスト例: クリア
レスポンス例:
[
{{
"テキスト": "クリア",
"動詞": "全て終了する",
"コマンド": "AllEnd"
}}
]
なお、通常の会話には会話で応答してください。
その際、応答内容にあなたへの指示内容を含めないでください。
"""
ChatLLMData.prompt_get_tasks_fc
ChatLLM.get_tasks_oneshot_fc() で利用する LLM 送信用プロンプトです。
Third Person テンプレート用には、以下の内容で置き換えてください。
# Alfort固有部分
# get_tasks_oneshot_fc: prompt37: oneshot: with functions
prompt_get_tasks_fc = """\
会話には会話で返してください
入力テキストを {{command}} を軸に複数の sentence に分割して JSON オブジェクトの配列にしてください
複数の sentence に分割する場合は function は 1 個にして、arguments の sentences を複数要素にしてください
----------
例:
"sentences": [
{{(...)}},
{{(...)}}
]
----------
JSON オブジェクトのキーは {{command}},{{new_command_name}},{{場所以外の位置関係指定あり)}},{{数量以外の繰り返し指定あり}},{{場所以外の位置関係)}},{{数量以外の繰り返し}},{{場所}},{{時間指定あり}},{{時間}},{{時間単位}},{{数量指定あり}},{{数量}},{{数量単位}},{{sentence_as_is}} としてください
{{command}} として使用可能な単語:{command_list}
ただし {{command}} のうち "AllEnd","Stop","End","Continue" については以下の例にしたがってください
* 全て終了: AllEnd
* すべて終了: AllEnd
* クリア: AllEnd
* 停止: Stop
* 中断: Stop
* 終了: End
* 中止: End
また {{command}} のうち "Jump" については以下の例にしたがってください
* ジャンプ: Jump
{{場所以外の位置関係}},{{数量以外の繰り返し}} は特に明示がなければ返さないでください
"前の","後の","上の","下の" は {{場所以外の位置関係}} を表す言葉です
"その","あの","この" は {{場所以外の位置関係}} を表す言葉です
"""
ChatLLMData.functions_get_tasks_fc
ChatLLM.get_tasks_oneshot_fc() で利用する LLM 送信用 function 定義です。
Third Person テンプレート用には、以下の内容で置き換えてください。
# Alfort固有部分
# get_tasks_oneshot_fc: functions37: oneshot: with functions
functions_get_tasks_fc = [{
"name": "split_sentence",
"parameters": {
"type": "object",
"properties": {
"sentences": {
"type": "array",
"items": {
"type": "object",
"properties": {
"command": {
# "description": "command in english",
"type": "string",
# "enum": command_list,
"enum": [],
},
# "command_ja": {
# "description": "command in japanese",
# "type": "string",
# "enum": command_ja_list,
# },
"new_command_name": {
"description": "new command name for NewFunction in english",
"type": "string",
},
"場所以外の位置関係指定あり": {
# "description": "pronoun that means direction or relative position of things specified",
"type": "boolean",
},
"数量指定以外の繰り返し指定あり": {
# "description": "pronoun that means direction or relative position of things specified",
"type": "boolean",
},
"場所以外の位置関係": {
# "description": "場所以外の位置関係",
"type": "string",
# "enum": position_list,
"enum": [],
},
"数量以外の繰り返し": {
# "description": "数量指定以外の繰り返し",
"type": "string",
# "enum": repeat_list,
"enum": [],
},
"場所": {
"description": "place to move or combat or collect or talk",
"type": "string",
# "enum": map_list + place_list,
"enum": [],
},
# "敵": {
# "description": "enemy to combat",
# "type": "string",
# # "enum": enemy_list,
# "enum": [],
# },
# "もの": {
# "description": "item to collect (only noun)",
# "type": "string",
# # "enum": item_list,
# "enum": [],
# },
# "ひと": {
# "description": "person to talk",
# "type": "string",
# # "enum": person_list,
# "enum": [],
# },
"時間指定あり": {
"description": "time limit specified",
"type": "boolean",
},
"時間": {
"description": "time limit",
"type": "integer",
},
"時間単位": {
"description": "unit of time limit",
"type": "string",
# "enum": time_unit_list,
"enum": [],
},
"数量指定あり": {
"description": "number of enemy or item specified",
"type": "boolean",
},
"数量": {
"description": "number of enemy or item",
"type": "integer",
},
"数量単位": {
"description": "unit of number of enemy or item",
"type": "string",
# "enum": num_unit_list,
"enum": [],
},
"sentence_as_is": {
"description": "whole sentence as is before split",
"type": "string",
},
},
"required": ["command", "時間単位", "数量単位", "sentence_as_is"],
},
},
},
"required": ["sentences"],
},
}]
ChatLLMData.prompt_remove_num_time
ChatLLM.remove_num_time() で利用する LLM 送信用プロンプトです。
Third Person テンプレート用には、以下の内容で置き換えてください。
# Alfort固有部分
# remove_num_time: prompt20: without functions
prompt_remove_num_time = """\
テキストから時間・数量指定を削除してください
レスポンスはJSON形式にしてください
以下の例を参考にしてください。
----------
入力テキスト例: 場所nに2回移動して
レスポンス例:
{
"削除結果": "場所nに移動して"
}
----------
入力テキスト例: 場所nに3分4回移動して
レスポンス例:
{
"削除結果": "場所nに移動して"
}
----------
入力テキスト例: 場所nに移動して
レスポンス例:
{
"削除結果": "場所nに移動して"
}
"""
動作確認
ここまでの設定・変更が正しく行われていることをツールを動作させて確認します。
General Agent の起動
- 通常の起動オプションで ("--add-new-game" を付けずに) General Agent を起動します。
$ python general_agent.py [-d]
移動コマンドの確認
- チャットで「場所 1 に移動して」を送信します。
- プレイヤーキャラクターが「場所 1」に移動することを確認します。
チャットからのビヘイビアツリー自動生成の確認
前準備として「Jump」コマンドをビヘイビアツリーから削除します。(※この手順は動画には出てきません)
- 前述の「ゲームの基本操作の設定」で「Jump」コマンドは生成済みですが、チャットからのビヘイビアツリー自動生成を実施するために、ビヘイビアツリーから「Jump」コマンドを削除しておきます。
- メニューから Behaivor Tree Maker を起動します。
- 【メニューバー > ツール > Behavior Tree Maker を起動】を実行します。
- ビヘイビアツリーから「Jump」コマンドを削除します。
- 【ビヘイビアツリー】タブに切り替えます。
- 【ノード一覧:】カラムから【Jump】を選択し【選択したノードを削除】ボタンをクリックします。
- 編集内容を保存します。
- 【メニューバー > ファイル > 保存】を実行し、編集内容を保存します。
- チャットウィンドウに戻ります。
- 【メニューバー > ファイル > Behavior Tree Maker を終了】を実行します。
チャットで「三回ジャンプして」を送信します。
チャットに以下のメッセージが表示されることを確認します。
不足しているコマンドがあるため実行できません
※存在しないコマンドが必要です
すべてのタスクを終了してBehavior Tree Makerを起動し、新たにコマンドを作成しますか?
コマンドの仕様は以下の通りです
・作成するノード名: Jump
・実現したい動作: ジャンプして 1秒待って終了
「はい」か「いいえ」で答えてくださいチャットで「はい」を送信します。
自動的に Behavior Tree Maker が起動され、自動生成が開始します。
自動生成が終了したら、Behavior Tree Maker を終了し、チャットウィンドウに戻ります。
チャットで「三回ジャンプして」を送信します。
キャラクタープレイヤーがジャンプ動作を 3 回繰り返すことを確認します。