feat: air-gapped deployment guide, no implicit downloads

- Ship tiktoken's cl100k_base inside the package and build the encoding
  from it, so token counting never downloads anything.
- Default EMBEDDINGS_CACHE_DIR to <data home>/models instead of FastEmbed's
  temp dir, and read tokenizer.json and repo metadata from that cache, so
  a model downloads once and survives reboots.
- TTS_PROVIDER=none and STT_PROVIDER=none switch the speech features off:
  the endpoints return 404, audio files fail to ingest with a clear
  message, /api/config reports tts_available/stt_available, and the UI
  hides the Speak and microphone buttons.
- Drop the Google Fonts Roboto import from the web UI.
- prefetch-models fills the cache the app reads; verify-offline checks the
  packaged encoding.
- Docs: new Air-Gapped Deployment guide, settings and cache notes.
This commit is contained in:
Alex committed 2026-09-15 17:54:24 +01:00
1 parent ca69d1ea29
commit 7da46c2bea
38 files changed
+100892 -95

No files matched your search

@@ -1,5 +1,7 @@
"""Local embeddings run through FastEmbed, configured from the model registry."""
import sys
import types
from unittest.mock import MagicMock, patch
import numpy as np
@@ -9,6 +11,9 @@ from docsgpt.vectorstore import embeddings_local
from docsgpt.vectorstore.embeddings_local import EmbeddingsWrapper
from docsgpt.vectorstore.model_registry import GRANITE_97M, MPNET
# The autouse fixture below replaces this for every test; keep the real one.
_READ_REPO_JSON = embeddings_local._read_repo_json
@pytest.fixture(autouse=True)
def _clear_registration():
@@ -142,6 +147,23 @@ class TestSettingsPassthrough:
EmbeddingsWrapper(MPNET.name)
assert text_embedding.call_args.kwargs["cache_dir"] == "/models"
def test_repo_metadata_reads_the_embedding_model_cache(self, monkeypatch, tmp_path):
"""Pooling metadata lives beside the model, not in a second hub cache."""
config = tmp_path / "config.json"
config.write_text('{"pooling_mode_cls_token": true}')
calls = []
def fake_download(repo_id, filename, local_files_only=False, cache_dir=None):
calls.append((local_files_only, cache_dir))
return str(config)
fake_hub = types.ModuleType("huggingface_hub")
fake_hub.hf_hub_download = fake_download
monkeypatch.setitem(sys.modules, "huggingface_hub", fake_hub)
monkeypatch.setattr(embeddings_local.settings, "EMBEDDINGS_CACHE_DIR", "/models")
assert _READ_REPO_JSON("org/model", "1_Pooling/config.json") == {"pooling_mode_cls_token": True}
assert calls == [(True, "/models")]
class TestEmbedding:
def test_embed_documents_returns_plain_lists(self, fake_fastembed):