Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion src/modeldock/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def configure(
def load(name: str, backend: Optional[str] = None, auto_install: Optional[bool] = None) -> Any:
"""Auto-install if missing, then return a ready-to-use client."""
if backend is not None:
return _manager().load(name, auto_install=auto_install)
return Manager(backend=backend).load(name, auto_install=auto_install)
return _manager().load(name, auto_install=auto_install)


Expand Down Expand Up @@ -90,29 +90,50 @@ def recommend(task: str) -> List[Any]:

def install(name: str, backend: Optional[str] = None) -> ModelRef:
"""Explicit download."""
if backend is not None:
return Manager(backend=backend).install(name)
return _manager().install(name)


def install_category(category: str, backend: Optional[str] = None) -> List[ModelRef]:
"""Bulk install by category."""
if backend is not None:
return Manager(backend=backend).install_category(category)
return _manager().install_category(category)


def update(name: str, backend: Optional[str] = None) -> ModelRef:
"""Pull a newer tag."""
if backend is not None:
return Manager(backend=backend).update(name)
return _manager().update(name)


def remove(name: str, backend: Optional[str] = None) -> None:
"""Uninstall."""
if backend is not None:
Manager(backend=backend).remove(name)
return
_manager().remove(name)


def verify(name: str, backend: Optional[str] = None) -> bool:
"""Integrity check."""
if backend is not None:
return Manager(backend=backend).verify(name)
return _manager().verify(name)


def run(name: str, prompt: Optional[str] = None, backend: Optional[str] = None, **opts: Any) -> Any:
"""Run an interactive session for a model in the active runtime.

With ``prompt`` runs a single completion; without it, drops into a REPL.
"""
if backend is not None:
return Manager(backend=backend).run(name, prompt=prompt, **opts)
return _manager().run(name, prompt=prompt, **opts)


class _CacheFacade:
"""Thin facade exposing cache operations on the singleton."""

Expand Down Expand Up @@ -167,6 +188,7 @@ def Manager(
"update",
"remove",
"verify",
"run",
"cache",
"configure",
"Manager",
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/test_sdk_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,37 @@ def test_sdk_load_via_fake_manager() -> None:
def test_modelref_and_backend_exports() -> None:
assert md.ModelRef.parse("llama3:8b").qualified_name() == "llama3:8b"
assert md.RuntimeBackend.from_value("ollama") == RuntimeBackend.OLLAMA


def test_sdk_run_via_fake_manager() -> None:
from tests.conftest import FakeCache, FakeRegistry, FakeRuntime

runtime = FakeRuntime()
mgr = md.ModelManager(runtime=runtime, registry=FakeRegistry(), cache=FakeCache())
result = mgr.run("llama3", prompt="hi")
assert result.success is True


def test_sdk_run_exported() -> None:
assert hasattr(md, "run")
assert "run" in md.__all__


def test_load_routes_backend_to_manager() -> None:
from modeldock.domain.model import RuntimeBackend

# Manager(backend=...) sets the active backend without touching a runtime.
mgr = md.Manager(backend="ollama")
assert mgr._backend == RuntimeBackend.OLLAMA


def test_info_surfaces_installed_tags_for_catalog_model() -> None:
from modeldock.domain.model import ModelRef
from tests.conftest import FakeCache, FakeRegistry, FakeRuntime

ref = ModelRef.parse("llama3:latest")
runtime = FakeRuntime(installed=[ref])
mgr = md.ModelManager(runtime=runtime, registry=FakeRegistry(), cache=FakeCache())
info = mgr.info("llama3")
assert info.installed is True
assert info.installed_tags == ["latest"]
Loading