Files
talemate/tests/test_volatile_context_placement.py

298 lines
11 KiB
Python
Raw Normal View History

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
"""
Tests for volatile_context_placement() the function that determines
whether volatile content (RAG, internal notes) goes before or after
the scene history in prompts.
Tests the priority chain:
1. Per-agent override ("on"/"off") takes precedence
2. "auto" defers to client.optimize_prompt_caching
3. No agent context "before_history"
"""
import pytest
from unittest.mock import Mock
from talemate.agents.base import (
AgentAction,
AgentActionConfig,
optimize_prompt_caching_action,
)
from talemate.agents.context import ActiveAgentContext, active_agent
from talemate.prompts.base import Prompt
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_agent(
agent_type: str, override_value: str = "auto", client_opc: bool = False
):
"""Build a minimal mock agent with an optimize_prompt_caching config."""
agent = Mock()
agent.agent_type = agent_type
action = optimize_prompt_caching_action()
action.config["optimize_prompt_caching"].value = override_value
agent.actions = {
"prompt_caching": action,
}
# Client
agent.client = Mock()
agent.client.optimize_prompt_caching = client_opc
return agent
def _make_agent_no_config(agent_type: str, client_opc: bool = False):
"""Build a mock agent that has NO optimize_prompt_caching config at all."""
agent = Mock()
agent.agent_type = agent_type
agent.actions = {
"some_action": AgentAction(
enabled=True,
label="Some Action",
config={
"other_setting": AgentActionConfig(
type="bool",
label="Other",
value=True,
),
},
),
}
agent.client = Mock()
agent.client.optimize_prompt_caching = client_opc
return agent
def _set_active_agent(agent):
"""Set the active agent context and return the token for cleanup."""
ctx = ActiveAgentContext(agent=agent, fn=lambda: None)
return active_agent.set(ctx)
def _call_volatile_context_placement():
"""Invoke volatile_context_placement via a Prompt instance."""
prompt = Prompt.get("conversation.dialogue-movie_script", vars={})
return prompt.volatile_context_placement()
# ---------------------------------------------------------------------------
# Tests: no agent context
# ---------------------------------------------------------------------------
class TestNoAgentContext:
def test_returns_before_history_when_no_active_agent(self):
"""With no active agent, default to before_history."""
# Ensure no agent is set
token = active_agent.set(None)
try:
result = _call_volatile_context_placement()
assert result == "before_history"
finally:
active_agent.reset(token)
# ---------------------------------------------------------------------------
# Tests: agent override = "on"
# ---------------------------------------------------------------------------
class TestAgentOverrideOn:
def test_returns_after_history_regardless_of_client(self):
"""Agent override 'on' → after_history, even if client is False."""
agent = _make_agent("conversation", override_value="on", client_opc=False)
token = _set_active_agent(agent)
try:
assert _call_volatile_context_placement() == "after_history"
finally:
active_agent.reset(token)
def test_returns_after_history_with_client_also_true(self):
"""Agent override 'on' + client True → after_history."""
agent = _make_agent("narrator", override_value="on", client_opc=True)
token = _set_active_agent(agent)
try:
assert _call_volatile_context_placement() == "after_history"
finally:
active_agent.reset(token)
# ---------------------------------------------------------------------------
# Tests: agent override = "off"
# ---------------------------------------------------------------------------
class TestAgentOverrideOff:
def test_returns_before_history_regardless_of_client(self):
"""Agent override 'off' → before_history, even if client is True."""
agent = _make_agent("director", override_value="off", client_opc=True)
token = _set_active_agent(agent)
try:
assert _call_volatile_context_placement() == "before_history"
finally:
active_agent.reset(token)
def test_returns_before_history_with_client_also_false(self):
"""Agent override 'off' + client False → before_history."""
agent = _make_agent("editor", override_value="off", client_opc=False)
token = _set_active_agent(agent)
try:
assert _call_volatile_context_placement() == "before_history"
finally:
active_agent.reset(token)
# ---------------------------------------------------------------------------
# Tests: agent override = "auto" (defer to client)
# ---------------------------------------------------------------------------
class TestAgentOverrideAuto:
def test_auto_with_client_true(self):
"""Agent 'auto' + client optimize_prompt_caching=True → after_history."""
agent = _make_agent("conversation", override_value="auto", client_opc=True)
token = _set_active_agent(agent)
try:
assert _call_volatile_context_placement() == "after_history"
finally:
active_agent.reset(token)
def test_auto_with_client_false(self):
"""Agent 'auto' + client optimize_prompt_caching=False → before_history."""
agent = _make_agent("summarizer", override_value="auto", client_opc=False)
token = _set_active_agent(agent)
try:
assert _call_volatile_context_placement() == "before_history"
finally:
active_agent.reset(token)
# ---------------------------------------------------------------------------
# Tests: agent with no optimize_prompt_caching config (fallback to client)
# ---------------------------------------------------------------------------
class TestAgentWithoutConfig:
def test_no_config_client_true(self):
"""Agent has no optimize_prompt_caching config + client True → after_history."""
agent = _make_agent_no_config("conversation", client_opc=True)
token = _set_active_agent(agent)
try:
assert _call_volatile_context_placement() == "after_history"
finally:
active_agent.reset(token)
def test_no_config_client_false(self):
"""Agent has no optimize_prompt_caching config + client False → before_history."""
agent = _make_agent_no_config("narrator", client_opc=False)
token = _set_active_agent(agent)
try:
assert _call_volatile_context_placement() == "before_history"
finally:
active_agent.reset(token)
# ---------------------------------------------------------------------------
# Tests: agent with no client
# ---------------------------------------------------------------------------
class TestAgentWithNoClient:
def test_auto_no_client(self):
"""Agent override 'auto' with client=None → before_history."""
agent = _make_agent("creator", override_value="auto", client_opc=False)
agent.client = None
token = _set_active_agent(agent)
try:
assert _call_volatile_context_placement() == "before_history"
finally:
active_agent.reset(token)
def test_on_no_client(self):
"""Agent override 'on' with client=None → after_history (override wins)."""
agent = _make_agent("creator", override_value="on", client_opc=False)
agent.client = None
token = _set_active_agent(agent)
try:
assert _call_volatile_context_placement() == "after_history"
finally:
active_agent.reset(token)
# ---------------------------------------------------------------------------
# Tests: optimize_prompt_caching_config factory
# ---------------------------------------------------------------------------
class TestOptimizePromptCachingAction:
def test_factory_returns_agent_action(self):
action = optimize_prompt_caching_action()
assert isinstance(action, AgentAction)
def test_factory_has_config_with_correct_key(self):
action = optimize_prompt_caching_action()
assert "optimize_prompt_caching" in action.config
assert isinstance(action.config["optimize_prompt_caching"], AgentActionConfig)
def test_factory_default_value_is_auto(self):
action = optimize_prompt_caching_action()
assert action.config["optimize_prompt_caching"].value == "auto"
def test_factory_has_three_choices(self):
action = optimize_prompt_caching_action()
config = action.config["optimize_prompt_caching"]
assert len(config.choices) == 3
values = [c["value"] for c in config.choices]
assert values == ["auto", "on", "off"]
def test_factory_returns_independent_instances(self):
"""Each call should return a new instance, not a shared reference."""
a = optimize_prompt_caching_action()
b = optimize_prompt_caching_action()
a.config["optimize_prompt_caching"].value = "on"
assert b.config["optimize_prompt_caching"].value == "auto"
# ---------------------------------------------------------------------------
# Tests: all 6 agents have the config
# ---------------------------------------------------------------------------
class TestAllAgentsHaveConfig:
"""Verify that every agent has the prompt_caching action with
optimize_prompt_caching config."""
@pytest.mark.parametrize(
"agent_cls_path",
[
"talemate.agents.conversation.ConversationAgent",
"talemate.agents.narrator.NarratorAgent",
"talemate.agents.director.DirectorAgent",
"talemate.agents.creator.CreatorAgent",
"talemate.agents.editor.EditorAgent",
"talemate.agents.summarize.SummarizeAgent",
"talemate.agents.world_state.WorldStateAgent",
],
)
def test_agent_has_optimize_prompt_caching(self, agent_cls_path):
import importlib
module_path, class_name = agent_cls_path.rsplit(".", 1)
module = importlib.import_module(module_path)
cls = getattr(module, class_name)
actions = cls.init_actions()
assert "prompt_caching" in actions, (
f"{class_name} missing 'prompt_caching' action"
)
action = actions["prompt_caching"]
assert isinstance(action, AgentAction)
assert action.config is not None, f"{class_name}.prompt_caching has no config"
assert "optimize_prompt_caching" in action.config, (
f"{class_name}.prompt_caching missing 'optimize_prompt_caching' config"
)
opc = action.config["optimize_prompt_caching"]
assert opc.value == "auto"
assert opc.type == "text"