diff --git a/backend/alembic/versions/37b5864e9cff_dalle3_deprecation.py b/backend/alembic/versions/37b5864e9cff_dalle3_deprecation.py new file mode 100644 index 0000000000..4b2aa65308 --- /dev/null +++ b/backend/alembic/versions/37b5864e9cff_dalle3_deprecation.py @@ -0,0 +1,79 @@ +"""dalle3 deprecation + +Revision ID: 37b5864e9cff +Revises: 2c7f9d3a84a0 +Create Date: 2026-05-12 10:29:16.985208 + +""" + +from alembic import op +import sqlalchemy as sa + +# revision identifiers, used by Alembic. +revision = "37b5864e9cff" +down_revision = "2c7f9d3a84a0" +branch_labels = None +depends_on = None + + +_DEPRECATED_IMAGE_PROVIDER_IDS = ("openai_dalle_3", "azure_dalle_3") + +_NEW_IMAGE_GENERATION_TOOL_DESCRIPTION = ( + "The Image Generation Action allows the agent to use GPT-IMAGE-1 to generate images. " + "The action will be used when the user asks the agent to generate an image." +) + + +def upgrade() -> None: + conn = op.get_bind() + + # Collect llm_provider IDs that back the deprecated image generation configs + # so we can clean them up after deleting the configs. Each image gen config + # owns its own LLM provider (created exclusively for image gen), and the + # LLMProvider -> ModelConfiguration FK cascade-deletes the model configuration. + llm_provider_ids = [ + row[0] + for row in conn.execute( + sa.text(""" + SELECT mc.llm_provider_id + FROM image_generation_config igc + JOIN model_configuration mc + ON mc.id = igc.model_configuration_id + WHERE igc.image_provider_id = ANY(:provider_ids) + """), + {"provider_ids": list(_DEPRECATED_IMAGE_PROVIDER_IDS)}, + ).fetchall() + ] + + conn.execute( + sa.text( + "DELETE FROM image_generation_config " + "WHERE image_provider_id = ANY(:provider_ids)" + ), + {"provider_ids": list(_DEPRECATED_IMAGE_PROVIDER_IDS)}, + ) + + if llm_provider_ids: + conn.execute( + sa.text( + "DELETE FROM llm_provider__user_group " + "WHERE llm_provider_id = ANY(:llm_provider_ids)" + ), + {"llm_provider_ids": llm_provider_ids}, + ) + conn.execute( + sa.text("DELETE FROM llm_provider WHERE id = ANY(:llm_provider_ids)"), + {"llm_provider_ids": llm_provider_ids}, + ) + + conn.execute( + sa.text( + "UPDATE tool SET description = :description " + "WHERE in_code_tool_id = 'ImageGenerationTool'" + ), + {"description": _NEW_IMAGE_GENERATION_TOOL_DESCRIPTION}, + ) + + +def downgrade() -> None: + pass diff --git a/backend/onyx/configs/app_configs.py b/backend/onyx/configs/app_configs.py index 2ec1334639..c0461dcdfe 100644 --- a/backend/onyx/configs/app_configs.py +++ b/backend/onyx/configs/app_configs.py @@ -1103,18 +1103,10 @@ ENTERPRISE_EDITION_ENABLED = ( # To configure image generation, please visit the Image Generation page in the Admin Panel. ##### # Azure Image Configurations -AZURE_IMAGE_API_VERSION = os.environ.get("AZURE_IMAGE_API_VERSION") or os.environ.get( - "AZURE_DALLE_API_VERSION" -) -AZURE_IMAGE_API_KEY = os.environ.get("AZURE_IMAGE_API_KEY") or os.environ.get( - "AZURE_DALLE_API_KEY" -) -AZURE_IMAGE_API_BASE = os.environ.get("AZURE_IMAGE_API_BASE") or os.environ.get( - "AZURE_DALLE_API_BASE" -) -AZURE_IMAGE_DEPLOYMENT_NAME = os.environ.get( - "AZURE_IMAGE_DEPLOYMENT_NAME" -) or os.environ.get("AZURE_DALLE_DEPLOYMENT_NAME") +AZURE_IMAGE_API_VERSION = os.environ.get("AZURE_IMAGE_API_VERSION") +AZURE_IMAGE_API_KEY = os.environ.get("AZURE_IMAGE_API_KEY") +AZURE_IMAGE_API_BASE = os.environ.get("AZURE_IMAGE_API_BASE") +AZURE_IMAGE_DEPLOYMENT_NAME = os.environ.get("AZURE_IMAGE_DEPLOYMENT_NAME") # configurable image model IMAGE_MODEL_NAME = os.environ.get("IMAGE_MODEL_NAME", "gpt-image-1") diff --git a/backend/onyx/server/manage/image_generation/api.py b/backend/onyx/server/manage/image_generation/api.py index 5b34d14692..ebd2e0b3d2 100644 --- a/backend/onyx/server/manage/image_generation/api.py +++ b/backend/onyx/server/manage/image_generation/api.py @@ -40,15 +40,12 @@ def _get_test_quality_for_model(model_name: str) -> str | None: """Returns the fastest quality setting for credential testing. - gpt-image-*: 'low' (fastest) - - dall-e-3: 'standard' (faster than 'hd') - Other models: None (use API default) """ model_lower = model_name.lower() if "gpt-image-" in model_lower: return "low" - elif "dall-e-3" in model_lower or "dalle-3" in model_lower: - return "standard" return None diff --git a/backend/onyx/server/manage/image_generation/models.py b/backend/onyx/server/manage/image_generation/models.py index 0ae62dfcd5..53d6148c79 100644 --- a/backend/onyx/server/manage/image_generation/models.py +++ b/backend/onyx/server/manage/image_generation/models.py @@ -23,7 +23,7 @@ class TestImageGenerationRequest(BaseModel): 2. From existing provider: Provide source_llm_provider_id (backend fetches API key) """ - model_name: str # e.g., "gpt-image-1", "dall-e-3" + model_name: str # e.g., "gpt-image-1" # Option 1: Direct API key provider: str | None = None # e.g., "openai", "azure" @@ -55,7 +55,7 @@ class ImageGenerationConfigCreate(BaseModel): # Required for both modes image_provider_id: str # Static unique key (e.g., "openai_gpt_image_1") - model_name: str # e.g., "gpt-image-1", "dall-e-3" + model_name: str # e.g., "gpt-image-1" # Option 1: Clone mode - use credentials from existing provider source_llm_provider_id: int | None = None @@ -79,7 +79,7 @@ class ImageGenerationConfigUpdate(BaseModel): """ # Required - model_name: str # e.g., "gpt-image-1", "dall-e-3" + model_name: str # e.g., "gpt-image-1" # Note: image_provider_id cannot be changed during update # Option 1: Clone mode - use credentials from existing provider diff --git a/backend/tests/integration/tests/image_generation/test_image_generation_config.py b/backend/tests/integration/tests/image_generation/test_image_generation_config.py index db6f715676..c96f97940e 100644 --- a/backend/tests/integration/tests/image_generation/test_image_generation_config.py +++ b/backend/tests/integration/tests/image_generation/test_image_generation_config.py @@ -43,7 +43,7 @@ def test_create_image_generation_config( config = ImageGenerationConfigManager.create( image_provider_id="test-openai-dalle", - model_name="dall-e-3", + model_name="gpt-image-1", provider="openai", api_key="sk-test-key-12345", is_default=False, @@ -51,7 +51,7 @@ def test_create_image_generation_config( ) assert config.image_provider_id == "test-openai-dalle" - assert config.model_name == "dall-e-3" + assert config.model_name == "gpt-image-1" assert config.is_default is False # Verify it exists in the list @@ -96,7 +96,7 @@ def test_create_duplicate_config_fails( # Create first config ImageGenerationConfigManager.create( image_provider_id="duplicate-test-id", - model_name="dall-e-3", + model_name="gpt-image-1", provider="openai", api_key="sk-test-key-1", user_performing_action=admin_user, @@ -127,7 +127,7 @@ def test_get_all_configs( # Create multiple configs config1 = ImageGenerationConfigManager.create( image_provider_id="config-1", - model_name="dall-e-3", + model_name="gpt-image-1", provider="openai", api_key="sk-key-1", user_performing_action=admin_user, @@ -160,7 +160,7 @@ def test_get_config_credentials( test_api_key = "sk-test-credentials-key-12345" config = ImageGenerationConfigManager.create( image_provider_id="credentials-test", - model_name="dall-e-3", + model_name="gpt-image-1", provider="openai", api_key=test_api_key, user_performing_action=admin_user, @@ -202,26 +202,26 @@ def test_update_config_direct_key_entry( # Create initial config config = ImageGenerationConfigManager.create( image_provider_id="update-direct-test", - model_name="dall-e-3", + model_name="gpt-image-1", provider="openai", api_key="sk-initial-key", user_performing_action=admin_user, ) - assert config.model_name == "dall-e-3" + assert config.model_name == "gpt-image-1" # Update with new credentials and model new_api_key = "sk-updated-key-12345" updated_config = ImageGenerationConfigManager.update( image_provider_id=config.image_provider_id, - model_name="dall-e-3", + model_name="gpt-image-1", provider="openai", api_key=new_api_key, user_performing_action=admin_user, ) assert updated_config.image_provider_id == config.image_provider_id - assert updated_config.model_name == "dall-e-3" + assert updated_config.model_name == "gpt-image-1" # Verify credentials were updated (masked: first 4 + **** + last 4) credentials = ImageGenerationConfigManager.get_credentials( @@ -240,13 +240,13 @@ def test_update_config_clone_mode( # Create initial config with direct credentials config = ImageGenerationConfigManager.create( image_provider_id="update-clone-test", - model_name="dall-e-3", + model_name="gpt-image-1", provider="openai", api_key="sk-initial-direct-key", user_performing_action=admin_user, ) - assert config.model_name == "dall-e-3" + assert config.model_name == "gpt-image-1" # Update by cloning from LLM provider updated_config = ImageGenerationConfigManager.update( @@ -275,7 +275,7 @@ def test_update_config_source_provider_not_found( # Create initial config config = ImageGenerationConfigManager.create( image_provider_id="update-bad-source-test", - model_name="dall-e-3", + model_name="gpt-image-1", provider="openai", api_key="sk-initial-key", user_performing_action=admin_user, @@ -304,7 +304,7 @@ def test_delete_config( # Create a config config = ImageGenerationConfigManager.create( image_provider_id="delete-test", - model_name="dall-e-3", + model_name="gpt-image-1", provider="openai", api_key="sk-delete-key", user_performing_action=admin_user, @@ -353,7 +353,7 @@ def test_set_default_config( # Create a config that is not default config = ImageGenerationConfigManager.create( image_provider_id="default-test", - model_name="dall-e-3", + model_name="gpt-image-1", provider="openai", api_key="sk-test-key", is_default=False, @@ -387,7 +387,7 @@ def test_set_default_clears_previous( # Create first config as default config1 = ImageGenerationConfigManager.create( image_provider_id="first-default", - model_name="dall-e-3", + model_name="gpt-image-1", provider="openai", api_key="sk-key-1", is_default=True, @@ -462,7 +462,7 @@ def test_create_config_missing_credentials( f"{API_SERVER_URL}/admin/image-generation/config", json={ "image_provider_id": "no-creds-test", - "model_name": "dall-e-3", + "model_name": "gpt-image-1", }, headers=admin_user.headers, ) @@ -481,7 +481,7 @@ def test_create_config_source_provider_not_found( f"{API_SERVER_URL}/admin/image-generation/config", json={ "image_provider_id": "bad-source-test", - "model_name": "dall-e-3", + "model_name": "gpt-image-1", "source_llm_provider_id": 999999, # Non-existent ID }, headers=admin_user.headers, diff --git a/backend/tests/integration/tests/llm_provider/test_llm_provider.py b/backend/tests/integration/tests/llm_provider/test_llm_provider.py index b80faba8e9..c8c2ae6312 100644 --- a/backend/tests/integration/tests/llm_provider/test_llm_provider.py +++ b/backend/tests/integration/tests/llm_provider/test_llm_provider.py @@ -2205,7 +2205,7 @@ def test_all_three_provider_types_no_mixup(reset: None) -> None: # noqa: ARG001 _create_image_gen_config( admin_user=admin_user, image_provider_id=image_gen_provider_id, - model_name="dall-e-3", + model_name="gpt-image-1", source_llm_provider_id=regular_provider["id"], is_default=True, ) @@ -2268,7 +2268,7 @@ def test_all_three_provider_types_no_mixup(reset: None) -> None: # noqa: ARG001 image_gen_config_data["is_default"] is True ), "Image gen config should be the default" assert ( - image_gen_config_data["model_name"] == "dall-e-3" + image_gen_config_data["model_name"] == "gpt-image-1" ), "Image gen config should have correct model name" # Step 5: Verify no mixup - image gen providers don't appear in LLM provider lists diff --git a/backend/tests/unit/onyx/image_gen/test_provider_building.py b/backend/tests/unit/onyx/image_gen/test_provider_building.py index 8ba004a803..de1d939105 100644 --- a/backend/tests/unit/onyx/image_gen/test_provider_building.py +++ b/backend/tests/unit/onyx/image_gen/test_provider_building.py @@ -204,32 +204,13 @@ def test_openai_provider_rejects_reference_images_for_unsupported_model() -> Non with pytest.raises(ValueError): provider.generate_image( prompt="edit this image", - model="dall-e-3", + model="unsupported-model", size="1024x1024", n=1, reference_images=[ReferenceImage(data=b"image-1", mime_type="image/png")], ) -def test_openai_provider_rejects_multiple_reference_images_for_dalle3() -> None: - provider = OpenAIImageGenerationProvider(api_key="test-key") - - with pytest.raises( - ValueError, - match="does not support image edits with reference images", - ): - provider.generate_image( - prompt="edit this image", - model="dall-e-3", - size="1024x1024", - n=1, - reference_images=[ - ReferenceImage(data=b"image-1", mime_type="image/png"), - ReferenceImage(data=b"image-2", mime_type="image/png"), - ], - ) - - def test_azure_provider_uses_image_generation_without_reference_images() -> None: provider = AzureImageGenerationProvider( api_key="test-key", @@ -303,31 +284,8 @@ def test_azure_provider_rejects_reference_images_for_unsupported_model() -> None with pytest.raises(ValueError): provider.generate_image( prompt="edit this image", - model="dall-e-3", + model="unsupported-model", size="1024x1024", n=1, reference_images=[ReferenceImage(data=b"image-1", mime_type="image/png")], ) - - -def test_azure_provider_rejects_multiple_reference_images_for_dalle3() -> None: - provider = AzureImageGenerationProvider( - api_key="test-key", - api_base="https://azure.example.com", - api_version="2024-05-01-preview", - ) - - with pytest.raises( - ValueError, - match="does not support image edits with reference images", - ): - provider.generate_image( - prompt="edit this image", - model="dall-e-3", - size="1024x1024", - n=1, - reference_images=[ - ReferenceImage(data=b"image-1", mime_type="image/png"), - ReferenceImage(data=b"image-2", mime_type="image/png"), - ], - ) diff --git a/backend/tests/unit/onyx/server/manage/llm/test_llm_provider_utils.py b/backend/tests/unit/onyx/server/manage/llm/test_llm_provider_utils.py index 7b55f3ac3d..e58c1a5924 100644 --- a/backend/tests/unit/onyx/server/manage/llm/test_llm_provider_utils.py +++ b/backend/tests/unit/onyx/server/manage/llm/test_llm_provider_utils.py @@ -277,8 +277,8 @@ class TestIsEmbeddingModel: def test_gpt4_not_embedding(self) -> None: assert is_embedding_model("gpt-4") is False - def test_dall_e_not_embedding(self) -> None: - assert is_embedding_model("dall-e-3") is False + def test_image_model_not_embedding(self) -> None: + assert is_embedding_model("gpt-image-1") is False def test_unknown_custom_model_not_embedding(self) -> None: """Custom/local models not in litellm's model DB should default to False.""" diff --git a/web/src/refresh-pages/admin/ImageGenerationPage/constants.ts b/web/src/refresh-pages/admin/ImageGenerationPage/constants.ts index 63e93f84b6..fd7189c7f5 100644 --- a/web/src/refresh-pages/admin/ImageGenerationPage/constants.ts +++ b/web/src/refresh-pages/admin/ImageGenerationPage/constants.ts @@ -38,14 +38,6 @@ export const IMAGE_PROVIDER_GROUPS: ProviderGroup[] = [ description: "A capable image generation model from OpenAI with strong prompt adherence.", }, - { - image_provider_id: "openai_dalle_3", - model_name: "dall-e-3", - provider_name: "openai", - title: "DALL-E 3", - description: - "OpenAI image generation model capable of generating rich and expressive images.", - }, ], }, { @@ -75,14 +67,6 @@ export const IMAGE_PROVIDER_GROUPS: ProviderGroup[] = [ description: "GPT Image 1 image generation model hosted on Microsoft Azure.", }, - { - image_provider_id: "azure_dalle_3", - model_name: "", // Extracted from deployment in target URI - provider_name: "azure", - title: "Azure OpenAI DALL-E 3", - description: - "DALL-E 3 image generation model hosted on Microsoft Azure.", - }, ], }, { diff --git a/web/tests/e2e/admin/image-generation/disconnect-provider.spec.ts b/web/tests/e2e/admin/image-generation/disconnect-provider.spec.ts index 0ec3d1bfbf..3e5af8b3c9 100644 --- a/web/tests/e2e/admin/image-generation/disconnect-provider.spec.ts +++ b/web/tests/e2e/admin/image-generation/disconnect-provider.spec.ts @@ -5,11 +5,11 @@ import { expectElementScreenshot } from "@tests/e2e/utils/visualRegression"; const IMAGE_GENERATION_URL = "/admin/configuration/image-generation"; const FAKE_CONNECTED_CONFIG = { - image_provider_id: "openai_dalle_3", + image_provider_id: "openai_gpt_image_1_5", model_configuration_id: 100, - model_name: "dall-e-3", + model_name: "gpt-image-1.5", llm_provider_id: 100, - llm_provider_name: "openai-dalle3", + llm_provider_name: "openai-gpt-image-1-5", is_default: false, }; @@ -71,7 +71,7 @@ test.describe("Image Generation Provider Disconnect", () => { timeout: 20000, }); - const card = getProviderCard(page, "openai_dalle_3"); + const card = getProviderCard(page, "openai_gpt_image_1_5"); await card.waitFor({ state: "visible", timeout: 10000 }); await expectElementScreenshot(mainContainer(page), { @@ -81,14 +81,14 @@ test.describe("Image Generation Provider Disconnect", () => { // Hover to reveal disconnect button, then verify await card.hover(); const disconnectButton = card.getByRole("button", { - name: "Disconnect DALL-E 3", + name: "Disconnect GPT Image 1.5", }); await expect(disconnectButton).toBeVisible(); await expect(disconnectButton).toBeEnabled(); // Mock the DELETE to succeed and update the config list await page.route( - "**/api/admin/image-generation/config/openai_dalle_3", + "**/api/admin/image-generation/config/openai_gpt_image_1_5", async (route) => { if (route.request().method() === "DELETE") { // Update the GET mock to return only the default config @@ -119,7 +119,7 @@ test.describe("Image Generation Provider Disconnect", () => { // Verify confirmation modal appears const confirmDialog = page.getByRole("dialog"); await expect(confirmDialog).toBeVisible({ timeout: 5000 }); - await expect(confirmDialog).toContainText("Disconnect DALL-E 3"); + await expect(confirmDialog).toContainText("Disconnect GPT Image 1.5"); await expectElementScreenshot(confirmDialog, { name: "image-gen-disconnect-non-default-modal", @@ -233,12 +233,12 @@ test.describe("Image Generation Provider Disconnect", () => { timeout: 20000, }); - // DALL-E 3 is not configured — should not have a disconnect button - const card = getProviderCard(page, "openai_dalle_3"); + // GPT Image 1.5 is not configured — should not have a disconnect button + const card = getProviderCard(page, "openai_gpt_image_1_5"); await card.waitFor({ state: "visible", timeout: 10000 }); const disconnectButton = card.getByRole("button", { - name: "Disconnect DALL-E 3", + name: "Disconnect GPT Image 1.5", }); await expect(disconnectButton).not.toBeVisible(); diff --git a/web/tests/e2e/admin/image-generation/image-generation-content.spec.ts b/web/tests/e2e/admin/image-generation/image-generation-content.spec.ts index 6b367aa208..2ddd875b92 100644 --- a/web/tests/e2e/admin/image-generation/image-generation-content.spec.ts +++ b/web/tests/e2e/admin/image-generation/image-generation-content.spec.ts @@ -9,8 +9,6 @@ const IMAGE_GENERATION_URL = const PROVIDERS = [ { id: "openai_gpt_image_1_5", title: "GPT Image 1.5" }, { id: "openai_gpt_image_1", title: "GPT Image 1" }, - { id: "openai_dalle_3", title: "DALL-E 3" }, - { id: "azure_dalle_3", title: "Azure OpenAI DALL-E 3" }, ]; // Helper to find a provider card by its aria-label @@ -82,7 +80,7 @@ test.describe("Image Generation Provider Configuration", () => { ); }); - test.describe("OpenAI DALL-E 3 Configuration", () => { + test.describe("OpenAI GPT Image 1 Configuration", () => { const OPENAI_API_KEY = process.env.OPENAI_API_KEY; test.skip(!OPENAI_API_KEY, "OPENAI_API_KEY environment variable not set"); @@ -91,22 +89,22 @@ test.describe("Image Generation Provider Configuration", () => { // Clean up the image generation config created during the test const apiClient = new OnyxApiClient(page.request); try { - await apiClient.deleteImageGenerationConfig("openai_dalle_3"); - console.log("[image-gen-test] Cleaned up DALL-E 3 config"); + await apiClient.deleteImageGenerationConfig("openai_gpt_image_1"); + console.log("[image-gen-test] Cleaned up GPT Image 1 config"); } catch (error) { console.warn( - `[image-gen-test] Failed to clean up DALL-E 3 config: ${error}` + `[image-gen-test] Failed to clean up GPT Image 1 config: ${error}` ); } }); - test.skip("should configure DALL-E 3 with API key", async ({ page }) => { - // Click Connect on DALL-E 3 card using aria-label - await openProviderModal(page, "openai_dalle_3"); + test.skip("should configure GPT Image 1 with API key", async ({ page }) => { + // Click Connect on GPT Image 1 card using aria-label + await openProviderModal(page, "openai_gpt_image_1"); // Wait for modal to open const modalDialog = page.getByRole("dialog", { - name: /connect dall-e 3/i, + name: /connect gpt image 1/i, }); await expect(modalDialog).toBeVisible({ timeout: 10000 }); @@ -141,13 +139,13 @@ test.describe("Image Generation Provider Configuration", () => { // Wait for page to update await page.waitForLoadState("networkidle"); - // Verify DALL-E 3 is now configured - should show "Current Default" - const dalleCard = getProviderCard(page, "openai_dalle_3"); + // Verify GPT Image 1 is now configured - should show "Current Default" + const gptImageCard = getProviderCard(page, "openai_gpt_image_1"); await expect( - dalleCard.getByRole("button", { name: "Current Default" }) + gptImageCard.getByRole("button", { name: "Current Default" }) ).toBeVisible({ timeout: 15000 }); - console.log("[image-gen-test] DALL-E 3 configured successfully"); + console.log("[image-gen-test] GPT Image 1 configured successfully"); }); }); }); diff --git a/web/tests/e2e/utils/onyxApiClient.ts b/web/tests/e2e/utils/onyxApiClient.ts index fb11a8f654..71af6bc47e 100644 --- a/web/tests/e2e/utils/onyxApiClient.ts +++ b/web/tests/e2e/utils/onyxApiClient.ts @@ -943,20 +943,20 @@ export class OnyxApiClient { * API: POST /api/admin/image-generation/config * Schema (ImageGenerationConfigCreate): * - image_provider_id: string (required) - unique key - * - model_name: string (required) - e.g., "dall-e-3" + * - model_name: string (required) - e.g., "gpt-image-1" * - provider: string - e.g., "openai" * - api_key: string * - is_default: boolean * * @param imageProviderId - Unique identifier for the image generation config - * @param modelName - Model name (defaults to "dall-e-3") + * @param modelName - Model name (defaults to "gpt-image-1") * @param provider - Provider name (defaults to "openai") * @param isDefault - Whether this should be the default config (defaults to true) * @returns The image_provider_id */ async createImageGenerationConfig( imageProviderId: string, - modelName: string = "dall-e-3", + modelName: string = "gpt-image-1", provider: string = "openai", isDefault: boolean = true ): Promise {