Studio: report persisted provenance from the dedup reply too

Both review points on the previous revision were correct.

- the already_loaded GGUF response still derived provenance from the
  filesystem, so a deduplicated /load flipped a local model to remote
  once its directory went away, the same flip already fixed for the
  status poll. It now reads the persisted value
- drop the .gguf suffix from the local classification in the settings
  sheet. A one-slash org/name.gguf is a repository id, not a file, as
  _is_direct_gguf_file_ref documents, so that suffix overrode the
  backend saying remote and offered filesystem placement guidance for a
  model that downloads

The contract now pins both GGUF responses using the provenance helper,
and pins the suffix out of the classification.
This commit is contained in:
Michael Han 2026-07-26 23:09:29 -07:00
commit 1f7adc6c5f
3 changed files with 16 additions and 12 deletions

View file

@ -4509,8 +4509,9 @@ async def _load_model_impl(
is_vision = llama_backend._is_vision,
is_lora = False,
is_gguf = True,
is_local_model = native_grant_backed
or is_local_path(llama_backend.model_identifier),
is_local_model = _loaded_is_local_model(
llama_backend, native_grant_backed, llama_backend.model_identifier
),
is_diffusion = llama_backend.is_diffusion,
is_audio = _gguf_is_audio,
audio_type = _gguf_audio,

View file

@ -394,14 +394,12 @@ export function ChatSettingsPanel({
ggufContextLength != null ||
(currentCheckpoint?.toLowerCase().endsWith(".gguf") ?? false);
// activeModelIsLocal is the backend's own classification and covers native
// picks. activeNativePathToken must not be used here: status reconciliation
// keeps it across a switch to a remote GGUF (no replacement token exists),
// so a stale token would label that remote model local.
// picks. Two things must not decide this: activeNativePathToken, which
// status reconciliation keeps across a switch to a remote GGUF (no
// replacement token exists), and a bare .gguf suffix, since the backend
// reads a one-slash org/name.gguf as a repository id, not a file.
const isLocalGguf =
isGguf &&
(activeModelIsLocal ||
isLocalModelPath(currentCheckpoint ?? "") ||
(currentCheckpoint?.toLowerCase().endsWith(".gguf") ?? false));
isGguf && (activeModelIsLocal || isLocalModelPath(currentCheckpoint ?? ""));
const ggufMaxContextLength = useChatRuntimeStore(
(s) => s.ggufMaxContextLength,
);

View file

@ -351,9 +351,12 @@ def test_local_mtp_warning_covers_path_and_native_gguf_sources():
assert "isGguf &&" in local.group(0)
assert "activeModelIsLocal" in local.group(0)
assert "isLocalModelPath" in local.group(0)
# A native token outlives a switch to a remote GGUF, so it must not
# classify the model here; activeModelIsLocal already covers native picks.
# Two signals must not classify the model here, because both mislabel a
# remote GGUF as local: a native token, which outlives a switch to a remote
# model, and a bare .gguf suffix, since a one-slash org/name.gguf is a
# repository id. activeModelIsLocal is the backend's own answer for both.
assert "activeNativePathToken" not in local.group(0)
assert ".gguf" not in local.group(0)
assert "isLocalGguf" in src.split('specFallbackReason === "drafter_not_found"', 1)[1]
@ -380,7 +383,9 @@ def test_local_mtp_warning_uses_backend_source_metadata():
# the filesystem would flip a local model to remote once its directory goes
# away underneath a running server.
assert "llama_backend._is_local_model = bool(native_grant_backed or config.is_local)" in route
assert "is_local_model = _loaded_is_local_model(" in route
# Both GGUF responses report it: the status poll and the already_loaded
# dedup reply. Either one re-deriving it reintroduces the flip.
assert route.count("is_local_model = _loaded_is_local_model(") >= 2
assert "backend.active_model_name and is_local_path(backend.active_model_name)" in route