add digest system, send actions in player prompt
This commit is contained in:
parent
80e98482e0
commit
d37de8a5ab
|
@ -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."
|
||||
|
|
|
@ -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 ""
|
||||
|
|
|
@ -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 "")
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)]
|
|
@ -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."
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue