Files
talemate/tests/test_template_uid_tracking.py
veguAI 42a8863e65 0.36.0 (#255)
Major Features

- API key encryption at rest using Fernet (OS keyring with file fallback)
- Prompt Manager: unified UI with template groups, priority ordering, override tracking, response extractors
- Scene context history review panel with token budgets and best-fit mode
- Multiple concurrent director chats with auto-generated titles
- Granular scene state reset dialog
- Time passage insert/edit/delete in scene view
- Image analysis via OpenAI-compatible and Talemate Client backends
- Volatile context placement after scene history for improved prompt caching

Improvements

- Configurable narrator generation length per narration type
- AI Aware conversation mode
- Summarizer: custom instructions, writing style inclusion, short line filtering
- Anthropic: adaptive thinking support, updated model list (opus-4-5/4-6, haiku-4-5)
- Google: gemini-3.1 support
- World editor: generate from topic, quick create state reinforcement, reorganized menus
- Node editor: promote scene modules to global
- Frontend: version mismatch detection, hideable bracket content, required scene name
- TTS: improved pause handling, audio tag support for vocal markers (ElevenLabs v3)
- Writing style template for AI-generated instructions
- Added Kimi.jinja2 LLM prompt template
- Option to disable character names in stopping strings
- Client response length enforcement options
- Graduated token count sliders
- Increased summarizer token threshold max

Bugfixes

- Fix bracket/paren/brace terminators stripped from message ends
- Fix colon in conversation causing content loss
- Fix "Use as reference" navigating to blank page
- Fix avatar regeneration and manual regenerate
- Fix conversation agent ignoring generation length
- Fix duplicate length instructions with reasoning enabled
- Fix trailing newline on message edits
- Fix summarize dialogue sending too much context with layered history
- Fix layered history inspection and construction issues
- Fix empty response handling in summarization
- Fix context ID dot notation with dotted character names
- Fix recursive retry in focal agent
- Fix leading whitespace causing duplicate prepared responses
- Fix summarization not stripping ANALYSIS OF lines
- Fix template group selection/removal in prompt manager
- Fix multiline text in parentheses/brackets parser
- Fix determine_character_name resolution
- Fix character activate/deactivate desyncing creative menu
- Fix character image generation missing context
- Fix LMStudio client not sending token limits
- Fix Recent Scene images on newer Chromium
- Fix sequential reinforcement messages cut off at first linebreak
- Fix reinforcement removal not clearing state
- Fixes #252, #256, #258

Deprecations

- Removed context investigations (replaced by AI-assisted RAG mixin)
- Removed deprecated prompt templates (fix-continuity-errors, fix-exposition, etc.)
- Removed conversation/edit.jinja2, auto break repetition, CLI reset layered history
---------

Co-authored-by: theDTV2 <47825738+theDTV2@users.noreply.github.com>
2026-03-15 12:00:57 +02:00

211 lines
6.7 KiB
Python

"""
Unit tests for template_uid tracking in PromptData.
Tests that when a Prompt is sent via Prompt.send(), the template_uid is correctly
passed through to the PromptData emitted via the prompt_sent signal.
"""
import pytest
from unittest.mock import AsyncMock
from talemate.client.base import PromptData
from talemate.prompts.base import Prompt, active_template_uid
class TestPromptDataTemplateUid:
"""Tests for the template_uid field in PromptData."""
def test_prompt_data_has_template_uid_field(self):
"""Verify PromptData model includes template_uid field."""
data = PromptData(
kind="test",
prompt="test prompt",
response="test response",
prompt_tokens=10,
response_tokens=5,
client_name="test-client",
client_type="test",
time=1.0,
template_uid="narrator.narrate-scene",
)
assert data.template_uid == "narrator.narrate-scene"
def test_prompt_data_template_uid_defaults_to_none(self):
"""Verify template_uid defaults to None when not provided."""
data = PromptData(
kind="test",
prompt="test prompt",
response="test response",
prompt_tokens=10,
response_tokens=5,
client_name="test-client",
client_type="test",
time=1.0,
)
assert data.template_uid is None
def test_prompt_data_serializes_template_uid(self):
"""Verify template_uid is included in model_dump() output."""
data = PromptData(
kind="test",
prompt="test prompt",
response="test response",
prompt_tokens=10,
response_tokens=5,
client_name="test-client",
client_type="test",
time=1.0,
template_uid="director.guide-scene",
)
dumped = data.model_dump()
assert "template_uid" in dumped
assert dumped["template_uid"] == "director.guide-scene"
class TestActiveTemplateUidContext:
"""Tests for the active_template_uid context variable."""
def test_active_template_uid_defaults_to_none(self):
"""Verify active_template_uid defaults to None."""
assert active_template_uid.get() is None
def test_active_template_uid_can_be_set_and_reset(self):
"""Verify context variable can be set and properly reset."""
assert active_template_uid.get() is None
token = active_template_uid.set("narrator.test")
assert active_template_uid.get() == "narrator.test"
active_template_uid.reset(token)
assert active_template_uid.get() is None
def test_active_template_uid_nested_contexts(self):
"""Verify nested context variable handling."""
assert active_template_uid.get() is None
token1 = active_template_uid.set("outer.template")
assert active_template_uid.get() == "outer.template"
token2 = active_template_uid.set("inner.template")
assert active_template_uid.get() == "inner.template"
active_template_uid.reset(token2)
assert active_template_uid.get() == "outer.template"
active_template_uid.reset(token1)
assert active_template_uid.get() is None
class TestPromptSendSetsTemplateUid:
"""Tests that Prompt.send() properly sets the active_template_uid context."""
@pytest.fixture
def mock_client(self):
"""Create a mock LLM client."""
client = AsyncMock()
client.send_prompt = AsyncMock(return_value="test response")
client.can_be_coerced = True
client.data_format = "json"
return client
@pytest.mark.asyncio
async def test_prompt_send_sets_context_variable(self, mock_client):
"""Verify Prompt.send() sets active_template_uid during client call."""
captured_uid = None
async def capture_uid(*args, **kwargs):
nonlocal captured_uid
captured_uid = active_template_uid.get()
return "test response"
mock_client.send_prompt = capture_uid
prompt = Prompt(
uid="narrator.test-template",
agent_type="narrator",
name="test-template",
template="Test prompt content",
)
await prompt.send(mock_client, kind="test")
assert captured_uid == "narrator.test-template"
@pytest.mark.asyncio
async def test_prompt_send_resets_context_after_completion(self, mock_client):
"""Verify context is reset after Prompt.send() completes."""
assert active_template_uid.get() is None
prompt = Prompt(
uid="narrator.test",
agent_type="narrator",
name="test",
template="Test prompt",
)
await prompt.send(mock_client, kind="test")
# Context should be reset after send completes
assert active_template_uid.get() is None
@pytest.mark.asyncio
async def test_prompt_send_resets_context_on_exception(self, mock_client):
"""Verify context is reset even if client.send_prompt raises."""
mock_client.send_prompt = AsyncMock(side_effect=Exception("Test error"))
prompt = Prompt(
uid="narrator.test",
agent_type="narrator",
name="test",
template="Test prompt",
)
with pytest.raises(Exception, match="Test error"):
await prompt.send(mock_client, kind="test")
# Context should still be reset
assert active_template_uid.get() is None
@pytest.mark.asyncio
async def test_prompt_send_with_empty_uid(self, mock_client):
"""Verify empty uid is handled gracefully."""
captured_uid = None
async def capture_uid(*args, **kwargs):
nonlocal captured_uid
captured_uid = active_template_uid.get()
return "test response"
mock_client.send_prompt = capture_uid
prompt = Prompt(
uid="",
agent_type="",
name="",
template="Test prompt",
)
await prompt.send(mock_client, kind="test")
# Empty string should be converted to None
assert captured_uid is None
@pytest.mark.asyncio
async def test_prompt_from_text_has_no_uid(self, mock_client):
"""Verify Prompt.from_text() results in None template_uid."""
captured_uid = None
async def capture_uid(*args, **kwargs):
nonlocal captured_uid
captured_uid = active_template_uid.get()
return "test response"
mock_client.send_prompt = capture_uid
prompt = Prompt.from_text("Some raw prompt text")
await prompt.send(mock_client, kind="test")
# from_text creates a prompt with empty uid
assert captured_uid is None