Skip to content

Commit

Permalink
new context handling
Browse files Browse the repository at this point in the history
  • Loading branch information
neph1 committed Jan 6, 2024
1 parent 001fcd4 commit 45974e1
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 33 deletions.
3 changes: 1 addition & 2 deletions backend_kobold_cpp.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
URL: "http://localhost:5001"
ENDPOINT: "/api/v1/generate"
STREAM: False
STREAM: True
STREAM_ENDPOINT: "/api/extra/generate/stream"
DATA_ENDPOINT: "/api/extra/generate/check"
DEFAULT_BODY: '{"stop_sequence": "\n\n\n\n", "max_length":750, "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}'
ANALYSIS_BODY: '{}'
GENERATION_BODY: '{"stop_sequence": "\n\n\n\n", "max_length":750, "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}'
18 changes: 9 additions & 9 deletions llm_config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
WORD_LIMIT: 200
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"
ANALYSIS_BODY: '{}'
MEMORY_SIZE: 512
PRE_PROMPT: 'You are a creative game keeper. You craft detailed worlds and interesting characters with unique and deep personalities for the player to interact with.'
BASE_PROMPT: "[Story context: {story_context}]; [History: {history}]; [USER_START] Rewrite [{input_text}] in your own words using the supplied Context and History to create a background for your text. Use about {max_words} words."
ACTION_PROMPT: "[Story context: {story_context}]; [History: {history}]; [USER_START] Rewrite the Action, and nothing else, in your own words using the supplied Context and Location. History is what happened before. Use less than {max_words} words. [Action: {input_text}]."
DIALOGUE_PROMPT: 'Story context: {story_context}; Location: {location}; The following is a conversation between {character1} and {character2}; {character1}:{character1_description}; {character2}:{character2_description}; {character2}s sentiment towards {character1}: {sentiment}. Write a single response as {character2} in third person pov, using {character2} description. If {character2} has a quest active, they will discuss it based on its status. Respond in JSON using this 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."}}. [USER_START]Continue the following conversation as {character2}: {previous_conversation}'
ITEM_PROMPT: 'Items:[{items}];Characters:[{character1},{character2}] \n\n [USER_START] Decide if an item was explicitly given, taken, dropped or put somewhere in the following text:[Text:{text}]. Insert your thoughts about whether an item was explicitly given, taken, put somewhere or dropped in "my thoughts", and the results in "item", "from" and "to", or make them empty if . Insert {character1}s sentiment towards {character2} in a single word in "sentiment assessment". Fill in the following JSON template: {{ "thoughts":"", "result": {{ "item":"", "from":"", "to":""}}, {{"sentiment":"sentiment assessment"}} }} End of example. \n\n Write your response in valid JSON.'
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."}'
ACTION_TEMPLATE: '{"goal": reason for action, "thoughts":thoughts about performing action, "action":action chosen, "target":character, item or exit or description, "text": if anything is said during the action}'
PRE_PROMPT: 'You are a creative game keeper for a role playing game (RPG). You craft detailed worlds and interesting characters with unique and deep personalities for the player to interact with.'
BASE_PROMPT: "<context>{context}</context>\n[USER_START] Rewrite [{input_text}] in your own words using the information found inside the <context> tags to create a background for your text. Use about {max_words} words."
DIALOGUE_PROMPT: '<context>{context}</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 <context> 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: 'The following is a combat scene between user {attacker} and {victim} in {location}, {location_description} into a vivid description. [USER_START] Rewrite the following combat result in about 150 words, using the characters weapons and their health status: 1.0 is highest, 0.0 is lowest. Combat Result: {attacker_msg}'
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: 'Story context: {story_context}; World info: {world_info};[USER_START] For a {story_type}, create a diverse character with rich personality that can be interacted with using the story context and keywords. {{quest_prompt}} Do not mention height. Story context: {story_context}; keywords: {keywords}. Fill in this JSON template and write nothing else: {{"name":"", "description": "50 words", "appearance": "25 words", "personality": "50 words", "money":(int), "level":"", "gender":"m/f/n", "age":(int), "race":""}}'
Expand All @@ -28,6 +28,6 @@ PLAYER_ENTER_PROMPT: 'Story context: {story_context}; World info: {world_info};
QUEST_PROMPT: '[Story context: {story_context}]; World info: {world_info}; Zone info: {zone_info}; Character: {character_card}; [USER_START] In an RPG described as {story_type}, {character_name} needs someone to perform a task. Based on the following input, come up with a suitable reason for it, using {character_name}s personality and history. Task info: {base_quest}. Fill in this JSON template and do not write anything else: {{"reason":""}} \n\n '
NOTE_QUEST_PROMPT: '[Story context: {story_context}]; World info: {world_info}; Zone info: {zone_info}; [USER_START]For an RPG described as {story_type}, generate a quest that starts from reading a note. The reader must find and talk to a person. Fill in the following JSON template and write nothing else.: {{"reason": "only what the note says. 50 words.", "type":"talk", "target":"who to talk to", "location":"", "name":"name of quest"}}'
NOTE_LORE_PROMPT: '[Story context: {story_context}]; World info: {world_info}; Zone info: {zone_info}; [USER_START]For an RPG described as {story_type}, Decide what is written on a note that has been found. Use the provided story and world information to generate a piece of lore. Use about 50 words.'
ACTION_PROMPT: 'Story context: {story_context};<location>{location}</location> \nAct as as {character_name} in a {story_type} RPG and perform an action.<description>{character}</description> <actions>{actions}</actions>. <items>{location_items}</items>. <characters>{characters}</characters> <exits>{exits}</exits>.\nPick an action according to {character_name}s description and mood. If suitable, select something to perform the action on (target). Make sure the action is from the list and is related to {character_name}s thoughts.\n Respond using JSON in the following format: """{{"goal": reason for action, "thoughts":thoughts about performing action, "action":action chosen, "target":character, item or exit or description, "text": if anything is said during the action}}"""<history>{history}</history>'
ACTION_PROMPT: '<context>{context}</context>\nAct as as {character_name}.\nUsing the information supplied inside the <context> tag, pick an action according to {character_name}s description and mood. If suitable, select something to perform the action on (target). The action should be in the supplied list and should be related to {character_name}s thoughts.\n Respond using JSON in the following format: """{action_template}"""'
USER_START: '### Instruction:'
USER_END: '### Response:'
USER_END: '### Response:\n'
30 changes: 30 additions & 0 deletions tale/llm/contexts/ActionContext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@


