diff --git a/src/modeldock/__init__.py b/src/modeldock/__init__.py index 04bc8aa..467744b 100644 --- a/src/modeldock/__init__.py +++ b/src/modeldock/__init__.py @@ -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) @@ -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.""" @@ -167,6 +188,7 @@ def Manager( "update", "remove", "verify", + "run", "cache", "configure", "Manager", diff --git a/tests/unit/test_sdk_api.py b/tests/unit/test_sdk_api.py index 682d98f..681e0a1 100644 --- a/tests/unit/test_sdk_api.py +++ b/tests/unit/test_sdk_api.py @@ -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"]