mirror of
https://github.com/vegu-ai/talemate.git
synced 2026-08-29 10:08:58 +02:00
fix: apply the director's character attribute limit to character card import (#166)
* fix: apply the director's character attribute limit to character card import * fix: address PR review feedback on card import attribute limit * fix: trim docs-index summary to avoid find_docs false positives
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
fixes:
|
||||
- "Character Card Import: A failed description or example dialogue generation no longer wipes the card's own text. Both steps clear the field before regenerating it so the card's version doesn't bias the rewrite, but a generation that errored out or came back empty left the character with nothing at all — an empty description then also blinded the attribute extraction that runs right after it. The card's original description and example dialogue are now restored whenever generation fails, returns nothing, or is cancelled, matching what the import already does when those generation steps are switched off."
|
||||
- "Character Card Import: A failed attribute extraction no longer overwrites the card's attributes. A generation that errored out or came back empty was still written to the character — as a sheet containing nothing but the character's own name — which dropped whatever attributes the card supplied (its gender, for example) and shadowed the character description in every prompt that renders the character sheet. An extraction that produces nothing now keeps the card's attributes, matching what the import already does when the attribute step is switched off."
|
||||
- "Character Card Import: The director's 'Limit character attributes' setting now applies to character card import. The setting governs every other character creation path the director manages, but import ignored it entirely — importing a card with a limit of 5 configured still produced an unbounded sheet, with nothing indicating why. Both import paths (the per-aspect flow and the Creator's Fast one-shot) now generate at most the configured number of attributes. Attributes the card itself supplies are untouched when attribute extraction is switched off."
|
||||
- "Pocket TTS: Pasting a Hugging Face token into the agent settings now enables the gated voice-cloning model download without restarting Talemate — previously a generation attempted before the token was set cached the fallback model without voice cloning, so following the in-app 'paste token and try again' instructions kept failing until a restart. While voice cloning is unavailable the agent card now also shows a warning."
|
||||
- "Prompt Templates: The `system_time` template function crashed with 'Invalid format string' on Windows — it used strftime codes only available on Linux/macOS. The time is now formatted platform-independently with identical output."
|
||||
- "Director Chat: Images created through the director chat's image generation action never appeared in the chat — the generation completed and the image was saved to the scene's assets, but the 'Image Generated' message was inserted into a newly created orphan chat instead of the conversation that requested it. The message is now inserted into the initiating chat."
|
||||
|
||||
@@ -159,7 +159,7 @@ The Character Management settings control how the director handles character cre
|
||||
|
||||
##### Limit character attributes
|
||||
|
||||
Controls the maximum number of attributes that will be generated when creating or updating character sheets. This applies when the director creates new characters or when character sheets are generated through templates.
|
||||
Controls the maximum number of attributes that will be generated when creating or updating character sheets. This applies when the director creates new characters, when character sheets are generated through templates, and when a character sheet is generated during [character card import](../../character-card-import.md).
|
||||
|
||||
- **0** (default): No limit - attributes are generated without restriction
|
||||
- **1-40**: Generates at most this many attributes (the character's own name is not counted)
|
||||
|
||||
@@ -113,7 +113,7 @@ By default, importing a character card runs several AI generation steps that enr
|
||||
|
||||
- **Generate Description**: AI rewrites the character description. When disabled, the card's original description is kept as-is.
|
||||
|
||||
- **Extract Attributes**: AI extracts a structured attribute sheet (age, appearance, personality, etc.) from the card.
|
||||
- **Extract Attributes**: AI extracts a structured attribute sheet (age, appearance, personality, etc.) from the card. The Director's [**Limit character attributes**](/talemate/user-guide/agents/director/settings/#limit-character-attributes) setting caps how many attributes this generates.
|
||||
|
||||
- **Generate Dialogue Instructions**: AI generates acting instructions that guide how the character speaks and behaves.
|
||||
|
||||
|
||||
@@ -385,9 +385,9 @@
|
||||
title: Character Card Import
|
||||
summary: How to import TavernAI-style character cards (Chara Card V0-V3, PNG/JPG/WebP images or JSON) from the home screen's
|
||||
Import dropzone or the Scene Library's Character Cards section to create a new scene. Covers the import dialog, character
|
||||
detection/manual selection, options for character book entries, alternate greetings, shared context setup, writing style
|
||||
template, player character setup, and troubleshooting
|
||||
failed analysis.
|
||||
detection/manual selection, the AI Generation section (per-step generation toggles with card-data fallback, Full/Minimal
|
||||
presets, and the director's attribute limit capping extracted attributes), options for character book entries, alternate
|
||||
greetings, shared context setup, writing style template, player character setup, and troubleshooting failed analysis.
|
||||
- path: user-guide/clients/auto-retry.md
|
||||
title: Auto Retry
|
||||
summary: 'Per-client sliders (0-5, default 0 = notify immediately) for automatically retrying response issues before the
|
||||
|
||||
@@ -681,6 +681,7 @@ async def _determine_character_attributes(
|
||||
character,
|
||||
loading_status: LoadingStatus,
|
||||
relevant_info: RelevantCharacterInfo,
|
||||
max_attrs: int | None = None,
|
||||
) -> None:
|
||||
"""Determine and set character attributes."""
|
||||
loading_status("Determine character attributes...")
|
||||
@@ -693,6 +694,7 @@ async def _determine_character_attributes(
|
||||
# context character the prompt's active-character loop misses it
|
||||
character=character,
|
||||
dynamic_instructions=relevant_info.to_dynamic_instructions(scenario=False),
|
||||
max_attributes=max_attrs,
|
||||
)
|
||||
|
||||
# an extraction that produced nothing must not replace the card's
|
||||
@@ -1303,6 +1305,7 @@ async def _process_character_fast(
|
||||
original_dialogue_examples_text: str,
|
||||
import_options: CharacterCardImportOptions,
|
||||
max_examples: int = 5,
|
||||
max_attrs: int | None = None,
|
||||
) -> None:
|
||||
"""Fast mode: route the enabled extraction aspects through the creator
|
||||
agent's consolidated generation (one prompt instead of one per aspect).
|
||||
@@ -1321,6 +1324,7 @@ async def _process_character_fast(
|
||||
original_dialogue_examples_text: Original dialogue examples text from character card
|
||||
import_options: Import options
|
||||
max_examples: Maximum number of dialogue examples to generate (default: 5)
|
||||
max_attrs: Maximum number of attributes to generate (None for unlimited)
|
||||
"""
|
||||
creator = instance.get_agent("creator")
|
||||
|
||||
@@ -1363,6 +1367,7 @@ async def _process_character_fast(
|
||||
scenario=False
|
||||
),
|
||||
max_examples=max_examples,
|
||||
max_attributes=max_attrs,
|
||||
content_role="text",
|
||||
)
|
||||
)
|
||||
@@ -1426,6 +1431,9 @@ async def _process_characters_for_import(
|
||||
director = instance.get_agent("director")
|
||||
creator = instance.get_agent("creator")
|
||||
|
||||
# 0 means unlimited
|
||||
max_attrs = director.cm_max_attributes or None
|
||||
|
||||
for character in characters:
|
||||
# Add character to character_data without activating
|
||||
# Characters will be activated later by _activate_characters_from_greeting
|
||||
@@ -1450,6 +1458,7 @@ async def _process_characters_for_import(
|
||||
relevant_info=relevant_info,
|
||||
original_dialogue_examples_text=original_dialogue_examples_text,
|
||||
import_options=import_options,
|
||||
max_attrs=max_attrs,
|
||||
)
|
||||
else:
|
||||
if import_options.extract_description:
|
||||
@@ -1461,7 +1470,10 @@ async def _process_characters_for_import(
|
||||
|
||||
if import_options.extract_attributes:
|
||||
await _determine_character_attributes(
|
||||
character, loading_status, relevant_info=relevant_info
|
||||
character,
|
||||
loading_status,
|
||||
relevant_info=relevant_info,
|
||||
max_attrs=max_attrs,
|
||||
)
|
||||
|
||||
if import_options.extract_dialogue_instructions:
|
||||
|
||||
@@ -84,6 +84,14 @@ def fast_mode(agents):
|
||||
agents.creator.actions["character_creation"].config["fast"].value = False
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def max_attributes(agents):
|
||||
config = agents.director.actions["character_management"].config["max_attributes"]
|
||||
previous = config.value
|
||||
yield lambda value: setattr(config, "value", value)
|
||||
config.value = previous
|
||||
|
||||
|
||||
def _scene_stub():
|
||||
return SimpleNamespace(
|
||||
name=None,
|
||||
@@ -801,6 +809,95 @@ async def test_process_characters_fast_mode_cancellation_propagates(fast_mode):
|
||||
assert character.example_dialogue == ["Hero: raw example"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Director attribute limit
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def test_process_characters_split_mode_sheet_capped_by_max_attributes(
|
||||
agents, max_attributes
|
||||
):
|
||||
max_attributes(2)
|
||||
character = _card_character()
|
||||
scene = _scene_stub()
|
||||
|
||||
async with MockClientContext():
|
||||
client_responses.get().append(
|
||||
sheet_response(
|
||||
"Hero", {"Age": "20", "Occupation": "knight", "Mood": "grim"}
|
||||
)
|
||||
)
|
||||
await _process_characters_for_import(
|
||||
scene,
|
||||
[character],
|
||||
["hi"],
|
||||
"",
|
||||
LoadingStatus(None),
|
||||
_options_all_disabled(extract_attributes=True),
|
||||
)
|
||||
|
||||
# enforced twice: prompt instruction + parser truncation
|
||||
assert "at most 2 attributes" in str(agents.client.prompt_history[0]["prompt"])
|
||||
assert character.base_attributes == {"Name": "Hero", "Age": "20"}
|
||||
|
||||
|
||||
async def test_process_characters_split_mode_zero_max_attributes_uncapped(agents):
|
||||
# 0 means unlimited - the default - and must not reach the prompt
|
||||
assert agents.director.cm_max_attributes == 0
|
||||
character = _card_character()
|
||||
scene = _scene_stub()
|
||||
|
||||
async with MockClientContext():
|
||||
client_responses.get().append(
|
||||
sheet_response(
|
||||
"Hero", {"Age": "20", "Occupation": "knight", "Mood": "grim"}
|
||||
)
|
||||
)
|
||||
await _process_characters_for_import(
|
||||
scene,
|
||||
[character],
|
||||
["hi"],
|
||||
"",
|
||||
LoadingStatus(None),
|
||||
_options_all_disabled(extract_attributes=True),
|
||||
)
|
||||
|
||||
assert "at most" not in str(agents.client.prompt_history[0]["prompt"])
|
||||
assert character.base_attributes == {
|
||||
"Name": "Hero",
|
||||
"Age": "20",
|
||||
"Occupation": "knight",
|
||||
"Mood": "grim",
|
||||
}
|
||||
|
||||
|
||||
async def test_process_characters_fast_mode_capped_by_max_attributes(
|
||||
fast_mode, max_attributes
|
||||
):
|
||||
agents = fast_mode
|
||||
max_attributes(2)
|
||||
character = _card_character()
|
||||
scene = _scene_stub()
|
||||
|
||||
async with MockClientContext():
|
||||
client_responses.get().append(
|
||||
unified_response(
|
||||
attributes="Age: 20\nOccupation: knight\nMood: grim",
|
||||
)
|
||||
)
|
||||
await _process_characters_for_import(
|
||||
scene,
|
||||
[character],
|
||||
["hi"],
|
||||
"",
|
||||
LoadingStatus(None),
|
||||
_options_all_disabled(extract_attributes=True),
|
||||
)
|
||||
|
||||
assert "At most 2 attributes" in str(agents.client.prompt_history[0]["prompt"])
|
||||
assert character.base_attributes == {"Age": "20", "Occupation": "knight"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"aspect,agent_attr,method",
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user