1
0
Fork 0

give worlds a name, save graphs to save folder as world, add a way to load custom actions

This commit is contained in:
Sean Sube 2024-05-02 09:20:47 -05:00
parent 793b4a7cb8
commit 4d7db75ffb
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
6 changed files with 48 additions and 28 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
adventure/custom_actions.py
worlds/ worlds/
__pycache__/ __pycache__/

View File

@ -163,9 +163,3 @@ def action_give(character: str, item_name: str) -> str:
destination_actor.items.append(item) destination_actor.items.append(item)
return f"You give the {item_name} item to {character}." return f"You give the {item_name} item to {character}."
def action_stop() -> str:
_, _, action_actor = get_current_context()
logger.info(f"{action_actor.name} end their turn")
return "You stop your actions and end your turn."

View File

@ -101,7 +101,7 @@ def generate_actor(
) )
def generate_world(agent: Agent, theme: str) -> World: def generate_world(agent: Agent, name: str, theme: str) -> World:
room_count = randint(3, 5) room_count = randint(3, 5)
logger.info(f"Generating a {theme} with {room_count} rooms") logger.info(f"Generating a {theme} with {room_count} rooms")
@ -177,4 +177,4 @@ def generate_world(agent: Agent, theme: str) -> World:
room.portals[direction] = dest_room.name room.portals[direction] = dest_room.name
dest_room.portals[opposite_directions[direction]] = room.name dest_room.portals[opposite_directions[direction]] = room.name
return World(rooms=rooms, theme=theme) return World(name=name, rooms=rooms, theme=theme)

View File

@ -1,3 +1,4 @@
from importlib import import_module
from json import load from json import load
from os import path from os import path
@ -53,7 +54,7 @@ def world_result_parser(value, agent, **kwargs):
return multi_function_or_str_result(value, agent=agent, **kwargs) return multi_function_or_str_result(value, agent=agent, **kwargs)
def simulate_world(world: World, steps: int = 10, callback=None): def simulate_world(world: World, steps: int = 10, callback=None, extra_actions=[]):
logger.info("Simulating the world") logger.info("Simulating the world")
# collect actors, so they are only processed once # collect actors, so they are only processed once
@ -68,6 +69,7 @@ def simulate_world(world: World, steps: int = 10, callback=None):
action_move, action_move,
action_take, action_take,
action_tell, action_tell,
*extra_actions,
] ]
) )
@ -105,6 +107,7 @@ def simulate_world(world: World, steps: int = 10, callback=None):
"What will you do next? Reply with a JSON function call, calling one of the actions." "What will you do next? Reply with a JSON function call, calling one of the actions."
), ),
context={ context={
# TODO: add custom action names or remove this list entirely
"actions": [ "actions": [
"ask", "ask",
"give", "give",
@ -147,7 +150,7 @@ def simulate_world(world: World, steps: int = 10, callback=None):
if callback: if callback:
callback(world, current_step) callback(world, current_step)
current_step += 1 set_step(current_step + 1)
# main # main
@ -157,6 +160,9 @@ def parse_args():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Generate and simulate a fantasy world" description="Generate and simulate a fantasy world"
) )
parser.add_argument(
"--actions", type=str, help="Extra actions to include in the simulation"
)
parser.add_argument( parser.add_argument(
"--steps", type=int, default=10, help="The number of simulation steps to run" "--steps", type=int, default=10, help="The number of simulation steps to run"
) )
@ -166,13 +172,13 @@ def parse_args():
parser.add_argument( parser.add_argument(
"--world", "--world",
type=str, type=str,
default="world.json", default="world",
help="The file to save the generated world to", help="The file to save the generated world to",
) )
parser.add_argument( parser.add_argument(
"--world-state", "--state",
type=str, type=str,
default="world-state.json", # default="world-state.json",
help="The file to save the world state to", help="The file to save the world state to",
) )
return parser.parse_args() return parser.parse_args()
@ -181,18 +187,23 @@ def parse_args():
def main(): def main():
args = parse_args() args = parse_args()
if args.world_state and path.exists(args.world_state): world_file = args.world + ".json"
logger.info(f"Loading world state from {args.world_state}") world_state_file = args.state or (args.world + ".state.json")
with open(args.world_state, "r") as f:
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(f)) state = WorldState(**load(f))
set_step(state.step) set_step(state.step)
create_agents(state.world, state.memory) create_agents(state.world, state.memory)
world = state.world world = state.world
elif args.world and path.exists(args.world): world.name = args.world
logger.info(f"Loading world from {args.world}") elif path.exists(world_file):
with open(args.world, "r") as f: logger.info(f"Loading world from {world_file}")
world = World(**load(f)) with open(world_file, "r") as f:
world = World(**load(f), name=args.world)
create_agents(world) create_agents(world)
else: else:
logger.info(f"Generating a new {args.theme} world") logger.info(f"Generating a new {args.theme} world")
@ -203,18 +214,29 @@ def main():
{}, {},
llm, llm,
) )
world = generate_world(agent, args.theme) world = generate_world(agent, args.world, args.theme)
create_agents(world) create_agents(world)
save_world(world, world_file)
logger.debug("Loaded world: %s", world) # load extra actions
extra_actions = []
if args.world: if args.actions:
save_world(world, args.world) logger.info(f"Loading extra actions from {args.actions}")
action_module, action_function = args.actions.rsplit(":", 1)
action_module = import_module(action_module)
action_function = getattr(action_module, action_function)
module_actions = action_function()
logger.info(
f"Loaded extra actions: {[action.__name__ for action in module_actions]}"
)
extra_actions.append(module_actions)
logger.debug("Simulating world: %s", world)
simulate_world( simulate_world(
world, world,
steps=args.steps, steps=args.steps,
callback=lambda w, s: save_world_state(w, s, args.world_state), callback=lambda w, s: save_world_state(w, s, world_state_file),
extra_actions=extra_actions,
) )

View File

@ -40,6 +40,7 @@ class Room:
@dataclass @dataclass
class World: class World:
name: str
rooms: List[Room] rooms: List[Room]
theme: str theme: str

View File

@ -1,5 +1,6 @@
from collections import deque from collections import deque
from json import dump from json import dump
from os import path
from typing import Dict, List, Sequence from typing import Dict, List, Sequence
from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage
@ -27,11 +28,12 @@ def graph_world(world: World, step: int):
graph = graphviz.Digraph(f"{world.theme}-{step}", format="png") graph = graphviz.Digraph(f"{world.theme}-{step}", format="png")
for room in world.rooms: for room in world.rooms:
room_label = "\n".join([room.name, *[actor.name for actor in room.actors]]) room_label = "\n".join([room.name, *[actor.name for actor in room.actors]])
graph.node(room.name, room_label) # , room.description) graph.node(room.name, room_label)
for direction, destination in room.portals.items(): for direction, destination in room.portals.items():
graph.edge(room.name, destination, label=direction) graph.edge(room.name, destination, label=direction)
graph.render(directory="worlds", view=True) graph_path = path.dirname(world.name)
graph.render(directory=graph_path)
def snapshot_world(world: World, step: int): def snapshot_world(world: World, step: int):