Studio: Fix chat template disappearing after browser refresh (#5209)
* fix: preserve chat template on refresh * chore: simplify chat template status lookup
This commit is contained in:
parent
e4e89f41c1
commit
05f46686de
6 changed files with 43 additions and 0 deletions
|
|
@ -1701,6 +1701,18 @@ class LlamaCppBackend:
|
|||
if chat_template_override:
|
||||
import tempfile
|
||||
|
||||
self._chat_template = chat_template_override
|
||||
flags = detect_reasoning_flags(
|
||||
self._chat_template,
|
||||
self._model_identifier,
|
||||
log_source = "GGUF chat template override",
|
||||
)
|
||||
self._supports_reasoning = flags["supports_reasoning"]
|
||||
self._reasoning_style = flags["reasoning_style"]
|
||||
self._reasoning_always_on = flags["reasoning_always_on"]
|
||||
self._supports_preserve_thinking = flags["supports_preserve_thinking"]
|
||||
self._supports_tools = flags["supports_tools"]
|
||||
|
||||
self._chat_template_file = tempfile.NamedTemporaryFile(
|
||||
mode = "w",
|
||||
suffix = ".jinja",
|
||||
|
|
|
|||
|
|
@ -283,6 +283,10 @@ class InferenceStatusResponse(BaseModel):
|
|||
supports_tools: bool = Field(
|
||||
False, description = "Whether the active model supports tool calling"
|
||||
)
|
||||
chat_template: Optional[str] = Field(
|
||||
None,
|
||||
description = "Jinja2 chat template string for the active model",
|
||||
)
|
||||
context_length: Optional[int] = Field(
|
||||
None, description = "Context length of the active model"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -985,6 +985,7 @@ async def get_status(
|
|||
reasoning_always_on = llama_backend.reasoning_always_on,
|
||||
supports_preserve_thinking = llama_backend.supports_preserve_thinking,
|
||||
supports_tools = llama_backend.supports_tools,
|
||||
chat_template = llama_backend.chat_template,
|
||||
context_length = llama_backend.context_length,
|
||||
max_context_length = llama_backend.max_context_length,
|
||||
native_context_length = llama_backend.native_context_length,
|
||||
|
|
@ -998,12 +999,19 @@ async def get_status(
|
|||
is_audio = False
|
||||
audio_type = None
|
||||
has_audio_input = False
|
||||
model_info = {}
|
||||
if backend.active_model_name:
|
||||
model_info = backend.models.get(backend.active_model_name, {})
|
||||
is_vision = model_info.get("is_vision", False)
|
||||
is_audio = model_info.get("is_audio", False)
|
||||
audio_type = model_info.get("audio_type")
|
||||
has_audio_input = model_info.get("has_audio_input", False)
|
||||
chat_template_info = model_info.get("chat_template_info", {})
|
||||
chat_template = (
|
||||
chat_template_info.get("template")
|
||||
if isinstance(chat_template_info, dict)
|
||||
else None
|
||||
)
|
||||
|
||||
# Non-GGUF: only gpt-oss Harmony is wired through the transformers
|
||||
# generation path. Other template-level reasoning / tool kwargs
|
||||
|
|
@ -1041,6 +1049,7 @@ async def get_status(
|
|||
reasoning_always_on = False,
|
||||
supports_preserve_thinking = False,
|
||||
supports_tools = False,
|
||||
chat_template = chat_template,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -320,11 +320,23 @@ class TestPydanticModels:
|
|||
"""Field exists in InferenceStatusResponse.model_fields."""
|
||||
assert "native_context_length" in InferenceStatusResponse.model_fields
|
||||
|
||||
def test_status_response_has_chat_template_field(self):
|
||||
"""Status includes chat_template so the UI can rehydrate after refresh."""
|
||||
assert "chat_template" in InferenceStatusResponse.model_fields
|
||||
|
||||
def test_status_response_defaults_none(self):
|
||||
"""Omitting native_context_length defaults to None."""
|
||||
resp = InferenceStatusResponse()
|
||||
assert resp.native_context_length is None
|
||||
|
||||
def test_status_response_chat_template_roundtrip(self):
|
||||
"""chat_template serializes and validates as part of status."""
|
||||
resp = InferenceStatusResponse(chat_template = "{{ messages }}")
|
||||
roundtripped = InferenceStatusResponse.model_validate_json(
|
||||
resp.model_dump_json()
|
||||
)
|
||||
assert roundtripped.chat_template == "{{ messages }}"
|
||||
|
||||
def test_roundtrip_preserves_value(self):
|
||||
"""model_validate_json(model_dump_json()) round-trips."""
|
||||
resp = LoadResponse(
|
||||
|
|
|
|||
|
|
@ -266,6 +266,10 @@ export function useChatModelRuntime() {
|
|||
? (statusRes.native_context_length ?? null)
|
||||
: null;
|
||||
const currentSpecType = normalizeSpeculativeType(statusRes.speculative_type);
|
||||
const nextDefaultChatTemplate =
|
||||
statusRes.chat_template === undefined
|
||||
? useChatRuntimeStore.getState().defaultChatTemplate
|
||||
: statusRes.chat_template;
|
||||
useChatRuntimeStore.setState({
|
||||
supportsReasoning,
|
||||
reasoningAlwaysOn,
|
||||
|
|
@ -282,6 +286,7 @@ export function useChatModelRuntime() {
|
|||
ggufNativeContextLength,
|
||||
modelRequiresTrustRemoteCode:
|
||||
statusRes.requires_trust_remote_code ?? false,
|
||||
defaultChatTemplate: nextDefaultChatTemplate,
|
||||
speculativeType: currentSpecType,
|
||||
loadedSpeculativeType: currentSpecType,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@ export interface InferenceStatusResponse {
|
|||
reasoning_always_on?: boolean;
|
||||
supports_preserve_thinking?: boolean;
|
||||
supports_tools?: boolean;
|
||||
chat_template?: string | null;
|
||||
context_length?: number | null;
|
||||
max_context_length?: number | null;
|
||||
native_context_length?: number | null;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue