diff --git a/studio/backend/core/inference/diffusion_families.py b/studio/backend/core/inference/diffusion_families.py index 4212ea13cd..b0d7c8658e 100644 --- a/studio/backend/core/inference/diffusion_families.py +++ b/studio/backend/core/inference/diffusion_families.py @@ -641,6 +641,25 @@ def assert_pipeline_class_available(pipeline_class: str, family_name: str) -> No ) +def family_pipeline_available(fam: Optional[DiffusionFamily]) -> bool: + """True when the installed diffusers actually has this family's pipeline class. + + The boolean twin of ``assert_pipeline_class_available``, for the listing routes: the newer + families exist only from diffusers 0.39, and the packaging leaves an older diffusers + installable on Python 3.9 (diffusers dropped 3.9 in 0.38, so the 0.39 floor has to be + conditional or the extra becomes unresolvable). Advertising Z-Image or Krea 2 in the picker + on such an environment offers a pick that can only fail, and no `pip install -U diffusers` + can fix it without also upgrading Python. Fails OPEN (True) when diffusers cannot be + imported at all, so a listing never hides a model over an unrelated import problem.""" + if fam is None: + return False + try: + import diffusers + except Exception: # noqa: BLE001 -- no diffusers here: the load path reports it properly + return True + return hasattr(diffusers, fam.pipeline_class) + + def family_gguf_loadable(fam: DiffusionFamily) -> bool: """True when a GGUF transformer can be assembled for this family. diff --git a/studio/backend/routes/models.py b/studio/backend/routes/models.py index 0f1f2634fe..54f0437e98 100644 --- a/studio/backend/routes/models.py +++ b/studio/backend/routes/models.py @@ -3672,11 +3672,18 @@ def _cached_repo_task(repo_info) -> Optional[str]: # the image families this backend can assemble, so an unsloth-hosted pipeline of an unsupported # class cleared the trust gate, was advertised as text-to-image, and then deterministically # failed validate_load_request, which detects the family the same way. + # The third gate is the installed diffusers: the newer families exist only from 0.39, which + # cannot be installed on Python 3.9 at all (diffusers dropped 3.9 in 0.38), so on such an + # environment a Z-Image / Krea 2 / FLUX.2 row is a pick that can only fail, and the suggested + # `pip install -U diffusers` cannot fix it. validate_load_request refuses the same way. try: from core.inference.diffusion import _is_trusted_diffusion_repo - from core.inference.diffusion_families import detect_family + from core.inference.diffusion_families import detect_family, family_pipeline_available - if not _is_trusted_diffusion_repo(repo_id) or detect_family(repo_id) is None: + fam = detect_family(repo_id) + if not _is_trusted_diffusion_repo(repo_id) or fam is None: + return None + if not family_pipeline_available(fam): return None return "text-to-image" except Exception: # noqa: BLE001 -- an import failure must not hide a usable repo diff --git a/studio/backend/tests/test_cached_gguf_routes.py b/studio/backend/tests/test_cached_gguf_routes.py index 43939054a7..a190e0d2f5 100644 --- a/studio/backend/tests/test_cached_gguf_routes.py +++ b/studio/backend/tests/test_cached_gguf_routes.py @@ -1854,3 +1854,44 @@ def test_cached_repo_task_agrees_with_the_image_loader(monkeypatch): assert ( task == "text-to-image" ) == loader_accepts, f"{repo_id}: picker task={task} but loader accepts={loader_accepts}" + + +def test_cached_picker_hides_a_family_this_diffusers_cannot_build(monkeypatch): + # The newer families exist only from diffusers 0.39, which cannot be installed on Python 3.9 at + # all (diffusers dropped 3.9 in 0.38). Advertising one there is a pick that can only fail, and + # `pip install -U diffusers` cannot fix it without a Python upgrade -- so the picker applies the + # same availability check validate_load_request does. + import types + + import routes.models as models_module + from core.inference.diffusion_families import detect_family, family_pipeline_available + + fam = detect_family("unsloth/Z-Image-Turbo") + assert fam is not None + # Present in this environment's diffusers, so the row is offered. + assert family_pipeline_available(fam) is True + + monkeypatch.setattr(models_module, "_repo_is_diffusers", lambda info: True) + monkeypatch.setattr( + "core.inference.diffusion._is_trusted_diffusion_repo", lambda repo_id: True + ) + info = types.SimpleNamespace(repo_id = "unsloth/Z-Image-Turbo") + assert models_module._cached_repo_task(info) == "text-to-image" + + # An older diffusers without the pipeline class hides the row instead. + monkeypatch.setattr( + "core.inference.diffusion_families.family_pipeline_available", lambda f: False + ) + assert models_module._cached_repo_task(info) is None + + +def test_family_pipeline_available_fails_open_without_diffusers(monkeypatch): + # No diffusers at all is a different problem; the load path reports it properly, and a listing + # must not silently hide every image model over it. + import sys + + from core.inference.diffusion_families import detect_family, family_pipeline_available + + monkeypatch.setitem(sys.modules, "diffusers", None) + assert family_pipeline_available(detect_family("unsloth/Z-Image-Turbo")) is True + assert family_pipeline_available(None) is False