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.