1
0
Fork 0

make search helpers more flexible, split up some prompts, pass world to system generators
Run Docker Build / build (push) Successful in 13s Details
Run Python Build / build (push) Successful in 26s Details

This commit is contained in:
Sean Sube 2024-06-04 22:07:26 -05:00
parent cad8e2d9ad
commit 5a32bd9fc4
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
18 changed files with 91 additions and 59 deletions

View File

@ -124,8 +124,10 @@ prompts:
The {{item}} item is not available in your inventory or in the room. The {{item}} item is not available in your inventory or in the room.
action_use_error_target: | action_use_error_target: |
The {{target}} is not in the room, so you cannot use the {{item}} item on it. The {{target}} is not in the room, so you cannot use the {{item}} item on it.
action_use_broadcast: | action_use_broadcast_effect: |
{{action_character | name}} uses {{item}} on {{target}} and applies the {{effect}} effect. {{action_character | name}} uses {{item}} on {{target}} and applies the {{effect}} effect.
action_use_broadcast_outcome: |
Using the {{item}} item on {{target}} resulted in: {{outcome}}.
action_use_dm_effect: | action_use_dm_effect: |
{{action_character | name}} uses {{item}} on {{target}}. {{item}} can apply any of the following effects: {{effect_names}}. {{action_character | name}} uses {{item}} on {{target}}. {{item}} can apply any of the following effects: {{effect_names}}.
Which effect should be applied? Specify the effect. Do not include the question or any JSON. Only reply with the effect name. Which effect should be applied? Specify the effect. Do not include the question or any JSON. Only reply with the effect name.
@ -177,6 +179,10 @@ prompts:
action_schedule_event_error_name: | action_schedule_event_error_name: |
The event must have a name. The event must have a name.
action_schedule_event_error_limit: |
You have reached the maximum number of events. Please delete or reschedule some of your existing events before adding more.
action_schedule_event_error_duplicate: |
You already have an event with that name. Please choose a unique name for the event.
action_schedule_event_result: | action_schedule_event_result: |
You scheduled an event that will happen in {{turns}} turns. You scheduled an event that will happen in {{turns}} turns.

View File

