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
125 lines
4.4 KiB
Python
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()
|