1
0
Fork 0

split up entry points and engine components, group all actions
Run Docker Build / build (push) Successful in 12s Details
Run Python Build / build (push) Successful in 26s Details

This commit is contained in:
Sean Sube 2024-06-16 17:52:15 -05:00
parent 60fb4c94cf
commit 2f26d4e883
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
11 changed files with 382 additions and 321 deletions

View File

@ -2,6 +2,7 @@ from logging import getLogger
from taleweave.context import (
action_context,
add_extra_actions,
broadcast,
get_agent_for_character,
get_character_agent_for_name,
@ -10,6 +11,7 @@ from taleweave.context import (
world_context,
)
from taleweave.errors import ActionError
from taleweave.systems.action import ACTION_SYSTEM_NAME
from taleweave.utils.conversation import loop_conversation
from taleweave.utils.search import (
find_character_in_room,
@ -337,3 +339,18 @@ def action_drop(item: str) -> str:
action_room.items.append(action_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,
],
)

View File

@ -5,6 +5,7 @@ from packit.agent import Agent, agent_easy_connect
from taleweave.context import (
action_context,
add_extra_actions,
broadcast,
get_agent_for_character,
get_current_turn,
@ -21,6 +22,7 @@ from taleweave.generate import (
generate_room,
link_rooms,
)
from taleweave.systems.action import ACTION_SYSTEM_NAME
from taleweave.utils.effect import apply_effects, is_effect_ready
from taleweave.utils.search import find_character_in_room
from taleweave.utils.string import normalize_name
@ -256,8 +258,11 @@ def init_optional() -> List[Callable]:
"""
Initialize the custom actions.
"""
return [
return add_extra_actions(
ACTION_SYSTEM_NAME,
[
action_explore,
action_search,
action_use,
]
],
)

View File

@ -1,5 +1,6 @@
from taleweave.context import (
action_context,
add_extra_actions,
get_agent_for_character,
get_current_turn,
get_game_config,
@ -7,6 +8,7 @@ from taleweave.context import (
)
from taleweave.errors import ActionError
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.template import format_prompt
@ -194,3 +196,18 @@ def check_calendar(count: int):
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,
],
)

View File

@ -34,10 +34,10 @@ current_character: Character | None = None
dungeon_master: Agent | None = None
# 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]] = {}
event_emitter = EventEmitter()
extra_actions: List[Callable[..., str]] = []
game_config: Config = DEFAULT_CONFIG
game_systems: List[GameSystem] = []
prompt_library: PromptLibrary = PromptLibrary(prompts={})
@ -186,8 +186,8 @@ def get_system_data(system: str) -> Any | None:
return system_data.get(system)
def get_extra_actions() -> List[Callable[..., str]]:
return extra_actions
def get_action_group(name: str) -> List[Callable[..., str]]:
return action_groups.get(name, [])
# endregion
@ -242,9 +242,9 @@ def set_system_data(system: str, data: Any):
system_data[system] = data
def set_extra_actions(actions: List[Callable[..., str]]):
global extra_actions
extra_actions = actions
def add_extra_actions(group: str, actions: List[Callable[..., str]]):
action_groups.setdefault(group, []).extend(actions)
return group, actions
# endregion

View File

@ -1,8 +1,12 @@
import argparse
from os import path
from os import environ, path
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.engine import load_or_initialize_system_data
from taleweave.game_system import GameSystem
from taleweave.generate import (
generate_character,
@ -11,7 +15,7 @@ from taleweave.generate import (
generate_room,
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.entity import World, WorldState
from taleweave.plugins import load_plugin
@ -30,9 +34,16 @@ from taleweave.utils.world import describe_entity
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():
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("--world", type=str, help="World file to edit")
parser.add_argument("--systems", type=str, nargs="*", help="Game systems to load")
@ -41,11 +52,9 @@ def parse_args():
subparsers.required = True
# Set up the 'list' command
list_parser = subparsers.add_parser(
"list", help="List all entities or entities of a specific type"
)
list_parser = subparsers.add_parser("list", help="List entities of a specific type")
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
@ -107,7 +116,7 @@ def parse_args():
def load_world(state_file, world_file) -> Tuple[World, WorldState | None]:
systems = []
systems = get_game_systems()
if state_file and path.exists(state_file):
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):
"""
Save the world to the given files.
This is intentionally a noop stub until the editor is more stable.
"""
if state:
print(f"Saving world {world.name} to {state_file}")
logger.warning(f"Saving world {world.name} to {state_file}")
return
with open(state_file, "w") as f:
save_yaml(f, dump_model(WorldState, state))
else:
print(f"Saving world {world.name} to {world_file}")
logger.warning(f"Saving world {world.name} to {world_file}")
return
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):
print(f"Listing {args.type}s")
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":
for room in list_rooms(world):
print(room.name)
logger.info(room.name)
if args.type == "portal":
for portal in list_portals(world):
print(portal.name)
logger.info(portal.name)
if args.type == "item":
for item in list_items(
world, include_character_inventory=True, include_item_inventory=True
):
print(item.name)
logger.info(item.name)
if args.type == "character":
for character in list_characters(world):
print(character.name)
logger.info(character.name)
def command_describe(args):
print(f"Describing {args.entity}")
world, _ = load_world(args.state, args.world)
print(world.name)
logger.info(f"Describing {args.entity} from world {world.name}")
if args.type == "room":
room = find_room(world, args.entity)
if not room:
print(f"Room {args.entity} not found")
logger.error(f"Room {args.entity} not found")
else:
print(describe_entity(room))
logger.info(describe_entity(room))
if args.type == "portal":
portal = find_portal(world, args.entity)
if not portal:
print(f"Portal {args.entity} not found")
logger.error(f"Portal {args.entity} not found")
else:
print(describe_entity(portal))
logger.info(describe_entity(portal))
if args.type == "item":
item = find_item(
@ -194,22 +206,21 @@ def command_describe(args):
include_item_inventory=True,
)
if not item:
print(f"Item {args.entity} not found")
logger.error(f"Item {args.entity} not found")
else:
print(describe_entity(item))
logger.info(describe_entity(item))
if args.type == "character":
character = find_character(world, args.entity)
if not character:
print(f"Character {args.entity} not found")
logger.error(f"Character {args.entity} not found")
else:
print(describe_entity(character))
logger.info(describe_entity(character))
def command_create(args):
print(f"Create {args.type} named {args.name}")
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
@ -217,9 +228,10 @@ def command_create(args):
def command_generate(args):
print(f"Generate {args.type} with prompt: {args.prompt}")
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()
systems = get_game_systems()
@ -231,12 +243,12 @@ def command_generate(args):
if args.type == "portal":
source_room = find_room(world, args.room)
if not source_room:
print(f"Room {args.room} not found")
logger.error(f"Room {args.room} not found")
return
destination_room = find_room(world, args.dest_room)
if not destination_room:
print(f"Room {args.dest_room} not found")
logger.error(f"Room {args.dest_room} not found")
return
outgoing_portal, incoming_portal = generate_portals(
@ -249,7 +261,7 @@ def command_generate(args):
# TODO: add item to character or container inventory
room = find_room(world, args.room)
if not room:
print(f"Room {args.room} not found")
logger.error(f"Room {args.room} not found")
return
item = generate_item(dungeon_master, world, systems)
@ -258,7 +270,7 @@ def command_generate(args):
if args.type == "character":
room = find_room(world, args.room)
if not room:
print(f"Room {args.room} not found")
logger.error(f"Room {args.room} not found")
return
character = generate_character(
@ -270,9 +282,8 @@ def command_generate(args):
def command_delete(args):
print(f"Delete {args.entity}")
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
@ -280,23 +291,22 @@ def command_delete(args):
def command_update(args):
print(f"Update {args.entity}")
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":
room = find_room(world, args.entity)
if not room:
print(f"Room {args.entity} not found")
logger.error(f"Room {args.entity} not found")
else:
print(describe_entity(room))
logger.info(describe_entity(room))
if args.type == "portal":
portal = find_portal(world, args.entity)
if not portal:
print(f"Portal {args.entity} not found")
logger.error(f"Portal {args.entity} not found")
else:
print(describe_entity(portal))
logger.info(describe_entity(portal))
if args.type == "item":
item = find_item(
@ -306,14 +316,14 @@ def command_update(args):
include_item_inventory=True,
)
if not item:
print(f"Item {args.entity} not found")
logger.error(f"Item {args.entity} not found")
else:
print(describe_entity(item))
logger.info(describe_entity(item))
if args.type == "character":
character = find_character(world, args.entity)
if not character:
print(f"Character {args.entity} not found")
logger.error(f"Character {args.entity} not found")
else:
if args.backstory:
character.backstory = args.backstory
@ -321,15 +331,14 @@ def command_update(args):
if args.description:
character.description = args.description
print(describe_entity(character))
logger.info(describe_entity(character))
save_world(args.state, args.world, world, state)
def command_link(args):
print(f"Linking rooms {args.rooms}")
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()
systems = get_game_systems()
@ -352,14 +361,16 @@ COMMAND_TABLE = {
def main():
args = parse_args()
print(args)
logger.debug(f"running with args: {args}")
load_prompt_library(args)
# load game systems before executing commands
systems: List[GameSystem] = []
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)
print(f"loaded extra systems: {module_systems}")
logger.info(f"loaded extra systems: {module_systems}")
systems.extend(module_systems)
set_game_systems(systems)

158
taleweave/engine.py Normal file
View File

@ -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

View File

@ -1,73 +1,67 @@
import argparse
import atexit
from functools import partial
from glob import glob
from itertools import count
from logging.config import dictConfig
from os import environ, path
from typing import List
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
# 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
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:
if path.exists(LOG_PATH):
with open(LOG_PATH, "r") as f:
config_logging = load_yaml(f)
dictConfig(config_logging)
else:
print("logging config not found")
print(f"logging config not found at {LOG_PATH}")
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
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
debugpy.listen(5679)
logger.info("waiting for debugger to attach...")
logger.warning("waiting for debugger to attach...")
debugpy.wait_for_client()
else:
logger = logger_with_colors(__name__)
if True:
from taleweave.context import (
get_current_turn,
get_prompt_library,
get_system_data,
set_current_turn,
set_current_world,
set_dungeon_master,
set_extra_actions,
set_game_config,
set_game_systems,
set_system_data,
subscribe,
)
from taleweave.engine import load_or_generate_world, simulate_world
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.entity import World, WorldState
from taleweave.models.entity import World
from taleweave.models.event import GenerateEvent
from taleweave.models.files import TemplateFile, WorldPrompt
from taleweave.models.prompt import PromptLibrary
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.planning import init_planning
from taleweave.utils.template import format_prompt
def int_or_inf(value: str) -> float | int:
@ -82,18 +76,8 @@ def parse_args():
parser = argparse.ArgumentParser(
description="Generate and simulate a text adventure world"
)
parser.add_argument(
"--actions",
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",
)
# config arguments
parser.add_argument(
"--config",
type=str,
@ -104,28 +88,11 @@ def parse_args():
action="store_true",
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(
"--player",
type=str,
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(
"--render",
action="store_true",
@ -136,26 +103,24 @@ def parse_args():
action="store_true",
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(
"--server",
action="store_true",
help="Whether to run the websocket server",
)
# data and plugin arguments
parser.add_argument(
"--state",
"--actions",
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(
"--turns",
type=int_or_inf,
default=10,
help="The number of simulation turns to run",
"--prompts",
type=str,
nargs="*",
help="The file to load game prompts from",
)
parser.add_argument(
"--systems",
@ -163,23 +128,57 @@ def parse_args():
nargs="*",
help="Extra systems to run in the simulation",
)
# generation arguments
parser.add_argument(
"--theme",
type=str,
default="fantasy",
help="The theme of the generated world",
"--add-rooms",
default=0,
type=int,
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(
"--world",
type=str,
default="world",
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(
"--world-template",
type=str,
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()
@ -196,8 +195,8 @@ def get_world_prompt(args) -> WorldPrompt:
return WorldPrompt(
name=args.world,
theme=args.theme,
flavor=args.flavor,
theme=args.world_theme,
flavor=args.world_flavor,
)
@ -217,111 +216,6 @@ def load_prompt_library(args) -> 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():
args = parse_args()
@ -365,28 +259,13 @@ def main():
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
for action_name in args.actions or []:
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(
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
systems: List[GameSystem] = []
@ -411,7 +290,14 @@ def main():
# load or generate the world
world_prompt = get_world_prompt(args)
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)
@ -426,21 +312,7 @@ def main():
if args.server:
server_system(world, world_turn)
# run game systems for each turn
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
simulate_world(world, systems, args.turns)
if __name__ == "__main__":

View File

@ -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):
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)
return getattr(plugin_module, override_function or function_name)

View File

@ -7,20 +7,12 @@ from packit.loops import loop_retry
from packit.results import function_result
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 (
broadcast,
get_action_group,
get_character_agent_for_name,
get_character_for_agent,
get_current_world,
get_extra_actions,
set_current_character,
set_current_room,
)
@ -35,6 +27,8 @@ from taleweave.utils.world import format_attributes
from .planning import get_notes_events
ACTION_SYSTEM_NAME = "action"
logger = getLogger(__name__)
@ -172,18 +166,7 @@ action_tools: Toolbox | None = None
def initialize_action(world: World):
global action_tools
extra_actions = get_extra_actions()
action_tools = Toolbox(
[
action_ask,
action_give,
action_examine,
action_move,
action_take,
action_tell,
*extra_actions,
]
)
action_tools = Toolbox(get_action_group(ACTION_SYSTEM_NAME))
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():
return [
GameSystem("action", initialize=initialize_action, simulate=simulate_action)
GameSystem(
ACTION_SYSTEM_NAME, initialize=initialize_action, simulate=simulate_action
)
]

View File

@ -9,17 +9,8 @@ from packit.loops import loop_retry
from packit.results import function_result
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 (
get_action_group,
get_character_agent_for_name,
get_current_turn,
get_game_config,
@ -31,24 +22,26 @@ from taleweave.errors import ActionError
from taleweave.game_system import GameSystem
from taleweave.models.entity import Character, Room, World
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.template import format_prompt
logger = getLogger(__name__)
PLANNING_SYSTEM_NAME = "planning"
# build a toolbox for the planners
planner_toolbox = Toolbox(
[
check_calendar,
erase_notes,
read_notes,
edit_note,
schedule_event,
summarize_notes,
take_note,
]
)
planning_tools: Toolbox | None = None
def initialize_planning(world: World):
global planning_tools
planning_tools = Toolbox(get_action_group(PLANNING_SYSTEM_NAME))
def get_notes_events(character: Character, current_turn: int):
@ -87,7 +80,7 @@ def prompt_character_planning(
room: Room,
character: Character,
agent: Agent,
planner_toolbox: Toolbox,
toolbox: Toolbox,
current_turn: int,
max_steps: int | None = None,
) -> str:
@ -121,14 +114,14 @@ def prompt_character_planning(
raise ActionError(
format_prompt(
"world_simulate_character_planning_error_unknown_tool",
actions=planner_toolbox.list_tools(),
actions=toolbox.list_tools(),
)
)
else:
raise ActionError(
format_prompt(
"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,
stop_condition=stop_condition,
toolbox=planner_toolbox,
toolbox=toolbox,
)
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:
try:
thoughts = prompt_character_planning(
room, character, agent, planner_toolbox, turn
room, character, agent, planning_tools, turn
)
logger.debug(f"{character.name} thinks: {thoughts}")
except Exception:

View File

@ -1,5 +1,7 @@
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):
return load(file, Loader=Loader)