import json
from tale.base import Location
from tale.llm.contexts.BaseContext import BaseContext


class ActionContext(BaseContext):

def __init__(self, story_context: str, story_type: str, character_name: str, character_card: str, event_history: str, location: Location):
super().__init__(story_context)
self.story_type = story_type
self.character_name = character_name
self.character_card = character_card
self.event_history = event_history
self.location = location


def to_prompt_string(self) -> str:
actions = ', '.join(['move, say, attack, wear, remove, wield, take, eat, drink, emote'])
characters = {}
for living in self.location.livings:
if living.visible and living.name != self.character_name.lower():
if living.alive:
characters[living.name] = living.short_description
else:
characters[living.name] = f"{living.short_description} (dead)"
exits = self.location.exits.keys()
items = [item.name for item in self.location.items if item.visible]
return f"Story context:{self.story_context}; Story type:{self.story_type}; Available actions: {actions}; Location:{self.location.name}, {self.location.description}; Available exits: {exits}; Self: {self.character_card}; Present items: {items}; Present characters: {json.dumps(characters)}; History:{self.event_history};"
9 changes: 9 additions & 0 deletions tale/llm/contexts/BaseContext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@


class BaseContext():

def __init__(self, story_context: str) -> None:
self.story_context = story_context

def to_prompt_string(self) -> str:
pass
24 changes: 24 additions & 0 deletions tale/llm/contexts/DialogueContext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@


from tale.llm.contexts.BaseContext import BaseContext


class DialogueContext(BaseContext):

def __init__(self,
story_context: str,
location_description: str,
speaker_card: str,
speaker_name: str,
target_name: str,
target_description: str):
super().__init__(story_context)
self.location_description = location_description
self.speaker_card = speaker_card
self.speaker_name = speaker_name
self.target_name = target_name
self.target_description = target_description


def to_prompt_string(self) -> str:
return f"Story context:{self.story_context}; Location:{self.location_description}; Self:{self.speaker_name}:{self.speaker_card}; Listener:{self.target_name}:{self.target_description};"
10 changes: 10 additions & 0 deletions tale/llm/contexts/EvokeContext.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from tale.llm.contexts.BaseContext import BaseContext

class EvokeContext(BaseContext):

def __init__(self, story_context: str, history: str) -> None:
super().__init__(story_context)
self.history = history

