Files
wehub-resource-sync e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:59:56 +08:00

125 lines
4.4 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""Persisted RAG embedding-model override (Settings -> General).
The stored value takes precedence over the ``RAG_EMBEDDING_MODEL`` env default in
``core.rag.config``. Vectors from different models live in different spaces, so
documents already indexed under the old model must be re-uploaded after a change
(the UI warns about this).
"""
from __future__ import annotations
import threading
import time
from typing import Any
EMBEDDING_MODEL_SETTING_KEY = "rag_embedding_model"
MAX_EMBEDDING_MODEL_LENGTH = 512
# The effective model is consulted on the embedder hot path (once per embed /
# tokenize call during ingestion), so the stored value is cached briefly instead
# of hitting sqlite each time. Writes invalidate immediately in-process; other
# readers converge within the TTL.
_CACHE_TTL_S = 2.0
_cached: tuple[float, str | None] | None = None
# Bumped on every write/invalidate. A reader captures it before the DB read and
# only fills the cache if it is unchanged afterward, so a read that overlapped a
# save cannot repopulate the cache with the pre-save value for the whole TTL.
_generation = 0
_lock = threading.Lock()
def _invalidate_cache() -> None:
global _cached, _generation
with _lock:
_cached = None
_generation += 1
def default_embedding_model() -> str:
"""The env/default model from rag config (``RAG_EMBEDDING_MODEL`` or bge)."""
from core.rag import config
return config.EMBEDDING_MODEL
def _coerce_embedding_model(value: Any) -> str | None:
if not isinstance(value, str):
return None
cleaned = value.strip()
if not cleaned or len(cleaned) > MAX_EMBEDDING_MODEL_LENGTH:
return None
# Newlines/control chars are never valid in a repo id or path.
if any(ord(ch) < 32 for ch in cleaned):
return None
return cleaned
def validate_embedding_model(value: Any) -> str:
cleaned = _coerce_embedding_model(value)
if cleaned is None:
raise ValueError(
"Embedding model must be a Hugging Face repo id (e.g. "
"'unsloth/bge-small-en-v1.5') or a local model path, up to "
f"{MAX_EMBEDDING_MODEL_LENGTH} characters."
)
return cleaned
def get_stored_embedding_model() -> str | None:
"""The persisted override, or None when unset/invalid."""
global _cached
now = time.monotonic()
with _lock:
cached = _cached
if cached is not None and now - cached[0] < _CACHE_TTL_S:
return cached[1]
gen = _generation
try:
from storage.studio_db import get_app_setting
stored = get_app_setting(EMBEDDING_MODEL_SETTING_KEY, None)
except Exception:
# Transient store failure: keep the last known value instead of
# silently reverting the embed/search hot path to the default model,
# which would mix vector spaces mid-ingestion.
with _lock:
if _cached is not None:
_cached = (time.monotonic(), _cached[1])
return _cached[1]
return None
value = _coerce_embedding_model(stored)
with _lock:
# Only cache when no save landed while we were reading; otherwise this
# value may be pre-save, and caching it would mask the new one for the
# TTL. The next reader re-reads the committed value.
if _generation == gen:
_cached = (time.monotonic(), value)
return value
def get_rag_embedding_model() -> str:
"""Effective embedding model: persisted override, else env/default."""
return get_stored_embedding_model() or default_embedding_model()
def set_rag_embedding_model(value: Any) -> str:
parsed = validate_embedding_model(value)
from storage.studio_db import upsert_app_settings
# Saving the default is not an override; keeps is_custom (and the UI's
# reset affordance) honest.
stored = parsed if parsed != default_embedding_model() else None
upsert_app_settings({EMBEDDING_MODEL_SETTING_KEY: stored})
_invalidate_cache()
return parsed
def reset_rag_embedding_model() -> str:
"""Clear the override; returns the (env/default) model now in effect."""
from storage.studio_db import upsert_app_settings
upsert_app_settings({EMBEDDING_MODEL_SETTING_KEY: None})
_invalidate_cache()
return default_embedding_model()