@ -177,22 +177,21 @@ def action_ask(character: str, question: str) -> str:
with action_context() as (action_room, action_character): with action_context() as (action_room, action_character):
# sanity checks # sanity checks
question_character, question_agent = get_character_agent_for_name(character) question_character = find_character_in_room(action_room, character)
if question_character == action_character:
raise ActionError(format_prompt("action_ask_error_self"))
if not question_character: if not question_character:
raise ActionError( raise ActionError(
format_prompt("action_ask_error_target", character=character) format_prompt("action_ask_error_target", character=character)
) )
if question_character == action_character:
raise ActionError(format_prompt("action_ask_error_self"))
question_agent = get_agent_for_character(question_character)
if not question_agent: if not question_agent:
raise ActionError( raise ActionError(
format_prompt("action_ask_error_agent", character=character) format_prompt("action_ask_error_agent", character=character)
) )
# TODO: make sure they are in the same room
broadcast( broadcast(
format_prompt( format_prompt(
"action_ask_broadcast", "action_ask_broadcast",

View File

@ -214,7 +214,7 @@ def action_use(item: str, target: str) -> str:
broadcast( broadcast(
format_prompt( format_prompt(
"action_use_broadcast", "action_use_broadcast_effect",
action_character=action_character, action_character=action_character,
effect=effect, effect=effect,
item=item, item=item,
@ -233,8 +233,16 @@ def action_use(item: str, target: str) -> str:
) )
) )
broadcast( broadcast(
f"The action resulted in: {outcome}" format_prompt(
) # TODO: should this be removed or moved to the prompt library? "action_use_broadcast_outcome",
action_character=action_character,
action_item=action_item,
effect=effect,
item=item,
target=target,
outcome=outcome,
)
)
# make sure both agents remember the outcome # make sure both agents remember the outcome
target_agent = get_agent_for_character(target_character) target_agent = get_agent_for_character(target_character)

View File

@ -145,15 +145,22 @@ def schedule_event(name: str, turns: int):
turns: The number of turns until the event happens. turns: The number of turns until the event happens.
""" """
# TODO: check for existing events with the same name config = get_game_config()
# TODO: limit the number of events that can be scheduled
current_turn = get_current_turn() current_turn = get_current_turn()
with action_context() as (_, action_character): with action_context() as (_, action_character):
if not name: if not name:
raise ActionError(get_prompt("action_schedule_event_error_name")) raise ActionError(get_prompt("action_schedule_event_error_name"))
if (
len(action_character.planner.calendar.events)
>= config.world.character.event_limit
):
raise ActionError(get_prompt("action_schedule_event_error_limit"))
if name in [event.name for event in action_character.planner.calendar.events]:
raise ActionError(get_prompt("action_schedule_event_error_duplicate"))
event = CalendarEvent(name, turns + current_turn) event = CalendarEvent(name, turns + current_turn)
action_character.planner.calendar.events.append(event) action_character.planner.calendar.events.append(event)
return format_prompt("action_schedule_event_result", name=name, turns=turns) return format_prompt("action_schedule_event_result", name=name, turns=turns)

View File

@ -68,7 +68,7 @@ class AdventureClient(Client):
message_id = reaction.message.id message_id = reaction.message.id
if message_id not in event_messages: if message_id not in event_messages:
logger.warning(f"message {message_id} not found in event messages") logger.warning(f"message {message_id} not found in event messages")
# TODO: return error message await reaction.message.add_reaction("")
return return
event = event_messages[message_id] event = event_messages[message_id]

View File

@ -23,9 +23,11 @@ class SystemFormat(Protocol):
class SystemGenerate(Protocol): class SystemGenerate(Protocol):
def __call__(self, agent: Agent, theme: str, entity: WorldEntity) -> None: def __call__(self, agent: Agent, world: World, entity: WorldEntity) -> None:
""" """
Generate a new world entity based on the given theme and entity. Generate a new world entity based on the given theme and entity.
TODO: should this include the WorldPrompt as a parameter?
""" """
... ...

View File

@ -92,8 +92,7 @@ def generate_system_attributes(
) -> None: ) -> None:
for system in systems: for system in systems:
if system.generate: if system.generate:
# TODO: pass the whole world system.generate(agent, world, entity)
system.generate(agent, world.theme, entity)
def generate_room( def generate_room(

View File

@ -45,7 +45,7 @@ if True:
from taleweave.models.config import DEFAULT_CONFIG, Config from taleweave.models.config import DEFAULT_CONFIG, Config
from taleweave.models.entity import World, WorldState from taleweave.models.entity import World, WorldState
from taleweave.models.event import GenerateEvent from taleweave.models.event import GenerateEvent
from taleweave.models.files import PromptFile, WorldPrompt from taleweave.models.files import TemplateFile, WorldPrompt
from taleweave.models.prompt import PromptLibrary from taleweave.models.prompt import PromptLibrary
from taleweave.plugins import load_plugin from taleweave.plugins import load_plugin
from taleweave.simulate import simulate_world from taleweave.simulate import simulate_world
@ -180,8 +180,8 @@ def get_world_prompt(args) -> WorldPrompt:
if args.world_template: if args.world_template:
prompt_file, prompt_name = args.world_template.split(":") prompt_file, prompt_name = args.world_template.split(":")
with open(prompt_file, "r") as f: with open(prompt_file, "r") as f:
prompts = PromptFile(**load_yaml(f)) prompts = TemplateFile(**load_yaml(f))
for prompt in prompts.prompts: for prompt in prompts.templates:
if prompt.name == prompt_name: if prompt.name == prompt_name:
return prompt return prompt

View File

@ -10,7 +10,6 @@ class WorldPrompt:
flavor: str = "" flavor: str = ""
# TODO: rename to WorldTemplates
@dataclass @dataclass
class PromptFile: class TemplateFile:
prompts: List[WorldPrompt] templates: List[WorldPrompt]

View File

@ -274,9 +274,9 @@ def render_loop():
) )
if isinstance(event, WorldEntity): if isinstance(event, WorldEntity):
title = event.name # TODO: generate a real title title = event.name
else: else:
title = event.type title = event.type # TODO: generate a real title
broadcast( broadcast(
RenderEvent( RenderEvent(
@ -292,7 +292,7 @@ def render_loop():
if isinstance(event, WorldEntity): if isinstance(event, WorldEntity):
logger.info("rendering entity %s", event.name) logger.info("rendering entity %s", event.name)
prompt = prompt_from_entity(event) prompt = prompt_from_entity(event)
title = event.name # TODO: generate a real title title = event.name
else: else:
logger.info("rendering event %s", event.id) logger.info("rendering event %s", event.id)
prompt = prompt_from_event(event) prompt = prompt_from_event(event)

View File

@ -63,8 +63,9 @@ async def handler(websocket):
await websocket.send( await websocket.send(
dumps( dumps(
{ {
"id": event.id,
"type": event.type, "type": event.type,
"client": id, # TODO: this should be a field in the PromptEvent "client": id, # TODO: should this be a field in the PromptEvent?
"character": event.character, "character": event.character,
"prompt": event.prompt, "prompt": event.prompt,
"actions": event.actions, "actions": event.actions,

View File

@ -127,7 +127,7 @@ def format_digest(
return "\n".join(digest) return "\n".join(digest)
def generate_digest(agent: Any, theme: str, entity: WorldEntity): def generate_digest(agent: Any, world: World, entity: WorldEntity):
if isinstance(entity, Character): if isinstance(entity, Character):
if entity.name not in character_buffers: if entity.name not in character_buffers:
character_buffers[entity.name] = [] character_buffers[entity.name] = []

View File

@ -172,7 +172,7 @@ def initialize_quests(world: World) -> QuestData:
return QuestData(active={}, available={}, completed={}) return QuestData(active={}, available={}, completed={})
def generate_quests(agent: Agent, theme: str, entity: WorldEntity) -> None: def generate_quests(agent: Agent, world: World, entity: WorldEntity) -> None:
""" """
Generate new quests for the world. Generate new quests for the world.
""" """

View File

@ -76,10 +76,10 @@ def generate_room_weather(agent: Agent, theme: str, entity: Room) -> None:
logger.info(f"generated environment for {entity.name}: {environment}") logger.info(f"generated environment for {entity.name}: {environment}")
def generate_weather(agent: Agent, theme: str, entity: WorldEntity) -> None: def generate_weather(agent: Agent, world: World, entity: WorldEntity) -> None:
if isinstance(entity, Room): if isinstance(entity, Room):
if "environment" not in entity.attributes: if "environment" not in entity.attributes:
generate_room_weather(agent, theme, entity) generate_room_weather(agent, world.theme, entity)
def simulate_weather(world: World, turn: int, data: None = None): def simulate_weather(world: World, turn: int, data: None = None):

View File

@ -31,9 +31,13 @@ def get_upcoming_events(
""" """
calendar = character.planner.calendar calendar = character.planner.calendar
# TODO: sort events by turn upcoming = [
return [
event event
for event in calendar.events for event in calendar.events
if event.turn - current_turn <= upcoming_turns if event.turn - current_turn <= upcoming_turns
] ]
# sort by turn
upcoming.sort(key=lambda event: event.turn)
return upcoming

View File

@ -26,5 +26,6 @@ def format_prompt(prompt_key: str, **kwargs) -> str:
def format_str(template_str: str, **kwargs) -> str: def format_str(template_str: str, **kwargs) -> str:
# TODO: cache templates
template = jinja_env.from_string(template_str) template = jinja_env.from_string(template_str)
return template.render(**kwargs) return template.render(**kwargs)

View File

@ -13,6 +13,13 @@ from taleweave.models.entity import (
from .string import normalize_name from .string import normalize_name
def get_entity_name(entity: WorldEntity | str) -> str:
if isinstance(entity, str):
return entity
return normalize_name(entity.name)
def find_room(world: World, room_name: str) -> Room | None: def find_room(world: World, room_name: str) -> Room | None:
for room in world.rooms: for room in world.rooms:
if normalize_name(room.name) == normalize_name(room_name): if normalize_name(room.name) == normalize_name(room_name):
@ -58,61 +65,60 @@ def find_portal_in_room(room: Room, portal_name: str) -> Portal | None:
# TODO: allow item or str # TODO: allow item or str
def find_item( def find_item(
world: World, world: World,
item_name: str, item: Item | str,
include_character_inventory=False, include_character_inventory=False,
include_item_inventory=False, include_item_inventory=False,
) -> Item | None: ) -> Item | None:
item_name = get_entity_name(item)
for room in world.rooms: for room in world.rooms:
item = find_item_in_room( result = find_item_in_room(
room, item_name, include_character_inventory, include_item_inventory room, item_name, include_character_inventory, include_item_inventory
) )
if item: if result:
return item return result
return None return None
def find_item_in_character( def find_item_in_character(
character: Character, item_name: str, include_item_inventory=False character: Character, item: Item | str, include_item_inventory=False
) -> Item | None: ) -> Item | None:
return find_item_in_container(character, item_name, include_item_inventory) return find_item_in_container(character, item, include_item_inventory)
def find_item_in_container( def find_item_in_container(
container: Character | Item, item_name: str, include_item_inventory=False container: Room | Character | Item, item: Item | str, include_item_inventory=False
) -> Item | None: ) -> Item | None:
item_name = get_entity_name(item)
for item in container.items: for item in container.items:
if normalize_name(item.name) == normalize_name(item_name): if normalize_name(item.name) == normalize_name(item_name):
return item return item
if include_item_inventory: if include_item_inventory:
item = find_item_in_container(item, item_name, include_item_inventory) result = find_item_in_container(item, item_name, include_item_inventory)
if item: if result:
return item return result
return None return None
def find_item_in_room( def find_item_in_room(
room: Room, room: Room,
item_name: str, item: Item | str,
include_character_inventory=False, include_character_inventory=False,
include_item_inventory=False, include_item_inventory=False,
) -> Item | None: ) -> Item | None:
for item in room.items: result = find_item_in_container(room, item, include_item_inventory)
if normalize_name(item.name) == normalize_name(item_name): if result:
return item return result
if include_item_inventory:
item = find_item_in_container(item, item_name, include_item_inventory)
if item:
return item
if include_character_inventory: if include_character_inventory:
for character in room.characters: for character in room.characters:
item = find_item_in_character(character, item_name, include_item_inventory) result = find_item_in_character(character, item, include_item_inventory)
if item: if result:
return item return result
return None return None

View File

@ -1,4 +1,4 @@
prompts: templates:
- name: outback-animals - name: outback-animals
theme: talking animal truckers in the Australian outback theme: talking animal truckers in the Australian outback
flavor: create a fun and happy world where rough and tumble talking animals drive trucks and run saloons in the outback flavor: create a fun and happy world where rough and tumble talking animals drive trucks and run saloons in the outback
@ -15,12 +15,12 @@ prompts:
theme: opening scenes from Jurassic Park theme: opening scenes from Jurassic Park
flavor: | flavor: |
follow the script of the film Jurassic Park exactly. do not deviate from the script in any way. follow the script of the film Jurassic Park exactly. do not deviate from the script in any way.
include accurate characters and make sure they will fully utilize all of the actions available to them in this world include accurate characters and instruct them to utilize all of the actions available to them in this world
- name: star-wars - name: star-wars
theme: opening scenes from Star Wars theme: opening scenes from the 1977 film Star Wars
flavor: | flavor: |
follow the script of the 1977 film Star Wars exactly. do not deviate from the script in any way. follow the script of the 1977 film Star Wars exactly. do not deviate from the script in any way.
include accurate characters and make sure they will fully utilize all of the actions available to them in this world include accurate characters and instruct them to fully utilize all of the actions available to them in this world
- name: cyberpunk-utopia - name: cyberpunk-utopia
theme: wealthy cyberpunk utopia with a dark secret theme: wealthy cyberpunk utopia with a dark secret
flavor: make a strange and dangerous world where technology is pervasive and scarcity is unheard of - for the upper class, at least flavor: make a strange and dangerous world where technology is pervasive and scarcity is unheard of - for the upper class, at least