From 8a4ecd25885cadefccfa190782922244a7de3b09 Mon Sep 17 00:00:00 2001 From: Sean Sube Date: Tue, 4 Jun 2024 21:15:36 -0500 Subject: [PATCH] load prompts from globs, provide more information to prompt templates, make pseudo-function parsing more flexible, add enter/exit messages to digest system --- prompts/llama-base.yml | 22 ++-------- prompts/llama-digest.yml | 24 +++++++++++ prompts/llama-quest.yml | 8 ++-- taleweave/actions/base.py | 5 ++- taleweave/actions/quest.py | 4 +- taleweave/generate.py | 2 +- taleweave/main.py | 8 ++++ taleweave/player.py | 17 +++++--- taleweave/simulate.py | 5 +-- taleweave/systems/digest.py | 85 ++++++++++++++++++++++++++++++++++--- taleweave/utils/prompt.py | 3 ++ taleweave/utils/world.py | 6 ++- 12 files changed, 145 insertions(+), 44 deletions(-) create mode 100644 prompts/llama-digest.yml diff --git a/prompts/llama-base.yml b/prompts/llama-base.yml index 4c05ac2..b629e2a 100644 --- a/prompts/llama-base.yml +++ b/prompts/llama-base.yml @@ -186,23 +186,7 @@ prompts: action_check_calendar_each: | {{name}} will happen in {{turns}} turn - # digest system - digest_action_move: | - {{event.character | name}} entered the room. - digest_action_take: | - {{event.character | name}} picked up the {{event.parameters[item]}}. - digest_action_give: | - {{event.character | name}} gave the {{event.parameters[item]}} to {{event.parameters[character]}}. - digest_action_drop: | - {{event.character | name}} dropped the {{event.parameters[item]}}. - digest_action_ask: | - {{event.character | name}} asked {{event.parameters[character]}} about something. - digest_action_tell: | - {{event.character | name}} told {{event.parameters[character]}} about something. - digest_action_examine: | - {{event.character | name}} examined the {{event.parameters[target]}}. - - # world defaults + # default dungeon master world_default_dungeon_master: | You are the dungeon master in charge of creating an engaging fantasy world full of interesting characters who interact with each other and explore their environment. Be creative and original, creating a world that is @@ -233,7 +217,7 @@ prompts: world_generate_room_broadcast_characters: | Generating {{character_count}} characters for room: {{name}} world_generate_room_broadcast_portals: | - Generating {{portal_count}} portals for room: {{name}} + Generating {{portal_count}} portals for room: {{room | name}} world_generate_portal_name_outgoing: | Generate the name of a portal that leads from the {{source_room}} room to the {{dest_room}} room and fits the world theme of {{world_theme}}. @@ -346,7 +330,7 @@ prompts: # world simulation world_simulate_character_action: | - You are currently in the {{room_name}} room. {{room_description}}. {{attributes}}. + You are currently in the {{room | name}} room. {{room | describe}}. {{attributes}}. The room contains the following characters: {{visible_characters}}. The room contains the following items: {{visible_items}}. Your inventory contains the following items: {{character_items}}. diff --git a/prompts/llama-digest.yml b/prompts/llama-digest.yml new file mode 100644 index 0000000..1af5987 --- /dev/null +++ b/prompts/llama-digest.yml @@ -0,0 +1,24 @@ +prompts: + # digest system + digest_action_move_other_enter: | + {{event.character | name}} entered the room through the {{source_portal | name}}. + digest_action_move_other_exit: | + {{event.character | name}} left the room, heading through the {{destination_portal | name}}. + digest_action_move_self_enter: | + You entered the room through the {{source_portal | name}}. + digest_action_move_self_exit: | + You left the room, heading through the {{destination_portal | name}}. + digest_action_move: | + {{event.character | name}} entered the room. + digest_action_take: | + {{event.character | name}} picked up the {{event.parameters[item]}}. + digest_action_give: | + {{event.character | name}} gave the {{event.parameters[item]}} to {{event.parameters[character]}}. + digest_action_drop: | + {{event.character | name}} dropped the {{event.parameters[item]}}. + digest_action_ask: | + {{event.character | name}} asked {{event.parameters[character]}} about something. + digest_action_tell: | + {{event.character | name}} told {{event.parameters[character]}} about something. + digest_action_examine: | + {{event.character | name}} examined the {{event.parameters[target]}}. diff --git a/prompts/llama-quest.yml b/prompts/llama-quest.yml index d5c79ca..95d31aa 100644 --- a/prompts/llama-quest.yml +++ b/prompts/llama-quest.yml @@ -1,18 +1,18 @@ prompts: action_accept_quest_error_none: No quests are available at the moment. action_accept_quest_error_name: | - {{character}} does not have a quest named "{{quest_name}}". + {{character}} does not have a quest named "{{quest.name}}". action_accept_quest_error_room: | {{character}} is not in the room. action_accept_quest_result: | - You have started the quest "{{quest_name}}". + You have started the quest "{{quest.name}}". action_submit_quest_error_active: | You do not have any active quests. action_submit_quest_error_none: No quests are available at the moment. action_submit_quest_error_name: | - {{character}} does not have a quest named "{{quest_name}}". + {{character}} does not have a quest named "{{quest.name}}". action_submit_quest_error_room: | {{character}} is not in the room. action_submit_quest_result: | - You have completed the quest "{{quest_name}}". \ No newline at end of file + You have completed the quest "{{quest.name}}". \ No newline at end of file diff --git a/taleweave/actions/base.py b/taleweave/actions/base.py index a7978e2..0378533 100644 --- a/taleweave/actions/base.py +++ b/taleweave/actions/base.py @@ -37,6 +37,7 @@ def action_examine(target: str) -> str: format_prompt( "action_examine_broadcast_action", action_character=action_character, + action_room=action_room, target=target, ) ) @@ -49,7 +50,7 @@ def action_examine(target: str) -> str: action_room=action_room, ) ) - return format_prompt("action_examine_result_room", action_room=action_room) + return format_prompt("action_examine_result_room", target_room=action_room) target_character = find_character_in_room(action_room, target) if target_character: @@ -118,7 +119,7 @@ def action_move(direction: str) -> str: format_prompt( "action_move_error_room", direction=direction, - destination=portal.destination, + portal=portal, ) ) diff --git a/taleweave/actions/quest.py b/taleweave/actions/quest.py index 6b42803..c27e9a1 100644 --- a/taleweave/actions/quest.py +++ b/taleweave/actions/quest.py @@ -74,11 +74,11 @@ def submit_quest(character: str) -> str: return format_prompt( "action_submit_quest_result", character=character, - quest=active_quest.name, + quest=active_quest, ) return format_prompt( "action_submit_quest_error_name", character=character, - quest=active_quest.name, + quest=active_quest, ) diff --git a/taleweave/generate.py b/taleweave/generate.py index cc9e3a9..f6d8111 100644 --- a/taleweave/generate.py +++ b/taleweave/generate.py @@ -523,7 +523,7 @@ def link_rooms( format_prompt( "world_generate_room_broadcast_portals", portal_count=num_portals, - name=room.name, + room=room, ) ) diff --git a/taleweave/main.py b/taleweave/main.py index 8b9d64d..920f34f 100644 --- a/taleweave/main.py +++ b/taleweave/main.py @@ -1,5 +1,6 @@ import atexit from functools import partial +from glob import glob from logging.config import dictConfig from os import environ, path from typing import List @@ -195,7 +196,11 @@ def get_world_prompt(args) -> WorldPrompt: def load_prompt_library(args) -> None: if args.prompts: + prompt_files = [] for prompt_file in args.prompts: + prompt_files.extend(glob(prompt_file, recursive=True)) + + for prompt_file in prompt_files: with open(prompt_file, "r") as f: new_library = PromptLibrary(**load_yaml(f)) logger.info(f"loaded prompt library from {prompt_file}") @@ -258,6 +263,7 @@ def load_or_generate_world( 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}") @@ -285,6 +291,8 @@ def load_or_generate_world( systems, room_count=args.rooms, ) + load_or_initialize_system_data(args, systems, world) + save_world(world, world_file) save_system_data(args, systems) diff --git a/taleweave/player.py b/taleweave/player.py index 409f972..5971192 100644 --- a/taleweave/player.py +++ b/taleweave/player.py @@ -10,6 +10,7 @@ from packit.toolbox import Toolbox from taleweave.context import action_context from taleweave.models.event import PromptEvent +from taleweave.utils import try_parse_float, try_parse_int logger = getLogger(__name__) @@ -122,12 +123,18 @@ class BasePlayer: def parse_value(value: str) -> str | bool | float | int: if value.startswith("~"): return value[1:] - if value.lower() in ["true", "false"]: - return value.lower() == "true" - if value.isdecimal(): - return float(value) - if value.isnumeric(): + + if value.lower() in ["true", "false", "yes", "no", "y", "n"]: + return value.lower() in ["true", "yes", "y"] + + int_value = try_parse_int(value) + if int_value is not None: return int(value) + + float_value = try_parse_float(value) + if float_value is not None: + return float(value) + return value params = { diff --git a/taleweave/simulate.py b/taleweave/simulate.py index 706b8c7..4700b7b 100644 --- a/taleweave/simulate.py +++ b/taleweave/simulate.py @@ -53,7 +53,7 @@ from taleweave.utils.effect import expire_effects from taleweave.utils.planning import expire_events, get_upcoming_events from taleweave.utils.prompt import format_prompt from taleweave.utils.search import find_containing_room -from taleweave.utils.world import describe_entity, format_attributes +from taleweave.utils.world import format_attributes logger = getLogger(__name__) @@ -136,8 +136,7 @@ def prompt_character_action( character_items=character_items, attributes=character_attributes, directions=room_directions, - room_name=room.name, - room_description=describe_entity(room), + room=room, visible_characters=room_characters, visible_items=room_items, notes_prompt=notes_prompt, diff --git a/taleweave/systems/digest.py b/taleweave/systems/digest.py index 393bfbe..a24d91f 100644 --- a/taleweave/systems/digest.py +++ b/taleweave/systems/digest.py @@ -1,29 +1,87 @@ from logging import getLogger -from typing import Dict, List +from typing import Any, Dict, List from taleweave.context import get_current_world, get_prompt_library, subscribe from taleweave.game_system import FormatPerspective, GameSystem from taleweave.models.entity import Character, Room, World, WorldEntity from taleweave.models.event import ActionEvent, GameEvent from taleweave.utils.prompt import format_str -from taleweave.utils.search import find_containing_room +from taleweave.utils.search import find_containing_room, find_portal, find_room logger = getLogger(__name__) +def create_move_digest( + world: World, + active_room: Room, + active_character: Character, + event: ActionEvent, +) -> str: + source_room = event.room + direction = str(event.parameters.get("direction")) + destination_portal = find_portal(world, direction) + if not destination_portal: + raise ValueError(f"Could not find portal for direction {direction}") + + destination_room = find_room(world, destination_portal.destination) + if not destination_room: + raise ValueError( + f"Could not find destination room {destination_portal.destination}" + ) + + # look up the source portal + source_portal = next( + ( + portal + for portal in destination_room.portals + if portal.destination == source_room.name + ), + None, + ) + if not source_portal: + raise ValueError(f"Could not find source portal for {destination_portal.name}") + + mode = "self" if (event.character == active_character) else "other" + mood = "enter" if (destination_room == active_room) else "exit" + + message = format_str( + f"digest_move_{mode}_{mood}", + destination_portal=destination_portal, + destination_room=destination_room, + direction=direction, + source_portal=source_portal, + source_room=source_room, + ) + return message + + def create_turn_digest( - active_room: Room, active_character: Character, turn_events: List[GameEvent] + world: World, + active_room: Room, + active_character: Character, + turn_events: List[GameEvent], ) -> List[str]: library = get_prompt_library() messages = [] for event in turn_events: if isinstance(event, ActionEvent): - if event.character == active_character or event.room == active_room: + # special handling for move actions + if event.action == "action_move": + message = create_move_digest( + world, active_room, active_character, event + ) + messages.append(message) + elif event.character == active_character or event.room == active_room: prompt_key = f"digest_{event.action}" if prompt_key in library.prompts: try: template = library.prompts[prompt_key] - message = format_str(template, event=event) + message = format_str( + template, + active_character=active_character, + active_room=active_room, + event=event, + ) messages.append(message) except Exception: logger.exception("error formatting digest event: %s", event) @@ -65,10 +123,16 @@ def format_digest( if not room: raise ValueError("Character not found in any room") - digest = create_turn_digest(room, entity, buffer) + digest = create_turn_digest(world, room, entity, buffer) return "\n".join(digest) +def generate_digest(agent: Any, theme: str, entity: WorldEntity): + if isinstance(entity, Character): + if entity.name not in character_buffers: + character_buffers[entity.name] = [] + + def initialize_digest(world: World): for room in world.rooms: for character in room.characters: @@ -77,4 +141,11 @@ def initialize_digest(world: World): def init(): subscribe(GameEvent, digest_listener) - return [GameSystem("digest", format=format_digest, initialize=initialize_digest)] + return [ + GameSystem( + "digest", + format=format_digest, + generate=generate_digest, + initialize=initialize_digest, + ) + ] diff --git a/taleweave/utils/prompt.py b/taleweave/utils/prompt.py index 2dd988c..0e3a588 100644 --- a/taleweave/utils/prompt.py +++ b/taleweave/utils/prompt.py @@ -3,6 +3,8 @@ from logging import getLogger from jinja2 import Environment from taleweave.context import get_prompt_library + +# from taleweave.utils.conversation import summarize_room from taleweave.utils.string import and_list, or_list from taleweave.utils.world import describe_entity, name_entity @@ -11,6 +13,7 @@ logger = getLogger(__name__) jinja_env = Environment() jinja_env.filters["describe"] = describe_entity jinja_env.filters["name"] = name_entity +# jinja_env.filters["summary"] = summarize_room jinja_env.filters["and_list"] = and_list jinja_env.filters["or_list"] = or_list diff --git a/taleweave/utils/world.py b/taleweave/utils/world.py index c68c42d..9563107 100644 --- a/taleweave/utils/world.py +++ b/taleweave/utils/world.py @@ -12,7 +12,11 @@ def describe_character( perspective: FormatPerspective = FormatPerspective.SECOND_PERSON, ) -> str: attribute_descriptions = format_attributes(character, perspective=perspective) - logger.info("describing character: %s, %s", character.name, attribute_descriptions) + logger.info( + "describing character: %s, attributes: '%s'", + character.name, + attribute_descriptions, + ) if perspective == FormatPerspective.SECOND_PERSON: character_description = character.backstory