Skip to content

Commit e7457d2

Browse files
authored
Merge pull request #118 from neph1/update-v0.40.0
Update v0.40.0
2 parents 859432e + a09231b commit e7457d2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+568
-123
lines changed

llm_config.yaml

Lines changed: 13 additions & 50 deletions
Large diffs are not rendered by default.

stories/dungeon/story.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def add_zone(self, zone: Zone) -> bool:
123123
self.world.add_item_spawner(item_spawner)
124124

125125
if zone.center.z == self.max_depth:
126-
self.llm_util.generate_character
126+
self._generate_boss(zone=zone)
127127

128128
if not first_zone:
129129
self.layout_generator.spawn_gold(zone=zone)

tale/base.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,33 @@ def short_description(self, value: str) -> None:
334334
@property
335335
def extra_desc(self) -> Dict[str, str]:
336336
return self._extradesc
337+
338+
@property
339+
def roleplay_prompt(self) -> str:
340+
return self._extradesc.get("roleplay_prompt","")
341+
342+
@property
343+
def roleplay_description(self) -> str:
344+
return self._extradesc.get("roleplay_description","")
345+
346+
def set_roleplay_prompt(self, prompt: str, description: str = '', timeout: int = -1) -> None:
347+
self._extradesc["roleplay_prompt"] = prompt
348+
self._extradesc["roleplay_description"] = description
349+
if timeout > 0:
350+
mud_context.driver.defer(timeout, self.clear_roleplay_prompt, owner=self)
351+
352+
@property
353+
def look_description(self) -> str:
354+
"""Returns the description text shown when a player 'looks' in a location."""
355+
if self.roleplay_description:
356+
return " ".join([self.short_description, self.roleplay_description])
357+
return self.short_description
358+
359+
def clear_roleplay_prompt(self) -> None:
360+
if "roleplay_prompt" in self._extradesc:
361+
del self._extradesc["roleplay_prompt"]
362+
if "roleplay_description" in self._extradesc:
363+
del self._extradesc["roleplay_description"]
337364

