1
0
Fork 0

add digest system, send actions in player prompt

This commit is contained in:
Sean Sube 2024-05-30 22:44:19 -05:00
parent 80e98482e0
commit d37de8a5ab
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
7 changed files with 150 additions and 12 deletions

View File

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

View File

@ -6,6 +6,7 @@ from typing import Any, Callable, Dict, List, Optional, Sequence
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
@ -89,7 +90,28 @@ class BasePlayer:
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
@ -170,12 +192,15 @@ class RemotePlayer(BasePlayer):
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):
@ -193,7 +218,7 @@ class RemotePlayer(BasePlayer):
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 ""

View File

@ -4,6 +4,7 @@ from random import shuffle
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,
@ -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
@ -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
@ -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,
@ -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 "")

View File

@ -115,9 +115,10 @@ def prompt_character_action(
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)

View File

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

View File

@ -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."
)

View File

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