diff --git a/pyproject.toml b/pyproject.toml index 6be68ee9..2ee3bb02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.masonry.api" [tool.poetry] name = "talemate" -version = "0.8.0" +version = "0.9.0" description = "AI-backed roleplay and narrative tools" authors = ["FinalWombat"] license = "GNU Affero General Public License v3.0" diff --git a/src/talemate/__init__.py b/src/talemate/__init__.py index e76aa49c..ab2e0f3f 100644 --- a/src/talemate/__init__.py +++ b/src/talemate/__init__.py @@ -2,4 +2,4 @@ from .agents import Agent from .client import TextGeneratorWebuiClient from .tale_mate import * -VERSION = "0.8.0" \ No newline at end of file +VERSION = "0.9.0" \ No newline at end of file diff --git a/src/talemate/agents/base.py b/src/talemate/agents/base.py index 3513eff7..b847d809 100644 --- a/src/talemate/agents/base.py +++ b/src/talemate/agents/base.py @@ -12,6 +12,32 @@ import talemate.util as util from talemate.emit import emit +__all__ = [ + "Agent", + "set_processing", +] + +def set_processing(fn): + """ + decorator that emits the agent status as processing while the function + is running. + + Done via a try - final block to ensure the status is reset even if + the function fails. + """ + + async def wrapper(self, *args, **kwargs): + try: + await self.emit_status(processing=True) + return await fn(self, *args, **kwargs) + finally: + await self.emit_status(processing=False) + + wrapper.__name__ = fn.__name__ + + return wrapper + + class Agent(ABC): """ Base agent class, defines a role @@ -19,6 +45,8 @@ class Agent(ABC): agent_type = "agent" verbose_name = None + + set_processing = set_processing @property def agent_details(self): @@ -51,16 +79,29 @@ class Agent(ABC): @property def status(self): if self.ready: - return "idle" + return "idle" if getattr(self, "processing", 0) == 0 else "busy" else: return "uninitialized" async def emit_status(self, processing: bool = None): - if processing is not None: - self.processing = processing - - status = "busy" if getattr(self, "processing", False) else self.status - + + # should keep a count of processing requests, and when the + # number is 0 status is "idle", if the number is greater than 0 + # status is "busy" + # + # increase / decrease based on value of `processing` + + if getattr(self, "processing", None) is None: + self.processing = 0 + + if not processing: + self.processing -= 1 + self.processing = max(0, self.processing) + else: + self.processing += 1 + + status = "busy" if self.processing > 0 else "idle" + emit( "agent_status", message=self.verbose_name or "", diff --git a/src/talemate/agents/conversation.py b/src/talemate/agents/conversation.py index 3a8be48b..05de59b2 100644 --- a/src/talemate/agents/conversation.py +++ b/src/talemate/agents/conversation.py @@ -11,7 +11,7 @@ from talemate.emit import emit from talemate.scene_message import CharacterMessage, DirectorMessage from talemate.prompts import Prompt -from .base import Agent +from .base import Agent, set_processing from .registry import register if TYPE_CHECKING: @@ -29,6 +29,8 @@ class ConversationAgent(Agent): agent_type = "conversation" verbose_name = "Conversation" + + min_dialogue_length = 75 def __init__( self, @@ -162,24 +164,17 @@ class ConversationAgent(Agent): if "#" in result: result = result.split("#")[0] - result = result.replace("\n", " ").strip() - - - # Check for occurrence of a character name followed by a colon - # that does NOT match the character name of the current character - if "." in result and re.search(rf"(?!{self.character.name})\w+:", result): - result = re.sub(rf"(?!{character.name})\w+:(.*\n*)*", "", result) - + result = result.replace("\n", "__LINEBREAK__").strip() # Removes partial sentence at the end result = re.sub(r"[^\.\?\!\*]+(\n|$)", "", result) - result = result.replace(" :", ":") - result = result.strip().strip('"').strip() - + result = result.replace("[", "*").replace("]", "*") result = result.replace("**", "*") + + result = result.replace("__LINEBREAK__", "\n") # if there is an uneven number of '*' add one to the end @@ -188,13 +183,12 @@ class ConversationAgent(Agent): return result + @set_processing async def converse(self, actor, editor=None): """ Have a conversation with the AI """ - await self.emit_status(processing=True) - history = actor.history self.current_memory_context = None @@ -212,7 +206,7 @@ class ConversationAgent(Agent): empty_result_count = 0 # Validate AI response - while loop_count < max_loops: + while loop_count < max_loops and len(total_result) < self.min_dialogue_length: log.debug("conversation agent", result=result) result = await self.client.send_prompt( await self.build_prompt(character, char_message=total_result) @@ -227,7 +221,7 @@ class ConversationAgent(Agent): loop_count += 1 - if len(total_result) >= 250: + if len(total_result) > self.min_dialogue_length: break # if result is empty, increment empty_result_count @@ -240,9 +234,6 @@ class ConversationAgent(Agent): result = result.replace(" :", ":") - # Removes any line starting with another character name followed by a colon - total_result = re.sub(rf"(?!{character.name})\w+:(.*\n*)*", "", total_result) - total_result = total_result.split("#")[0] # Removes partial sentence at the end @@ -277,6 +268,4 @@ class ConversationAgent(Agent): # Add message and response to conversation history actor.scene.push_history(messages) - await self.emit_status(processing=False) - return messages diff --git a/src/talemate/agents/director.py b/src/talemate/agents/director.py index deeb5fd9..23e34b5d 100644 --- a/src/talemate/agents/director.py +++ b/src/talemate/agents/director.py @@ -14,6 +14,7 @@ from talemate.automated_action import AutomatedAction import talemate.automated_action as automated_action from .conversation import ConversationAgent from .registry import register +from .base import set_processing if TYPE_CHECKING: from talemate import Actor, Character, Player, Scene @@ -68,37 +69,34 @@ class DirectorAgent(ConversationAgent): log.info("question_direction", response=response) return response, evaluation, prompt - + @set_processing async def direct(self, character: Character, goal_override:str=None): - await self.emit_status(processing=True) analysis, current_goal, action = await self.decide_action(character, goal_override=goal_override) - try: - if action == "watch": - return None + if action == "watch": + return None + + if action == "direct": + return await self.direct_character_with_self_reflection(character, analysis, goal_override=current_goal) + + if action.startswith("narrate"): - if action == "direct": - return await self.direct_character_with_self_reflection(character, analysis, goal_override=current_goal) + narration_type = action.split(":")[1] - if action.startswith("narrate"): - - narration_type = action.split(":")[1] - - direct_narrative = await self.direct_narrative(analysis, narration_type=narration_type, goal=current_goal) - if direct_narrative: - narrator = self.scene.get_helper("narrator").agent - narrator_response = await narrator.progress_story(direct_narrative) - if not narrator_response: - return None - narrator_message = NarratorMessage(narrator_response, source="progress_story") - self.scene.push_history(narrator_message) - emit("narrator", narrator_message) - return True - finally: - await self.emit_status(processing=False) + direct_narrative = await self.direct_narrative(analysis, narration_type=narration_type, goal=current_goal) + if direct_narrative: + narrator = self.scene.get_helper("narrator").agent + narrator_response = await narrator.progress_story(direct_narrative) + if not narrator_response: + return None + narrator_message = NarratorMessage(narrator_response, source="progress_story") + self.scene.push_history(narrator_message) + emit("narrator", narrator_message) + return True + @set_processing async def direct_narrative(self, analysis:str, narration_type:str="progress", goal:str=None): if goal is None: @@ -120,6 +118,7 @@ class DirectorAgent(ConversationAgent): return response + @set_processing async def direct_character_with_self_reflection(self, character: Character, analysis:str, goal_override:str=None): max_retries = 3 @@ -162,6 +161,7 @@ class DirectorAgent(ConversationAgent): return response + @set_processing async def transform_character_direction_to_inner_monologue(self, character:Character, direction:str): inner_monologue = await Prompt.request( @@ -179,6 +179,7 @@ class DirectorAgent(ConversationAgent): return inner_monologue + @set_processing async def direct_character( self, character: Character, @@ -229,6 +230,7 @@ class DirectorAgent(ConversationAgent): + @set_processing async def direct_character_self_reflect(self, direction:str, character: Character, goal:str, direction_prompt:Prompt) -> (bool, str): change_matches = ["change", "retry", "alter", "reconsider"] @@ -253,6 +255,7 @@ class DirectorAgent(ConversationAgent): return keep, response + @set_processing async def direct_character_analyze(self, direction:str, character: Character, goal:str, direction_prompt:Prompt): prompt = Prompt.get("director.direct-character-analyze", vars={ @@ -317,6 +320,7 @@ class DirectorAgent(ConversationAgent): else: return "" + @set_processing async def goal_analyze(self, goal:str): prompt = Prompt.get("director.goal-analyze", vars={ diff --git a/src/talemate/agents/narrator.py b/src/talemate/agents/narrator.py index 603edba6..7549f582 100644 --- a/src/talemate/agents/narrator.py +++ b/src/talemate/agents/narrator.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Callable, List, Optional, Union import talemate.util as util from talemate.emit import wait_for_input from talemate.prompts import Prompt +from talemate.agents.base import set_processing from .conversation import ConversationAgent from .registry import register @@ -23,10 +24,6 @@ class NarratorAgent(ConversationAgent): if "#" in result: result = result.split("#")[0] - - # Removes partial sentence at the end - # result = re.sub(r"[^\.\?\!]+(\n|$)", "", result) - cleaned = [] for line in result.split("\n"): if ":" in line.strip(): @@ -35,14 +32,12 @@ class NarratorAgent(ConversationAgent): return "\n".join(cleaned) + @set_processing async def narrate_scene(self): """ Narrate the scene """ - await self.emit_status(processing=True) - - response = await Prompt.request( "narrator.narrate-scene", self.client, @@ -55,17 +50,14 @@ class NarratorAgent(ConversationAgent): response = f"*{response.strip('*')}*" - await self.emit_status(processing=False) - return response + @set_processing async def progress_story(self, narrative_direction:str=None): """ Narrate the scene """ - await self.emit_status(processing=True) - scene = self.scene director = scene.get_helper("director").agent pc = scene.get_player_character() @@ -113,17 +105,13 @@ class NarratorAgent(ConversationAgent): response = response.replace("*", "") response = f"*{response}*" - await self.emit_status(processing=False) - return response + @set_processing async def narrate_query(self, query:str, at_the_end:bool=False, as_narrative:bool=True): """ Narrate a specific query """ - - await self.emit_status(processing=True) - response = await Prompt.request( "narrator.narrate-query", self.client, @@ -141,15 +129,14 @@ class NarratorAgent(ConversationAgent): if as_narrative: response = f"*{response}*" - await self.emit_status(processing=False) return response + @set_processing async def narrate_character(self, character): """ Narrate a specific character """ - await self.emit_status(processing=True) budget = self.client.max_token_length - 300 memory_budget = min(int(budget * 0.05), 200) @@ -176,11 +163,9 @@ class NarratorAgent(ConversationAgent): response = self.clean_result(response.strip()) response = f"*{response}*" - await self.emit_status(processing=False) return response - - + @set_processing async def augment_context(self): """ diff --git a/src/talemate/agents/summarize.py b/src/talemate/agents/summarize.py index 30cc0baa..11cd9648 100644 --- a/src/talemate/agents/summarize.py +++ b/src/talemate/agents/summarize.py @@ -8,7 +8,7 @@ import talemate.util as util from talemate.prompts import Prompt from talemate.scene_message import DirectorMessage -from .base import Agent +from .base import Agent, set_processing from .registry import register import structlog @@ -40,6 +40,7 @@ class SummarizeAgent(Agent): super().connect(scene) scene.signals["history_add"].connect(self.on_history_add) + @set_processing async def build_archive(self, scene): end = None @@ -67,7 +68,6 @@ class SummarizeAgent(Agent): if end is None: # nothing to archive yet return - await self.emit_status(processing=True) extra_context = None if recent_entry: @@ -91,13 +91,12 @@ class SummarizeAgent(Agent): ) scene.push_archive(data_objects.ArchiveEntry(summarized, start, end)) - await self.emit_status(processing=False) return True + @set_processing async def analyze_dialoge(self, dialogue): instruction = "Examine the dialogue from the beginning and find the first line that marks a scene change. Repeat the line back to me exactly as it is written" - await self.emit_status(processing=True) prepare_response = "The first line that marks a scene change is: " @@ -110,10 +109,9 @@ class SummarizeAgent(Agent): response = self.clean_result(response) - await self.emit_status(processing=False) - return response + @set_processing async def summarize( self, text: str, @@ -125,8 +123,6 @@ class SummarizeAgent(Agent): Summarize the given text """ - await self.emit_status(processing=True) - response = await Prompt.request("summarizer.summarize-dialogue", self.client, "summarize", vars={ "dialogue": text, "scene": self.scene, @@ -135,14 +131,12 @@ class SummarizeAgent(Agent): self.scene.log.info("summarize", dialogue=text, response=response) - await self.emit_status(processing=False) - return self.clean_result(response) + @set_processing async def simple_summary( self, text: str, prompt_kind: str = "summarize", instructions: str = "Summarize" ): - await self.emit_status(processing=True) prompt = [ text, "", @@ -153,62 +147,52 @@ class SummarizeAgent(Agent): response = await self.client.send_prompt("\n".join(map(str, prompt)), kind=prompt_kind) if ":" in response: response = response.split(":")[1].strip() - await self.emit_status(processing=False) return response + @set_processing async def request_world_state(self): - await self.emit_status(processing=True) - try: - - t1 = time.time() + t1 = time.time() - _, world_state = await Prompt.request( - "summarizer.request-world-state", - self.client, - "analyze", - vars = { - "scene": self.scene, - "max_tokens": self.client.max_token_length, - "object_type": "character", - "object_type_plural": "characters", - } - ) - - self.scene.log.debug("request_world_state", response=world_state, time=time.time() - t1) - - return world_state - finally: - await self.emit_status(processing=False) + _, world_state = await Prompt.request( + "summarizer.request-world-state", + self.client, + "analyze", + vars = { + "scene": self.scene, + "max_tokens": self.client.max_token_length, + "object_type": "character", + "object_type_plural": "characters", + } + ) + + self.scene.log.debug("request_world_state", response=world_state, time=time.time() - t1) + + return world_state + @set_processing async def request_world_state_inline(self): """ EXPERIMENTAL, Overall the one shot request seems about as coherent as the inline request, but the inline request is is about twice as slow and would need to run on every dialogue line. """ - await self.emit_status(processing=True) - try: - - t1 = time.time() + t1 = time.time() - # first, we need to get the marked items (objects etc.) + # first, we need to get the marked items (objects etc.) - marked_items_response = await Prompt.request( - "summarizer.request-world-state-inline-items", - self.client, - "analyze_freeform", - vars = { - "scene": self.scene, - "max_tokens": self.client.max_token_length, - } - ) - - self.scene.log.debug("request_world_state_inline", marked_items=marked_items_response, time=time.time() - t1) - - return marked_items_response - finally: - await self.emit_status(processing=False) - \ No newline at end of file + marked_items_response = await Prompt.request( + "summarizer.request-world-state-inline-items", + self.client, + "analyze_freeform", + vars = { + "scene": self.scene, + "max_tokens": self.client.max_token_length, + } + ) + + self.scene.log.debug("request_world_state_inline", marked_items=marked_items_response, time=time.time() - t1) + + return marked_items_response \ No newline at end of file diff --git a/src/talemate/client/context.py b/src/talemate/client/context.py index a89187f6..05506232 100644 --- a/src/talemate/client/context.py +++ b/src/talemate/client/context.py @@ -11,11 +11,17 @@ __all__ = [ 'ContextModel', ] + +class ConversationContext(BaseModel): + talking_character: str = None + other_characters: list[str] = Field(default_factory=list) + class ContextModel(BaseModel): """ Pydantic model for the context data. """ nuke_repetition: float = Field(0.0, ge=0.0, le=3.0) + conversation: ConversationContext = Field(default_factory=ConversationContext) # Define the context variable as an empty dictionary context_data = ContextVar('context_data', default=ContextModel().dict()) diff --git a/src/talemate/client/openai.py b/src/talemate/client/openai.py index ed6a7fcc..633a7749 100644 --- a/src/talemate/client/openai.py +++ b/src/talemate/client/openai.py @@ -9,7 +9,6 @@ from talemate.client.registry import register from talemate.emit import emit from talemate.config import load_config import talemate.client.system_prompts as system_prompts - import structlog __all__ = [ @@ -142,5 +141,14 @@ class OpenAIClient: log.debug("openai response", response=response) + emit("prompt_sent", data={ + "kind": kind, + "prompt": prompt, + "response": response, + # TODO use tiktoken + "prompt_tokens": "?", + "response_tokens": "?", + }) + self.emit_status(processing=False) return response diff --git a/src/talemate/client/textgenwebui.py b/src/talemate/client/textgenwebui.py index 02e44440..62ae7d7b 100644 --- a/src/talemate/client/textgenwebui.py +++ b/src/talemate/client/textgenwebui.py @@ -417,11 +417,21 @@ class TextGeneratorWebuiClient(RESTTaleMateClient): prompt, ) + stopping_strings = ["<|end_of_turn|>"] + + conversation_context = client_context_attribute("conversation") + + stopping_strings += [ + f"{character}:" for character in conversation_context["other_characters"] + ] + + log.debug("prompt_config_conversation", stopping_strings=stopping_strings, conversation_context=conversation_context) + config = { "prompt": prompt, "max_new_tokens": 75, "chat_prompt_size": self.max_token_length, - "stopping_strings": ["<|end_of_turn|>", "\n\n"], + "stopping_strings": stopping_strings, } config.update(PRESET_TALEMATE_CONVERSATION) @@ -616,7 +626,15 @@ class TextGeneratorWebuiClient(RESTTaleMateClient): response = response.split("#")[0] self.emit_status(processing=False) - await asyncio.sleep(0.01) + + emit("prompt_sent", data={ + "kind": kind, + "prompt": message["prompt"], + "response": response, + "prompt_tokens": token_length, + "response_tokens": int(len(response) / 3.6) + }) + return response diff --git a/src/talemate/emit/signals.py b/src/talemate/emit/signals.py index 17a7ba1b..b886d4a2 100644 --- a/src/talemate/emit/signals.py +++ b/src/talemate/emit/signals.py @@ -14,6 +14,7 @@ ReceiveInput = signal("receive_input") ClientStatus = signal("client_status") AgentStatus = signal("agent_status") ClientBootstraps = signal("client_bootstraps") +PromptSent = signal("prompt_sent") RemoveMessage = signal("remove_message") @@ -42,4 +43,5 @@ handlers = { "world_state": WorldState, "archived_history": ArchivedHistory, "message_edited": MessageEdited, + "prompt_sent": PromptSent, } diff --git a/src/talemate/prompts/templates/conversation/dialogue.jinja2 b/src/talemate/prompts/templates/conversation/dialogue.jinja2 index 0269a568..515f99a0 100644 --- a/src/talemate/prompts/templates/conversation/dialogue.jinja2 +++ b/src/talemate/prompts/templates/conversation/dialogue.jinja2 @@ -5,7 +5,9 @@ <|CLOSE_SECTION|> <|SECTION:CHARACTERS|> {% for character in characters -%} -{{ character.name }}: {{ character.description }} +{{ character.name }}: +{{ character.filtered_sheet(['name', 'description', 'age', 'gender']) }} +{{ query_memory(character.name+' personality', as_question_answer= False) }} {% endfor %} <|CLOSE_SECTION|> @@ -28,9 +30,7 @@ Based on {{ talking_character.name}}'s example dialogue style, create a continua You may chose to have {{ talking_character.name}} respond to {{main_character.name}}'s last message, or you may chose to have {{ talking_character.name}} perform a new action that is in line with {{ talking_character.name}}'s character. -{% if scene.history and scene.history[-1].type == "director" -%} -Follow the instructions to you for your next message as {{ talking_character.name}}. NEVER directly respond to the instructions, but use the direction we have given you as you perform {{ talking_character.name }}'s response to {{main_character.name}}. You can separate thoughts and actual dialogue by containing thoughts inside curly brackets. Example: "{stuff you want to keep private} stuff you want to say publicly." -{% endif -%} +Use an informal and colloquial register with a conversational tone…Overall, their dialog is Informal, conversational, natural, and spontaneous, with a sense of immediacy. <|CLOSE_SECTION|> <|SECTION:SCENE|> diff --git a/src/talemate/prompts/templates/creator/character-attributes-human.jinja2 b/src/talemate/prompts/templates/creator/character-attributes-human.jinja2 index d2090b51..442b175b 100644 --- a/src/talemate/prompts/templates/creator/character-attributes-human.jinja2 +++ b/src/talemate/prompts/templates/creator/character-attributes-human.jinja2 @@ -48,12 +48,12 @@ Examples: John, Mary, Jane, Bob, Alice, etc. Respond with a number only {% endif -%} {% if character_sheet.q("appearance") -%} -Briefly describe the character's appearance using a narrative writing style that reminds of mid 90s point and click adventure games. (2 - 3 sentences). {{ spice("Make it {spice}.", spices) }} +Briefly describe the character's appearance using a narrative writing style that reminds of mid 90s point and click adventure games. (1 - 2 sentences). {{ spice("Make it {spice}.", spices) }} {% endif -%} {% block generate_appearance %} {% endblock %} {% if character_sheet.q("personality") -%} -Briefly describe the character's personality using a narrative writing style that reminds of mid 90s point and click adventure games. (2 - 3 sentences). {{ spice("Make it {spice}.", spices) }} +Briefly describe the character's personality using a narrative writing style that reminds of mid 90s point and click adventure games. (1 - 2 sentences). {{ spice("Make it {spice}.", spices) }} {% endif -%} {% if character_sheet.q("family and fiends") %} List close family and friends of {{ character_sheet("name") }}. Respond with a comma separated list of names. (2 - 3 names, include age) @@ -69,7 +69,7 @@ List some things that {{ character_sheet("name") }} dislikes. Respond with a com Examples: cats, dogs, pizza, etc. {% endif -%} {% if character_sheet.q("clothes and accessories") -%} -Briefly describe the character's clothes and accessories using a narrative writing style that reminds of mid 90s point and click adventure games. (2 - 3 sentences). {{ spice("Make it {spice}.", spices) }} +Briefly describe the character's clothes and accessories using a narrative writing style that reminds of mid 90s point and click adventure games. (1 - 2 sentences). {{ spice("Make it {spice}.", spices) }} {% endif %} {% block generate_misc %}{% endblock -%} {% for custom_attribute, instructions in custom_attributes.items() -%} diff --git a/src/talemate/prompts/templates/creator/character-description.jinja2 b/src/talemate/prompts/templates/creator/character-description.jinja2 index fb1b43b8..0137949e 100644 --- a/src/talemate/prompts/templates/creator/character-description.jinja2 +++ b/src/talemate/prompts/templates/creator/character-description.jinja2 @@ -4,6 +4,6 @@ <|SECTION:TASK|> Summarize {{ character.name }} based on the character sheet above. -Use a narrative writing style that reminds of mid 90s point and click adventure games about a {{ content_context }} +Use a narrative writing style that reminds of mid 90s point and click adventure games about {{ content_context }} <|CLOSE_SECTION|> {{ set_prepared_response(character.name+ " is ") }} \ No newline at end of file diff --git a/src/talemate/server/character_creator.py b/src/talemate/server/character_creator.py index 22247d12..bfea5517 100644 --- a/src/talemate/server/character_creator.py +++ b/src/talemate/server/character_creator.py @@ -148,13 +148,13 @@ class CharacterCreatorServerPlugin: async def handle_submit_step3(self, data:dict): creator = self.scene.get_helper("creator").agent - character, _ = self.apply_step_data(data) + character, step_data = self.apply_step_data(data) self.emit_step_start(3) description = await creator.create_character_description( character, - content_context=self.character_creation_data.scenario_context, + content_context=step_data.scenario_context, ) character.description = description diff --git a/src/talemate/server/websocket_server.py b/src/talemate/server/websocket_server.py index ea6d1703..6e48c1fa 100644 --- a/src/talemate/server/websocket_server.py +++ b/src/talemate/server/websocket_server.py @@ -292,6 +292,14 @@ class WebsocketHandler(Receiver): } ) + def handle_prompt_sent(self, emission: Emission): + self.queue_put( + { + "type": "prompt_sent", + "data": emission.data, + } + ) + def handle_clear_screen(self, emission: Emission): self.queue_put( { diff --git a/src/talemate/tale_mate.py b/src/talemate/tale_mate.py index afddfe05..4a3c088a 100644 --- a/src/talemate/tale_mate.py +++ b/src/talemate/tale_mate.py @@ -23,6 +23,7 @@ from talemate.exceptions import ExitScene, RestartSceneLoop, ResetScene, Talemat from talemate.world_state import WorldState from talemate.config import SceneConfig from talemate.scene_assets import SceneAssets +from talemate.client.context import ClientContext, ConversationContext import talemate.automated_action as automated_action @@ -139,6 +140,23 @@ class Character: return "" return random.choice(self.example_dialogue) + + def filtered_sheet(self, attributes: list[str]): + + """ + Same as sheet but only returns the attributes in the given list + + Attributes that dont exist will be ignored + """ + + sheet_list = [] + + for key, value in self.base_attributes.items(): + if key.lower() not in attributes: + continue + sheet_list.append(f"{key}: {value}") + + return "\n".join(sheet_list) def save(self, file_path: str): """ @@ -413,8 +431,14 @@ class Actor: self.agent.character = self.character - messages = await self.agent.converse(self, editor=editor) - await asyncio.sleep(0) + conversation_context = ConversationContext( + talking_character=self.character.name, + other_characters=[actor.character.name for actor in self.scene.actors if actor != self], + ) + + with ClientContext(conversation=conversation_context): + messages = await self.agent.converse(self, editor=editor) + return messages diff --git a/talemate_frontend/src/components/DebugToolPromptLog.vue b/talemate_frontend/src/components/DebugToolPromptLog.vue new file mode 100644 index 00000000..7841debd --- /dev/null +++ b/talemate_frontend/src/components/DebugToolPromptLog.vue @@ -0,0 +1,86 @@ + + \ No newline at end of file diff --git a/talemate_frontend/src/components/DebugToolPromptView.vue b/talemate_frontend/src/components/DebugToolPromptView.vue new file mode 100644 index 00000000..b5306e8d --- /dev/null +++ b/talemate_frontend/src/components/DebugToolPromptView.vue @@ -0,0 +1,68 @@ + + + + \ No newline at end of file diff --git a/talemate_frontend/src/components/DebugTools.vue b/talemate_frontend/src/components/DebugTools.vue new file mode 100644 index 00000000..41e0bdc8 --- /dev/null +++ b/talemate_frontend/src/components/DebugTools.vue @@ -0,0 +1,54 @@ + + \ No newline at end of file diff --git a/talemate_frontend/src/components/SceneMessages.vue b/talemate_frontend/src/components/SceneMessages.vue index e333489f..bd366703 100644 --- a/talemate_frontend/src/components/SceneMessages.vue +++ b/talemate_frontend/src/components/SceneMessages.vue @@ -160,7 +160,7 @@ export default { return } - + if (data.message) { if (data.type === 'character') { const [character, text] = data.message.split(':'); diff --git a/talemate_frontend/src/components/TalemateApp.vue b/talemate_frontend/src/components/TalemateApp.vue index 8f405523..e3cf8ae5 100644 --- a/talemate_frontend/src/components/TalemateApp.vue +++ b/talemate_frontend/src/components/TalemateApp.vue @@ -53,6 +53,14 @@ + + + + mdi-bug Debug Tools + + + + @@ -94,6 +102,7 @@ Talemate + mdi-bug mdi-cog mdi-application-cog @@ -145,6 +154,7 @@ import CharacterSheet from './CharacterSheet.vue'; import SceneHistory from './SceneHistory.vue'; import CreativeEditor from './CreativeEditor.vue'; import AppConfig from './AppConfig.vue'; +import DebugTools from './DebugTools.vue'; export default { components: { @@ -160,6 +170,7 @@ export default { SceneHistory, CreativeEditor, AppConfig, + DebugTools, }, name: 'TalemateApp', data() { @@ -169,6 +180,7 @@ export default { sceneActive: false, drawer: false, sceneDrawer: true, + debugDrawer: false, websocket: null, inputDisabled: false, waitingForInput: false, @@ -369,6 +381,8 @@ export default { this.sceneDrawer = !this.sceneDrawer; else if (navigation == "settings") this.drawer = !this.drawer; + else if (navigation == "debug") + this.debugDrawer = !this.debugDrawer; }, getClients() { if (!this.$refs.aiClient) { diff --git a/templates/llm-prompt/Xwin-LM.jinja2 b/templates/llm-prompt/Xwin-LM.jinja2 new file mode 100644 index 00000000..b9f3762e --- /dev/null +++ b/templates/llm-prompt/Xwin-LM.jinja2 @@ -0,0 +1 @@ +{{ system_message }} USER: {{ set_response(prompt, " ASSISTANT:") }} \ No newline at end of file