split up entry points and engine components, group all actions
This commit is contained in:
parent
60fb4c94cf
commit
2f26d4e883
|
@ -2,6 +2,7 @@ from logging import getLogger
|
||||||
|
|
||||||
from taleweave.context import (
|
from taleweave.context import (
|
||||||
action_context,
|
action_context,
|
||||||
|
add_extra_actions,
|
||||||
broadcast,
|
broadcast,
|
||||||
get_agent_for_character,
|
get_agent_for_character,
|
||||||
get_character_agent_for_name,
|
get_character_agent_for_name,
|
||||||
|
@ -10,6 +11,7 @@ from taleweave.context import (
|
||||||
world_context,
|
world_context,
|
||||||
)
|
)
|
||||||
from taleweave.errors import ActionError
|
from taleweave.errors import ActionError
|
||||||
|
from taleweave.systems.action import ACTION_SYSTEM_NAME
|
||||||
from taleweave.utils.conversation import loop_conversation
|
from taleweave.utils.conversation import loop_conversation
|
||||||
from taleweave.utils.search import (
|
from taleweave.utils.search import (
|
||||||
find_character_in_room,
|
find_character_in_room,
|
||||||
|
@ -337,3 +339,18 @@ def action_drop(item: str) -> str:
|
||||||
action_room.items.append(action_item)
|
action_room.items.append(action_item)
|
||||||
|
|
||||||
return format_prompt("action_drop_result", item=item)
|
return format_prompt("action_drop_result", item=item)
|
||||||
|
|
||||||
|
|
||||||
|
def init():
|
||||||
|
return add_extra_actions(
|
||||||
|
ACTION_SYSTEM_NAME,
|
||||||
|
[
|
||||||
|
action_examine,
|
||||||
|
action_move,
|
||||||
|
action_take,
|
||||||
|
action_tell,
|
||||||
|
action_ask,
|
||||||
|
action_give,
|
||||||
|
action_drop,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from packit.agent import Agent, agent_easy_connect
|
||||||
|
|
||||||
from taleweave.context import (
|
from taleweave.context import (
|
||||||
action_context,
|
action_context,
|
||||||
|
add_extra_actions,
|
||||||
broadcast,
|
broadcast,
|
||||||
get_agent_for_character,
|
get_agent_for_character,
|
||||||
get_current_turn,
|
get_current_turn,
|
||||||
|
@ -21,6 +22,7 @@ from taleweave.generate import (
|
||||||
generate_room,
|
generate_room,
|
||||||
link_rooms,
|
link_rooms,
|
||||||
)
|
)
|
||||||
|
from taleweave.systems.action import ACTION_SYSTEM_NAME
|
||||||
from taleweave.utils.effect import apply_effects, is_effect_ready
|
from taleweave.utils.effect import apply_effects, is_effect_ready
|
||||||
from taleweave.utils.search import find_character_in_room
|
from taleweave.utils.search import find_character_in_room
|
||||||
from taleweave.utils.string import normalize_name
|
from taleweave.utils.string import normalize_name
|
||||||
|
@ -256,8 +258,11 @@ def init_optional() -> List[Callable]:
|
||||||
"""
|
"""
|
||||||
Initialize the custom actions.
|
Initialize the custom actions.
|
||||||
"""
|
"""
|
||||||
return [
|
return add_extra_actions(
|
||||||
|
ACTION_SYSTEM_NAME,
|
||||||
|
[
|
||||||
action_explore,
|
action_explore,
|
||||||
action_search,
|
action_search,
|
||||||
action_use,
|
action_use,
|
||||||
]
|
],
|
||||||
|
)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from taleweave.context import (
|
from taleweave.context import (
|
||||||
action_context,
|
action_context,
|
||||||
|
add_extra_actions,
|
||||||
get_agent_for_character,
|
get_agent_for_character,
|
||||||
get_current_turn,
|
get_current_turn,
|
||||||
get_game_config,
|
get_game_config,
|
||||||
|
@ -7,6 +8,7 @@ from taleweave.context import (
|
||||||
)
|
)
|
||||||
from taleweave.errors import ActionError
|
from taleweave.errors import ActionError
|
||||||
from taleweave.models.planning import CalendarEvent
|
from taleweave.models.planning import CalendarEvent
|
||||||
|
from taleweave.systems.planning import PLANNING_SYSTEM_NAME
|
||||||
from taleweave.utils.planning import get_recent_notes
|
from taleweave.utils.planning import get_recent_notes
|
||||||
from taleweave.utils.template import format_prompt
|
from taleweave.utils.template import format_prompt
|
||||||
|
|
||||||
|
@ -194,3 +196,18 @@ def check_calendar(count: int):
|
||||||
for event in events
|
for event in events
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def init():
|
||||||
|
return add_extra_actions(
|
||||||
|
PLANNING_SYSTEM_NAME,
|
||||||
|
[
|
||||||
|
take_note,
|
||||||
|
read_notes,
|
||||||
|
erase_notes,
|
||||||
|
edit_note,
|
||||||
|
summarize_notes,
|
||||||
|
schedule_event,
|
||||||
|
check_calendar,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
|
@ -34,10 +34,10 @@ current_character: Character | None = None
|
||||||
dungeon_master: Agent | None = None
|
dungeon_master: Agent | None = None
|
||||||
|
|
||||||
# game context
|
# game context
|
||||||
# TODO: wrap this into a class that can be passed around
|
# TODO: wrap these into a class that can be passed around
|
||||||
|
action_groups: Dict[str, List[Callable[..., str]]] = {}
|
||||||
character_agents: Dict[str, Tuple[Character, Agent]] = {}
|
character_agents: Dict[str, Tuple[Character, Agent]] = {}
|
||||||
event_emitter = EventEmitter()
|
event_emitter = EventEmitter()
|
||||||
extra_actions: List[Callable[..., str]] = []
|
|
||||||
game_config: Config = DEFAULT_CONFIG
|
game_config: Config = DEFAULT_CONFIG
|
||||||
game_systems: List[GameSystem] = []
|
game_systems: List[GameSystem] = []
|
||||||
prompt_library: PromptLibrary = PromptLibrary(prompts={})
|
prompt_library: PromptLibrary = PromptLibrary(prompts={})
|
||||||
|
@ -186,8 +186,8 @@ def get_system_data(system: str) -> Any | None:
|
||||||
return system_data.get(system)
|
return system_data.get(system)
|
||||||
|
|
||||||
|
|
||||||
def get_extra_actions() -> List[Callable[..., str]]:
|
def get_action_group(name: str) -> List[Callable[..., str]]:
|
||||||
return extra_actions
|
return action_groups.get(name, [])
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
@ -242,9 +242,9 @@ def set_system_data(system: str, data: Any):
|
||||||
system_data[system] = data
|
system_data[system] = data
|
||||||
|
|
||||||
|
|
||||||
def set_extra_actions(actions: List[Callable[..., str]]):
|
def add_extra_actions(group: str, actions: List[Callable[..., str]]):
|
||||||
global extra_actions
|
action_groups.setdefault(group, []).extend(actions)
|
||||||
extra_actions = actions
|
return group, actions
|
||||||
|
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import argparse
|
import argparse
|
||||||
from os import path
|
from os import environ, path
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from packit.utils import logger_with_colors
|
||||||
|
|
||||||
from taleweave.context import get_dungeon_master, get_game_systems, set_game_systems
|
from taleweave.context import get_dungeon_master, get_game_systems, set_game_systems
|
||||||
|
from taleweave.engine import load_or_initialize_system_data
|
||||||
from taleweave.game_system import GameSystem
|
from taleweave.game_system import GameSystem
|
||||||
from taleweave.generate import (
|
from taleweave.generate import (
|
||||||
generate_character,
|
generate_character,
|
||||||
|
@ -11,7 +15,7 @@ from taleweave.generate import (
|
||||||
generate_room,
|
generate_room,
|
||||||
link_rooms,
|
link_rooms,
|
||||||
)
|
)
|
||||||
from taleweave.main import load_or_initialize_system_data
|
from taleweave.main import load_prompt_library
|
||||||
from taleweave.models.base import dump_model
|
from taleweave.models.base import dump_model
|
||||||
from taleweave.models.entity import World, WorldState
|
from taleweave.models.entity import World, WorldState
|
||||||
from taleweave.plugins import load_plugin
|
from taleweave.plugins import load_plugin
|
||||||
|
@ -30,9 +34,16 @@ from taleweave.utils.world import describe_entity
|
||||||
|
|
||||||
ENTITY_TYPES = ["room", "portal", "item", "character"]
|
ENTITY_TYPES = ["room", "portal", "item", "character"]
|
||||||
|
|
||||||
|
logger = logger_with_colors(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# load environment variables before anything else
|
||||||
|
load_dotenv(environ.get("TALEWEAVE_ENV", ".env"), override=True)
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
parser = argparse.ArgumentParser(description="Taleweave Editor")
|
parser = argparse.ArgumentParser(description="Taleweave Editor")
|
||||||
|
parser.add_argument("--prompts", type=str, nargs="*", help="Prompt files to load")
|
||||||
parser.add_argument("--state", type=str, help="State file to edit")
|
parser.add_argument("--state", type=str, help="State file to edit")
|
||||||
parser.add_argument("--world", type=str, help="World file to edit")
|
parser.add_argument("--world", type=str, help="World file to edit")
|
||||||
parser.add_argument("--systems", type=str, nargs="*", help="Game systems to load")
|
parser.add_argument("--systems", type=str, nargs="*", help="Game systems to load")
|
||||||
|
@ -41,11 +52,9 @@ def parse_args():
|
||||||
subparsers.required = True
|
subparsers.required = True
|
||||||
|
|
||||||
# Set up the 'list' command
|
# Set up the 'list' command
|
||||||
list_parser = subparsers.add_parser(
|
list_parser = subparsers.add_parser("list", help="List entities of a specific type")
|
||||||
"list", help="List all entities or entities of a specific type"
|
|
||||||
)
|
|
||||||
list_parser.add_argument(
|
list_parser.add_argument(
|
||||||
"type", help="Type of entity to list", choices=ENTITY_TYPES, nargs="?"
|
"type", help="Type of entity to list", choices=ENTITY_TYPES
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set up the 'describe' command
|
# Set up the 'describe' command
|
||||||
|
@ -107,7 +116,7 @@ def parse_args():
|
||||||
|
|
||||||
|
|
||||||
def load_world(state_file, world_file) -> Tuple[World, WorldState | None]:
|
def load_world(state_file, world_file) -> Tuple[World, WorldState | None]:
|
||||||
systems = []
|
systems = get_game_systems()
|
||||||
|
|
||||||
if state_file and path.exists(state_file):
|
if state_file and path.exists(state_file):
|
||||||
with open(state_file, "r") as f:
|
with open(state_file, "r") as f:
|
||||||
|
@ -129,14 +138,19 @@ def load_world(state_file, world_file) -> Tuple[World, WorldState | None]:
|
||||||
|
|
||||||
|
|
||||||
def save_world(state_file, world_file, world: World, state: WorldState | None):
|
def save_world(state_file, world_file, world: World, state: WorldState | None):
|
||||||
|
"""
|
||||||
|
Save the world to the given files.
|
||||||
|
|
||||||
|
This is intentionally a noop stub until the editor is more stable.
|
||||||
|
"""
|
||||||
if state:
|
if state:
|
||||||
print(f"Saving world {world.name} to {state_file}")
|
logger.warning(f"Saving world {world.name} to {state_file}")
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(state_file, "w") as f:
|
with open(state_file, "w") as f:
|
||||||
save_yaml(f, dump_model(WorldState, state))
|
save_yaml(f, dump_model(WorldState, state))
|
||||||
else:
|
else:
|
||||||
print(f"Saving world {world.name} to {world_file}")
|
logger.warning(f"Saving world {world.name} to {world_file}")
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(world_file, "w") as f:
|
with open(world_file, "w") as f:
|
||||||
|
@ -144,47 +158,45 @@ def save_world(state_file, world_file, world: World, state: WorldState | None):
|
||||||
|
|
||||||
|
|
||||||
def command_list(args):
|
def command_list(args):
|
||||||
print(f"Listing {args.type}s")
|
|
||||||
world, _ = load_world(args.state, args.world)
|
world, _ = load_world(args.state, args.world)
|
||||||
print(world.name)
|
logger.info(f"Listing {args.type}s from world {world.name}")
|
||||||
|
|
||||||
if args.type == "room":
|
if args.type == "room":
|
||||||
for room in list_rooms(world):
|
for room in list_rooms(world):
|
||||||
print(room.name)
|
logger.info(room.name)
|
||||||
|
|
||||||
if args.type == "portal":
|
if args.type == "portal":
|
||||||
for portal in list_portals(world):
|
for portal in list_portals(world):
|
||||||
print(portal.name)
|
logger.info(portal.name)
|
||||||
|
|
||||||
if args.type == "item":
|
if args.type == "item":
|
||||||
for item in list_items(
|
for item in list_items(
|
||||||
world, include_character_inventory=True, include_item_inventory=True
|
world, include_character_inventory=True, include_item_inventory=True
|
||||||
):
|
):
|
||||||
print(item.name)
|
logger.info(item.name)
|
||||||
|
|
||||||
if args.type == "character":
|
if args.type == "character":
|
||||||
for character in list_characters(world):
|
for character in list_characters(world):
|
||||||
print(character.name)
|
logger.info(character.name)
|
||||||
|
|
||||||
|
|
||||||
def command_describe(args):
|
def command_describe(args):
|
||||||
print(f"Describing {args.entity}")
|
|
||||||
world, _ = load_world(args.state, args.world)
|
world, _ = load_world(args.state, args.world)
|
||||||
print(world.name)
|
logger.info(f"Describing {args.entity} from world {world.name}")
|
||||||
|
|
||||||
if args.type == "room":
|
if args.type == "room":
|
||||||
room = find_room(world, args.entity)
|
room = find_room(world, args.entity)
|
||||||
if not room:
|
if not room:
|
||||||
print(f"Room {args.entity} not found")
|
logger.error(f"Room {args.entity} not found")
|
||||||
else:
|
else:
|
||||||
print(describe_entity(room))
|
logger.info(describe_entity(room))
|
||||||
|
|
||||||
if args.type == "portal":
|
if args.type == "portal":
|
||||||
portal = find_portal(world, args.entity)
|
portal = find_portal(world, args.entity)
|
||||||
if not portal:
|
if not portal:
|
||||||
print(f"Portal {args.entity} not found")
|
logger.error(f"Portal {args.entity} not found")
|
||||||
else:
|
else:
|
||||||
print(describe_entity(portal))
|
logger.info(describe_entity(portal))
|
||||||
|
|
||||||
if args.type == "item":
|
if args.type == "item":
|
||||||
item = find_item(
|
item = find_item(
|
||||||
|
@ -194,22 +206,21 @@ def command_describe(args):
|
||||||
include_item_inventory=True,
|
include_item_inventory=True,
|
||||||
)
|
)
|
||||||
if not item:
|
if not item:
|
||||||
print(f"Item {args.entity} not found")
|
logger.error(f"Item {args.entity} not found")
|
||||||
else:
|
else:
|
||||||
print(describe_entity(item))
|
logger.info(describe_entity(item))
|
||||||
|
|
||||||
if args.type == "character":
|
if args.type == "character":
|
||||||
character = find_character(world, args.entity)
|
character = find_character(world, args.entity)
|
||||||
if not character:
|
if not character:
|
||||||
print(f"Character {args.entity} not found")
|
logger.error(f"Character {args.entity} not found")
|
||||||
else:
|
else:
|
||||||
print(describe_entity(character))
|
logger.info(describe_entity(character))
|
||||||
|
|
||||||
|
|
||||||
def command_create(args):
|
def command_create(args):
|
||||||
print(f"Create {args.type} named {args.name}")
|
|
||||||
world, state = load_world(args.state, args.world)
|
world, state = load_world(args.state, args.world)
|
||||||
print(world.name)
|
logger.info(f"Create {args.type} named {args.name} in world {world.name}")
|
||||||
|
|
||||||
# TODO: Create the entity
|
# TODO: Create the entity
|
||||||
|
|
||||||
|
@ -217,9 +228,10 @@ def command_create(args):
|
||||||
|
|
||||||
|
|
||||||
def command_generate(args):
|
def command_generate(args):
|
||||||
print(f"Generate {args.type} with prompt: {args.prompt}")
|
|
||||||
world, state = load_world(args.state, args.world)
|
world, state = load_world(args.state, args.world)
|
||||||
print(world.name)
|
logger.info(
|
||||||
|
f"Generating {args.type} for world {world.name} using prompt: {args.prompt}"
|
||||||
|
)
|
||||||
|
|
||||||
dungeon_master = get_dungeon_master()
|
dungeon_master = get_dungeon_master()
|
||||||
systems = get_game_systems()
|
systems = get_game_systems()
|
||||||
|
@ -231,12 +243,12 @@ def command_generate(args):
|
||||||
if args.type == "portal":
|
if args.type == "portal":
|
||||||
source_room = find_room(world, args.room)
|
source_room = find_room(world, args.room)
|
||||||
if not source_room:
|
if not source_room:
|
||||||
print(f"Room {args.room} not found")
|
logger.error(f"Room {args.room} not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
destination_room = find_room(world, args.dest_room)
|
destination_room = find_room(world, args.dest_room)
|
||||||
if not destination_room:
|
if not destination_room:
|
||||||
print(f"Room {args.dest_room} not found")
|
logger.error(f"Room {args.dest_room} not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
outgoing_portal, incoming_portal = generate_portals(
|
outgoing_portal, incoming_portal = generate_portals(
|
||||||
|
@ -249,7 +261,7 @@ def command_generate(args):
|
||||||
# TODO: add item to character or container inventory
|
# TODO: add item to character or container inventory
|
||||||
room = find_room(world, args.room)
|
room = find_room(world, args.room)
|
||||||
if not room:
|
if not room:
|
||||||
print(f"Room {args.room} not found")
|
logger.error(f"Room {args.room} not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
item = generate_item(dungeon_master, world, systems)
|
item = generate_item(dungeon_master, world, systems)
|
||||||
|
@ -258,7 +270,7 @@ def command_generate(args):
|
||||||
if args.type == "character":
|
if args.type == "character":
|
||||||
room = find_room(world, args.room)
|
room = find_room(world, args.room)
|
||||||
if not room:
|
if not room:
|
||||||
print(f"Room {args.room} not found")
|
logger.error(f"Room {args.room} not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
character = generate_character(
|
character = generate_character(
|
||||||
|
@ -270,9 +282,8 @@ def command_generate(args):
|
||||||
|
|
||||||
|
|
||||||
def command_delete(args):
|
def command_delete(args):
|
||||||
print(f"Delete {args.entity}")
|
|
||||||
world, state = load_world(args.state, args.world)
|
world, state = load_world(args.state, args.world)
|
||||||
print(world.name)
|
logger.info(f"Delete {args.entity} from world {world.name}")
|
||||||
|
|
||||||
# TODO: Delete the entity
|
# TODO: Delete the entity
|
||||||
|
|
||||||
|
@ -280,23 +291,22 @@ def command_delete(args):
|
||||||
|
|
||||||
|
|
||||||
def command_update(args):
|
def command_update(args):
|
||||||
print(f"Update {args.entity}")
|
|
||||||
world, state = load_world(args.state, args.world)
|
world, state = load_world(args.state, args.world)
|
||||||
print(world.name)
|
logger.info(f"Update {args.entity} in world {world.name}")
|
||||||
|
|
||||||
if args.type == "room":
|
if args.type == "room":
|
||||||
room = find_room(world, args.entity)
|
room = find_room(world, args.entity)
|
||||||
if not room:
|
if not room:
|
||||||
print(f"Room {args.entity} not found")
|
logger.error(f"Room {args.entity} not found")
|
||||||
else:
|
else:
|
||||||
print(describe_entity(room))
|
logger.info(describe_entity(room))
|
||||||
|
|
||||||
if args.type == "portal":
|
if args.type == "portal":
|
||||||
portal = find_portal(world, args.entity)
|
portal = find_portal(world, args.entity)
|
||||||
if not portal:
|
if not portal:
|
||||||
print(f"Portal {args.entity} not found")
|
logger.error(f"Portal {args.entity} not found")
|
||||||
else:
|
else:
|
||||||
print(describe_entity(portal))
|
logger.info(describe_entity(portal))
|
||||||
|
|
||||||
if args.type == "item":
|
if args.type == "item":
|
||||||
item = find_item(
|
item = find_item(
|
||||||
|
@ -306,14 +316,14 @@ def command_update(args):
|
||||||
include_item_inventory=True,
|
include_item_inventory=True,
|
||||||
)
|
)
|
||||||
if not item:
|
if not item:
|
||||||
print(f"Item {args.entity} not found")
|
logger.error(f"Item {args.entity} not found")
|
||||||
else:
|
else:
|
||||||
print(describe_entity(item))
|
logger.info(describe_entity(item))
|
||||||
|
|
||||||
if args.type == "character":
|
if args.type == "character":
|
||||||
character = find_character(world, args.entity)
|
character = find_character(world, args.entity)
|
||||||
if not character:
|
if not character:
|
||||||
print(f"Character {args.entity} not found")
|
logger.error(f"Character {args.entity} not found")
|
||||||
else:
|
else:
|
||||||
if args.backstory:
|
if args.backstory:
|
||||||
character.backstory = args.backstory
|
character.backstory = args.backstory
|
||||||
|
@ -321,15 +331,14 @@ def command_update(args):
|
||||||
if args.description:
|
if args.description:
|
||||||
character.description = args.description
|
character.description = args.description
|
||||||
|
|
||||||
print(describe_entity(character))
|
logger.info(describe_entity(character))
|
||||||
|
|
||||||
save_world(args.state, args.world, world, state)
|
save_world(args.state, args.world, world, state)
|
||||||
|
|
||||||
|
|
||||||
def command_link(args):
|
def command_link(args):
|
||||||
print(f"Linking rooms {args.rooms}")
|
|
||||||
world, state = load_world(args.state, args.world)
|
world, state = load_world(args.state, args.world)
|
||||||
print(world.name)
|
logger.info(f"Linking rooms {args.rooms} in world {world.name}")
|
||||||
|
|
||||||
dungeon_master = get_dungeon_master()
|
dungeon_master = get_dungeon_master()
|
||||||
systems = get_game_systems()
|
systems = get_game_systems()
|
||||||
|
@ -352,14 +361,16 @@ COMMAND_TABLE = {
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
print(args)
|
logger.debug(f"running with args: {args}")
|
||||||
|
|
||||||
|
load_prompt_library(args)
|
||||||
|
|
||||||
# load game systems before executing commands
|
# load game systems before executing commands
|
||||||
systems: List[GameSystem] = []
|
systems: List[GameSystem] = []
|
||||||
for system_name in args.systems or []:
|
for system_name in args.systems or []:
|
||||||
print(f"loading extra systems from {system_name}")
|
logger.info(f"loading extra systems from {system_name}")
|
||||||
module_systems = load_plugin(system_name)
|
module_systems = load_plugin(system_name)
|
||||||
print(f"loaded extra systems: {module_systems}")
|
logger.info(f"loaded extra systems: {module_systems}")
|
||||||
systems.extend(module_systems)
|
systems.extend(module_systems)
|
||||||
|
|
||||||
set_game_systems(systems)
|
set_game_systems(systems)
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
from functools import partial
|
||||||
|
from itertools import count
|
||||||
|
from logging import getLogger
|
||||||
|
from os import path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from packit.agent import Agent, agent_easy_connect
|
||||||
|
from packit.memory import make_limited_memory
|
||||||
|
|
||||||
|
from taleweave.context import (
|
||||||
|
get_current_turn,
|
||||||
|
get_system_data,
|
||||||
|
set_current_turn,
|
||||||
|
set_dungeon_master,
|
||||||
|
set_system_data,
|
||||||
|
)
|
||||||
|
from taleweave.game_system import GameSystem
|
||||||
|
from taleweave.generate import generate_room, generate_world, link_rooms
|
||||||
|
from taleweave.models.config import Config
|
||||||
|
from taleweave.models.entity import World, WorldState
|
||||||
|
from taleweave.models.files import WorldPrompt
|
||||||
|
from taleweave.state import create_agents, save_world
|
||||||
|
from taleweave.utils.file import load_yaml
|
||||||
|
from taleweave.utils.template import format_prompt
|
||||||
|
|
||||||
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def load_or_initialize_system_data(
|
||||||
|
world_path: str, systems: List[GameSystem], world: World
|
||||||
|
):
|
||||||
|
for system in systems:
|
||||||
|
if system.data:
|
||||||
|
system_data_file = f"{world_path}.{system.name}.json"
|
||||||
|
|
||||||
|
if path.exists(system_data_file):
|
||||||
|
logger.info(f"loading system data from {system_data_file}")
|
||||||
|
data = system.data.load(system_data_file)
|
||||||
|
set_system_data(system.name, data)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logger.info(f"no system data found at {system_data_file}")
|
||||||
|
|
||||||
|
if system.initialize:
|
||||||
|
logger.info(f"initializing system data for {system.name}")
|
||||||
|
data = system.initialize(world)
|
||||||
|
set_system_data(system.name, data)
|
||||||
|
|
||||||
|
|
||||||
|
def save_system_data(world_path: str, systems: List[GameSystem]):
|
||||||
|
for system in systems:
|
||||||
|
if system.data:
|
||||||
|
system_data_file = f"{world_path}.{system.name}.json"
|
||||||
|
logger.info(f"saving system data to {system_data_file}")
|
||||||
|
system.data.save(system_data_file, get_system_data(system.name))
|
||||||
|
|
||||||
|
|
||||||
|
def load_or_generate_world(
|
||||||
|
world_path: str,
|
||||||
|
state_path: str | None,
|
||||||
|
config: Config,
|
||||||
|
players, # TODO: type me
|
||||||
|
systems: List[GameSystem],
|
||||||
|
world_prompt: WorldPrompt,
|
||||||
|
add_rooms: int = 0,
|
||||||
|
room_count: int | None = None,
|
||||||
|
):
|
||||||
|
world_file = world_path + ".json"
|
||||||
|
world_state_file = state_path or (world_path + ".state.json")
|
||||||
|
|
||||||
|
memory = {}
|
||||||
|
turn = 0
|
||||||
|
|
||||||
|
# prepare an agent for the world builder
|
||||||
|
llm = agent_easy_connect()
|
||||||
|
memory_factory = partial(
|
||||||
|
make_limited_memory, limit=config.world.character.memory_limit
|
||||||
|
)
|
||||||
|
world_builder = Agent(
|
||||||
|
"World Builder",
|
||||||
|
format_prompt(
|
||||||
|
"world_generate_dungeon_master",
|
||||||
|
flavor=world_prompt.flavor,
|
||||||
|
theme=world_prompt.theme,
|
||||||
|
),
|
||||||
|
{},
|
||||||
|
llm,
|
||||||
|
memory_factory=memory_factory,
|
||||||
|
)
|
||||||
|
set_dungeon_master(world_builder)
|
||||||
|
|
||||||
|
if path.exists(world_state_file):
|
||||||
|
logger.info(f"loading world state from {world_state_file}")
|
||||||
|
with open(world_state_file, "r") as f:
|
||||||
|
state = WorldState(**load_yaml(f))
|
||||||
|
|
||||||
|
set_current_turn(state.turn)
|
||||||
|
load_or_initialize_system_data(world_path, systems, state.world)
|
||||||
|
|
||||||
|
memory = state.memory
|
||||||
|
turn = state.turn
|
||||||
|
world = state.world
|
||||||
|
elif path.exists(world_file):
|
||||||
|
logger.info(f"loading world from {world_file}")
|
||||||
|
with open(world_file, "r") as f:
|
||||||
|
world = World(**load_yaml(f))
|
||||||
|
|
||||||
|
load_or_initialize_system_data(world_path, systems, world)
|
||||||
|
else:
|
||||||
|
logger.info(f"generating a new world using theme: {world_prompt.theme}")
|
||||||
|
world = generate_world(
|
||||||
|
world_builder,
|
||||||
|
world_path,
|
||||||
|
world_prompt.theme,
|
||||||
|
systems,
|
||||||
|
room_count=room_count,
|
||||||
|
)
|
||||||
|
load_or_initialize_system_data(world_path, systems, world)
|
||||||
|
|
||||||
|
# TODO: check if there have been any changes before saving
|
||||||
|
save_world(world, world_file)
|
||||||
|
save_system_data(world_path, systems)
|
||||||
|
|
||||||
|
if add_rooms:
|
||||||
|
new_rooms = []
|
||||||
|
for i in range(add_rooms):
|
||||||
|
logger.info(f"generating room {i + 1} of {add_rooms}")
|
||||||
|
room = generate_room(
|
||||||
|
world_builder, world, systems, current_room=i, total_rooms=add_rooms
|
||||||
|
)
|
||||||
|
new_rooms.append(room)
|
||||||
|
world.rooms.append(room)
|
||||||
|
|
||||||
|
# if the world was already full, no new rooms will be added
|
||||||
|
if new_rooms:
|
||||||
|
link_rooms(world_builder, world, systems, new_rooms)
|
||||||
|
|
||||||
|
# create agents for each character after adding any new rooms
|
||||||
|
create_agents(world, memory=memory, players=players)
|
||||||
|
return (world, world_state_file, turn)
|
||||||
|
|
||||||
|
|
||||||
|
def simulate_world(world: World, systems: List[GameSystem], turns: int):
|
||||||
|
# run game systems for each turn
|
||||||
|
logger.info(f"simulating the world for {turns} turns using systems: {systems}")
|
||||||
|
for i in count():
|
||||||
|
current_turn = get_current_turn()
|
||||||
|
logger.info(f"simulating turn {i} of {turns} (world turn {current_turn})")
|
||||||
|
|
||||||
|
for system in systems:
|
||||||
|
if system.simulate:
|
||||||
|
logger.info(f"running system {system.name}")
|
||||||
|
system.simulate(world, current_turn)
|
||||||
|
|
||||||
|
set_current_turn(current_turn + 1)
|
||||||
|
if i >= turns:
|
||||||
|
logger.info("reached turn limit at world turn %s", current_turn + 1)
|
||||||
|
break
|
|
@ -1,73 +1,67 @@
|
||||||
import argparse
|
import argparse
|
||||||
import atexit
|
import atexit
|
||||||
from functools import partial
|
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from itertools import count
|
|
||||||
from logging.config import dictConfig
|
from logging.config import dictConfig
|
||||||
from os import environ, path
|
from os import environ, path
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from packit.agent import Agent, agent_easy_connect
|
|
||||||
from packit.memory import make_limited_memory
|
|
||||||
from packit.utils import logger_with_colors
|
from packit.utils import logger_with_colors
|
||||||
|
|
||||||
# configure logging
|
# this is the ONLY taleweave import allowed before the logger has been created
|
||||||
# this is the only taleweave import allowed before the logger has been created
|
|
||||||
from taleweave.utils.file import load_yaml
|
from taleweave.utils.file import load_yaml
|
||||||
|
|
||||||
LOG_PATH = "logging.json"
|
# load environment variables before anything else
|
||||||
|
load_dotenv(environ.get("TALEWEAVE_ENV", ".env"), override=True)
|
||||||
|
|
||||||
|
# configure logging
|
||||||
|
# TODO: move this to a separate module
|
||||||
|
|
||||||
|
LOG_PATH = environ.get("TALEWEAVE_LOGGING", "logging.json")
|
||||||
try:
|
try:
|
||||||
if path.exists(LOG_PATH):
|
if path.exists(LOG_PATH):
|
||||||
with open(LOG_PATH, "r") as f:
|
with open(LOG_PATH, "r") as f:
|
||||||
config_logging = load_yaml(f)
|
config_logging = load_yaml(f)
|
||||||
dictConfig(config_logging)
|
dictConfig(config_logging)
|
||||||
else:
|
else:
|
||||||
print("logging config not found")
|
print(f"logging config not found at {LOG_PATH}")
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print("error loading logging config: %s" % (err))
|
print(f"error loading logging config: {err}")
|
||||||
|
|
||||||
|
|
||||||
logger = logger_with_colors(__name__) # , level="DEBUG")
|
|
||||||
|
|
||||||
load_dotenv(environ.get("TALEWEAVE_ENV", ".env"), override=True)
|
|
||||||
|
|
||||||
# start the debugger, if needed
|
# start the debugger, if needed
|
||||||
if environ.get("DEBUG", "false").lower() == "true":
|
if environ.get("DEBUG", "false").lower() in ["true", "1", "yes", "t", "y"]:
|
||||||
|
logger = logger_with_colors(__name__, level="DEBUG")
|
||||||
|
|
||||||
import debugpy
|
import debugpy
|
||||||
|
|
||||||
debugpy.listen(5679)
|
debugpy.listen(5679)
|
||||||
logger.info("waiting for debugger to attach...")
|
logger.warning("waiting for debugger to attach...")
|
||||||
debugpy.wait_for_client()
|
debugpy.wait_for_client()
|
||||||
|
else:
|
||||||
|
logger = logger_with_colors(__name__)
|
||||||
|
|
||||||
|
|
||||||
if True:
|
if True:
|
||||||
from taleweave.context import (
|
from taleweave.context import (
|
||||||
get_current_turn,
|
|
||||||
get_prompt_library,
|
get_prompt_library,
|
||||||
get_system_data,
|
|
||||||
set_current_turn,
|
|
||||||
set_current_world,
|
set_current_world,
|
||||||
set_dungeon_master,
|
|
||||||
set_extra_actions,
|
|
||||||
set_game_config,
|
set_game_config,
|
||||||
set_game_systems,
|
set_game_systems,
|
||||||
set_system_data,
|
|
||||||
subscribe,
|
subscribe,
|
||||||
)
|
)
|
||||||
|
from taleweave.engine import load_or_generate_world, simulate_world
|
||||||
from taleweave.game_system import GameSystem
|
from taleweave.game_system import GameSystem
|
||||||
from taleweave.generate import generate_room, generate_world, link_rooms
|
|
||||||
from taleweave.models.config import DEFAULT_CONFIG, Config
|
from taleweave.models.config import DEFAULT_CONFIG, Config
|
||||||
from taleweave.models.entity import World, WorldState
|
from taleweave.models.entity import World
|
||||||
from taleweave.models.event import GenerateEvent
|
from taleweave.models.event import GenerateEvent
|
||||||
from taleweave.models.files import TemplateFile, WorldPrompt
|
from taleweave.models.files import TemplateFile, WorldPrompt
|
||||||
from taleweave.models.prompt import PromptLibrary
|
from taleweave.models.prompt import PromptLibrary
|
||||||
from taleweave.plugins import load_plugin
|
from taleweave.plugins import load_plugin
|
||||||
from taleweave.state import create_agents, save_world, save_world_state
|
from taleweave.state import save_world_state
|
||||||
from taleweave.systems.action import init_action
|
from taleweave.systems.action import init_action
|
||||||
from taleweave.systems.planning import init_planning
|
from taleweave.systems.planning import init_planning
|
||||||
from taleweave.utils.template import format_prompt
|
|
||||||
|
|
||||||
|
|
||||||
def int_or_inf(value: str) -> float | int:
|
def int_or_inf(value: str) -> float | int:
|
||||||
|
@ -82,18 +76,8 @@ def parse_args():
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
description="Generate and simulate a text adventure world"
|
description="Generate and simulate a text adventure world"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--actions",
|
# config arguments
|
||||||
type=str,
|
|
||||||
nargs="*",
|
|
||||||
help="Extra actions to include in the simulation",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--add-rooms",
|
|
||||||
default=0,
|
|
||||||
type=int,
|
|
||||||
help="The number of new rooms to generate before starting the simulation",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config",
|
"--config",
|
||||||
type=str,
|
type=str,
|
||||||
|
@ -104,28 +88,11 @@ def parse_args():
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Whether to run the simulation in a Discord bot",
|
help="Whether to run the simulation in a Discord bot",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--flavor",
|
|
||||||
type=str,
|
|
||||||
default="",
|
|
||||||
help="Some additional flavor text for the generated world",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--optional-actions",
|
|
||||||
action="store_true",
|
|
||||||
help="Whether to include optional actions in the simulation",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--player",
|
"--player",
|
||||||
type=str,
|
type=str,
|
||||||
help="The name of the character to play as",
|
help="The name of the character to play as",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--prompts",
|
|
||||||
type=str,
|
|
||||||
nargs="*",
|
|
||||||
help="The file to load game prompts from",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--render",
|
"--render",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
|
@ -136,26 +103,24 @@ def parse_args():
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Whether to render entities as they are generated",
|
help="Whether to render entities as they are generated",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"--rooms",
|
|
||||||
type=int,
|
|
||||||
help="The number of rooms to generate",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--server",
|
"--server",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Whether to run the websocket server",
|
help="Whether to run the websocket server",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# data and plugin arguments
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--state",
|
"--actions",
|
||||||
type=str,
|
type=str,
|
||||||
help="The file to save the world state to. Defaults to $world.state.json, if not set",
|
nargs="*",
|
||||||
|
help="Extra actions to include in the simulation",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--turns",
|
"--prompts",
|
||||||
type=int_or_inf,
|
type=str,
|
||||||
default=10,
|
nargs="*",
|
||||||
help="The number of simulation turns to run",
|
help="The file to load game prompts from",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--systems",
|
"--systems",
|
||||||
|
@ -163,23 +128,57 @@ def parse_args():
|
||||||
nargs="*",
|
nargs="*",
|
||||||
help="Extra systems to run in the simulation",
|
help="Extra systems to run in the simulation",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# generation arguments
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--theme",
|
"--add-rooms",
|
||||||
type=str,
|
default=0,
|
||||||
default="fantasy",
|
type=int,
|
||||||
help="The theme of the generated world",
|
help="The number of new rooms to generate before starting the simulation",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--rooms",
|
||||||
|
type=int,
|
||||||
|
help="The number of rooms to generate",
|
||||||
|
)
|
||||||
|
|
||||||
|
# simulation arguments
|
||||||
|
parser.add_argument(
|
||||||
|
"--turns",
|
||||||
|
type=int_or_inf,
|
||||||
|
default=10,
|
||||||
|
help="The number of simulation turns to run",
|
||||||
|
)
|
||||||
|
|
||||||
|
# world arguments
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--world",
|
"--world",
|
||||||
type=str,
|
type=str,
|
||||||
default="world",
|
default="world",
|
||||||
help="The file to save the generated world to",
|
help="The file to save the generated world to",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--world-flavor",
|
||||||
|
type=str,
|
||||||
|
default="",
|
||||||
|
help="Some additional flavor text for the generated world",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--world-state",
|
||||||
|
type=str,
|
||||||
|
help="The file to save the world state to. Defaults to $world.state.json, if not set",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--world-template",
|
"--world-template",
|
||||||
type=str,
|
type=str,
|
||||||
help="The template file to load the world prompt from",
|
help="The template file to load the world prompt from",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--world-theme",
|
||||||
|
type=str,
|
||||||
|
default="fantasy",
|
||||||
|
help="The theme of the generated world",
|
||||||
|
)
|
||||||
return parser.parse_args()
|
return parser.parse_args()
|
||||||
|
|
||||||
|
|
||||||
|
@ -196,8 +195,8 @@ def get_world_prompt(args) -> WorldPrompt:
|
||||||
|
|
||||||
return WorldPrompt(
|
return WorldPrompt(
|
||||||
name=args.world,
|
name=args.world,
|
||||||
theme=args.theme,
|
theme=args.world_theme,
|
||||||
flavor=args.flavor,
|
flavor=args.world_flavor,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -217,111 +216,6 @@ def load_prompt_library(args) -> None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def load_or_initialize_system_data(
|
|
||||||
world_path: str, systems: List[GameSystem], world: World
|
|
||||||
):
|
|
||||||
for system in systems:
|
|
||||||
if system.data:
|
|
||||||
system_data_file = f"{world_path}.{system.name}.json"
|
|
||||||
|
|
||||||
if path.exists(system_data_file):
|
|
||||||
logger.info(f"loading system data from {system_data_file}")
|
|
||||||
data = system.data.load(system_data_file)
|
|
||||||
set_system_data(system.name, data)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
logger.info(f"no system data found at {system_data_file}")
|
|
||||||
|
|
||||||
if system.initialize:
|
|
||||||
logger.info(f"initializing system data for {system.name}")
|
|
||||||
data = system.initialize(world)
|
|
||||||
set_system_data(system.name, data)
|
|
||||||
|
|
||||||
|
|
||||||
def save_system_data(args, systems: List[GameSystem]):
|
|
||||||
for system in systems:
|
|
||||||
if system.data:
|
|
||||||
system_data_file = f"{args.world}.{system.name}.json"
|
|
||||||
logger.info(f"saving system data to {system_data_file}")
|
|
||||||
system.data.save(system_data_file, get_system_data(system.name))
|
|
||||||
|
|
||||||
|
|
||||||
def load_or_generate_world(
|
|
||||||
args, config: Config, players, systems: List[GameSystem], world_prompt: WorldPrompt
|
|
||||||
):
|
|
||||||
world_file = args.world + ".json"
|
|
||||||
world_state_file = args.state or (args.world + ".state.json")
|
|
||||||
add_rooms = args.add_rooms
|
|
||||||
|
|
||||||
memory = {}
|
|
||||||
turn = 0
|
|
||||||
|
|
||||||
# prepare an agent for the world builder
|
|
||||||
llm = agent_easy_connect()
|
|
||||||
memory_factory = partial(
|
|
||||||
make_limited_memory, limit=config.world.character.memory_limit
|
|
||||||
)
|
|
||||||
world_builder = Agent(
|
|
||||||
"World Builder",
|
|
||||||
format_prompt(
|
|
||||||
"world_generate_dungeon_master",
|
|
||||||
flavor=world_prompt.flavor,
|
|
||||||
theme=world_prompt.theme,
|
|
||||||
),
|
|
||||||
{},
|
|
||||||
llm,
|
|
||||||
memory_factory=memory_factory,
|
|
||||||
)
|
|
||||||
set_dungeon_master(world_builder)
|
|
||||||
|
|
||||||
if path.exists(world_state_file):
|
|
||||||
logger.info(f"loading world state from {world_state_file}")
|
|
||||||
with open(world_state_file, "r") as f:
|
|
||||||
state = WorldState(**load_yaml(f))
|
|
||||||
|
|
||||||
set_current_turn(state.turn)
|
|
||||||
load_or_initialize_system_data(args.world, systems, state.world)
|
|
||||||
|
|
||||||
memory = state.memory
|
|
||||||
turn = state.turn
|
|
||||||
world = state.world
|
|
||||||
elif path.exists(world_file):
|
|
||||||
logger.info(f"loading world from {world_file}")
|
|
||||||
with open(world_file, "r") as f:
|
|
||||||
world = World(**load_yaml(f))
|
|
||||||
|
|
||||||
load_or_initialize_system_data(args.world, systems, world)
|
|
||||||
else:
|
|
||||||
logger.info(f"generating a new world using theme: {world_prompt.theme}")
|
|
||||||
world = generate_world(
|
|
||||||
world_builder,
|
|
||||||
args.world,
|
|
||||||
world_prompt.theme,
|
|
||||||
systems,
|
|
||||||
room_count=args.rooms,
|
|
||||||
)
|
|
||||||
load_or_initialize_system_data(args.world, systems, world)
|
|
||||||
|
|
||||||
# TODO: check if there have been any changes before saving
|
|
||||||
save_world(world, world_file)
|
|
||||||
save_system_data(args, systems)
|
|
||||||
|
|
||||||
new_rooms = []
|
|
||||||
for i in range(add_rooms):
|
|
||||||
logger.info(f"generating room {i + 1} of {add_rooms}")
|
|
||||||
room = generate_room(
|
|
||||||
world_builder, world, systems, current_room=i, total_rooms=add_rooms
|
|
||||||
)
|
|
||||||
new_rooms.append(room)
|
|
||||||
world.rooms.append(room)
|
|
||||||
|
|
||||||
if new_rooms:
|
|
||||||
link_rooms(world_builder, world, systems, new_rooms)
|
|
||||||
|
|
||||||
create_agents(world, memory=memory, players=players)
|
|
||||||
return (world, world_state_file, turn)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
||||||
|
@ -365,28 +259,13 @@ def main():
|
||||||
|
|
||||||
atexit.register(shutdown_threads)
|
atexit.register(shutdown_threads)
|
||||||
|
|
||||||
# load built-in but optional actions
|
|
||||||
extra_actions = []
|
|
||||||
if args.optional_actions:
|
|
||||||
logger.info("loading optional actions")
|
|
||||||
from taleweave.actions.optional import init_optional
|
|
||||||
|
|
||||||
optional_actions = init_optional()
|
|
||||||
logger.info(
|
|
||||||
f"loaded optional actions: {[action.__name__ for action in optional_actions]}"
|
|
||||||
)
|
|
||||||
extra_actions.extend(optional_actions)
|
|
||||||
|
|
||||||
# load extra actions from plugins
|
# load extra actions from plugins
|
||||||
for action_name in args.actions or []:
|
for action_name in args.actions or []:
|
||||||
logger.info(f"loading extra actions from {action_name}")
|
logger.info(f"loading extra actions from {action_name}")
|
||||||
module_actions = load_plugin(action_name)
|
action_group, module_actions = load_plugin(action_name)
|
||||||
logger.info(
|
logger.info(
|
||||||
f"loaded extra actions: {[action.__name__ for action in module_actions]}"
|
f"loaded extra actions to group '{action_group}': {[action.__name__ for action in module_actions]}"
|
||||||
)
|
)
|
||||||
extra_actions.extend(module_actions)
|
|
||||||
|
|
||||||
set_extra_actions(extra_actions)
|
|
||||||
|
|
||||||
# set up the game systems
|
# set up the game systems
|
||||||
systems: List[GameSystem] = []
|
systems: List[GameSystem] = []
|
||||||
|
@ -411,7 +290,14 @@ def main():
|
||||||
# load or generate the world
|
# load or generate the world
|
||||||
world_prompt = get_world_prompt(args)
|
world_prompt = get_world_prompt(args)
|
||||||
world, world_state_file, world_turn = load_or_generate_world(
|
world, world_state_file, world_turn = load_or_generate_world(
|
||||||
args, config, players, systems, world_prompt=world_prompt
|
args.world,
|
||||||
|
args.world_state,
|
||||||
|
config,
|
||||||
|
players,
|
||||||
|
systems,
|
||||||
|
world_prompt=world_prompt,
|
||||||
|
room_count=args.rooms,
|
||||||
|
add_rooms=args.add_rooms,
|
||||||
)
|
)
|
||||||
set_current_world(world)
|
set_current_world(world)
|
||||||
|
|
||||||
|
@ -426,21 +312,7 @@ def main():
|
||||||
if args.server:
|
if args.server:
|
||||||
server_system(world, world_turn)
|
server_system(world, world_turn)
|
||||||
|
|
||||||
# run game systems for each turn
|
simulate_world(world, systems, args.turns)
|
||||||
logger.info(f"simulating the world for {args.turns} turns using systems: {systems}")
|
|
||||||
for i in count():
|
|
||||||
current_turn = get_current_turn()
|
|
||||||
logger.info(f"simulating turn {i} of {args.turns} (world turn {current_turn})")
|
|
||||||
|
|
||||||
for system in systems:
|
|
||||||
if system.simulate:
|
|
||||||
logger.info(f"running system {system.name}")
|
|
||||||
system.simulate(world, current_turn)
|
|
||||||
|
|
||||||
set_current_turn(current_turn + 1)
|
|
||||||
if i >= args.turns:
|
|
||||||
logger.info("reached turn limit at world turn %s", current_turn + 1)
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -7,6 +7,7 @@ def load_plugin(name: str, override_function: str | None = None):
|
||||||
|
|
||||||
|
|
||||||
def get_plugin_function(name: str, override_function: str | None = None):
|
def get_plugin_function(name: str, override_function: str | None = None):
|
||||||
module_name, function_name = name.rsplit(":", 1)
|
module_name, *rest = name.rsplit(":", 1)
|
||||||
|
function_name = rest[0] if rest else "init"
|
||||||
plugin_module = import_module(module_name)
|
plugin_module = import_module(module_name)
|
||||||
return getattr(plugin_module, override_function or function_name)
|
return getattr(plugin_module, override_function or function_name)
|
||||||
|
|
|
@ -7,20 +7,12 @@ from packit.loops import loop_retry
|
||||||
from packit.results import function_result
|
from packit.results import function_result
|
||||||
from packit.toolbox import Toolbox
|
from packit.toolbox import Toolbox
|
||||||
|
|
||||||
from taleweave.actions.base import (
|
|
||||||
action_ask,
|
|
||||||
action_examine,
|
|
||||||
action_give,
|
|
||||||
action_move,
|
|
||||||
action_take,
|
|
||||||
action_tell,
|
|
||||||
)
|
|
||||||
from taleweave.context import (
|
from taleweave.context import (
|
||||||
broadcast,
|
broadcast,
|
||||||
|
get_action_group,
|
||||||
get_character_agent_for_name,
|
get_character_agent_for_name,
|
||||||
get_character_for_agent,
|
get_character_for_agent,
|
||||||
get_current_world,
|
get_current_world,
|
||||||
get_extra_actions,
|
|
||||||
set_current_character,
|
set_current_character,
|
||||||
set_current_room,
|
set_current_room,
|
||||||
)
|
)
|
||||||
|
@ -35,6 +27,8 @@ from taleweave.utils.world import format_attributes
|
||||||
|
|
||||||
from .planning import get_notes_events
|
from .planning import get_notes_events
|
||||||
|
|
||||||
|
ACTION_SYSTEM_NAME = "action"
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -172,18 +166,7 @@ action_tools: Toolbox | None = None
|
||||||
def initialize_action(world: World):
|
def initialize_action(world: World):
|
||||||
global action_tools
|
global action_tools
|
||||||
|
|
||||||
extra_actions = get_extra_actions()
|
action_tools = Toolbox(get_action_group(ACTION_SYSTEM_NAME))
|
||||||
action_tools = Toolbox(
|
|
||||||
[
|
|
||||||
action_ask,
|
|
||||||
action_give,
|
|
||||||
action_examine,
|
|
||||||
action_move,
|
|
||||||
action_take,
|
|
||||||
action_tell,
|
|
||||||
*extra_actions,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def simulate_action(world: World, turn: int, data: Any | None = None):
|
def simulate_action(world: World, turn: int, data: Any | None = None):
|
||||||
|
@ -215,5 +198,7 @@ def simulate_action(world: World, turn: int, data: Any | None = None):
|
||||||
|
|
||||||
def init_action():
|
def init_action():
|
||||||
return [
|
return [
|
||||||
GameSystem("action", initialize=initialize_action, simulate=simulate_action)
|
GameSystem(
|
||||||
|
ACTION_SYSTEM_NAME, initialize=initialize_action, simulate=simulate_action
|
||||||
|
)
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,17 +9,8 @@ from packit.loops import loop_retry
|
||||||
from packit.results import function_result
|
from packit.results import function_result
|
||||||
from packit.toolbox import Toolbox
|
from packit.toolbox import Toolbox
|
||||||
|
|
||||||
from taleweave.actions.planning import (
|
|
||||||
check_calendar,
|
|
||||||
edit_note,
|
|
||||||
erase_notes,
|
|
||||||
get_recent_notes,
|
|
||||||
read_notes,
|
|
||||||
schedule_event,
|
|
||||||
summarize_notes,
|
|
||||||
take_note,
|
|
||||||
)
|
|
||||||
from taleweave.context import (
|
from taleweave.context import (
|
||||||
|
get_action_group,
|
||||||
get_character_agent_for_name,
|
get_character_agent_for_name,
|
||||||
get_current_turn,
|
get_current_turn,
|
||||||
get_game_config,
|
get_game_config,
|
||||||
|
@ -31,24 +22,26 @@ from taleweave.errors import ActionError
|
||||||
from taleweave.game_system import GameSystem
|
from taleweave.game_system import GameSystem
|
||||||
from taleweave.models.entity import Character, Room, World
|
from taleweave.models.entity import Character, Room, World
|
||||||
from taleweave.utils.conversation import make_keyword_condition, summarize_room
|
from taleweave.utils.conversation import make_keyword_condition, summarize_room
|
||||||
from taleweave.utils.planning import expire_events, get_upcoming_events
|
from taleweave.utils.planning import (
|
||||||
|
expire_events,
|
||||||
|
get_recent_notes,
|
||||||
|
get_upcoming_events,
|
||||||
|
)
|
||||||
from taleweave.utils.search import find_containing_room
|
from taleweave.utils.search import find_containing_room
|
||||||
from taleweave.utils.template import format_prompt
|
from taleweave.utils.template import format_prompt
|
||||||
|
|
||||||
logger = getLogger(__name__)
|
logger = getLogger(__name__)
|
||||||
|
|
||||||
|
PLANNING_SYSTEM_NAME = "planning"
|
||||||
|
|
||||||
# build a toolbox for the planners
|
# build a toolbox for the planners
|
||||||
planner_toolbox = Toolbox(
|
planning_tools: Toolbox | None = None
|
||||||
[
|
|
||||||
check_calendar,
|
|
||||||
erase_notes,
|
def initialize_planning(world: World):
|
||||||
read_notes,
|
global planning_tools
|
||||||
edit_note,
|
|
||||||
schedule_event,
|
planning_tools = Toolbox(get_action_group(PLANNING_SYSTEM_NAME))
|
||||||
summarize_notes,
|
|
||||||
take_note,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_notes_events(character: Character, current_turn: int):
|
def get_notes_events(character: Character, current_turn: int):
|
||||||
|
@ -87,7 +80,7 @@ def prompt_character_planning(
|
||||||
room: Room,
|
room: Room,
|
||||||
character: Character,
|
character: Character,
|
||||||
agent: Agent,
|
agent: Agent,
|
||||||
planner_toolbox: Toolbox,
|
toolbox: Toolbox,
|
||||||
current_turn: int,
|
current_turn: int,
|
||||||
max_steps: int | None = None,
|
max_steps: int | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
@ -121,14 +114,14 @@ def prompt_character_planning(
|
||||||
raise ActionError(
|
raise ActionError(
|
||||||
format_prompt(
|
format_prompt(
|
||||||
"world_simulate_character_planning_error_unknown_tool",
|
"world_simulate_character_planning_error_unknown_tool",
|
||||||
actions=planner_toolbox.list_tools(),
|
actions=toolbox.list_tools(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise ActionError(
|
raise ActionError(
|
||||||
format_prompt(
|
format_prompt(
|
||||||
"world_simulate_character_planning_error_json",
|
"world_simulate_character_planning_error_json",
|
||||||
actions=planner_toolbox.list_tools(),
|
actions=toolbox.list_tools(),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -155,7 +148,7 @@ def prompt_character_planning(
|
||||||
),
|
),
|
||||||
result_parser=result_parser,
|
result_parser=result_parser,
|
||||||
stop_condition=stop_condition,
|
stop_condition=stop_condition,
|
||||||
toolbox=planner_toolbox,
|
toolbox=toolbox,
|
||||||
)
|
)
|
||||||
|
|
||||||
if agent.memory:
|
if agent.memory:
|
||||||
|
@ -189,7 +182,7 @@ def simulate_planning(world: World, turn: int, data: Any | None = None):
|
||||||
if agent.memory and len(agent.memory) > 0:
|
if agent.memory and len(agent.memory) > 0:
|
||||||
try:
|
try:
|
||||||
thoughts = prompt_character_planning(
|
thoughts = prompt_character_planning(
|
||||||
room, character, agent, planner_toolbox, turn
|
room, character, agent, planning_tools, turn
|
||||||
)
|
)
|
||||||
logger.debug(f"{character.name} thinks: {thoughts}")
|
logger.debug(f"{character.name} thinks: {thoughts}")
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
from yaml import Loader, dump, load
|
from yaml import Loader, dump, load
|
||||||
|
|
||||||
|
# this module MUST NOT import any other taleweave modules, since it is used to initialize the logger
|
||||||
|
|
||||||
|
|
||||||
def load_yaml(file):
|
def load_yaml(file):
|
||||||
return load(file, Loader=Loader)
|
return load(file, Loader=Loader)
|
||||||
|
|
Loading…
Reference in New Issue