def to_prompt_string(self) -> str:
return f"Story context:{self.story_context}; History:{self.history};"
49 changes: 29 additions & 20 deletions tale/llm/llm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
from tale.base import Location
from tale.image_gen.base_gen import ImageGeneratorBase
from tale.llm.character import CharacterBuilding
from tale.llm.contexts.ActionContext import ActionContext
from tale.llm.contexts.EvokeContext import EvokeContext
from tale.llm.llm_ext import DynamicStory
from tale.llm.llm_io import IoUtil
from tale.llm.contexts.DialogueContext import DialogueContext
from tale.llm.quest_building import QuestBuilding
from tale.llm.story_building import StoryBuilding
from tale.llm.world_building import WorldBuilding
Expand Down Expand Up @@ -37,9 +40,10 @@ def __init__(self, io_util: IoUtil = None):
self.default_body = json.loads(backend_config['DEFAULT_BODY'])
self.memory_size = config_file['MEMORY_SIZE']
self.pre_prompt = config_file['PRE_PROMPT'] # type: str
self.base_prompt = config_file['BASE_PROMPT'] # type: str
self.evoke_prompt = config_file['BASE_PROMPT'] # type: str
self.combat_prompt = config_file['COMBAT_PROMPT'] # type: str
self.word_limit = config_file['WORD_LIMIT']
self.short_word_limit = config_file['SHORT_WORD_LIMIT']
self.story_background_prompt = config_file['STORY_BACKGROUND_PROMPT'] # type: str
self.json_grammar = config_file['JSON_GRAMMAR'] # type: str
self.__story = None # type: DynamicStory
Expand Down Expand Up @@ -83,13 +87,11 @@ def evoke(self, player_io: TextBuffer, message: str, short_len : bool=False, rol
return output_template.format(message=message, text=cached_look), rolling_prompt

trimmed_message = parse_utils.remove_special_chars(str(message))

amount = 25
context = EvokeContext(story_context=self.__story_context, history=rolling_prompt if not skip_history or alt_prompt else '')
prompt = self.pre_prompt
prompt += alt_prompt or (self.base_prompt.format(
story_context=self.__story_context,
history=rolling_prompt if not skip_history or alt_prompt else '',
max_words=self.word_limit if not short_len else amount,
prompt += alt_prompt or (self.evoke_prompt.format(
context=context.to_prompt_string(),
max_words=self.word_limit if not short_len else self.short_word_limit,
input_text=str(trimmed_message)))

request_body = deepcopy(self.default_body)
Expand All @@ -98,9 +100,9 @@ def evoke(self, player_io: TextBuffer, message: str, short_len : bool=False, rol
text = self.io_util.synchronous_request(request_body, prompt=prompt)
llm_cache.cache_look(text, text_hash_value)
return output_template.format(message=message, text=text), rolling_prompt

text = self.io_util.stream_request(request_body=request_body, player_io=player_io, prompt=prompt, io=self.connection)
player_io.print(output_template.format(message=message, text=text), end=False, format=True, line_breaks=False)
text = self.io_util.stream_request(request_body, player_io, self.connection, prompt=prompt)

llm_cache.cache_look(text, text_hash_value)

return '\n', rolling_prompt
Expand All @@ -114,16 +116,17 @@ def generate_dialogue(self, conversation: str,
location_description = '',
event_history='',
short_len : bool=False):
return self._character.generate_dialogue(conversation,
character_card=character_card,
character_name=character_name,
target=target,
target_description=target_description,
sentiment=sentiment,
location_description=location_description,
story_context=self.__story_context,
event_history=event_history,
short_len=short_len)
dialogue_context = DialogueContext(story_context=self.__story_context,
location_description=location_description,
speaker_card=character_card,
speaker_name=character_name,
target_name=target,
target_description=target_description)
return self._character.generate_dialogue(context=dialogue_context,
conversation=conversation,
sentiment=sentiment,
event_history=event_history,
short_len=short_len)

def update_memory(self, rolling_prompt: str, response_text: str):
""" Keeps a history of the last couple of events"""
Expand Down Expand Up @@ -234,7 +237,13 @@ def generate_image(self, character_name: str, character_appearance: dict = '', s
return result

def free_form_action(self, location: Location, character_name: str, character_card: str = '', event_history: str = ''):
return self._character.free_form_action(self.__story_context, self.__story_type, location, character_name, character_card, event_history)
action_context = ActionContext(story_context=self.__story_context,
story_type=self.__story_type,
character_name=character_name,
character_card=character_card,
event_history=event_history,
location=location)
return self._character.free_form_action(action_context)


def set_story(self, story: DynamicStory):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_llm_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_perform_travel_action(self):
def test_generate_dialogue(self):
# mostly testing that prompt works
self.llm_util._character.io_util.response = ['{"response":"Hello there", "sentiment":"cheerful", "give":"ale"}']
result, item, sentiment = self.llm_util._character.generate_dialogue(conversation='test conversation',
result, item, sentiment = self.llm_util.generate_dialogue(conversation='test conversation',
character_card='{}',
character_name='Norhardt',
target='Arto',
Expand All @@ -80,7 +80,7 @@ def test_generate_dialogue(self):
def test_generate_dialogue_json(self):
# mostly testing that prompt works
self.llm_util._character.io_util.response = ["{\n \"response\": \"Autumn greets Test character with a warm smile, her golden hair shining in the sunlight. She returns the greeting, her voice filled with kindness, \'Hello there, how can I assist you today?\'\"\n}"]
result, item, sentiment = self.llm_util._character.generate_dialogue(conversation='test conversation',
result, item, sentiment = self.llm_util.generate_dialogue(conversation='test conversation',
character_card='{}',
character_name='Norhardt',
target='Arto',
Expand Down

0 comments on commit 45974e1

Please sign in to comment.