Files
talemate/tests/test_graphs.py
veguAI 5f26134647 Director chat (#17)
* clarify instructions

* incrase default context length for attribute gen

* character progression node

* Call Agent Function conditional

* director chat tweaks

* fix issue where graph property editing would reload the graph and lose unsaved changes

* prompt tweaks

* character name optional

* use blur

* prompt tweaks

* director actions

* rename reason to instructions

* fix argument node type conversion

* prompt tweaks

* director action create character node improved

* nting

* scene budge and function name reorg

* memory nodes

* prompt tweaks

* get_arguments_node allow filter fn override

* query world information node

* smarter dict collector key values

* linting

* dedicated director action argument node

* node style

* FunctionWrapper find_nodes and first_node
CallForEach now suppoorts dict in items socket

* focal improvements

* world entry management nodes

* linting

* director action change world information

* instruction tweaks

* director action confirmation flow

* raise on reject

* director action confirmation progress

* single chat

* polish ux

* separation of components

* ux polish

* tweaks

* agent personas

* linting

* initial chat message override in persona

* agent persona to system message

* linting

* director chat compaction

* linting

* fix codeblock linebreaks

* prompt twaeks

* error message socket

* get scene types node

* collect None values

* linting

* director action nodes for scene direction management

* proimpt tweaks

* fix issue of director chat not working right on new scenes

* prompt tweaks

* director action summary node

* rename to content classification

* scene nodes to get/set intro, desc, title, content classification

* linting

* allow somew extra calls

* director action nodes

* fix query contextdb to use iterate value

* director action ndoes

* linting

* fix double cancellation issue on websocket plugin handlers

* fix node editor losing changes when switching to a different talemate tab and back

* fix resize handler

* fix group overlap bug during snap action

* clear validation messages from property editor

* improve node search matching

* fix dynamic socket issues

* cleanup

* allow hot reload of new DA or command modules

* linting

* fix tests

* director modes

* allow changing director persona from chat interface

* tweaks

* separate state reinf component

* cleanup

* separate components for char attrib and details

* separate component for spirce collection

* separate writing style component

* cleanup

* remove applicable_agents

* persist chat mode

* linting

* ux tweaks

* director chat better context managemnet

* wording

* displat budgets in UX

* reorg

* Validate Is Not Set Node

* character nodes

* add extra output sockets

* fix compact error

* fix compact error

* fancy diffs

* nodes updated

* summarizer settings node

* fix type hint

* add useful output sockets

* history archive nodes

* add dedupe_enabled and response_length properties

* prompt tweaks

* linting

* nodes

* prompt tweaks

* prompt tweaks

* linting

* better instruct_character actions

* fix Get node to work with tuples and sets

* query character information should include details

* lint

* tweak instructions

* context id impl

* context id

* fix registry id

* context id socket

* context id

* build prompt improvements

* extract list node

* context_id as socket type

* remove empty separators

* linting

* character cibtext

* Fix advanced format always executing

* expose context id on character attrib

* CombineList node

* Dynamic instructions ndoe can now be fed list of strings

* return the context id object

* expose context id when unpacking memory doc

* progress on improved direction query action

* linting

* tweaks

* fix dynamic sockets not being copied during node clone

* fix dynamic sockets being lost when creating module from selection

* fix nodes spawning in too small to contain title

* sort choices

* hide prop value if related socket is connected

* shorten character context ids

* fix ai function type conversion issue that would cast everything to str

* hash context id

* context id value

* tests

* linting

* rename and tests

* linting

* refactor context id handler a bit

* context id shenanigans

* fix tests

* cleanup

* remove unused imports

* history context id handler

* refactor context id into proper module structure

* linting

* retrieve context

* world entry context ids

* story config context ids

* story config context

* linting

* no longer needed

* context id progress

* fix tests

* scene type inspection context ids

* linting

* prompt tweaks

* prompt tweaks

* shift+alt drag node for counterpart creation

* node property editor will now confirm close if it has changes

* transfer name property

* node coutnerpart fixes

* counterpart copy size fixes

* character_status socket

* fix director confirm node error when called outside of director chat context

* if input and output socket counterpart def

* prompt tweaks

* director action nodes

* no longer needed

* instruct character creation

* fix title

* toggle character

* linting

* GPT-OSS base templte

* wass reasoning_tokens to model_prompt

* pass reasoning_tokens to model prompt template

* gpt-oss preset

* to warning

* prompt tweaks

* prompt tweaks

* wording

* prompt tweaks

* pass through error message

* new exceptions

* clean up

* status field

* better response parsing

* linting

* add sockets and field to GetWorldEntry

* auto size node to fit all the widgets when adding to the graph

* contextual generate node correctly validates context_name as required

* fix issue where alt dragging a single node wouldnt work if other nodes were selected

* create group from node selection clears the selection after creation

* group from selected nodes - presets

* fix ctrl enter in text properties adding extra newline

* mark unresolved required sockets

* linting

* fix issue where connections where incorrectly flagged as unresolved

* Add GetCharacterDetail and SetCharacterDetail nodes to manage character details

- Introduced GetCharacterDetail node to retrieve character details based on a name input.
- Added SetCharacterDetail node to set character details with specified name and value.
- Updated existing GetCharacterAttribute node to handle cases where attributes may not exist, ensuring safe access to context IDs.

* Add Character Context Module and Update Instruct Character Updates

- Introduced a new character context module with various nodes for managing character attributes, details, and descriptions.
- Removed obsolete nodes from the instruct-character-updates module to streamline functionality.
- Adjusted node positions and properties for better organization and clarity in the graph structure.

* linting

* some context id stuff

* linting

* determine character dialogue instructions can now rewrite existing instructions

* allow none values

* context id node fixes

* prompt tweaks

* Add CounterPart functionality to DefineFunction and GetFunction nodes

* linting

* character config updates

* module sync

* dialogue_instructions -> acting_instructions

* linting

* story conig updates

* fix tests

* remove old action

* scan context ids

* director action tweaks

* Add scene_type_ids output to GetSceneTypes node

* director action nodes

* linting

* director agent nodes

* director action direct scene

* nodes

* nodes

* context id collector

* linting

* Handle empty content in DynamicInstruction string representation

* Add new color items "Prepare" and "Special" to recent nodes context menu

* Rename and separate hook processing methods in FocalContext for clarity

* Refactor action result handling in DirectorChatMixin to improve feedback mechanism and streamline chat state updates

* Add custom instructions feature to DirectorChatMixin and update chat instructions template to display them. Refactor existing action configurations for clarity.

* Add chat common tasks and limitations template for Director. Include scenarios for story creation, character behavior, memory issues, and repetition handling.

* Update chat template to clarify the role of the Director and include Talemate system context. Add common tasks template for enhanced chat functionality.

* prompt tweaks

* Add scene code block processing to DirectorConsoleChatMessageMarkdown component. Enhance markdown rendering by integrating scene text parsing and updating styles for scene blocks.

* Enhance NodeEditor and NodeEditorLibrary components with a new module library drawer and improved node display. Introduce tree view for better organization of modules, including scenes and agents, and update node display labels for clarity. Refactor resizing logic for the editor container.

* Implement scene view toggle and exit confirmation in NodeEditor. Move creative mode controls from TalemateApp to NodeEditor, enhancing user experience with new buttons and confirmation prompts for exiting creative mode.

* linting
2025-09-21 00:18:57 +03:00

233 lines
6.4 KiB
Python

import os
import json
import pytest
import contextvars
import talemate.agents as agents
import talemate.game.engine.nodes.load_definitions # noqa: F401
import talemate.agents.director # noqa: F401
import talemate.agents.memory
from talemate.context import ActiveScene
from talemate.tale_mate import Scene
import talemate.agents.tts.voice_library as voice_library
import talemate.instance as instance
from talemate.game.engine.nodes.core import (
Graph,
GraphState,
)
import structlog
from talemate.game.engine.nodes.layout import load_graph_from_file
from talemate.game.engine.nodes.registry import import_talemate_node_definitions
from talemate.client import ClientBase
from collections import deque
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TEST_GRAPH_DIR = os.path.join(BASE_DIR, "data", "graphs")
RESULTS_DIR = os.path.join(BASE_DIR, "data", "graphs", "results")
UPDATE_RESULTS = False
log = structlog.get_logger("talemate.test_graphs")
# This runs once for the entire test session
@pytest.fixture(scope="session", autouse=True)
def load_node_definitions():
import_talemate_node_definitions()
def load_test_graph(name) -> Graph:
path = os.path.join(TEST_GRAPH_DIR, f"{name}.json")
graph, _ = load_graph_from_file(path)
return graph
def bootstrap_engine():
voice_library.VOICE_LIBRARY = voice_library.VoiceLibrary(voices={})
for agent_type in agents.AGENT_CLASSES:
if agent_type == "memory":
agent = MockMemoryAgent()
else:
agent = agents.AGENT_CLASSES[agent_type]()
instance.AGENTS[agent_type] = agent
client_reponses = contextvars.ContextVar("client_reponses", default=deque())
class MockClientContext:
async def __aenter__(self):
try:
self.client_reponses = client_reponses.get()
except LookupError:
_client_reponses = deque()
self.token = client_reponses.set(_client_reponses)
self.client_reponses = _client_reponses
return self.client_reponses
async def __aexit__(self, exc_type, exc_value, traceback):
if hasattr(self, "token"):
client_reponses.reset(self.token)
class MockMemoryAgent(talemate.agents.memory.MemoryAgent):
async def add_many(self, items: list[dict]):
pass
async def delete(self, filters: dict):
pass
class MockClient(ClientBase):
def __init__(self, name: str):
self.name = name
self.remote_model_name = "test-model"
self.current_status = "idle"
self.prompt_history = []
@property
def enabled(self):
return True
async def send_prompt(
self, prompt, kind="conversation", finalize=lambda x: x, retries=2, **kwargs
):
"""Override send_prompt to return a pre-defined response instead of calling LLM.
If no responses are configured, returns an empty string.
Records the prompt in prompt_history for later inspection.
"""
response_stack = client_reponses.get()
self.prompt_history.append({"prompt": prompt, "kind": kind})
if not response_stack:
return ""
return response_stack.popleft()
class MockScene(Scene):
@property
def auto_progress(self):
"""
These tests currently assume that auto_progress is True
"""
return True
@pytest.fixture
def mock_scene():
scene = MockScene()
bootstrap_scene(scene)
return scene
def bootstrap_scene(mock_scene):
bootstrap_engine()
client = MockClient("test_client")
for agent in instance.AGENTS.values():
agent.client = client
agent.scene = mock_scene
director = instance.get_agent("director")
conversation = instance.get_agent("conversation")
summarizer = instance.get_agent("summarizer")
editor = instance.get_agent("editor")
world_state = instance.get_agent("world_state")
mock_scene.mock_client = client
return {
"director": director,
"conversation": conversation,
"summarizer": summarizer,
"editor": editor,
"world_state": world_state,
}
def make_assert_fn(name: str, write_results: bool = False):
async def assert_fn(state: GraphState):
if write_results or not os.path.exists(
os.path.join(RESULTS_DIR, f"{name}.json")
):
with open(os.path.join(RESULTS_DIR, f"{name}.json"), "w") as f:
json.dump(state.shared, f, indent=4)
else:
with open(os.path.join(RESULTS_DIR, f"{name}.json"), "r") as f:
expected = json.load(f)
assert state.shared == expected
return assert_fn
def make_graph_test(name: str, write_results: bool = False):
async def test_graph(scene):
assert_fn = make_assert_fn(name, write_results)
def error_handler(state, error: Exception):
raise error
with ActiveScene(scene):
graph = load_test_graph(name)
assert graph is not None
graph.callbacks.append(assert_fn)
graph.error_handlers.append(error_handler)
await graph.execute()
return test_graph
@pytest.mark.asyncio
async def test_graph_core(mock_scene):
fn = make_graph_test("test-harness-core", False)
await fn(mock_scene)
@pytest.mark.asyncio
async def test_graph_data(mock_scene):
fn = make_graph_test("test-harness-data", False)
await fn(mock_scene)
@pytest.mark.asyncio
async def test_graph_scene(mock_scene):
fn = make_graph_test("test-harness-scene", False)
await fn(mock_scene)
@pytest.mark.asyncio
async def test_graph_functions(mock_scene):
fn = make_graph_test("test-harness-functions", False)
await fn(mock_scene)
@pytest.mark.asyncio
async def test_graph_agents(mock_scene):
fn = make_graph_test("test-harness-agents", False)
await fn(mock_scene)
@pytest.mark.asyncio
async def test_graph_prompt(mock_scene):
fn = make_graph_test("test-harness-prompt", False)
async with MockClientContext() as client_reponses:
client_reponses.append("The sum of 1 and 5 is 6.")
client_reponses.append('```json\n{\n "result": 6\n}\n```')
await fn(mock_scene)
@pytest.mark.asyncio
async def test_graph_collectors(mock_scene):
fn = make_graph_test("test-harness-collectors", False)
await fn(mock_scene)
@pytest.mark.asyncio
async def test_graph_context_ids(mock_scene):
fn = make_graph_test("test-harness-context-ids", False)
await fn(mock_scene)