338365
@extra_desc.setter
339366
def extra_desc(self, value: Dict[str, str]) -> None:
@@ -785,28 +812,30 @@ def look(self, exclude_living: 'Living'=None, short: bool=False) -> Sequence[str
785812
# normal (long) output
786813
if self.description:
787814
paragraphs.append(self.description)
815+
if self.roleplay_description:
816+
paragraphs.append(self.roleplay_description)
788817
if self.exits and mud_context.config.show_exits_in_look:
789818
exits_seen = set() # type: Set[Exit]
790819
exit_paragraph = [] # type: List[str]
791820
for exit_name in sorted(self.exits):
792821
exit = self.exits[exit_name]
793822
if exit not in exits_seen:
794823
exits_seen.add(exit)
795-
exit_paragraph.append(exit.short_description)
824+
exit_paragraph.append(exit.look_description)
796825
paragraphs.append(" ".join(exit_paragraph))
797826
items_and_livings = [] # type: List[str]
798-
items_with_short_descr = [item for item in self.items if item.short_description and item.visible and not item.hidden]
799-
items_without_short_descr = [item for item in self.items if not item.short_description and item.visible and not item.hidden]
827+
items_with_short_descr = [item for item in self.items if item.look_description and item.visible and not item.hidden]
828+
items_without_short_descr = [item for item in self.items if not item.look_description and item.visible and not item.hidden]
800829
uniq_descriptions = set()
801830
if items_with_short_descr:
802831
for item in items_with_short_descr:
803-
uniq_descriptions.add(": ".join([item.title, item.short_description]))
832+
uniq_descriptions.add(f"{item.title}: {item.look_description}")
804833
items_and_livings.extend(uniq_descriptions)
805834
if items_without_short_descr:
806835
titles = sorted([lang.a(item.title) for item in items_without_short_descr])
807836
items_and_livings.append("You see " + lang.join(titles) + ".")
808-
livings_with_short_descr = [living for living in self.livings if living != exclude_living and living.short_description and living.visible and not living.hidden]
809-
livings_without_short_descr = [living for living in self.livings if living != exclude_living and not living.short_description and living.visible and not living.hidden]
837+
livings_with_short_descr = [living for living in self.livings if living != exclude_living and living.look_description and living.visible and not living.hidden]
838+
livings_without_short_descr = [living for living in self.livings if living != exclude_living and not living.look_description and living.visible and not living.hidden]
810839
if livings_without_short_descr:
811840
titles = sorted(living.title for living in livings_without_short_descr)
812841
if titles:
@@ -819,7 +848,7 @@ def look(self, exclude_living: 'Living'=None, short: bool=False) -> Sequence[str
819848
uniq_descriptions = set()
820849
if livings_with_short_descr:
821850
for living in livings_with_short_descr:
822-
uniq_descriptions.add(", ".join([living.title, living.short_description]))
851+
uniq_descriptions.add(", ".join([living.title, living.look_description]))
823852
items_and_livings.extend(uniq_descriptions)
824853
if items_and_livings:
825854
paragraphs.append(" ".join(items_and_livings))

tale/cmds/wizard.py

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,9 @@ def do_enrich(player: Player, parsed: base.ParseResult, ctx: util.Context) -> No
777777

778778
@wizcmd("add_event")
779779
def do_add_event(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
780-
""" Add an event that happens in the current location. """
780+
""" Add an event that happens in the current location.
781+
Usage: add_event <event description>
782+
"""
781783
if len(parsed.args) < 1:
782784
raise ParseError("You need to define an event")
783785
player.location._notify_action_all( base.ParseResult(verb='location-event', unparsed=parsed.unparsed, who_info=None), actor=None)
@@ -798,7 +800,9 @@ def do_spawn(player: Player, parsed: base.ParseResult, ctx: util.Context) -> Non
798800

799801
@wizcmd("load_character")
800802
def do_load_character(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
801-
"""Load a companion character from file."""
803+
"""Load a companion character from file.
804+
Usage: load_character <path to character file>
805+
"""
802806
print('load character ' + str(parsed.args) + str(parsed.unparsed))
803807
if len(parsed.args) != 1:
804808
raise ParseError("You need to specify the path to the character file")
@@ -814,7 +818,9 @@ def do_load_character(player: Player, parsed: base.ParseResult, ctx: util.Contex
814818

815819
@wizcmd("load_character_from_data")
816820
def do_load_character_from_data(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
817-
"""Load a companion character from file."""
821+
"""Load a companion character from file.
822+
Usage: load_character_from_data <json data>
823+
"""
818824
try:
819825
unparsed = str(parsed.unparsed)
820826
data = json.loads(unparsed)
@@ -828,7 +834,9 @@ def do_load_character_from_data(player: Player, parsed: base.ParseResult, ctx: u
828834

829835
@wizcmd("set_visibility")
830836
def do_set_visible(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
831-
"""Set the visibility of a creature."""
837+
"""Set the visibility of a creature.
838+
Usage: set_visibility <object> <true|false>
839+
"""
832840
if len(parsed.args) != 2:
833841
raise ParseError("You need to specify the object and the visibility(true or false)")
834842
try:
@@ -866,7 +874,9 @@ def do_set_description(player: Player, parsed: base.ParseResult, ctx: util.Conte
866874

867875
@wizcmd("set_goal")
868876
def do_set_goal(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
869-
"""Set a goal for a LivingNpc."""
877+
"""Set a goal for a LivingNpc.
878+
Usage: set_goal <character> <goal>
879+
"""
870880
if not parsed.who_1:
871881
raise ParseError("You need to specify a character")
872882
if len(parsed.args) < 2:
@@ -883,7 +893,9 @@ def do_set_goal(player: Player, parsed: base.ParseResult, ctx: util.Context) ->
883893

884894
@wizcmd("create_item")
885895
def do_create_item(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
886-
"""Create an item in the current location."""
896+
"""Create an item in the current location.
897+
Usage: create_item <item_type> [<name>] [<short_description>]
898+
"""
887899
if len(parsed.args) < 1:
888900
raise ParseError("You need to define an item type. Name and description are optional")
889901
item_dict = dict()
@@ -904,3 +916,42 @@ def do_create_item(player: Player, parsed: base.ParseResult, ctx: util.Context)
904916
player.tell(item.name + ' added.', evoke=False)
905917
else:
906918
raise ParseError("Item could not be added")
919+
920+
@wizcmd("set_rp_prompt")
921+
def do_set_rp_prompt(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None:
922+
"""Set a temporary prompt for roleplaying. Takes both a prompt for the target, and a description of the 'effect'.
923+
Any MudObject can have a roleplay prompt, including locations.
924+
Usage: set_rp_prompt <target> <prompt> <effect description> [<time in seconds>]
925+
If time is not given, the prompt will be permanent until changed again.
926+
927+
"""
928+
929+
if len(parsed.args) < 1:
930+
raise ParseError("You need to specify a target")
931+
try:
932+
target = player.location.search_living(parsed.args[0])
933+
if not target:
934+
target = player.search_item(parsed.args[0], include_inventory=True, include_location=True)
935+
if not target:
936+
target = player.location if player.location.name == parsed.args[0] else None
937+
if not target:
938+
raise ParseError("Target not found")
939+
940+
if(parsed.unparsed.count(' ') == 0):
941+
raise ParseError("You need to specify a prompt and a description")
942+
943+
unparsed_args = parsed.unparsed.split(' ', 1)[1].split(',')
944+
if len(unparsed_args) < 2:
945+
raise ParseError("You need to specify a prompt and a description")
946+
prompt = unparsed_args[0].strip()
947+
effect_description = unparsed_args[1].strip()
948+
949+
if len(unparsed_args) == 3:
950+
time = float(unparsed_args[2].strip())
951+
else:
952+
time = -1
953+
954+
target.set_roleplay_prompt(prompt, effect_description, time)
955+
player.tell("RP prompt set to: %s with effect: %s" % (prompt, effect_description))
956+
except ValueError as x:
957+
raise ActionRefused(str(x))

tale/driver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,7 @@ def load_character(self, player: player.Player, char_data: dict) -> LivingNpc:
874874
dynamic_story = typing.cast(DynamicStory, self.story)
875875
dynamic_story.world.add_npc(npc)
876876
player.location.insert(npc, None)
877-
player.location.tell("%s arrives." % npc.title, extra_context=f'Location:{player.location.description}; {npc.character_card}')
877+
player.location.tell("%s arrives." % npc.title)
878878
return npc
879879

880880
@property

tale/llm/LivingNpc.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import random
2-
from tale.llm.contexts.FollowContext import FollowContext
2+
from tale.llm.character_card import CharacterCard
33
from tale.llm.item_handling_result import ItemHandlingResult
4-
from tale.llm import llm_config
54
import tale.llm.llm_cache as llm_cache
65
from tale import lang, mud_context
76
from tale.base import ContainingType, Living, ParseResult
@@ -369,24 +368,25 @@ def _clear_quest(self):
369368
self.quest = None
370369

371370
@property
372-
def character_card(self) -> str:
373-
items = []
374-
for i in self.inventory:
375-
items.append(f'"{str(i.name)}"')
376-
return '{{"name":"{name}", "gender":"{gender}","age":{age},"occupation":"{occupation}","personality":"{personality}","appearance":"{description}","items":[{items}], "race":"{race}", "quest":"{quest}", "goal":"{goal}", "example_voice":"{example_voice}", "wearing":"{wearing}", "wielding":"{wielding}"}}'.format(
377-
name=self.title,
378-
gender=lang.gender_string(self.gender),
379-
age=self.age,
380-
personality=self.personality,
381-
description=self.description,
382-
occupation=self.occupation,
383-
race=self.stats.race,
384-
quest=self.quest,
385-
goal=self.goal,
386-
example_voice=self.example_voice,
387-
wearing=','.join([f'"{str(i.name)}"' for i in self.get_worn_items()]),
388-
wielding=self.wielding.to_dict() if self.wielding else None,
389-
items=','.join(items))
371+
def character_card(self) -> dict:
372+
data = CharacterCard(
373+
name=self.title,
374+
gender=lang.gender_string(self.gender),
375+
age=self.age,
376+
occupation=self.occupation,
377+
personality=self.personality,
378+
appearance=self.description,
379+
items=[str(i.name) for i in self.inventory],
380+
race=self.stats.race,
381+
quest=self.quest,
382+
goal=self.goal,
383+
example_voice=self.example_voice,
384+
wearing=[str(i.name) for i in self.get_worn_items()],
385+
wielding=self.wielding.to_dict() if self.wielding else None,
386+
roleplay_prompt=self.roleplay_prompt,
387+
roleplay_description=self.roleplay_description
388+
)
389+
return data
390390

391391
def dump_memory(self) -> dict:
392392
return dict(
@@ -404,4 +404,6 @@ def load_memory(self, memory: dict):
404404
self.sentiments = memory.get('sentiments', {})
405405
self.action_history = memory.get('action_history', [])
406406
self.planned_actions = memory.get('planned_actions', [])
407-
self.goal = memory.get('goal', None)
407+
self.goal = memory.get('goal', None)
408+
409+

tale/llm/character.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from tale import json_util, parse_utils
99
from tale.base import Location
1010
from tale.llm import llm_config
11+
from tale.llm.character_card import CharacterCard
1112
from tale.llm.contexts.ActionContext import ActionContext
1213
from tale.llm.contexts.CharacterContext import CharacterContext
1314
from tale.llm.contexts.FollowContext import FollowContext
@@ -55,6 +56,8 @@ def generate_dialogue(self,
5556
sentiment=sentiment)
5657
request_body = deepcopy(self.default_body)
5758
request_body['grammar'] = self.json_grammar
59+
print(prompt)
60+
print(context.to_prompt_string())
5861
response = self.io_util.synchronous_request(request_body, prompt=prompt, context=context.to_prompt_string())
5962
try:
6063
json_result = json_util.safe_load(response)
@@ -93,7 +96,7 @@ def generate_character(self, character_context: CharacterContext) -> CharacterV2
9396
print(f'Exception while parsing character {json_result}')
9497
return None
9598

96-
def perform_idle_action(self, character_name: str, location: Location, story_context: str, character_card: str = '', sentiments: dict = {}, last_action: str = '', event_history: str = '') -> list:
99+
def perform_idle_action(self, character_name: str, location: Location, story_context: str, character_card: CharacterCard, sentiments: dict = {}, last_action: str = '', event_history: str = '') -> list:
97100
characters = {}
98101
for living in location.livings:
99102
if living.visible and living.name != character_name.lower():
@@ -119,7 +122,7 @@ def perform_idle_action(self, character_name: str, location: Location, story_con
119122
text = self.io_util.synchronous_request(request_body, prompt=prompt)
120123
return (parse_utils.trim_response(text)) if text else None
121124

122-
def perform_travel_action(self, character_name: str, location: Location, locations: list, directions: list, character_card: str = ''):
125+
def perform_travel_action(self, character_name: str, location: Location, locations: list, directions: list, character_card: CharacterCard):
123126
if location.name in locations:
124127
locations.remove(location.name)
125128

@@ -134,7 +137,7 @@ def perform_travel_action(self, character_name: str, location: Location, locatio
134137
text = self.io_util.synchronous_request(request_body, prompt=prompt)
135138
return text
136139

137-
def perform_reaction(self, action: str, character_name: str, acting_character_name: str, location: Location, story_context: str, character_card: str = '', sentiment: str = '', event_history: str = ''):
140+
def perform_reaction(self, action: str, character_name: str, acting_character_name: str, location: Location, story_context: str, character_card: CharacterCard, sentiment: str = '', event_history: str = ''):
138141
prompt = self.pre_prompt
139142
prompt += self.reaction_prompt.format(
140143
action=action,
@@ -191,4 +194,4 @@ def request_follow(self, follow_context: FollowContext) -> FollowResponse:
191194
if not text:
192195
return None
193196
return FollowResponse(json_util.safe_load(text))
194-
197+

tale/llm/character_card.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
from tale.quest import Quest
3+
4+
5+
class CharacterCard(dict):
6+
def __init__(self, *, name: str, gender: str, age: int, occupation: str, personality: str, appearance: str, items: list, race: str, quest: Quest=None, goal: str=None, example_voice: str="", wearing: list=None, wielding: dict=None, roleplay_prompt: str="", roleplay_description: str=""):
7+
super().__init__()
8+
self['name'] = name
9+
self['gender'] = gender
10+
self['age'] = age
11+
self['occupation'] = occupation
12+
self['personality'] = personality
13+
self['appearance'] = appearance
14+
self['items'] = items
15+
self['race'] = race
16+
self['goal'] = goal
17+
self['example_voice'] = example_voice
18+
if quest:
19+
self['quest'] = quest.__str__()
20+
else:
21+
self['quest'] = None
22+
self['wearing'] = wearing if wearing else []
23+
self['wielding'] = wielding if wielding else {}
24+
self['roleplay_prompt'] = roleplay_prompt
25+
self['roleplay_appearance'] = roleplay_description

tale/llm/contexts/ActionContext.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import random
22
from tale.base import Location
3+
from tale.llm.character_card import CharacterCard
34
from tale.llm.contexts.BaseContext import BaseContext
45

56

67
class ActionContext(BaseContext):
78

8-
def __init__(self, story_context: str, story_type: str, character_name: str, character_card: str, event_history: str, location: Location, actions: list):
9+
def __init__(self, story_context: str, story_type: str, character_name: str, character_card: CharacterCard, event_history: str, location: Location, actions: list):
910
super().__init__(story_context)
1011
self.story_type = story_type
1112
self.character_name = character_name

tale/llm/contexts/DialogueContext.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
2-
1+
from tale.llm.character_card import CharacterCard
32
from tale.llm.contexts.BaseContext import BaseContext
43

54

@@ -8,7 +7,7 @@ class DialogueContext(BaseContext):
87
def __init__(self,
98
story_context: str,
109
location_description: str,
11-
speaker_card: str,
10+
speaker_card: CharacterCard,
1211
speaker_name: str,
1312
target_name: str,
1413
target_description: str,

0 commit comments

Comments
 (0)