Milesoft OpenAI Extension (openai-ext)

1. Overview & Purpose

The OpenAI Extension (openai-ext) is a lightweight, secure, and production-ready JVM wrapper for integrating OpenAI Services within the Milesoft ecosystem. Currently, it focuses on simplifying the generation of high-quality images via the OpenAI Images Generation API (specifically using the state-of-the-art DALL-E 3 model).

It encapsulates low-level REST API requests, handles authentication headers seamlessly, structures standard payload constraints, and ensures robust, schema-compliant responses through strongly typed domain models.

Key Benefits & Core Capabilities:

  • Simplified Image Generation: Generates images programmatically via DALL-E 3 with default production settings, requiring only a text prompt.
  • Robust Client & Service Abstraction: Provides both a granular client (ImageClient) for low-level custom requests and a high-level service (ImageService) for direct task execution.
  • Strict Domain Models: Fully typed request and response objects ensure structural integrity, with built-in validation rules (using jakarta.validation) to prevent malformed API calls.
  • Revised Prompt Transparency: Seamlessly captures OpenAI's revised prompt (generated by DALL-E to enhance prompt quality) for debugging and auditing purposes.
  • Standard Configuration Integration: Integrates naturally with Milesoft core systems (e.g., SecretManagerService) for secure, externalized API key management.

2. Installation & Dependency Configuration

The OpenAI Extension library is hosted inside our secure Google Cloud Artifact Registry.

To pull this package into your downstream application, verify your dependency configuration:

2.1 Gradle Setup (build.gradle)

Apply the Google Cloud Artifact Registry plugin and define the Maven repository pointing to our secure artifact URL:

plugins {
    id "com.google.cloud.artifactregistry.gradle-plugin" version "2.2.5"
    id "java"
}

repositories {
    mavenLocal()
    mavenCentral()
    maven {
        url "artifactregistry://us-west2-maven.pkg.dev/milesoft-repo/repo-maven"
    }
}

dependencies {
    // Required base foundation libraries
    implementation "io.milesoft:commons:2.0.1"
    implementation "io.milesoft:clients:2.0.1"
    
    // Milesoft OpenAI Integration SDK
    implementation "io.milesoft:openai-ext:2.0.1"
}

3. Configuring OpenAI in Spring Boot

To leverage dependency injection within your application, register the core ImageService class as a managed @Bean in a @Configuration container.

3.1 Properties Configuration

In a production deployment, the OpenAI API key must be externalized and resolved securely. We recommend storing the sensitive API key inside Google Cloud Secret Manager.

secrets:
  openAi:
    # Target name of the secret stored in GCP Secret Manager
    apiKey: "prod-openai-api-key"

3.2 Secure Bean Configuration

Resolve the API key using your local credentials provider (such as SecretManagerService) and let Spring manage the standard singleton instance:

import static com.google.common.base.Preconditions.checkNotNull;

import io.milesoft.openai.services.ImageService;
import io.milesoft.secrets.services.SecretManagerService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OpenAiConfig {

    @Bean
    public ImageService imageService(SecretManagerService secretManagerService,
        @Value("${secrets.openAi.apiKey}") String secretApiKey) {

        checkNotNull(secretManagerService, "secretManagerService must not be null");

        // Dynamically load the secret from Google Cloud Secret Manager
        final String apiKey = secretManagerService.loadSecret(secretApiKey);

        return new ImageService(apiKey);
    }
}

4. Domain & Data Types

The library enforces strict type safety through lightweight, structured domain models.

4.1 Image Request (ImageRequest)

Represents the request payload submitted to the DALL-E endpoint. It includes annotations from Jakarta Validation to ensure parameters are formatted properly before hitting the wire.

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@EqualsAndHashCode
@ToString
public class ImageRequest {

    @NotBlank
    private String model;          // e.g., "dall-e-3"

    @NotBlank
    private String prompt;         // Detailed text prompt for the image

    @NotBlank
    private String size;           // Resolution, e.g., "1024x1024"

    @NotBlank
    private String quality;        // "standard" or "hd"

    @JsonProperty("response_format")
    @NotBlank
    private String format;         // "url" or "b64_json"

    @JsonProperty("n")
    @NotNull
    private Integer num;           // Number of images to generate (usually 1 for dall-e-3)
}

4.2 Image Payload (Image)

Encapsulates an individual generated image, including the public output URL and DALL-E's internally rewritten version of your prompt.

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@EqualsAndHashCode
@ToString
public class Image {

    @JsonProperty("revised_prompt")
    @NotBlank
    private String revisedPrompt;  // DALL-E's refined prompt used for generation

    @NotBlank
    private String url;             // Secure public URL to access/download the image
}

4.3 Image Response (ImageResponse)

A verified container returned from the OpenAI API containing the timestamp and collection of output graphics.

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import java.time.ZonedDateTime;
import java.util.List;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@EqualsAndHashCode
@ToString
public class ImageResponse {

