mirror of
https://github.com/vegu-ai/talemate.git
synced 2026-09-01 19:48:52 +02:00
feat: enhance reasoning pattern handling in client configuration and UI
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
- "Message Regeneration: Fixed a bug where regenerating a message that fails (e.g., missing agent or function) would permanently remove the original message from the scene. The original message is now restored to both history and UI on failure, and unhandled exceptions during regeneration are caught."
|
||||
- "Anthropic Client: Fixed generation error caused by missing required max_tokens parameter when response length capping is disabled. Now defaults to the model's API output token limit."
|
||||
- "Anthropic Client: Cleaned up supported models list, removing outdated dated model IDs."
|
||||
- "Reasoning Pattern: Fixed client reasoning pattern round-tripping through the default fallback. Blanking the pattern in the client config no longer silently re-saves as `.*?</think>`, and the template-derived pattern (e.g. Gemma 4's `<channel|>`) is now correctly adopted when the stored pattern is empty. The Client Config placeholder also shows the actual template-derived default instead of a hard-coded value."
|
||||
- Fixed crash in voice library character manager when a character was deactivated.
|
||||
- Fixed blank page on some Windows 10 systems where JavaScript files were served with incorrect MIME type (text/plain), causing Chromium-based browsers to reject module scripts.
|
||||
- Fixed "No episodes available" placeholder being selectable in the episode list.
|
||||
|
||||
@@ -708,31 +708,34 @@ class ClientBase:
|
||||
|
||||
if (
|
||||
spec.reasoning_pattern
|
||||
and spec.reasoning_pattern != self.reason_response_pattern
|
||||
and spec.reasoning_pattern != self.client_config.reason_response_pattern
|
||||
):
|
||||
log.info("reasoning pattern determined from prompt template", spec=spec)
|
||||
self.client_config.reason_response_pattern = spec.reasoning_pattern
|
||||
|
||||
return prompt
|
||||
|
||||
def prompt_template_example(self):
|
||||
def prompt_template_example(self) -> tuple[str | None, str | None, PromptSpec]:
|
||||
if not getattr(self, "model_name", None):
|
||||
return None, None
|
||||
return None, None, PromptSpec()
|
||||
|
||||
if not self.enabled:
|
||||
return None, None
|
||||
return None, None, PromptSpec()
|
||||
|
||||
model_name = self.model_name
|
||||
if self.lock_template:
|
||||
model_name = locked_model_template(self.name, self.model_name)
|
||||
|
||||
return model_prompt(
|
||||
spec = PromptSpec()
|
||||
rendered, template_file = model_prompt(
|
||||
model_name,
|
||||
"{sysmsg}",
|
||||
"{prompt}<|BOT|>{LLM coercion}",
|
||||
default_template=self.default_prompt_template,
|
||||
reasoning_tokens=self.validated_reason_tokens if self.reason_enabled else 0,
|
||||
spec=spec,
|
||||
)
|
||||
return rendered, template_file, spec
|
||||
|
||||
def split_prompt_for_coercion(self, prompt: str) -> tuple[str, str]:
|
||||
"""
|
||||
@@ -878,7 +881,9 @@ class ClientBase:
|
||||
|
||||
default_prompt_template = self.default_prompt_template
|
||||
|
||||
prompt_template_example, prompt_template_file = self.prompt_template_example()
|
||||
prompt_template_example, prompt_template_file, prompt_template_spec = (
|
||||
self.prompt_template_example()
|
||||
)
|
||||
has_prompt_template = (
|
||||
prompt_template_file and prompt_template_file != default_prompt_template
|
||||
)
|
||||
@@ -898,7 +903,7 @@ class ClientBase:
|
||||
log.debug("auto_determine_prompt_template", model_name=self.model_name)
|
||||
self.auto_determine_prompt_template_attempt = self.model_name
|
||||
self.determine_prompt_template()
|
||||
prompt_template_example, prompt_template_file = (
|
||||
prompt_template_example, prompt_template_file, prompt_template_spec = (
|
||||
self.prompt_template_example()
|
||||
)
|
||||
has_prompt_template = (
|
||||
@@ -913,6 +918,9 @@ class ClientBase:
|
||||
"has_prompt_template": has_prompt_template,
|
||||
"dedicated_default_template": dedicated_default_template,
|
||||
"template_file": prompt_template_file,
|
||||
"reason_response_pattern_default": (
|
||||
prompt_template_spec.reasoning_pattern or DEFAULT_REASONING_PATTERN
|
||||
),
|
||||
"meta": self.Meta().model_dump(),
|
||||
"error_action": None,
|
||||
"double_coercion": self.double_coercion,
|
||||
@@ -964,7 +972,7 @@ class ClientBase:
|
||||
"reason_enabled": self.reason_enabled,
|
||||
"reason_tokens": self.reason_tokens,
|
||||
"min_reason_tokens": self.min_reason_tokens,
|
||||
"reason_response_pattern": self.reason_response_pattern,
|
||||
"reason_response_pattern": self.client_config.reason_response_pattern,
|
||||
"reason_prefill": self.reason_prefill,
|
||||
"reason_failure_behavior": self.reason_failure_behavior,
|
||||
"requires_reasoning_pattern": self.requires_reasoning_pattern,
|
||||
|
||||
@@ -6,9 +6,13 @@ from pathlib import Path
|
||||
|
||||
from talemate import VERSION
|
||||
from talemate.changelog import list_revision_entries, delete_changelog_files
|
||||
from talemate.client.model_prompts import model_prompt
|
||||
from talemate.client.model_prompts import model_prompt, PromptSpec
|
||||
from talemate.client.registry import CLIENT_CLASSES
|
||||
from talemate.client.base import ClientBase, locked_model_template
|
||||
from talemate.client.base import (
|
||||
ClientBase,
|
||||
DEFAULT_REASONING_PATTERN,
|
||||
locked_model_template,
|
||||
)
|
||||
from talemate.config import Config as AppConfigData
|
||||
from talemate.config.schema import GamePlayerCharacter
|
||||
from talemate.config import get_config, Config, update_config
|
||||
@@ -219,8 +223,23 @@ class ConfigPlugin(Plugin):
|
||||
client_name=payload.client_name,
|
||||
)
|
||||
|
||||
spec = PromptSpec()
|
||||
client = None
|
||||
if payload.client_name:
|
||||
try:
|
||||
client = get_client(payload.client_name)
|
||||
except KeyError:
|
||||
client = None
|
||||
reasoning_tokens = 0
|
||||
if client is not None and getattr(client, "reason_enabled", False):
|
||||
reasoning_tokens = getattr(client, "validated_reason_tokens", 0) or 0
|
||||
|
||||
prompt_template_example, prompt_template_file = model_prompt(
|
||||
model_name, "sysmsg", "prompt<|BOT|>{LLM coercion}"
|
||||
model_name,
|
||||
"sysmsg",
|
||||
"prompt<|BOT|>{LLM coercion}",
|
||||
reasoning_tokens=reasoning_tokens,
|
||||
spec=spec,
|
||||
)
|
||||
|
||||
log.info(
|
||||
@@ -237,6 +256,9 @@ class ConfigPlugin(Plugin):
|
||||
"prompt_template_example": prompt_template_example,
|
||||
"has_prompt_template": True if prompt_template_example else False,
|
||||
"template_file": prompt_template_file,
|
||||
"reason_response_pattern_default": (
|
||||
spec.reasoning_pattern or DEFAULT_REASONING_PATTERN
|
||||
),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@@ -206,12 +206,14 @@
|
||||
<v-btn @click.stop="client.reason_response_pattern=''" size="small" color="primary" variant="text">{{ 'Default' }}</v-btn>
|
||||
<!-- gpt-oss -->
|
||||
<v-btn @click.stop="client.reason_response_pattern='.*?final<\\|message\\|>'" size="small" color="primary" variant="text">{{ 'gpt-oss' }}</v-btn>
|
||||
<!-- gemma-4 -->
|
||||
<v-btn @click.stop="client.reason_response_pattern='.*?<channel\\|>'" size="small" color="primary" variant="text">{{ 'gemma-4' }}</v-btn>
|
||||
<!-- ◁/think▷ -->
|
||||
<v-btn @click.stop="client.reason_response_pattern='.*?◁/think▷'" size="small" color="primary" variant="text">{{ '.*?◁/think▷' }}</v-btn>
|
||||
<!-- </think> -->
|
||||
<v-btn @click.stop="client.reason_response_pattern='.*?</think>'" size="small" color="primary" variant="text">{{ '.*?</think>' }}</v-btn>
|
||||
</v-sheet>
|
||||
<v-text-field v-model="client.reason_response_pattern" label="Pattern to strip from the response if the model is reasoning" hint="This is a regular expression that will be used to strip out the thinking tokens from the response." placeholder=".*?</think>"></v-text-field>
|
||||
<v-text-field v-model="client.reason_response_pattern" label="Pattern to strip from the response if the model is reasoning" hint="This is a regular expression that will be used to strip out the thinking tokens from the response." :placeholder="client.data && client.data.reason_response_pattern_default ? client.data.reason_response_pattern_default : '.*?</think>'"></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="client.reason_enabled && client.requires_reasoning_pattern">
|
||||
@@ -705,6 +707,9 @@ export default {
|
||||
this.client.data.has_prompt_template = data.data.has_prompt_template;
|
||||
this.client.data.prompt_template_example = data.data.prompt_template_example;
|
||||
this.client.data.template_file = data.data.template_file;
|
||||
if (data.data.reason_response_pattern_default !== undefined) {
|
||||
this.client.data.reason_response_pattern_default = data.data.reason_response_pattern_default;
|
||||
}
|
||||
this.waitingForTemplateSelection = false;
|
||||
} else if (data.type === 'config' && data.action === 'std_llm_templates') {
|
||||
console.log("Got std templates", data.data.templates);
|
||||
|
||||
@@ -767,3 +767,18 @@ class TestPromptSpec:
|
||||
)
|
||||
assert spec.reasoning_pattern is not None
|
||||
assert "seed:think" in spec.reasoning_pattern
|
||||
|
||||
def test_spec_reasoning_pattern_gpt_oss(self, model_prompt_with_std):
|
||||
"""GPT-OSS template always sets reasoning_pattern."""
|
||||
mp, _ = model_prompt_with_std
|
||||
spec = PromptSpec()
|
||||
mp(
|
||||
model_name="__spec_test__",
|
||||
system_message="sys",
|
||||
prompt="user<|BOT|>",
|
||||
default_template="GPT-OSS.jinja2",
|
||||
spec=spec,
|
||||
)
|
||||
assert spec.reasoning_pattern is not None
|
||||
assert "final" in spec.reasoning_pattern
|
||||
assert "message" in spec.reasoning_pattern
|
||||
|
||||
Reference in New Issue
Block a user