From 6774d240982d809d2f34a2b89d7bfc9144862d2d Mon Sep 17 00:00:00 2001 From: rickard Date: Fri, 1 Nov 2024 20:58:28 +0100 Subject: [PATCH 01/10] load story wearables --- tale/json_story.py | 4 +++- tale/wearable.py | 15 +++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tale/json_story.py b/tale/json_story.py index 866a89b4..e6d612ba 100644 --- a/tale/json_story.py +++ b/tale/json_story.py @@ -1,4 +1,4 @@ -from tale import load_items +from tale import load_items, wearable from tale.items import generic from tale.llm.dynamic_story import DynamicStory from tale.player import Player @@ -34,6 +34,8 @@ def init(self, driver) -> None: self._catalogue._creatures = world['catalogue']['creatures'] if world['catalogue']['items']: self._catalogue._items = world['catalogue']['items'] + if world['catalogue']['wearables']: + wearable.add_story_wearables(world['catalogue']['wearables']) if world.get('world', None): if world['world']['items']: # Keep this so that saved items in worlds will transfer to locations. But don't save them. diff --git a/tale/wearable.py b/tale/wearable.py index 8203f9cb..a400a61c 100644 --- a/tale/wearable.py +++ b/tale/wearable.py @@ -35,14 +35,8 @@ def load_wearables_from_json(file_path): wearables_fantasy = load_wearables_from_json('../items/wearables_fantasy.json') wearables_modern = load_wearables_from_json('../items/wearables_modern.json') +wearbles_story = [] -# Disclaimer: Not to limit the player, but to give the generator some hints -female_clothing_modern = {'dress', 'dress_shirt', 'blouse', 'skirt', 'bra', 'panties', 'thong', 'stockings', 'top'} -male_clothing_modern = {'suit', 'boxers', 'briefs', 'shirt'} -neutral_clothing_modern = {'t-shirt', 'shirt', 'jeans', 'sneakers', 'belt', 'dress_shoes', 'hat', 'coveralls', 'sweater', 'socks', 'coat', 'jacket'} - - - dressable_body_types = [BodyType.HUMANOID, BodyType.SEMI_BIPEDAL, BodyType.WINGED_MAN] def body_parts_for_bodytype(bodytype: BodyType) -> list: @@ -53,13 +47,18 @@ def body_parts_for_bodytype(bodytype: BodyType) -> list: return None def random_wearable_for_body_part(bodypart: WearLocation, setting: str = 'fantasy', armor_only = False) -> dict: + wearables = [] if setting == 'fantasy': wearables = wearables_fantasy - else: + elif setting == 'modern' or setting == 'sci-fi' or setting == 'post-apocalyptic': wearables = wearables_modern + wearables.extend(wearbles_story) available_wearables = [item for item in wearables if item['location'] == bodypart and (not armor_only or item.get('ac', 0) > 0)] if not available_wearables: return None wearable = random.choice(available_wearables) wearable['short_descr'] = f"{random.choice(wearable_colors)} {wearable['name']}" return wearable + +def add_story_wearables(wearables: list): + wearbles_story.extend(wearables) \ No newline at end of file From c15e741a882d8cd368770c19ed79c795d3b058a6 Mon Sep 17 00:00:00 2001 From: rickard Date: Fri, 1 Nov 2024 21:47:17 +0100 Subject: [PATCH 02/10] some combat tweaks and durability decrease --- tale/base.py | 2 ++ tale/combat.py | 51 +++++++++++++++++++++++++++----------------- tests/test_combat.py | 7 +++--- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/tale/base.py b/tale/base.py index 49dc9fc3..bb0c2634 100644 --- a/tale/base.py +++ b/tale/base.py @@ -440,6 +440,7 @@ def __init__(self, name: str, title: str = "", *, descr: str = "", short_descr: self.rent = 0.0 # price to keep in store / day self.weight = 0.0 # some abstract unit self.takeable = True # can this item be taken/picked up? + self.durability = 100 # how long can this item last? super().__init__(name, title=title, descr=descr, short_descr=short_descr) def init(self) -> None: @@ -459,6 +460,7 @@ def to_dict(self) -> Dict[str, Any]: "rent": self.rent, "weight": self.weight, "takeable": self.takeable, + "durability": self.durability, "location" : self.location.name if self.location else '' } diff --git a/tale/combat.py b/tale/combat.py index f585a57e..5a7eee6d 100644 --- a/tale/combat.py +++ b/tale/combat.py @@ -52,6 +52,11 @@ def _calculate_armor_bonus(self, actor: 'base.Living', body_part: WearLocation = return wearable.ac if wearable else 1 return actor.stats.ac + 1 + def _subtract_armor_durability(self, actor: 'base.Living', body_part: WearLocation, amount: int): + wearable = actor.get_wearable(body_part) + if wearable: + wearable.durability -= amount + def resolve_body_part(self, defender: 'base.Living', size_factor: float, target_part: WearLocation = None) -> WearLocation: """ Resolve the body part that was hit. """ body_parts = body_parts_for_bodytype(defender.stats.bodytype) @@ -94,49 +99,55 @@ def resolve_attack(self) -> str: for attacker in self.attackers: random_defender = random.choice(self.defenders) text_result, damage_to_defender = self._round(attacker, random_defender) - texts.extend(text_result) + texts.append(text_result) random_defender.stats.hp -= damage_to_defender if random_defender.stats.hp < 1: - texts.append(f'{random_defender.title} dies') + texts.append(f'{random_defender.title} dies from their injuries.') for defender in self.defenders: + if defender.stats.hp < 1: + continue random_attacker = random.choice(self.attackers) text_result, damage_to_attacker = self._round(defender, random_attacker) - texts.extend(text_result) + texts.append(text_result) random_attacker.stats.hp -= damage_to_attacker if random_attacker.stats.hp < 1: - texts.append(f'{random_attacker.title} dies') + texts.append(f'{random_attacker.title} dies from their injuries.') - return ', '.join(texts) + return '\n'.join(texts) def _round(self, actor1: 'base.Living', actor2: 'base.Living') -> Tuple[List[str], int]: attack_result = self._calculate_attack_success(actor1) - texts = [] + attack_text = f'{actor1.title} attacks {actor2.title} with their {actor1.wielding.name}' if attack_result < 0: if attack_result < -actor1.stats.weapon_skills.get(actor1.wielding.type) + 5: - texts.append(f'{actor1.title} performs a critical hit on {actor2.title}') + attack_text += ' and hits critically' block_result = 100 else: - texts.append(f'{actor1.title} hits {actor2.title}') + attack_text += ' and hits' block_result = self._calculate_block_success(actor1, actor2) - + if block_result < 0: - texts.append(f'but {actor2.title} blocks') + attack_text += f', but {actor2.title} blocks with their {actor2.wielding.name}' + actor2.wielding.durability -= random.randint(1, 10) else: - actor1_strength = self._calculate_weapon_bonus(actor1) * actor1.stats.size.order + actor1_attack = self._calculate_weapon_bonus(actor1) * actor1.stats.size.order body_part = self.resolve_body_part(actor2, actor1.stats.size.order / actor2.stats.size.order, target_part=self.target_body_part) - actor2_strength = self._calculate_armor_bonus(actor2, body_part) * actor2.stats.size.order - damage_to_defender = int(max(0, actor1_strength - actor2_strength)) + actor2_defense = self._calculate_armor_bonus(actor2, body_part) * actor2.stats.size.order + damage_to_defender = int(max(0, actor1_attack - actor2_defense)) if damage_to_defender > 0: - texts.append(f', {actor2.title} is injured in the {body_part.name.lower()}') + attack_text += f', and {actor2.title} is injured in the {body_part.name.lower()}.' + elif actor1_attack < actor2_defense: + attack_text += f', but {actor2.title}\' armor protects them.' + self._subtract_armor_durability(actor2, body_part, actor1_attack) else: - texts.append(f', {actor2.title} is unharmed') - return texts, damage_to_defender + attack_text += f', but {actor2.title} is unharmed.' + return attack_text, damage_to_defender elif attack_result > 50: - texts.append(f'{actor1.title} misses {actor2.title} completely') + attack_text + f', but misses completely.' elif attack_result > 25: - texts.append(f'{actor1.title} misses {actor2.title}') + attack_text + f', but misses.' else: - texts.append(f'{actor1.title} barely misses {actor2.title}') - return texts, 0 + attack_text + f', and barely misses.' + return attack_text, 0 diff --git a/tests/test_combat.py b/tests/test_combat.py index 0d968ede..1589c248 100644 --- a/tests/test_combat.py +++ b/tests/test_combat.py @@ -32,9 +32,10 @@ def test_resolve_attack(self): combat = Combat([attacker], [defender]) text = combat.resolve_attack() - assert('attacker hits' in text or 'attacker performs a critical hit' in text) + assert('hits' in text) assert('defender is injured' in text) assert('defender dies' in text) + assert not 'defender attacks' in text def test_block_ranged_fails(self): @@ -235,8 +236,8 @@ def test_resolve_attack_group(self): text = combat.resolve_attack() self._assert_combat(attacker, defender, text) - assert('attacker hits' in text or 'attacker performs a critical hit' in text) - assert('attacker2 hits' in text or 'attacker2 performs a critical hit' in text) + assert('attacker attacks') + assert('attacker2 attacks') def test_start_attack_no_combat_points(self): attacker = Player(name='att', gender='m') From a64ae4b1f115cbbd230f943c7e847fc9b5de2936 Mon Sep 17 00:00:00 2001 From: rickard Date: Sun, 3 Nov 2024 14:20:43 +0100 Subject: [PATCH 03/10] add option to use password for koboldcpp api add api key to streaming headers --- backend_kobold_cpp.yaml | 3 ++- tale/llm/io_adapters.py | 16 +++++++++------- tale/llm/llm_io.py | 6 ++++-- tests/test_living_npc.py | 1 + 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/backend_kobold_cpp.yaml b/backend_kobold_cpp.yaml index 683b6adc..ca72b1f2 100644 --- a/backend_kobold_cpp.yaml +++ b/backend_kobold_cpp.yaml @@ -5,4 +5,5 @@ JSON_GRAMMAR_KEY: "grammar" STREAM_ENDPOINT: "/api/extra/generate/stream" DATA_ENDPOINT: "/api/extra/generate/check" DEFAULT_BODY: '{"stop_sequence": "", "max_length":1500, "max_context_length":4096, "temperature":0.5, "top_k":120, "top_a":0.0, "top_p":0.85, "typical_p":1.0, "tfs":1.0, "rep_pen":1.2, "rep_pen_range":256, "sampler_order":[6,0,1,3,4,2,5], "seed":-1}' -GENERATION_BODY: '{"stop_sequence": "", "max_length":1500, "max_context_length":4096, "temperature":1.0, "top_k":120, "top_a":0.0, "top_p":0.85, "typical_p":1.0, "tfs":1.0, "rep_pen":1.2, "rep_pen_range":256, "sampler_order":[6,0,1,3,4,2,5], "seed":-1}' \ No newline at end of file +GENERATION_BODY: '{"stop_sequence": "", "max_length":1500, "max_context_length":4096, "temperature":1.0, "top_k":120, "top_a":0.0, "top_p":0.85, "typical_p":1.0, "tfs":1.0, "rep_pen":1.2, "rep_pen_range":256, "sampler_order":[6,0,1,3,4,2,5], "seed":-1}' +API_PASSWORD: "" # if koboldcpp is run with the --password flag, this must be set to the same password \ No newline at end of file diff --git a/tale/llm/io_adapters.py b/tale/llm/io_adapters.py index 7cbb4a2a..41f569d7 100644 --- a/tale/llm/io_adapters.py +++ b/tale/llm/io_adapters.py @@ -22,7 +22,7 @@ def __init__(self, url: str, stream_endpoint: str, user_start_prompt: str = '', self.prompt_end = prompt_end @abstractmethod - def stream_request(self, request_body: dict, io = None, wait: bool = False) -> str: + def stream_request(self, headers: dict, request_body: dict, io = None, wait: bool = False) -> str: pass @abstractmethod @@ -44,8 +44,8 @@ def __init__(self, url: str, stream_endpoint: str, data_endpoint: str, user_star self.data_endpoint = data_endpoint self.place_context_in_memory = False - def stream_request(self, request_body: dict, io: PlayerConnection = None, wait: bool = False) -> str: - result = asyncio.run(self._do_stream_request(self.url + self.stream_endpoint, request_body)) + def stream_request(self, headers: dict, request_body: dict, io: PlayerConnection = None, wait: bool = False) -> str: + result = asyncio.run(self._do_stream_request(self.url + self.stream_endpoint, headers, request_body)) try: if result: @@ -54,9 +54,10 @@ def stream_request(self, request_body: dict, io: PlayerConnection = None, wait: print("Error parsing response from backend - ", exc) return '' - async def _do_stream_request(self, url: str, request_body: dict,) -> bool: + async def _do_stream_request(self, url: str, headers: dict, request_body: dict,) -> bool: """ Send request to stream endpoint async to not block the main thread""" async with aiohttp.ClientSession() as session: + session.headers.update(headers) async with session.post(url, data=json.dumps(request_body)) as response: if response.status == 200: return True @@ -103,14 +104,15 @@ def set_prompt(self, request_body: dict, prompt: str, context: str = '') -> dict class LlamaCppAdapter(AbstractIoAdapter): - def stream_request(self, request_body: dict, io: PlayerConnection = None, wait: bool = False) -> str: - return asyncio.run(self._do_stream_request(self.url + self.stream_endpoint, request_body, io = io)) + def stream_request(self, headers: dict, request_body: dict, io: PlayerConnection = None, wait: bool = False) -> str: + return asyncio.run(self._do_stream_request(self.url + self.stream_endpoint, headers, request_body, io = io)) - async def _do_stream_request(self, url: str, request_body: dict, io: PlayerConnection) -> str: + async def _do_stream_request(self, url: str, headers: dict, request_body: dict, io: PlayerConnection) -> str: """ Send request to stream endpoint async to not block the main thread""" request_body['stream'] = True text = '' async with aiohttp.ClientSession() as session: + session.headers.update(headers) async with session.post(url, data=json.dumps(request_body)) as response: if response.status != 200: print("Error occurred:", response.status) diff --git a/tale/llm/llm_io.py b/tale/llm/llm_io.py index a79476bb..aa6bbdc5 100644 --- a/tale/llm/llm_io.py +++ b/tale/llm/llm_io.py @@ -13,6 +13,7 @@ def __init__(self, config: dict = None, backend_config: dict = None): self.backend = config['BACKEND'] self.url = backend_config['URL'] self.endpoint = backend_config['ENDPOINT'] + headers = {} if self.backend != 'kobold_cpp': headers = json.loads(backend_config['OPENAI_HEADERS']) headers['Authorization'] = f"Bearer {backend_config['OPENAI_API_KEY']}" @@ -20,8 +21,9 @@ def __init__(self, config: dict = None, backend_config: dict = None): self.headers = headers self.io_adapter = LlamaCppAdapter(self.url, backend_config['STREAM_ENDPOINT'], config.get('USER_START', ''), config.get('USER_END', ''), config.get('SYSTEM_START', ''), config.get('PROMPT_END', '')) else: + headers['Authorization'] = f"Bearer {backend_config['API_PASSWORD']}" + self.headers = headers self.io_adapter = KoboldCppAdapter(self.url, backend_config['STREAM_ENDPOINT'], backend_config['DATA_ENDPOINT'], config.get('USER_START', ''), config.get('USER_END', ''), config.get('SYSTEM_START', ''), config.get('PROMPT_END', '')) - self.headers = {} self.stream = backend_config['STREAM'] @@ -46,7 +48,7 @@ def asynchronous_request(self, request_body: dict, prompt: str, context: str = ' def stream_request(self, request_body: dict, prompt: str, context: str = '', io = None, wait: bool = False) -> str: if self.io_adapter: request_body = self.io_adapter.set_prompt(request_body, prompt, context) - return self.io_adapter.stream_request(request_body, io, wait) + return self.io_adapter.stream_request(self.headers, request_body, io, wait) # fall back if no io adapter return self.synchronous_request(request_body=request_body, prompt=prompt, context=context) diff --git a/tests/test_living_npc.py b/tests/test_living_npc.py index ee656d25..7b594dc8 100644 --- a/tests/test_living_npc.py +++ b/tests/test_living_npc.py @@ -182,6 +182,7 @@ class TestLivingNpcActions(): 'OPENAI_HEADERS': '', 'OPENAI_API_KEY': '', 'OPENAI_JSON_FORMAT': '', + 'API_PASSWORD': '' } driver = FakeDriver() driver.story = DynamicStory() From 9caafaed174183fabbb2411d0624d8ed5bfbd4dd Mon Sep 17 00:00:00 2001 From: rickard Date: Sun, 3 Nov 2024 14:21:59 +0100 Subject: [PATCH 04/10] add new combat prompt --- llm_config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/llm_config.yaml b/llm_config.yaml index 5b915c40..d5754b2a 100644 --- a/llm_config.yaml +++ b/llm_config.yaml @@ -1,6 +1,6 @@ WORD_LIMIT: 200 # max number of words the model is encoraged to generate. not a hard limit SHORT_WORD_LIMIT: 25 # max number of words when asked to write something short. not a hard limit -BACKEND: "kobold_cpp" # valid options: "openai", "llama_cpp", "kobold_cpp". if using ooba, use and modify openai template +BACKEND: "llama_cpp" # valid options: "openai", "llama_cpp", "kobold_cpp". if using ooba, use and modify openai template MEMORY_SIZE: 512 UNLIMITED_REACTS: False DIALOGUE_TEMPLATE: '{{"response":"may be both dialogue and action.", "sentiment":"sentiment based on response", "give":"if any physical item of {character2}s is given as part of the dialogue. Or nothing."}}' @@ -19,7 +19,7 @@ ITEM_TYPES: ["Weapon", "Wearable", "Health", "Money", "Trash"] PRE_PROMPT: 'You are a creative game keeper for an interactive fiction story telling session. You craft detailed worlds and interesting characters with unique and deep personalities for the player to interact with. Always follow the instructions given, never acknowledge the task or speak directly to the user or respond with anything besides the request.' BASE_PROMPT: '{context}\n[USER_START] Rewrite [{input_text}] in your own words. The information inside the tags should be used to ensure it fits the story. Use about {max_words} words.' DIALOGUE_PROMPT: '{context}\nThe following is a conversation between {character1} and {character2}; {character2}s sentiment towards {character1}: {sentiment}. Write a single response as {character2} in third person pov, using {character2} description and other information found inside the tags. If {character2} has a quest active, they will discuss it based on its status. Respond in JSON using this template: """{dialogue_template}""". [USER_START]Continue the following conversation as {character2}: {previous_conversation}' -COMBAT_PROMPT: '{context}\nThe following is a combat scene between {attackers} and {defenders} in {location}. [USER_START] Describe the following combat result in about 150 words in vivid language, using the characters weapons and their health status: 1.0 is highest, 0.0 is lowest. Combat Result: {input_text}' +COMBAT_PROMPT: '{context}\nThe following is a combat scene between {attackers} and {defenders} in {location}. [USER_START] Describe the following combat result in about 150 words in vivid language, using the characters weapons and describe their health status without mentioning numbers: 1.0 is highest, 0.0 is dead. {input_text}' PRE_JSON_PROMPT: 'Below is an instruction that describes a task, paired with an input that provides further context. Write a response in valid JSON format that appropriately completes the request.' CREATE_CHARACTER_PROMPT: '{context}\n[USER_START] Create a diverse character with rich personality that can be interacted with using the story context and keywords. {{quest_prompt}} Do not mention height. Keywords: {keywords}. Fill in the blanks in this JSON template and write nothing else: {character_template}' CREATE_LOCATION_PROMPT: '{context}\nZone info: {zone_info}; Exit json example: {exit_template}; Npc or mob example: {npc_template}. Existing connected locations: {exit_locations}. [USER_START] Using the information supplied inside the tags, describe the following location: {location_name}. {items_prompt} {spawn_prompt} Add a brief description, and one to three additional exits leading to new locations. Fill in this JSON template and do not write anything else: {location_template}. Write the response in valid JSON.' From 291eb0d8dc4ff4698404aa0eaa1c1c3b2d79a66a Mon Sep 17 00:00:00 2001 From: rickard Date: Sun, 3 Nov 2024 14:22:38 +0100 Subject: [PATCH 05/10] load wearables in json_story --- tale/json_story.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tale/json_story.py b/tale/json_story.py index e6d612ba..cffb02c3 100644 --- a/tale/json_story.py +++ b/tale/json_story.py @@ -34,7 +34,7 @@ def init(self, driver) -> None: self._catalogue._creatures = world['catalogue']['creatures'] if world['catalogue']['items']: self._catalogue._items = world['catalogue']['items'] - if world['catalogue']['wearables']: + if world['catalogue'].get('wearables', None): wearable.add_story_wearables(world['catalogue']['wearables']) if world.get('world', None): if world['world']['items']: From e01537070b67679d34fc3d455e0823b239c41551 Mon Sep 17 00:00:00 2001 From: rickard Date: Sun, 3 Nov 2024 14:34:55 +0100 Subject: [PATCH 06/10] remove restart command --- tale/cmds/wizard.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tale/cmds/wizard.py b/tale/cmds/wizard.py index 4ea94a5e..2b427f57 100644 --- a/tale/cmds/wizard.py +++ b/tale/cmds/wizard.py @@ -904,9 +904,3 @@ def do_create_item(player: Player, parsed: base.ParseResult, ctx: util.Context) player.tell(item.name + ' added.', evoke=False) else: raise ParseError("Item could not be added") - -@wizcmd("restart_story") -def do_restart(player: Player, parsed: base.ParseResult, ctx: util.Context) -> None: - """Restart the game.""" - player.tell("Restarting the game... Please reconnect") - os.execv(sys.executable, ['python3'] + sys.argv) \ No newline at end of file From 3098f5aeedb676599c1e6e6401596eb5489ea120 Mon Sep 17 00:00:00 2001 From: rickard Date: Sun, 3 Nov 2024 14:36:08 +0100 Subject: [PATCH 07/10] fix test --- tests/test_story.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_story.py b/tests/test_story.py index d3fbdddf..1f236d6e 100644 --- a/tests/test_story.py +++ b/tests/test_story.py @@ -24,6 +24,7 @@ class TestStoryContext: 'OPENAI_HEADERS': '', 'OPENAI_API_KEY': '', 'OPENAI_JSON_FORMAT': '', + 'API_PASSWORD': '', } def test_initialization(self): From fccd69f5d6b53ce1b4774617a5687ab2db8ebef9 Mon Sep 17 00:00:00 2001 From: rickard Date: Sun, 3 Nov 2024 14:39:33 +0100 Subject: [PATCH 08/10] reset config --- llm_config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llm_config.yaml b/llm_config.yaml index d5754b2a..b1f3ac9d 100644 --- a/llm_config.yaml +++ b/llm_config.yaml @@ -1,6 +1,6 @@ WORD_LIMIT: 200 # max number of words the model is encoraged to generate. not a hard limit SHORT_WORD_LIMIT: 25 # max number of words when asked to write something short. not a hard limit -BACKEND: "llama_cpp" # valid options: "openai", "llama_cpp", "kobold_cpp". if using ooba, use and modify openai template +BACKEND: "kobold_cpp" # valid options: "openai", "llama_cpp", "kobold_cpp". if using ooba, use and modify openai template MEMORY_SIZE: 512 UNLIMITED_REACTS: False DIALOGUE_TEMPLATE: '{{"response":"may be both dialogue and action.", "sentiment":"sentiment based on response", "give":"if any physical item of {character2}s is given as part of the dialogue. Or nothing."}}' From 3ef48122691a64e4824cdf74bdc0e6e73517f569 Mon Sep 17 00:00:00 2001 From: rickard Date: Sun, 3 Nov 2024 20:09:17 +0100 Subject: [PATCH 09/10] add a test for story wearables --- tests/files/world_story/world.json | 10 ++++++++++ tests/test_json_story.py | 5 ++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/files/world_story/world.json b/tests/files/world_story/world.json index e0e7ce6f..9392ddf4 100644 --- a/tests/files/world_story/world.json +++ b/tests/files/world_story/world.json @@ -202,6 +202,16 @@ "descr": "A wolf", "type": "Mob" } + ], + "wearables": [ + { + "name": "fur jacket", + "short_descr": "A fur jacket", + "descr": "A warm fur jacket", + "location": "TORSO", + "type": "Wearable", + "ac": 1 + } ] } } diff --git a/tests/test_json_story.py b/tests/test_json_story.py index 61db1251..c3e6616e 100644 --- a/tests/test_json_story.py +++ b/tests/test_json_story.py @@ -5,7 +5,7 @@ from tale.coord import Coord from tale.items import generic import tale.parse_utils as parse_utils -from tale import util +from tale import util, wearable from tale.base import Location from tale.driver_if import IFDriver from tale.json_story import JsonStory @@ -75,6 +75,9 @@ def test_load_story(self): assert self.story.day_cycle assert self.story.random_events + assert len(wearable.wearbles_story) == 1 + assert wearable.wearbles_story[0]['name'] == 'fur jacket' + def test_add_location(self): new_location = Location('New Location', 'New Location') From f09b2840037bcdc07cc6c6e9d83095570f7c39da Mon Sep 17 00:00:00 2001 From: rickard Date: Sun, 3 Nov 2024 20:23:24 +0100 Subject: [PATCH 10/10] fix for headers and add test --- tale/llm/llm_io.py | 3 ++- tests/test_json_story.py | 4 ++-- tests/test_llm_io.py | 16 ++++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/tale/llm/llm_io.py b/tale/llm/llm_io.py index aa6bbdc5..593f54e6 100644 --- a/tale/llm/llm_io.py +++ b/tale/llm/llm_io.py @@ -21,7 +21,8 @@ def __init__(self, config: dict = None, backend_config: dict = None): self.headers = headers self.io_adapter = LlamaCppAdapter(self.url, backend_config['STREAM_ENDPOINT'], config.get('USER_START', ''), config.get('USER_END', ''), config.get('SYSTEM_START', ''), config.get('PROMPT_END', '')) else: - headers['Authorization'] = f"Bearer {backend_config['API_PASSWORD']}" + if 'API_PASSWORD' in backend_config and backend_config['API_PASSWORD']: + headers['Authorization'] = f"Bearer {backend_config['API_PASSWORD']}" self.headers = headers self.io_adapter = KoboldCppAdapter(self.url, backend_config['STREAM_ENDPOINT'], backend_config['DATA_ENDPOINT'], config.get('USER_START', ''), config.get('USER_END', ''), config.get('SYSTEM_START', ''), config.get('PROMPT_END', '')) diff --git a/tests/test_json_story.py b/tests/test_json_story.py index c3e6616e..7837194d 100644 --- a/tests/test_json_story.py +++ b/tests/test_json_story.py @@ -12,6 +12,7 @@ from tale.mob_spawner import MobSpawner class TestJsonStory(): + wearable.wearbles_story = [] driver = IFDriver(screen_delay=99, gui=False, web=True, wizard_override=True) driver.game_clock = util.GameDateTime(datetime.datetime(year=2023, month=1, day=1), 1) story = JsonStory('tests/files/world_story/', parse_utils.load_story_config(parse_utils.load_json('tests/files/world_story/story_config.json'))) @@ -74,8 +75,7 @@ def test_load_story(self): assert self.story.day_cycle assert self.story.random_events - - assert len(wearable.wearbles_story) == 1 + assert wearable.wearbles_story[0]['name'] == 'fur jacket' diff --git a/tests/test_llm_io.py b/tests/test_llm_io.py index 2227c3af..11b41f46 100644 --- a/tests/test_llm_io.py +++ b/tests/test_llm_io.py @@ -76,6 +76,22 @@ def test_set_prompt_llama_cpp(self): assert('context' in result['messages'][1]['content']) assert(result['messages'][0]['content'] != 'context') + + def test_password_in_header(self): + config_file = self._load_config() + config_file['BACKEND'] = 'kobold_cpp' + + backend_config = self._load_backend_config('kobold_cpp') + io_util = IoUtil(config=config_file, backend_config=backend_config) + + assert not io_util.headers + + backend_config = self._load_backend_config('kobold_cpp') + backend_config['API_PASSWORD'] = 'test_password' + io_util = IoUtil(config=config_file, backend_config=backend_config) + + assert io_util.headers['Authorization'] == f"Bearer {backend_config['API_PASSWORD']}" + @responses.activate def test_error_response(self): config_file = self._load_config()