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.
action_use_error_target: |
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_use_broadcast_outcome: |
Using the {{item}} item on {{target}} resulted in: {{outcome}}.
action_use_dm_effect: |
{{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.
@ -177,6 +179,10 @@ prompts:
action_schedule_event_error_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: |
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):
# sanity checks
question_character, question_agent = get_character_agent_for_name(character)
if question_character == action_character:
raise ActionError(format_prompt("action_ask_error_self"))
question_character = find_character_in_room(action_room, character)
if not question_character:
raise ActionError(
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:
raise ActionError(
format_prompt("action_ask_error_agent", character=character)
)
# TODO: make sure they are in the same room
broadcast(
format_prompt(
"action_ask_broadcast",

View File

@ -214,7 +214,7 @@ def action_use(item: str, target: str) -> str:
broadcast(
format_prompt(
"action_use_broadcast",
"action_use_broadcast_effect",
action_character=action_character,
effect=effect,
item=item,
@ -233,8 +233,16 @@ def action_use(item: str, target: str) -> str:
)
)
broadcast(
f"The action resulted in: {outcome}"
) # TODO: should this be removed or moved to the prompt library?
format_prompt(
"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
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.
"""
# TODO: check for existing events with the same name
# TODO: limit the number of events that can be scheduled
config = get_game_config()
current_turn = get_current_turn()
with action_context() as (_, action_character):
if not 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)
action_character.planner.calendar.events.append(event)
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
if message_id not in event_messages:
logger.warning(f"message {message_id} not found in event messages")
# TODO: return error message
await reaction.message.add_reaction("")
return
event = event_messages[message_id]

View File

@ -23,9 +23,11 @@ class SystemFormat(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.
TODO: should this include the WorldPrompt as a parameter?
"""
...

View File

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

View File

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

View File

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

View File

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

View File

@ -63,8 +63,9 @@ async def handler(websocket):
await websocket.send(
dumps(
{
"id": event.id,
"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,
"prompt": event.prompt,
"actions": event.actions,

View File

@ -127,7 +127,7 @@ def format_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 entity.name not in character_buffers:
character_buffers[entity.name] = []

View File

@ -172,7 +172,7 @@ def initialize_quests(world: World) -> QuestData:
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.
"""

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}")
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 "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):

View File

@ -31,9 +31,13 @@ def get_upcoming_events(
"""
calendar = character.planner.calendar
# TODO: sort events by turn
return [
upcoming = [
event
for event in calendar.events
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:
# TODO: cache templates
template = jinja_env.from_string(template_str)
return template.render(**kwargs)

View File

@ -13,6 +13,13 @@ from taleweave.models.entity import (
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:
for room in world.rooms:
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
def find_item(
world: World,
item_name: str,
item: Item | str,
include_character_inventory=False,
include_item_inventory=False,
) -> Item | None:
item_name = get_entity_name(item)
for room in world.rooms:
item = find_item_in_room(
result = find_item_in_room(
room, item_name, include_character_inventory, include_item_inventory
)
if item:
return item
if result:
return result
return None
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:
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(
container: Character | Item, item_name: str, include_item_inventory=False
container: Room | Character | Item, item: Item | str, include_item_inventory=False
) -> Item | None:
item_name = get_entity_name(item)
for item in container.items:
if normalize_name(item.name) == normalize_name(item_name):
return item
if include_item_inventory:
item = find_item_in_container(item, item_name, include_item_inventory)
if item:
return item
result = find_item_in_container(item, item_name, include_item_inventory)
if result:
return result
return None
def find_item_in_room(
room: Room,
item_name: str,
item: Item | str,
include_character_inventory=False,
include_item_inventory=False,
) -> Item | None:
for item in room.items:
if normalize_name(item.name) == normalize_name(item_name):
return item
if include_item_inventory:
item = find_item_in_container(item, item_name, include_item_inventory)
if item:
return item
result = find_item_in_container(room, item, include_item_inventory)
if result:
return result
if include_character_inventory:
for character in room.characters:
item = find_item_in_character(character, item_name, include_item_inventory)
if item:
return item
result = find_item_in_character(character, item, include_item_inventory)
if result:
return result
return None

View File

@ -1,4 +1,4 @@
prompts:
templates:
- name: outback-animals
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
@ -15,12 +15,12 @@ prompts:
theme: opening scenes from Jurassic Park
flavor: |
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
theme: opening scenes from Star Wars
theme: opening scenes from the 1977 film Star Wars
flavor: |
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
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