cddb07a176
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled
278 lines
10 KiB
Python
278 lines
10 KiB
Python
import logging
|
|
|
|
import pytest
|
|
from PIL import Image
|
|
|
|
from invokeai.app.services.config.config_default import InvokeAIAppConfig
|
|
from invokeai.app.services.external_generation.errors import (
|
|
ExternalProviderCapabilityError,
|
|
ExternalProviderNotConfiguredError,
|
|
ExternalProviderNotFoundError,
|
|
)
|
|
from invokeai.app.services.external_generation.external_generation_base import ExternalProvider
|
|
from invokeai.app.services.external_generation.external_generation_common import (
|
|
ExternalGeneratedImage,
|
|
ExternalGenerationRequest,
|
|
ExternalGenerationResult,
|
|
ExternalReferenceImage,
|
|
)
|
|
from invokeai.app.services.external_generation.external_generation_default import ExternalGenerationService
|
|
from invokeai.backend.model_manager.configs.external_api import (
|
|
ExternalApiModelConfig,
|
|
ExternalImageSize,
|
|
ExternalModelCapabilities,
|
|
)
|
|
|
|
|
|
class DummyProvider(ExternalProvider):
|
|
def __init__(self, provider_id: str, configured: bool, result: ExternalGenerationResult | None = None) -> None:
|
|
super().__init__(InvokeAIAppConfig(), logging.getLogger("test"))
|
|
self.provider_id = provider_id
|
|
self._configured = configured
|
|
self._result = result
|
|
self.last_request: ExternalGenerationRequest | None = None
|
|
|
|
def is_configured(self) -> bool:
|
|
return self._configured
|
|
|
|
def generate(self, request: ExternalGenerationRequest) -> ExternalGenerationResult:
|
|
self.last_request = request
|
|
assert self._result is not None
|
|
return self._result
|
|
|
|
|
|
def _build_model(capabilities: ExternalModelCapabilities) -> ExternalApiModelConfig:
|
|
return ExternalApiModelConfig(
|
|
key="external_test",
|
|
name="External Test",
|
|
provider_id="openai",
|
|
provider_model_id="gpt-image-1",
|
|
capabilities=capabilities,
|
|
)
|
|
|
|
|
|
def _build_request(
|
|
*,
|
|
model: ExternalApiModelConfig,
|
|
mode: str = "txt2img",
|
|
seed: int | None = None,
|
|
num_images: int = 1,
|
|
width: int = 64,
|
|
height: int = 64,
|
|
init_image: Image.Image | None = None,
|
|
mask_image: Image.Image | None = None,
|
|
reference_images: list[ExternalReferenceImage] | None = None,
|
|
) -> ExternalGenerationRequest:
|
|
return ExternalGenerationRequest(
|
|
model=model,
|
|
mode=mode, # type: ignore[arg-type]
|
|
prompt="A test prompt",
|
|
seed=seed,
|
|
num_images=num_images,
|
|
width=width,
|
|
height=height,
|
|
image_size=None,
|
|
init_image=init_image,
|
|
mask_image=mask_image,
|
|
reference_images=reference_images or [],
|
|
metadata=None,
|
|
)
|
|
|
|
|
|
def _make_image() -> Image.Image:
|
|
return Image.new("RGB", (64, 64), color="black")
|
|
|
|
|
|
def test_generate_requires_registered_provider() -> None:
|
|
model = _build_model(ExternalModelCapabilities(modes=["txt2img"]))
|
|
request = _build_request(model=model)
|
|
service = ExternalGenerationService({}, logging.getLogger("test"))
|
|
|
|
with pytest.raises(ExternalProviderNotFoundError):
|
|
service.generate(request)
|
|
|
|
|
|
def test_generate_requires_configured_provider() -> None:
|
|
model = _build_model(ExternalModelCapabilities(modes=["txt2img"]))
|
|
request = _build_request(model=model)
|
|
provider = DummyProvider("openai", configured=False)
|
|
service = ExternalGenerationService({"openai": provider}, logging.getLogger("test"))
|
|
|
|
with pytest.raises(ExternalProviderNotConfiguredError):
|
|
service.generate(request)
|
|
|
|
|
|
def test_generate_validates_mode_support() -> None:
|
|
model = _build_model(ExternalModelCapabilities(modes=["txt2img"]))
|
|
request = _build_request(model=model, mode="img2img", init_image=_make_image())
|
|
provider = DummyProvider("openai", configured=True, result=ExternalGenerationResult(images=[]))
|
|
service = ExternalGenerationService({"openai": provider}, logging.getLogger("test"))
|
|
|
|
with pytest.raises(ExternalProviderCapabilityError, match="Mode 'img2img'"):
|
|
service.generate(request)
|
|
|
|
|
|
def test_generate_requires_init_image_for_img2img() -> None:
|
|
model = _build_model(ExternalModelCapabilities(modes=["img2img"]))
|
|
request = _build_request(model=model, mode="img2img")
|
|
provider = DummyProvider("openai", configured=True, result=ExternalGenerationResult(images=[]))
|
|
service = ExternalGenerationService({"openai": provider}, logging.getLogger("test"))
|
|
|
|
with pytest.raises(ExternalProviderCapabilityError, match="requires an init image"):
|
|
service.generate(request)
|
|
|
|
|
|
def test_generate_requires_mask_for_inpaint() -> None:
|
|
model = _build_model(ExternalModelCapabilities(modes=["inpaint"]))
|
|
request = _build_request(model=model, mode="inpaint", init_image=_make_image())
|
|
provider = DummyProvider("openai", configured=True, result=ExternalGenerationResult(images=[]))
|
|
service = ExternalGenerationService({"openai": provider}, logging.getLogger("test"))
|
|
|
|
with pytest.raises(ExternalProviderCapabilityError, match="requires a mask"):
|
|
service.generate(request)
|
|
|
|
|
|
def test_generate_validates_reference_images() -> None:
|
|
model = _build_model(ExternalModelCapabilities(modes=["txt2img"], supports_reference_images=False))
|
|
request = _build_request(
|
|
model=model,
|
|
reference_images=[ExternalReferenceImage(image=_make_image())],
|
|
)
|
|
provider = DummyProvider("openai", configured=True, result=ExternalGenerationResult(images=[]))
|
|
service = ExternalGenerationService({"openai": provider}, logging.getLogger("test"))
|
|
|
|
with pytest.raises(ExternalProviderCapabilityError, match="Reference images"):
|
|
service.generate(request)
|
|
|
|
|
|
def test_generate_validates_limits() -> None:
|
|
model = _build_model(
|
|
ExternalModelCapabilities(
|
|
modes=["txt2img"],
|
|
supports_reference_images=True,
|
|
max_reference_images=1,
|
|
max_images_per_request=1,
|
|
)
|
|
)
|
|
request = _build_request(
|
|
model=model,
|
|
num_images=2,
|
|
reference_images=[
|
|
ExternalReferenceImage(image=_make_image()),
|
|
ExternalReferenceImage(image=_make_image()),
|
|
],
|
|
)
|
|
provider = DummyProvider("openai", configured=True, result=ExternalGenerationResult(images=[]))
|
|
service = ExternalGenerationService({"openai": provider}, logging.getLogger("test"))
|
|
|
|
with pytest.raises(ExternalProviderCapabilityError, match="supports at most"):
|
|
service.generate(request)
|
|
|
|
|
|
def test_generate_validates_allowed_aspect_ratios() -> None:
|
|
model = _build_model(
|
|
ExternalModelCapabilities(
|
|
modes=["txt2img"],
|
|
allowed_aspect_ratios=["1:1", "16:9"],
|
|
aspect_ratio_sizes={
|
|
"1:1": ExternalImageSize(width=1024, height=1024),
|
|
"16:9": ExternalImageSize(width=1344, height=768),
|
|
},
|
|
)
|
|
)
|
|
request = _build_request(model=model)
|
|
provider = DummyProvider("openai", configured=True, result=ExternalGenerationResult(images=[]))
|
|
service = ExternalGenerationService({"openai": provider}, logging.getLogger("test"))
|
|
|
|
response = service.generate(request)
|
|
assert response.images == []
|
|
assert provider.last_request is not None
|
|
assert provider.last_request.width == 1024
|
|
assert provider.last_request.height == 1024
|
|
|
|
|
|
def test_generate_validates_allowed_aspect_ratios_with_bucket_sizes() -> None:
|
|
model = _build_model(
|
|
ExternalModelCapabilities(
|
|
modes=["txt2img"],
|
|
allowed_aspect_ratios=["1:1", "16:9"],
|
|
aspect_ratio_sizes={
|
|
"1:1": ExternalImageSize(width=1024, height=1024),
|
|
"16:9": ExternalImageSize(width=1344, height=768),
|
|
},
|
|
)
|
|
)
|
|
request = _build_request(model=model, width=160, height=90)
|
|
provider = DummyProvider("openai", configured=True, result=ExternalGenerationResult(images=[]))
|
|
service = ExternalGenerationService({"openai": provider}, logging.getLogger("test"))
|
|
|
|
response = service.generate(request)
|
|
|
|
assert response.images == []
|
|
assert provider.last_request is not None
|
|
assert provider.last_request.width == 1344
|
|
assert provider.last_request.height == 768
|
|
|
|
|
|
def test_generate_happy_path() -> None:
|
|
model = _build_model(ExternalModelCapabilities(modes=["txt2img"], supports_seed=True))
|
|
request = _build_request(model=model, seed=42)
|
|
result = ExternalGenerationResult(images=[ExternalGeneratedImage(image=_make_image(), seed=42)])
|
|
provider = DummyProvider("openai", configured=True, result=result)
|
|
service = ExternalGenerationService({"openai": provider}, logging.getLogger("test"))
|
|
|
|
response = service.generate(request)
|
|
|
|
assert response is result
|
|
assert provider.last_request == request
|
|
|
|
|
|
def test_generate_resizes_inpaint_result_to_original_init_size() -> None:
|
|
model = _build_model(ExternalModelCapabilities(modes=["inpaint"]))
|
|
request = _build_request(
|
|
model=model,
|
|
mode="inpaint",
|
|
width=128,
|
|
height=128,
|
|
init_image=_make_image(),
|
|
mask_image=_make_image(),
|
|
)
|
|
generated_large = Image.new("RGB", (128, 128), color="black")
|
|
result = ExternalGenerationResult(images=[ExternalGeneratedImage(image=generated_large, seed=1)])
|
|
provider = DummyProvider("openai", configured=True, result=result)
|
|
service = ExternalGenerationService({"openai": provider}, logging.getLogger("test"))
|
|
|
|
response = service.generate(request)
|
|
|
|
assert request.init_image is not None
|
|
assert response.images[0].image.width == request.init_image.width
|
|
assert response.images[0].image.height == request.init_image.height
|
|
assert response.images[0].seed == 1
|
|
|
|
|
|
def test_qwen_image_edit_max_enforces_three_reference_images() -> None:
|
|
from invokeai.backend.model_manager.starter_models import alibabacloud_qwen_image_edit_max
|
|
|
|
capabilities = alibabacloud_qwen_image_edit_max.capabilities
|
|
assert capabilities is not None
|
|
assert capabilities.max_reference_images == 3
|
|
|
|
model = ExternalApiModelConfig(
|
|
key="qwen_image_edit_max",
|
|
name="Qwen Image Edit Max",
|
|
provider_id="alibabacloud",
|
|
provider_model_id="qwen-image-edit-max",
|
|
capabilities=capabilities,
|
|
)
|
|
request = _build_request(
|
|
model=model,
|
|
reference_images=[ExternalReferenceImage(image=_make_image()) for _ in range(4)],
|
|
)
|
|
provider = DummyProvider("alibabacloud", configured=True, result=ExternalGenerationResult(images=[]))
|
|
service = ExternalGenerationService({"alibabacloud": provider}, logging.getLogger("test"))
|
|
|
|
with pytest.raises(ExternalProviderCapabilityError, match="supports at most 3 reference images"):
|
|
service.generate(request)
|
|
|
|
assert provider.last_request is None
|