    @NotNull
    @Valid
    private List<Image> data;      // Collection of generated images

    @NotNull
    @Valid
    private ZonedDateTime created; // Creation timestamp
}

5. API Reference Guide

5.1 ImageClient

The low-level REST client targeting OpenAI's API.

Method Signatures Return Type Description
ImageClient(String apiKey) Constructor Instantiates the client with a non-blank OpenAI API Key.
generation(ImageRequest request) ImageResponse Performs an authenticated POST request to https://api.openai.com/v1/images/generations using the parsed ImageRequest.

5.2 ImageService

The primary high-level service offering a simplified, production-default API wrapper.

Method Signatures Return Type Description
ImageService(String apiKey) Constructor Instantiates the service and its underlying ImageClient.
ImageService(ImageClient client) Constructor Package-private constructor used for mocking and test isolation.
generateImage(String prompt) String Generates a single 1024x1024 standard resolution image via "dall-e-3" and returns its URL. Logs the revised prompt at debug level.

6. Real-World Integration Patterns

6.1 Programmatic Generation & Cloud Storage Upload

In production, generated URLs returned by OpenAI are ephemeral (they typically expire within an hour). A common design pattern is to generate the image, download/pipe it directly to a permanent asset host (like Cloudinary), and store the secure static URL.

Here is an example of an asset generation pattern:

import io.milesoft.cloudinary.domain.Image;
import io.milesoft.cloudinary.domain.ImageType;
import io.milesoft.cloudinary.services.CloudinaryService;
import io.milesoft.openai.services.ImageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class AssetGenerationService {

    private static final String CLOUDINARY_FOLDER = "ai-generated-assets";
    
    private final ImageService imageService;
    private final CloudinaryService cloudinaryService;

    public AssetGenerationService(ImageService imageService, CloudinaryService cloudinaryService) {
        this.imageService = imageService;
        this.cloudinaryService = cloudinaryService;
    }

    /**
     * Generates a permanent visual asset for a story prompt.
     */
    public String createPermanentAsset(String storyContent) {
        log.info("Generating graphic prompt from story content...");
        
        // 1. Generate the image via DALL-E 3 using our extension
        String ephemeralUrl = imageService.generateImage(storyContent);
        
        // 2. Upload/Mirror the ephemeral file to permanent Cloudinary storage
        Image permanentImage = cloudinaryService.uploadImage(
            ephemeralUrl, 
            CLOUDINARY_FOLDER, 
            ImageType.Public
        );

        log.info("Asset successfully permanently hosted: {}", permanentImage.secureUrl());
        return permanentImage.secureUrl();
    }
}

6.2 CLI Execution with ImageGenerator

The library includes a packaged command-line utility, ImageGenerator, which is ideal for testing credentials, validating models, or manual asset generation during local development.

The CLI reads the API key from /application.private.properties at the key openAi.apiKey.

Running via Terminal:

To execute a prompt directly using Gradle inside the openai-ext project directory:

./gradlew run --args="A beautiful watercolor painting of a sleepy tabby cat sitting on a stack of old books"

Code Layout of ImageGenerator:

import static io.milesoft.openai.constants.PropertyConstants.OPENAI_KEY;
import static io.milesoft.openai.constants.PropertyConstants.PROPERTIES_PRIVATE;

import io.milesoft.commons.utils.PropertyUtils;
import io.milesoft.openai.services.ImageService;
import java.util.Properties;

public class ImageGenerator {

    public static void main(String[] args) {
        // Read sensitive properties from local untracked file
        final Properties properties = PropertyUtils.read(PROPERTIES_PRIVATE);
        final String apiKey = properties.getProperty(OPENAI_KEY);
        final ImageService imageService = new ImageService(apiKey);

        final String prompt = args.length > 0
            ? args[0]
            : "your prompt here";

        // Dispatch generation request and display output URL
        final String url = imageService.generateImage(prompt);
        System.out.println(url);
    }
}

7. Troubleshooting & FAQs

Q: Why do generated image links stop working after some time?

A: OpenAI's design limits the lifetime of raw URLs returned from DALL-E generations (typically expiring after an hour). For long-term availability, you must download the byte payload or use our Cloudinary integration (cloudinary-ext) to persist the image immediately upon generation.

Q: Why does generateImage log standard debug text even when the prompt succeeds?

A: This is a key feature of ImageService. To aid in prompt engineering and testing, ImageService.generateImage captures the revised_prompt that OpenAI's models produce to optimize DALL-E's final output, logging it at the debug level. To inspect this, configure your application logging:

logging:
  level:
    io.milesoft.openai.services: DEBUG

Q: What resolutions are supported?

A: While the high-level ImageService.generateImage forces 1024x1024 for optimized DALL-E 3 behavior, you can customize resolutions (e.g., 1024x1792 or 1792x1024) by invoking ImageClient.generation(...) directly with a custom ImageRequest.