Fix native GGUF context ceiling and guard picker template reads
Restore the native context store field so the sidebar slider keeps the full ceiling for drag and drop GGUFs. Limit local chat template reads to the browse allowlist, skip malformed repo ids, and drop unused model picker exports.
This commit is contained in:
parent
1611514ebe
commit
f82327c8c1
11 changed files with 42 additions and 36 deletions
|
|
@ -6,12 +6,17 @@ from __future__ import annotations
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from jinja2 import TemplateError
|
||||
from jinja2.sandbox import ImmutableSandboxedEnvironment
|
||||
|
||||
from hub.services.models.folder_browser import (
|
||||
_build_browse_allowlist,
|
||||
_is_path_inside_allowlist,
|
||||
)
|
||||
from utils.models.gguf_metadata import read_gguf_chat_template
|
||||
from utils.models.model_config import (
|
||||
_extract_quant_label,
|
||||
|
|
@ -22,6 +27,7 @@ from utils.models.model_config import (
|
|||
from utils.paths.path_utils import (
|
||||
get_cache_path,
|
||||
is_local_path,
|
||||
normalize_path,
|
||||
resolve_cached_repo_id_case,
|
||||
)
|
||||
|
||||
|
|
@ -29,6 +35,12 @@ from .schemas import ValidateChatTemplateResponse
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_VALID_REPO_ID = re.compile(r"^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$")
|
||||
|
||||
|
||||
def _is_valid_repo_id(repo_id: str) -> bool:
|
||||
return bool(_VALID_REPO_ID.fullmatch(repo_id))
|
||||
|
||||
_TOKENIZER_CONFIG_PATHS = ("tokenizer_config.json", "LLM/tokenizer_config.json")
|
||||
_JINJA_TEMPLATE_PATHS = ("chat_template.jinja", "LLM/chat_template.jinja")
|
||||
_PROCESSOR_TEMPLATE_PATHS = ("chat_template.json", "LLM/chat_template.json")
|
||||
|
|
@ -221,13 +233,20 @@ def read_default_chat_template(
|
|||
|
||||
if is_local_path(name):
|
||||
try:
|
||||
target = Path(normalize_path(name)).expanduser()
|
||||
if not _is_path_inside_allowlist(target, _build_browse_allowlist()):
|
||||
logger.debug("Refused chat template read outside allowed folders: %s", name)
|
||||
return None
|
||||
if name.lower().endswith(".gguf"):
|
||||
return read_gguf_chat_template(name)
|
||||
return _chat_template_from_dir(Path(name), gguf_variant)
|
||||
return read_gguf_chat_template(str(target))
|
||||
return _chat_template_from_dir(target, gguf_variant)
|
||||
except Exception as exc:
|
||||
logger.debug("Could not read local chat template for %s: %s", name, exc)
|
||||
return None
|
||||
|
||||
if not _is_valid_repo_id(name):
|
||||
return None
|
||||
|
||||
resolved = resolve_cached_repo_id_case(name)
|
||||
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -1211,6 +1211,9 @@ export function ChatPage({
|
|||
const ggufContextLength = useChatRuntimeStore(
|
||||
(state) => state.ggufContextLength,
|
||||
);
|
||||
const ggufNativeContextLength = useChatRuntimeStore(
|
||||
(state) => state.ggufNativeContextLength,
|
||||
);
|
||||
const contextUsage = useChatRuntimeStore((state) => state.contextUsage);
|
||||
const modelsFromStore = useChatRuntimeStore((state) => state.models);
|
||||
const lorasFromStore = useChatRuntimeStore((state) => state.loras);
|
||||
|
|
@ -1987,6 +1990,7 @@ export function ChatPage({
|
|||
activeGgufVariant: null,
|
||||
ggufContextLength: null,
|
||||
ggufMaxContextLength: null,
|
||||
ggufNativeContextLength: null,
|
||||
activeNativePathToken: null,
|
||||
// Clear previous-model counters, else the relaxed external-provider
|
||||
// render gate shows stale stats until the next completion.
|
||||
|
|
@ -2720,6 +2724,7 @@ export function ChatPage({
|
|||
modelId={inferenceParams.checkpoint}
|
||||
ggufVariant={activeGgufVariant ?? null}
|
||||
isGguf={activeModelIsGguf}
|
||||
nativeContextLength={ggufNativeContextLength}
|
||||
loadedContextLength={ggufContextLength}
|
||||
loadedConfig={activeModelConfig}
|
||||
onReload={handleReloadActiveModel}
|
||||
|
|
|
|||
|
|
@ -700,6 +700,9 @@ export function useChatModelRuntime() {
|
|||
const reportedMaxCtx = loadResponse.is_gguf
|
||||
? (loadResponse.max_context_length ?? null)
|
||||
: null;
|
||||
const reportedNativeCtx = loadResponse.is_gguf
|
||||
? (loadResponse.native_context_length ?? null)
|
||||
: null;
|
||||
// A successful reload has applied settings, so clear pending custom
|
||||
// context state and display the backend-reported effective context.
|
||||
const keepCustomCtx = null;
|
||||
|
|
@ -732,6 +735,7 @@ export function useChatModelRuntime() {
|
|||
useChatRuntimeStore.setState({
|
||||
ggufContextLength: nativeCtx,
|
||||
ggufMaxContextLength,
|
||||
ggufNativeContextLength: reportedNativeCtx,
|
||||
modelRequiresTrustRemoteCode:
|
||||
loadResponse.requires_trust_remote_code ?? false,
|
||||
supportsReasoning,
|
||||
|
|
|
|||
|
|
@ -165,6 +165,9 @@ export function applyActiveModelStatusToStore(
|
|||
const ggufMaxContextLength = status.is_gguf
|
||||
? (status.max_context_length ?? null)
|
||||
: null;
|
||||
const ggufNativeContextLength = status.is_gguf
|
||||
? (status.native_context_length ?? null)
|
||||
: null;
|
||||
const currentSpecType = normalizeSpeculativeType(status.speculative_type);
|
||||
const prevState = useChatRuntimeStore.getState();
|
||||
const clampedReasoningEffort =
|
||||
|
|
@ -199,6 +202,7 @@ export function applyActiveModelStatusToStore(
|
|||
: true,
|
||||
ggufContextLength: currentGgufContextLength,
|
||||
ggufMaxContextLength,
|
||||
ggufNativeContextLength,
|
||||
...(status.is_gguf ? {} : { activeNativePathToken: null }),
|
||||
modelRequiresTrustRemoteCode: status.requires_trust_remote_code ?? false,
|
||||
defaultChatTemplate: nextDefaultChatTemplate,
|
||||
|
|
|
|||
|
|
@ -493,6 +493,7 @@ type ChatRuntimeStore = {
|
|||
activeGgufVariant: string | null;
|
||||
ggufContextLength: number | null;
|
||||
ggufMaxContextLength: number | null;
|
||||
ggufNativeContextLength: number | null;
|
||||
modelRequiresTrustRemoteCode: boolean;
|
||||
supportsReasoning: boolean;
|
||||
reasoningAlwaysOn: boolean;
|
||||
|
|
@ -955,6 +956,7 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set, get) => ({
|
|||
activeGgufVariant: null,
|
||||
ggufContextLength: null,
|
||||
ggufMaxContextLength: null,
|
||||
ggufNativeContextLength: null,
|
||||
modelRequiresTrustRemoteCode: false,
|
||||
supportsReasoning: false,
|
||||
reasoningAlwaysOn: false,
|
||||
|
|
@ -1210,6 +1212,7 @@ export const useChatRuntimeStore = create<ChatRuntimeStore>((set, get) => ({
|
|||
activeNativePathToken: null,
|
||||
ggufContextLength: null,
|
||||
ggufMaxContextLength: null,
|
||||
ggufNativeContextLength: null,
|
||||
modelRequiresTrustRemoteCode: false,
|
||||
contextUsage: null,
|
||||
supportsReasoning: false,
|
||||
|
|
|
|||
|
|
@ -118,7 +118,6 @@ export type {
|
|||
ExternalModelOption,
|
||||
LoraModelOption,
|
||||
ModelOption,
|
||||
ModelPickTarget,
|
||||
ModelSelectorChangeMeta,
|
||||
} from "./model-selector/types";
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import type {
|
|||
CachedModelRepo,
|
||||
LocalModelInfo,
|
||||
} from "@/features/chat/api/chat-api";
|
||||
import { useChatPickerInventory } from "@/features/model-picker/inventory/use-chat-picker-inventory";
|
||||
import { useChatPickerInventory } from "../../inventory/use-chat-picker-inventory";
|
||||
import type { GgufVariantDetail } from "@/features/chat/types/api";
|
||||
import { DotTag } from "@/features/hub/catalog/dot-tag";
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ interface SidebarModelConfigProps {
|
|||
modelId: string;
|
||||
ggufVariant: string | null;
|
||||
isGguf: boolean;
|
||||
nativeContextLength: number | null;
|
||||
loadedContextLength: number | null;
|
||||
loadedConfig: PerModelConfig;
|
||||
onReload: (config: PerModelConfig) => void;
|
||||
|
|
@ -52,6 +53,7 @@ export function SidebarModelConfig({
|
|||
modelId,
|
||||
ggufVariant,
|
||||
isGguf,
|
||||
nativeContextLength,
|
||||
loadedContextLength,
|
||||
loadedConfig,
|
||||
onReload,
|
||||
|
|
@ -69,10 +71,10 @@ export function SidebarModelConfig({
|
|||
ggufVariant: ggufVariant ?? undefined,
|
||||
isGguf,
|
||||
isDownloaded: true,
|
||||
contextLength: null,
|
||||
contextLength: nativeContextLength,
|
||||
},
|
||||
};
|
||||
}, [modelId, ggufVariant, isGguf]);
|
||||
}, [modelId, ggufVariant, isGguf, nativeContextLength]);
|
||||
|
||||
return (
|
||||
<ModelConfigPage
|
||||
|
|
|
|||
|
|
@ -8,12 +8,8 @@ export type {
|
|||
ExternalModelOption,
|
||||
LoraModelOption,
|
||||
ModelOption,
|
||||
ModelPickTarget,
|
||||
ModelSelectorChangeMeta,
|
||||
} from "./components/model-selector";
|
||||
export { FolderBrowser } from "./components/model-selector/folder-browser";
|
||||
export type { FolderBrowserProps } from "./components/model-selector/folder-browser";
|
||||
export { ModelDeleteAction } from "./components/model-selector/model-delete-action";
|
||||
export {
|
||||
applyModelLoadConfigToRuntime,
|
||||
applyPerModelConfigToRuntime,
|
||||
|
|
@ -21,10 +17,7 @@ export {
|
|||
perModelConfigsEqual,
|
||||
} from "./model-config/apply-per-model-config";
|
||||
export {
|
||||
DEFAULT_PER_MODEL_CONFIG,
|
||||
normalizeMaxSeqLength,
|
||||
type PerModelConfig,
|
||||
deletePerModelConfig,
|
||||
deletePerModelConfigsForModel,
|
||||
resolveInitialConfig,
|
||||
} from "./model-config/per-model-config";
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import {
|
|||
} from "@/features/hub/lib/model-identity";
|
||||
|
||||
export {
|
||||
ggufVariantsMatch,
|
||||
modelIdsMatch,
|
||||
normalizeGgufVariantIdentity,
|
||||
normalizeModelIdentity,
|
||||
} from "@/features/hub/lib/model-identity";
|
||||
|
|
|
|||
|
|
@ -9,8 +9,6 @@ import {
|
|||
normalizeModelIdentity,
|
||||
} from "./model-identity";
|
||||
|
||||
export { modelStorageKey as configEntryKey };
|
||||
|
||||
export interface PerModelConfig {
|
||||
customContextLength: number | null;
|
||||
maxSeqLength: number | null;
|
||||
|
|
@ -557,25 +555,6 @@ export function deletePerModelConfig(
|
|||
}
|
||||
}
|
||||
|
||||
export function deletePerModelConfigsForModel(modelId: string): void {
|
||||
const map = readMap();
|
||||
const targetIdentity = normalizeModelIdentity(modelId);
|
||||
let changed = false;
|
||||
for (const key of Object.keys(map)) {
|
||||
const storedModelId = modelIdFromStorageKey(key);
|
||||
if (
|
||||
storedModelId &&
|
||||
normalizeModelIdentity(storedModelId) === targetIdentity
|
||||
) {
|
||||
delete map[key];
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
writeMap(map);
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveInitialConfig(
|
||||
modelId: string,
|
||||
ggufVariant?: string | null,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue