Skip to content

Commit

Permalink
add digest system, send actions in player prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
ssube committed May 31, 2024
1 parent 80e9848 commit d37de8a
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 12 deletions.
4 changes: 3 additions & 1 deletion taleweave/actions/optional.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,9 @@ def action_use(item: str, target: str) -> str:
)
outcome = dungeon_master(
f"{action_character.name} uses the {chosen_name} effect of {item} on {target}. "
f"{describe_character(action_character)}. {describe_character(target_character)}. {describe_entity(action_item)}. "
f"{describe_character(action_character)}. "
f"{describe_character(target_character)}. "
f"{describe_entity(action_item)}. "
f"What happens? How does {target} react? Be creative with the results. The outcome can be good, bad, or neutral."
"Decide based on the characters involved and the item being used."
"Specify the outcome of the action. Do not include the question or any JSON. Only include the outcome of the action."
Expand Down
31 changes: 28 additions & 3 deletions taleweave/player.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from langchain_core.messages import AIMessage, BaseMessage, HumanMessage
from packit.agent import Agent
from packit.toolbox import Toolbox

from taleweave.context import action_context
from taleweave.models.event import PromptEvent
Expand Down Expand Up @@ -89,7 +90,28 @@ def invoke(self, prompt: str, context: Dict[str, Any], **kwargs) -> Any:
Ask the player for input.
"""

return self(prompt, **context)
return self(prompt, **context, **kwargs)

def format_psuedo_functions(self, toolbox: Toolbox) -> str:
"""
Format pseudo functions for the player prompt.
"""
functions = []
for tool in toolbox.list_definitions():
function_data = tool["function"]
function_name = f"~{function_data['name']}"
function_args = []
for name, info in (
function_data.get("parameters", {}).get("properties", {}).items()
):
function_args.append(f"{name}={info['type']}")

if function_args:
functions.append(function_name + ":" + ",".join(function_args))
else:
functions.append(function_name)

return "\n".join(functions)

def parse_pseudo_function(self, reply: str):
# turn other replies into a JSON function call
Expand Down Expand Up @@ -170,12 +192,15 @@ def __init__(
self.input_queue = Queue()
self.send_prompt = send_prompt

def __call__(self, prompt: str, **kwargs) -> str:
def __call__(self, prompt: str, toolbox: Toolbox | None = None, **kwargs) -> str:
"""
Ask the player for input.
"""

formatted_prompt = prompt.format(**kwargs)
if toolbox:
formatted_prompt += self.format_psuedo_functions(toolbox)

self.memory.append(HumanMessage(content=formatted_prompt))

with action_context() as (current_room, current_character):
Expand All @@ -193,7 +218,7 @@ def __call__(self, prompt: str, **kwargs) -> str:
logger.exception("error getting reply from remote player")

if self.fallback_agent:
logger.info("prompting fallback agent: {self.fallback_agent.name}")
logger.info(f"prompting fallback agent: {self.fallback_agent.name}")
return self.fallback_agent(prompt, **kwargs)

return ""
29 changes: 24 additions & 5 deletions taleweave/render/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import List

from taleweave.context import get_current_world, get_dungeon_master
from taleweave.game_system import FormatPerspective
from taleweave.models.entity import Room, WorldEntity
from taleweave.models.event import (
ActionEvent,
Expand Down Expand Up @@ -32,7 +33,11 @@ def prompt_from_parameters(
if target_character:
logger.debug("adding character to prompt: %s", target_character.name)
pre.append(f"with {target_character.name}")
post.append(describe_entity(target_character))
post.append(
describe_entity(
target_character, perspective=FormatPerspective.THIRD_PERSON
)
)

if "item" in parameters:
# look up the item
Expand All @@ -47,7 +52,9 @@ def prompt_from_parameters(
if target_item:
logger.debug("adding item to prompt: %s", target_item.name)
pre.append(f"using the {target_item.name}")
post.append(describe_entity(target_item))
post.append(
describe_entity(target_item, perspective=FormatPerspective.THIRD_PERSON)
)

if "target" in parameters:
# could be a room, character, or item
Expand All @@ -60,13 +67,21 @@ def prompt_from_parameters(
if target_room:
logger.debug("adding room to prompt: %s", target_room.name)
pre.append(f"in the {target_room.name}")
post.append(describe_entity(target_room))
post.append(
describe_entity(
target_room, perspective=FormatPerspective.THIRD_PERSON
)
)

target_character = find_character_in_room(action_room, target_name)
if target_character:
logger.debug("adding character to prompt: %s", target_character.name)
pre.append(f"with {target_character.name}")
post.append(describe_entity(target_character))
post.append(
describe_entity(
target_character, perspective=FormatPerspective.THIRD_PERSON
)
)

target_item = find_item_in_room(
action_room,
Expand All @@ -77,7 +92,11 @@ def prompt_from_parameters(
if target_item:
logger.debug("adding item to prompt: %s", target_item.name)
pre.append(f"using the {target_item.name}")
post.append(describe_entity(target_item))
post.append(
describe_entity(
target_item, perspective=FormatPerspective.THIRD_PERSON
)
)

return (" and ".join(pre) if pre else "", " and ".join(post) if post else "")

Expand Down
3 changes: 2 additions & 1 deletion taleweave/simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,10 @@ def result_parser(value, **kwargs):
pass

if could_be_json(value):
# TODO: only emit valid actions that parse and run correctly
event = ActionEvent.from_json(value, room, character)
else:
# TODO: this should be removed and throw
# TODO: this path should be removed and throw
event = ResultEvent(value, room, character)

broadcast(event)
Expand Down
90 changes: 90 additions & 0 deletions taleweave/systems/digest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from typing import Dict, List

from taleweave.context import get_current_world, 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.search import find_containing_room


def create_turn_digest(
active_room: Room, active_character: Character, turn_events: List[GameEvent]
) -> List[str]:
messages = []
for event in turn_events:
if isinstance(event, ActionEvent):
if event.character == active_character or event.room == active_room:
if event.action == "move":
# TODO: differentiate between entering and leaving
messages.append(f"{event.character.name} entered the room.")
elif event.action == "take":
messages.append(
f"{event.character.name} picked up the {event.parameters['item']}."
)
elif event.action == "give":
messages.append(
f"{event.character.name} gave {event.parameters['item']} to {event.parameters['character']}."
)
elif event.action == "ask":
messages.append(
f"{event.character.name} asked {event.parameters['character']} about something."
)
elif event.action == "tell":
messages.append(
f"{event.character.name} told {event.parameters['character']} something."
)
elif event.action == "examine":
messages.append(
f"{event.character.name} examined the {event.parameters['target']}."
)

return messages


character_buffers: Dict[str, List[GameEvent]] = {}


def digest_listener(event: GameEvent):
if isinstance(event, ActionEvent):
character = event.character.name

# append the event to every character's buffer except the one who triggered it
# the actor should have their buffer reset, because they can only act on their turn

for name, buffer in character_buffers.items():
if name == character:
buffer.clear()
else:
buffer.append(event)


def format_digest(
entity: WorldEntity,
perspective: FormatPerspective = FormatPerspective.SECOND_PERSON,
) -> str:
if not isinstance(entity, Character):
return ""

buffer = character_buffers[entity.name]

world = get_current_world()
if not world:
raise ValueError("No world found")

room = find_containing_room(world, entity)
if not room:
raise ValueError("Character not found in any room")

digest = create_turn_digest(room, entity, buffer)
return "\n".join(digest)


def initialize_digest(world: World):
for room in world.rooms:
for character in room.characters:
character_buffers[character.name] = []


def init():
subscribe(GameEvent, digest_listener)
return [GameSystem("digest", format=format_digest, initialize=initialize_digest)]
3 changes: 2 additions & 1 deletion taleweave/systems/sim/hygiene_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ def action_wash(unused: bool) -> str:

dungeon_master = get_dungeon_master()
outcome = dungeon_master(
f"{action_character.name} washes themselves in the {action_room.name}. {describe_entity(action_room)}. {describe_entity(action_character)}"
f"{action_character.name} washes themselves in the {action_room.name}. "
f"{describe_entity(action_room)}. {describe_entity(action_character)}"
f"{action_character.name} was {hygiene} to start with. How clean are they after washing? Respond with 'clean' or 'dirty'."
"If the room has a shower or running water, they should be cleaner. If the room is dirty, they should end up dirtier."
)
Expand Down
2 changes: 1 addition & 1 deletion taleweave/utils/world.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def describe_static(entity: WorldEntity) -> str:

def describe_entity(
entity: WorldEntity,
perspective: FormatPerspective = FormatPerspective.SECOND_PERSON,
perspective: FormatPerspective = FormatPerspective.THIRD_PERSON,
) -> str:
if isinstance(entity, Character):
return describe_character(entity, perspective)
Expand Down

0 comments on commit d37de8a

Please sign in to comment.