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(
|
outcome = dungeon_master(
|
||||||
f"{action_character.name} uses the {chosen_name} effect of {item} on {target}. "
|
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."
|
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."
|
"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."
|
"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 langchain_core.messages import AIMessage, BaseMessage, HumanMessage
|
||||||
from packit.agent import Agent
|
from packit.agent import Agent
|
||||||
|
from packit.toolbox import Toolbox
|
||||||
|
|
||||||
from taleweave.context import action_context
|
from taleweave.context import action_context
|
||||||
from taleweave.models.event import PromptEvent
|
from taleweave.models.event import PromptEvent
|
||||||
|
@ -89,7 +90,28 @@ class BasePlayer:
|
||||||
Ask the player for input.
|
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):
|
def parse_pseudo_function(self, reply: str):
|
||||||
# turn other replies into a JSON function call
|
# turn other replies into a JSON function call
|
||||||
|
@ -170,12 +192,15 @@ class RemotePlayer(BasePlayer):
|
||||||
self.input_queue = Queue()
|
self.input_queue = Queue()
|
||||||
self.send_prompt = send_prompt
|
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.
|
Ask the player for input.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
formatted_prompt = prompt.format(**kwargs)
|
formatted_prompt = prompt.format(**kwargs)
|
||||||
|
if toolbox:
|
||||||
|
formatted_prompt += self.format_psuedo_functions(toolbox)
|
||||||
|
|
||||||
self.memory.append(HumanMessage(content=formatted_prompt))
|
self.memory.append(HumanMessage(content=formatted_prompt))
|
||||||
|
|
||||||
with action_context() as (current_room, current_character):
|
with action_context() as (current_room, current_character):
|
||||||
|
@ -193,7 +218,7 @@ class RemotePlayer(BasePlayer):
|
||||||
logger.exception("error getting reply from remote player")
|
logger.exception("error getting reply from remote player")
|
||||||
|
|
||||||
if self.fallback_agent:
|
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 self.fallback_agent(prompt, **kwargs)
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -4,6 +4,7 @@ from random import shuffle
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from taleweave.context import get_current_world, get_dungeon_master
|
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.entity import Room, WorldEntity
|
||||||
from taleweave.models.event import (
|
from taleweave.models.event import (
|
||||||
ActionEvent,
|
ActionEvent,
|
||||||
|
@ -32,7 +33,11 @@ def prompt_from_parameters(
|
||||||
if target_character:
|
if target_character:
|
||||||
logger.debug("adding character to prompt: %s", target_character.name)
|
logger.debug("adding character to prompt: %s", target_character.name)
|
||||||
pre.append(f"with {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:
|
if "item" in parameters:
|
||||||
# look up the item
|
# look up the item
|
||||||
|
@ -47,7 +52,9 @@ def prompt_from_parameters(
|
||||||
if target_item:
|
if target_item:
|
||||||
logger.debug("adding item to prompt: %s", target_item.name)
|
logger.debug("adding item to prompt: %s", target_item.name)
|
||||||
pre.append(f"using the {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:
|
if "target" in parameters:
|
||||||
# could be a room, character, or item
|
# could be a room, character, or item
|
||||||
|
@ -60,13 +67,21 @@ def prompt_from_parameters(
|
||||||
if target_room:
|
if target_room:
|
||||||
logger.debug("adding room to prompt: %s", target_room.name)
|
logger.debug("adding room to prompt: %s", target_room.name)
|
||||||
pre.append(f"in the {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)
|
target_character = find_character_in_room(action_room, target_name)
|
||||||
if target_character:
|
if target_character:
|
||||||
logger.debug("adding character to prompt: %s", target_character.name)
|
logger.debug("adding character to prompt: %s", target_character.name)
|
||||||
pre.append(f"with {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(
|
target_item = find_item_in_room(
|
||||||
action_room,
|
action_room,
|
||||||
|
@ -77,7 +92,11 @@ def prompt_from_parameters(
|
||||||
if target_item:
|
if target_item:
|
||||||
logger.debug("adding item to prompt: %s", target_item.name)
|
logger.debug("adding item to prompt: %s", target_item.name)
|
||||||
pre.append(f"using the {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 "")
|
return (" and ".join(pre) if pre else "", " and ".join(post) if post else "")
|
||||||
|
|
||||||
|
|
|
@ -115,9 +115,10 @@ def prompt_character_action(
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if could_be_json(value):
|
if could_be_json(value):
|
||||||
|
# TODO: only emit valid actions that parse and run correctly
|
||||||
event = ActionEvent.from_json(value, room, character)
|
event = ActionEvent.from_json(value, room, character)
|
||||||
else:
|
else:
|
||||||
# TODO: this should be removed and throw
|
# TODO: this path should be removed and throw
|
||||||
event = ResultEvent(value, room, character)
|
event = ResultEvent(value, room, character)
|
||||||
|
|
||||||
broadcast(event)
|
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()
|
dungeon_master = get_dungeon_master()
|
||||||
outcome = 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'."
|
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."
|
"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(
|
def describe_entity(
|
||||||
entity: WorldEntity,
|
entity: WorldEntity,
|
||||||
perspective: FormatPerspective = FormatPerspective.SECOND_PERSON,
|
perspective: FormatPerspective = FormatPerspective.THIRD_PERSON,
|
||||||
) -> str:
|
) -> str:
|
||||||
if isinstance(entity, Character):
|
if isinstance(entity, Character):
|
||||||
return describe_character(entity, perspective)
|
return describe_character(entity, perspective)
|
||||||
|
|
Loading…
Reference in New Issue