From ccf41739903b3f5a70d227344bec674627beee55 Mon Sep 17 00:00:00 2001 From: m4 Date: Fri, 24 Jul 2026 07:36:34 +0800 Subject: [PATCH] feat(registry): reject image-only models in chat model saves --- EvoScientist/model_registry/errors.py | 2 ++ EvoScientist/model_registry/http_api.py | 21 +++++++++++++++++++++ tests/test_model_registry_http.py | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/EvoScientist/model_registry/errors.py b/EvoScientist/model_registry/errors.py index 47f29ef..9b97a29 100644 --- a/EvoScientist/model_registry/errors.py +++ b/EvoScientist/model_registry/errors.py @@ -52,6 +52,7 @@ CAPABILITY_UNSUPPORTED_BY_ADAPTER = "CAPABILITY_UNSUPPORTED_BY_ADAPTER" MODEL_CAPABILITY_UNAVAILABLE = "MODEL_CAPABILITY_UNAVAILABLE" UNSUPPORTED_RUNTIME_PARAMETER = "UNSUPPORTED_RUNTIME_PARAMETER" MODEL_LIMITS_UNCONFIRMED = "MODEL_LIMITS_UNCONFIRMED" +IMAGE_MODEL_NOT_CHAT_MODEL = "IMAGE_MODEL_NOT_CHAT_MODEL" CONTEXT_BUDGET_UNSATISFIABLE = "CONTEXT_BUDGET_UNSATISFIABLE" PROVIDER_UNREACHABLE = "PROVIDER_UNREACHABLE" @@ -88,6 +89,7 @@ ERROR_HTTP_STATUS: dict[str, int] = { MODEL_CAPABILITY_UNAVAILABLE: 422, UNSUPPORTED_RUNTIME_PARAMETER: 422, MODEL_LIMITS_UNCONFIRMED: 422, + IMAGE_MODEL_NOT_CHAT_MODEL: 422, CONTEXT_BUDGET_UNSATISFIABLE: 422, PROVIDER_UNREACHABLE: 422, VALIDATION_FAILED: 422, diff --git a/EvoScientist/model_registry/http_api.py b/EvoScientist/model_registry/http_api.py index 41a844d..0ac501b 100644 --- a/EvoScientist/model_registry/http_api.py +++ b/EvoScientist/model_registry/http_api.py @@ -51,6 +51,7 @@ from .endpoint_policy import EndpointPolicy from .errors import ( CREDENTIAL_NOT_CONFIGURED, FORBIDDEN, + IMAGE_MODEL_NOT_CHAT_MODEL, INVALID_REQUEST, MODEL_LIMITS_UNCONFIRMED, MODEL_NOT_AVAILABLE, @@ -381,6 +382,26 @@ def validate_registry_save( ``resolve_parameters``, and the resolve-time tightening rules are enforced by the resolver/adapter layer. """ + from EvoScientist.image_gen import is_image_generation_model + + for provider_index, provider in enumerate(registry.providers): + for model_index, model in enumerate(provider.models): + if model.enabled and is_image_generation_model(model.upstream_model_id): + raise ModelRegistryError( + IMAGE_MODEL_NOT_CHAT_MODEL, + "Image-only models must be configured in the " + "image_generation section of config.yaml, not as chat models.", + details=[ + { + "path": ( + f"providers[{provider_index}].models" + f"[{model_index}].upstream_model_id" + ), + "code": IMAGE_MODEL_NOT_CHAT_MODEL, + } + ], + ) + for provider_index, provider in enumerate(registry.providers): provider_path = f"providers[{provider_index}]" try: diff --git a/tests/test_model_registry_http.py b/tests/test_model_registry_http.py index 1d01186..910fa0d 100644 --- a/tests/test_model_registry_http.py +++ b/tests/test_model_registry_http.py @@ -384,6 +384,17 @@ def test_put_registry_rejects_ssrf_endpoint(active_client): assert body["details"][0]["path"].startswith("providers[0].base_url") +def test_put_registry_rejects_image_only_model(active_client): + registry = _current_registry(active_client)["registry"] + registry["providers"][0]["models"][0]["upstream_model_id"] = "gpt-image-2" + + response = _put(active_client, registry) + assert response.status_code == 422 + body = response.json() + assert body["code"] == "IMAGE_MODEL_NOT_CHAT_MODEL" + assert body["details"][0]["path"].endswith("upstream_model_id") + + def test_put_registry_rejects_unsupported_parameter(active_client): registry = _current_registry(active_client)["registry"] # The glm-5.2 contract caps temperature at 1.