mirror of
https://github.com/mudler/LocalAI.git
synced 2026-07-30 09:57:57 -04:00
* feat(config): add model artifact source contract Assisted-by: Codex:GPT-5 [Codex] * feat(downloader): add authenticated raw-byte progress Assisted-by: Codex:GPT-5 [Codex] * feat(huggingface): resolve immutable snapshot manifests Assisted-by: Codex:GPT-5 [Codex] * feat(models): add artifact storage primitives Assisted-by: Codex:GPT-5 [Codex] * feat(models): materialize pinned Hugging Face snapshots Assisted-by: Codex:GPT-5 [Codex] * feat(models): bind managed snapshots at runtime Assisted-by: Codex:GPT-5 [Codex] * feat(gallery): materialize model artifacts during install Assisted-by: Codex:GPT-5 [Codex] * feat(gallery): declare managed Hugging Face artifacts Assisted-by: Codex:GPT-5 [Codex] * feat(models): preload managed model artifacts Assisted-by: Codex:GPT-5 [Codex] * fix(gallery): retain shared artifact caches on delete Assisted-by: Codex:GPT-5 [Codex] * feat(models): report artifact acquisition progress Assisted-by: Codex:GPT-5 [Codex] * refactor(backends): load managed models from ModelFile Assisted-by: Codex:GPT-5 [Codex] * refactor(backends): load staged speech model snapshots Assisted-by: Codex:GPT-5 [Codex] * refactor(backends): use staged snapshots in engine backends Assisted-by: Codex:GPT-5 [Codex] * test(distributed): cover staged artifact snapshots Assisted-by: Codex:GPT-5 [Codex] * docs: explain managed model artifacts Assisted-by: Codex:GPT-5 [Codex] * docs: add product design context Assisted-by: Codex:GPT-5 [Codex] * feat(ui): show model artifact download progress Assisted-by: Codex:GPT-5 [Codex] * Eagerly materialize Hugging Face artifacts Materialize HF-backed model references as managed GGUF artifacts during load, with lazy download retained only as fallback. Assisted-by: Codex:GPT-5 [shell] * Refactor HF downloads through a shared executor Assisted-by: Codex:GPT-5 [shell] * drop Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io> Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
82 lines
3.0 KiB
Python
82 lines
3.0 KiB
Python
import ast
|
|
import os
|
|
import tempfile
|
|
import unittest
|
|
from types import SimpleNamespace
|
|
|
|
from model_utils import require_snapshot_file, resolve_model_reference
|
|
|
|
|
|
class ResolveModelReferenceTest(unittest.TestCase):
|
|
def test_existing_model_file_wins(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
request = SimpleNamespace(Model="owner/repo", ModelFile=directory)
|
|
self.assertEqual(resolve_model_reference(request), (directory, True))
|
|
|
|
def test_missing_model_file_preserves_legacy_repo_fallback(self):
|
|
request = SimpleNamespace(Model=" owner/repo ", ModelFile="/does/not/exist")
|
|
self.assertEqual(resolve_model_reference(request), ("owner/repo", False))
|
|
|
|
def test_empty_request_uses_explicit_default(self):
|
|
request = SimpleNamespace(Model=" ", ModelFile="")
|
|
self.assertEqual(
|
|
resolve_model_reference(request, "default/repo"),
|
|
("default/repo", False),
|
|
)
|
|
|
|
def test_requires_the_only_matching_snapshot_file(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
checkpoint = os.path.join(directory, "model.nemo")
|
|
with open(checkpoint, "wb"):
|
|
pass
|
|
self.assertEqual(require_snapshot_file(directory, ".nemo"), checkpoint)
|
|
|
|
def test_rejects_ambiguous_packaged_files(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
for name in ("first.nemo", "second.nemo"):
|
|
with open(os.path.join(directory, name), "wb"):
|
|
pass
|
|
with self.assertRaisesRegex(ValueError, "exactly one"):
|
|
require_snapshot_file(directory, ".nemo")
|
|
|
|
|
|
class MigratedBackendSourceTest(unittest.TestCase):
|
|
def test_general_backends_use_shared_resolution(self):
|
|
common_dir = os.path.dirname(__file__)
|
|
python_root = os.path.dirname(common_dir)
|
|
for backend in (
|
|
"transformers",
|
|
"diffusers",
|
|
"qwen-asr",
|
|
"fish-speech",
|
|
"nemo",
|
|
"voxcpm",
|
|
"qwen-tts",
|
|
"liquid-audio",
|
|
"vllm",
|
|
"vllm-omni",
|
|
"sglang",
|
|
):
|
|
with self.subTest(backend=backend):
|
|
with open(
|
|
os.path.join(python_root, backend, "backend.py"), encoding="utf-8"
|
|
) as backend_file:
|
|
tree = ast.parse(backend_file.read())
|
|
imports = {
|
|
alias.name
|
|
for node in ast.walk(tree)
|
|
if isinstance(node, ast.ImportFrom) and node.module == "model_utils"
|
|
for alias in node.names
|
|
}
|
|
calls = {
|
|
node.func.id
|
|
for node in ast.walk(tree)
|
|
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)
|
|
}
|
|
self.assertIn("resolve_model_reference", imports)
|
|
self.assertIn("resolve_model_reference", calls)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|