OpenAI Image Generation Application with Spring Boot
In this article, I will demonstrate the creation of a simple Spring Boot application that utilizes the OpenAI-API to generate images from natural language descriptions.
All of the provided code examples can be accessed on GitHub
OpenAI
Before we begin, it is important to familiarize ourselves with the OpenAI. OpenAI is a one of the leading provider of tools and APIs for Artificial Intelligence and Machine Learning. Lets take a look at the different APIs and tools provided by OpenAI that can help add AI capabilities to applications:
- GPT-3 API: allows to generate natural language text. This API can be used for tasks such as text generation, summarization, and question answering.
- DALL-E API: allows to generate images from natural language descriptions. This API can be used for tasks such as image generation, object detection, and image classification to applications such as game development and e-commerce.
- OpenAI Playground: is a web-based interface that allows to experiment with different models and parameters. This tool can be used to test different models and fine-tune their parameters.
- Language API: allows to access a variety of natural language processing models for tasks such as text generation, summarization, and question answering. This API can be used to add natural language processing capabilities to applications such as chatbots and virtual assistants.
- Vision API: allows to access a variety of image processing models for tasks such as image classification, object detection, and image generation. This API can be used to add image processing capabilities to applications such as security cameras and object recognition systems.
- Translation API: allows to translate text from one language to another. This API can be used to add multilingual capabilities to applications.
- Completion API: allows to complete text based on a given prompt or context. This API can be used to add predictive typing capabilities to applications such as text editors and messaging systems.
- Formality API: allows to classify text as either formal or informal. This API can be used to ensure that text generated by the application is appropriate for the intended audience.
In this article, we will be limiting our examination to the image generation capabilities of the DALL-E API, in order to stay within the scope.
Spring Boot Application
Now that we know the relevant information about OpenAI, we can start creating our Spring Boot application. Spring Boot is a framework that allows us to build Java applications quickly and easily by doing many of the configuration tasks automatically.
1. Dependencies
First, let’s create a new Spring Boot project using the Spring Initializer with following dependencies:
- spring-boot-starter-web: includes all the necessary libraries to create a web application (RESTful web service)
- spring-cloud-starter-openfeign: it allows us to easily create HTTP client for calling DALL-E API.
- lombok: it help us to reduce amount of boilerplate code that we need to write and maintain, and making our code more concise and readable.
In addition, the following dependency is required for the example, which can be added via maven oder gradle:
- validation-api: a Java Bean Validation specification’s API that provides a way of defining constraints on the model and validation them.
more details about validation-api see chapter 2. Data Tranfer Objects.
To enable the use of OpenFeign in the project, it is necessary to add the @EnableFeignClients
annotation to the Spring Boot Application class. This annotation enables the scanning of Feign clients in the project and allows them to be used.
@SpringBootApplication
@EnableFeignClients
public class ImageGeneratorServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ImageGeneratorServiceApplication.class, args);
}
}
2. Data Transfer Objects
Data Transfer Objects (DTOs) are used to transfer data between layers of an application, in our case, between the presentation layer and the business logic layer. They provide a simple, lightweight representation of the data and can include validation for the data being transferred.
Let’s create GenerateImageRequest
and GenerateImageResponse
classes that represent the requiest and response objects for the image generation API.
@Data
public class GenerateImageRequest {
@NotBlank
private String prompt;
@ValidSize
private String size;
@Min(1)
@Max(10)
@JsonProperty("num_images")
private int numImages;
}
The GenerateImageRequest
class contains fields for the prompt, size, and number of images to generate. It is annotated with @Data
which is provided by Lombok library to automatically generate getters, setters, equals, hashCode, toString, and constructors for all fields in the class.
The following annotations from validation-api dependency are used in the class for input validation:
@NotBlank
ensures prompt is not null or empty@ValidSize
custom validation for image size field (valid sizes for DALL-E API are 256x256, 512x512 and 1024x1024 pixel)@Min(1)
and@Max(10)
ensures numImages has a value within a range (valid number of generating of images for one request is 1 to 10 )
Custom Validation
@Documented
@Constraint(validatedBy = SizeValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ValidSize {
String message() default "Invalid value. This is not a valid size.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public class SizeValidator implements ConstraintValidator<ValidSize, String> {
private static final List<String> validSizes = Arrays.asList("1024x1024", "512x512", "256x256");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return validSizes.contains(value);
}
}
In the example code provided a custom validator called @ValidSize
. It is composed of two parts: the constraint annotation @ValidSize
and the validator class SizeValidator
. The validator checks if the value of the field is present in a predefined list of valid sizes (1024x1024, 512x512 and 256x256). If it’s not present, it returns false. The message in the annotation is for providing a custom error message in case of validation failure.
I will not delve deeper into this topic as the implementation of custom annotations is not the focus of this article.
@Data
public class GenerateImageResponse {
private List<GeneratedImage> data;
}
@Data
public class GeneratedImage {
private String url;
}
The GenerateImageResponse
class that holds the response data of image generation. The field data is a list of GeneratedImage
. GeneratedImage
class contains one field url which hold the URL of the generated image from DALL-E API. Both classes are annotated with @Data
which is provided by Lombok library to automatically generate getters, setters, equals, hashCode, toString, and constructors for all fields in the class.
3. Configurations
By using confiugrations in the application, we ca write more robust, flexible and easy to maintain applications that can be easily adapted to different environments and requirements.
Let’s add the configuration settings for the OpenAI image generator API to application.yml file.
openai:
image-generator:
url: https://api.openai.com
api-key: <YOUR-API-KEY>
- url: the base URL for the OpenAI API
- api-key: the API key that is used to authenticate the application with the OpenAI API.
To generate an API key for OpenAI, you must first create an account on their website, then add a payment method, and finally, you can generate your API key under the “API Keys” section in the “User” menu. To view the pricing information for OpenAI, you can visit their website. Don’t worry about the cost, as after adding your billing information, you will receive free credits for 3–4 months, which is equivalent to around $18.
Finally, we will create a Spring Boot Configuration class to configure the client by providing an API Key for authentication:
@Configuration
public class ImageGeneratorConfig {
@Value("${openai.image-generator.api-key}")
private String apiKey;
@Bean
public RequestInterceptor apiKeyInterceptor() {
return template -> template.header("Authorization", "Bearer " + apiKey);
}
}
The class is annotated with @Configuration
, which indicates that it is a Spring configuration class. The class contains a single method apiKeyInterceptor
which is annotated with @Bean
. This method returns a RequestInterceptor
that is used to add an Authorization
header to the request with the value of "Bearer " + apiKey.
4. Feign Client
As already mentioned, Feign is a declarative web service client that makes it easier to write web service clients in Java.
@FeignClient(name = "imageGenerator", url = "${openai.image-generator.url}", configuration = ImageGeneratorConfig.class)
public interface ImageGeneratorClient {
@PostMapping(value = "/v1/images/generations")
GenerateImageResponse generateImage(@RequestBody final GenerateImageRequest request);
}
The ImageGeneratorClient
interface is annotated with @FeignClient
, which is used to specify the name and URL of the API. The name
attribute specifies the name of the client, and the url
attribute specifies the URL of the API (in that case we are using the url from the application.yml file configurations). The configuration
attribute is used to specify the configuration class for the client (in that case we are using created configuration class ImageGeneratorConfig
to provide the authorizations to the client).
The ImageGeneratorClient
interface also contains a method generateImage
which is annotated with @PostMapping
annotation, to specify the endpoint for generating images and it takes and return already created DTOs classes GenerateImageRequest
and GenerateImageResponse
.
5. Service
In Spring Boot, the @Service annotation is used to mark a class as a service layer component, indicating that it contains business logic and is eligible for autodetection by the Spring context.
Currently, we do not require any business logic as the required validation and settings have already been implemented through validators and configurations. However, to ensure a clean microservice architecture, we will create a class for the service layer in which business logic can be added in the future, such as caching of the images.
public interface ImageGeneratorService {
GenerateImageResponse generateImage(GenerateImageRequest generateImageRequest);
}
@Service
@AllArgsConstructor
public class ImageGeneratorServiceImpl implements ImageGeneratorService {
private final ImageGeneratorClient imageGeneratorClient;
public GenerateImageResponse generateImage(final GenerateImageRequest request) {
final GenerateImageResponse response = imageGeneratorClient.generateImage(request);
// if necessary, the response can be further processed here
return response;
}
}
Service interface ImageGeneratorService
with implementation ImageGeneratorServiceImpl
is used to separate the business logic of an application from its implementation details. This separation of concerns allows for more maintainable, testable, and flexible code.
The ImageGeneratorService
is responsible for calling the generateImage
method of the ImageGeneratorClient
and processing the response if necessary. The ImageGeneratorServiceImpl
is an implementation of the ImageGeneratorService
which is annotated with @Service
and @AllArgsConstructor
, indicating that it is a Spring service bean and that it has a constructor that takes in a single parameter of type ImageGeneratorClient
.
6. Rest Controller
In Spring Boot, the @RestController annotation is used to mark a class as a RESTful web service controller.
In last step we need to create a REST controller that maps HTTP requests to the generateImage
method of the ImageGeneratorService
:
@RestController
@RequestMapping("/images")
@AllArgsConstructor
public class ImageGeneratorController {
private static final Logger LOG = LoggerFactory.getLogger(ImageGeneratorController.class);
private final ImageGeneratorService imageGeneratorService;
@PostMapping("generate")
public ResponseEntity<GenerateImageResponse> generateImage(@Valid @RequestBody final GenerateImageRequest request) {
LOG.info("Received image generation request: {}", request);
final GenerateImageResponse response = imageGeneratorService.generateImage(request);
LOG.info("Generated image: {}", response);
return ResponseEntity.ok(response);
}
}
The class is annotated with @RestController
and @RequestMapping("/images")
, indicating that it is a REST controller and that it handles requests at the "/images" endpoint. The generateImage
method is annotated with @PostMapping("generate")
, indicating that it handles POST
requests at the "/images/generate" endpoint. The method takes in a single parameter of type GenerateImageRequest
and returns a GenerateImageResponse
with a list of generated image urls.
Ready to use
There are several ways to run a Spring Boot application locally:
- Using the Spring Boot Maven Plugin by running the following command in the root directory of your project:
mvn spring-boot:run
- Using the Spring Boot Gradle Plugin by running the following command in the root directory of your project:
./gradlew bootRun
- Using an IDE such as Eclipse, IntelliJ IDEA, or Spring Tool Suite by import the project and then run the main class of your application as a Java application.
After the application is running create a POST request to the endpoint https://localhost:8080/images/generate
with request body of GenerateImageRequest
class. There are several programs that can be used, such as cURL:
curl -X POST -H "Content-Type: application/json" -d '{"prompt":"robot saying thank you","size":"512x512","num_images":1}' http://localhost:8080/images/generate
or Postman:
In summary, this article has provided an overview of how to create a Spring Boot application that utilizes the OpenAI API to generate images based on text prompts, while also highlighting best practices for microservices architecture, and the importance of using libraries like OpenFeign and Lombok to simplify the interaction with external APIs, and Bean Validation to ensure the validity of requests before they are sent to the API. With this in mind, I wish you happy coding!