- At the OpenAI developer site, sign up for an account and create an API key. Currently the link to your API keys is at https://platform.openai.com/api-keys . OpenAI now recommends project API keys. See this page for more information.
- Add the API key to an environment variable called
OPENAI_API_KEY
. - Create a new Java project called
aijavalabs
in your favorite IDE with either Gradle or Maven as your build tool. - Review the documentation for the Open AI Text-to-speech API.
- Create a new class called
TextToSpeechService
in a package of your choosing. - Add a private, static, final attribute called
OPENAI_API_KEY
of typeString
that reads the API key from the environment variable.
private static final String OPENAI_API_KEY = System.getenv("OPENAI_API_KEY");
-
Add a method called
generateMp3
that returns abyte[]
. The method should take three arguments:model
of typeString
input
of typeString
voice
of typeString
-
Add a local variable called
payload
, which is a JSON string that contains the text you want to convert to speech:
String payload = """
{
"model": "%s",
"input": "%s",
"voice": "%s"
}
""".formatted(model, input.replaceAll("\\s+", " ").trim(), voice);
-
For those parameters:
- The value of the
model
variable must be eithertts-1
ortts-1-hd
. The non-hd version is almost certainly good enough, but use whichever one you prefer. - The value of
voice
must be one of:alloy
,echo
,fable
,onyx
,nova
, orshimmer
. Pick any one you like. - The
input
parameter is the text you wish to convert to an mp3.
- The value of the
-
Next, create an
HttpRequest
object that will include thepayload
in a POST request:
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.openai.com/v1/audio/speech"))
.header("Authorization", "Bearer %s".formatted(OPENAI_API_KEY))
.header("Content-Type", "application/json")
.header("Accept", "audio/mpeg")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
- To send a request, you need an
HttpClient
instance. Starting in Java 21, theHttpClient
class implementsAutoCloseable
, so you can use it in a try-with-resources block. Here is the code to send the request:
public byte[] generateMp3(String model, String input, String voice) {
// ... from before ...
try (HttpClient client = HttpClient.newHttpClient()) {
HttpResponse<byte[]> response =
client.send(request, HttpResponse.BodyHandlers.ofByteArray());
return response.body();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
- You'll probably want to save the returned byte array to a file. Create a class called
FileUtils
and add the following static method:
public static void saveBinaryFile(byte[] data, String fileName) {
Path directory = Paths.get("src/main/resources");
Path filePath = directory.resolve(fileName);
try {
Files.write(filePath, data, StandardOpenOption.CREATE_NEW);
System.out.printf("Saved %s to %s%n", fileName, directory.toAbsolutePath());
} catch (IOException e) {
throw new UncheckedIOException("Error writing audio to file", e);
}
}
- Add a test to drive the system:
@Test
void testGenerateMp3() {
var service = new TextToSpeechService();
byte[] result = service.generateMp3(
"tts-1",
"""
Now that I know how to generate audio from text,
I can use this feature in my applications.""",
"fable"
);
FileUtils.saveBinaryFile(result, "test.mp3");
assertNotNull(result);
assertTrue(result.length > 0);
}
- Run the test. The console should that
test.mp3
was saved in thesrc/main/resources
folder. You can play the file to hear the text you provided.
- Whenever you access the OpenAI API, you need to specify a model. To know which models are available, the OpenAI API provides an endpoint to list them. Review the documentation for that endpoint here.
- You'll find that our task is to send an HTTP GET request to
https://api.openai.com/v1/models
with the requiredAuthorization
header, and that the result is a block of JSON data. - We're going to want to parse that JSON data, as well as generate JSON for other interactions. For that, we need a parser. We'll use the Gson library from Google. Add the following dependency to your build file:
dependencies {
implementation("com.google.code.gson:gson:2.11.0")
}
- Add a class called
OpenAiRecords
to your project. It will hold all the records we need to parse the JSON data. - Inside the
OpenAiRecords
class, add a record calledModelList
, which itself contains a record calledModel
:
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class OpenAiRecords {
// List the models
public record ModelList(List<Model> data) {
public record Model(
String id,
long created,
@SerializedName("owned_by") String ownedBy) {
}
}
}
- Create a new class called
OpenAiService
in a package of your choosing. - Add a private, static, final attribute called
OPENAI_API_KEY
of typeString
that reads the API key from the environment variable.
private static final String OPENAI_API_KEY = System.getenv("OPENAI_API_KEY");
- Also add a constant for the Models endpoint:
private static final String MODELS_URL = "https://api.openai.com/v1/models";
- Add a private, static, final attribute called
GSON
of typeGson
that creates a newGson
instance:
private final Gson gson = new Gson();
- Add a method called
listModels
that returns aList<String>
. This method will send a GET request to the OpenAI API and return aModelList
.
public ModelList listModels() {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(MODELS_URL))
.header("Authorization", "Bearer %s".formatted(API_KEY))
.header("Accept", "application/json")
.build();
try (var client = HttpClient.newHttpClient()) {
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
return gson.fromJson(response.body(), ModelList.class);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
- Add a test to drive the system:
import org.junit.jupiter.api.Test;
import java.util.HashSet;
import java.util.List;
import static com.kousenit.OpenAiRecords.ModelList;
import static org.junit.jupiter.api.Assertions.assertTrue;
class OpenAiServiceTest {
private final OpenAiService service = new OpenAiService();
@Test
void listModels() {
List<String> models = service.listModels().data().stream()
.map(ModelList.Model::id)
.sorted()
.toList();
models.forEach(System.out::println);
assertTrue(new HashSet<>(models).containsAll(
List.of("dall-e-3", "gpt-3.5-turbo", "gpt-4o",
"tts-1", "whisper-1")));
}
}
- Run the test. You should see a list of model names in the console, and the assertion should pass.
- Download the latest version of Ollama from the Ollama website.
- Run the installer and follow the instructions.
- From a command prompt, enter
ollama run gemma2
to download and install the Gemma 2 model from Google (an open source model based on their Gemini model) locally. - Try out a couple of sample prompts at the command line, like
Why is the sky blue?
orGiven the power required to train large language models, how do companies ever expect to make money?
. - When you're finished, type
/bye
to exit.
- Ollama installs a small web server on your system. Verify that it is running by accessing http://localhost:11434 in a browser. You should get back the string,
Ollama is running
. - Ollama maintains a programmatic API accessible through a RESTful web service. The documentation is located here.
- We'll start by accessing the
api/generate
endpoint, as it is the simplest. It takes a JSON object with three fields:model
,prompt
, andstream
. Themodel
field is the name of the model you want to use (gemma2
in our case), theprompt
field is the question you want the model to answer, and thestream
field is a boolean that determines whether the response should be streamed back to the client. - Create a Java project (or simply add to the existing one) in your favorite IDE with either Gradle or Maven as your build tool.
- Create a class called
OllamaService
in a package of your choosing. - Add a method called
generate
that takes aString
and returns aString
. - If you created a new project, add the Gson dependency to your build file and synchronize the project. If you added to an existing project, you can skip this step.
- Model the input and output JSON with Java records. To do so, add a class called
OllamaRecords
to your project.
public class OllamaRecords {
public record OllamaRequest(String model, String prompt, boolean stream) {
}
}
- The output consists of a JSON object with
model
,created_at
,response
, anddone
field (among other optional fields we won't use right now). Add a record for that:
public class OllamaRecords {
public record OllamaRequest(
String model,
String prompt,
boolean stream) {
}
public record OllamaResponse(
String model,
String created_at, // Shouldn't this be camel case?
String response,
boolean done) {
}
}
- Now we have enough to create the
OllamaService
class and use it to access the chat endpoint. Add the following code to theOllamaService
class:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import static com.kousenit.OllamaRecords.*;
public class OllamaService {
private static final HttpClient client = HttpClient.newHttpClient();
private static final String URL = "http://localhost:11434";
private final Gson gson = new GsonBuilder()
.setPrettyPrinting()
.create();
public OllamaResponse generate(OllamaRequest request) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(URL + "/api/generate"))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
gson.toJson(ollamaRequest)))
.build();
try {
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
return gson.fromJson(response.body(), OllamaResponse.class);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
}
- Create a test class to verify the
OllamaService
class works as expected. Here is a sample:
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
import static com.kousenit.OllamaRecords.*;
import static org.junit.jupiter.api.Assertions.*;
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
public class OllamaServiceTest {
private final OllamaService service = new OllamaService();
@Test
public void testGenerate() {
var ollamaRequest = new OllamaRequest("gemma2", "Why is the sky blue?", false);
OllamaResponse ollamaResponse = service.generate(ollamaRequest);
String answer = ollamaResponse.response();
System.out.println(answer);
assertTrue(answer.contains("scattering"));
}
}
- Run the test to verify the service works as expected.
- Add an overload of the
generate
method toOllamaService
that takes two Strings: one for themodel
and one for theprompt
, and returns anOllamaResponse
object.
public OllamaResponse generate(String model, String prompt) {
return generate(new OllamaRequest(model, prompt, false));
}
- Adding convenience methods like that to make the calls easier for a client is a common practice. Add a test to verify the new method works.
@Test
void generate_with_model_and_name() {
var ollamaResponse = service.generate("gemma2", "Why is the sky blue?");
String answer = ollamaResponse.response();
System.out.println(answer);
assertTrue(answer.contains("scattering"));
}
- To see what a streaming result looks like, add a method to send the message and return the response as a
String
:
public String generateStreaming(OllamaRequest ollamaRequest) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(URL + "/api/generate"))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(gson.toJson(ollamaRequest)))
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
- Add a test to see the streaming result:
@Test
public void streaming_generate_request() {
var request = new OllamaRequest("gemma2", "Why is the sky blue?", true);
String response = service.generateStreaming(request);
System.out.println(response);
}
- Run the test to see the streaming result. It will be similar to:
{"model":"gemma2","created_at":"2024-07-08T20:15:46.965689Z","response":"The","done":false}
{"model":"gemma2","created_at":"2024-07-08T20:15:46.991009Z","response":" sky","done":false}
{"model":"gemma2","created_at":"2024-07-08T20:15:47.019408Z","response":" appears","done":false}
{"model":"gemma2","created_at":"2024-07-08T20:15:47.050245Z","response":" blue","done":false}
{"model":"gemma2","created_at":"2024-07-08T20:15:47.095448Z","response":" due","done":false}
{"model":"gemma2","created_at":"2024-07-08T20:15:47.126318Z","response":" to","done":false}
followed by lots more lines, until the done
field is true
.
- The
created_at
field in theOllamaResponse
record should becreatedAt
. You can fix this by adding a@SerializedName
annotation to the field:
public record OllamaResponse(
String model,
@SerializedName("created_at") String createdAt,
String response,
boolean done) {
}
- Run one of the existing tests to see that the field is populated correctly.
- As an alternative, you can create the
Gson
object using a builder and set a field naming policy. In that case, inside theOllamaService
, you would create theGson
object like this:
private final Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.setPrettyPrinting()
.create();
- Make sure the test prints the full
OllamaResponse
, then run the test again to see that the field is populated correctly. You may also want to add an assertion that thecreatedAt
field of the response is not null.
- Ollama also supports vision models, like
moondream
, that can read images and generate text descriptions from them. The images must be uploaded in the form of Base 64 encoded strings. - We're going to need a multimodal model that supports vision requests. A small one, useful for experiements, is called
moondream
. Download it by running the commandollama pull moondream
. (Note this is apull
rather than arun
-- we don't plan to run the vision model at the command line.) - Add a new record to
OllamaRecords
calledOllamaVisionRequest
with the following fields:model
of typeString
prompt
of typeString
stream
of typeboolean
images
of typeList<String>
public record OllamaVisionRequest(
String model,
String prompt,
boolean stream,
List<String> images) {}
- Fortunately, the output response from vision models is the same as from the text models, so you can reuse the
OllamaResponse
record. - Rather than require the client to submit the image in that form, let's provide a method to convert an image from a URL into the proper string. We'll call this method inside a compact constructor, a cool feature of records.
- Inside the
OllamaVisionRequest
record, add a compact constructor that takes aString
path to a local image and converts it to a Base 64 encoded string.
public record OllamaVisionRequest(
String model,
String prompt,
boolean stream,
List<String> images) {
public OllamaVisionRequest {
images = images.stream()
.map(this::encodeImage)
.collect(Collectors.toList());
}
private String encodeImage(String path) {
try {
byte[] imageBytes = Files.readAllBytes(Paths.get(path));
return Base64.getEncoder().encodeToString(imageBytes);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
- Add a new method to
OllamaService
calledgenerateVision
that takes anOllamaVisionRequest
object and returns anOllamaResponse
object. - We need an image to view. Here is one if you don't already have one available:
- Add the following test to the
OllamaServiceTest
class:
@Test
void test_vision_generate() {
var request = new OllamaVisionRequest("moondream",
"""
Generate a text description of this image
suitable for accessibility in HTML.
""",
false,
List.of("src/main/resources/images/stablediffusion/cats_playing_cards.png"));
OllamaResponse ollamaResponse = service.generateVision(request);
assertNotNull(ollamaResponse);
System.out.println(ollamaResponse.response());
}
- Add the
generateVision
method to theOllamaService
class:
public OllamaResponse generateVision(OllamaVisionRequest visionRequest) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(URL + "/api/generate"))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
gson.toJson(visionRequest)))
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return gson.fromJson(response.body(), OllamaResponse.class);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
-
The result is inside the
response
field of theOllamaResponse
object. Run the test to see the result. -
That's an awful lot of duplicated code. Surely we can do better? We can, by using a sealed interface.
- Now comes the question: how do we send the vision request to the service? We'd rather not duplicate all the existing code. Fortunately, records can implement interfaces. In this case, we'll use a sealed interface, so that only permitted classes can implement it.
- Refactor our existing code by renaming the
OllamaRequest
record toOllamaTextRequest
. - Make sure the tests for the text model still pass.
- Create a sealed interface called
OllamaRequest
with two permitted classes:OllamaTextRequest
andOllamaVisionRequest
and add it to ourOllamaRecords
class. Addimplements OllamaRequest
to both records.
public class OllamaRecords {
public sealed interface OllamaRequest
permits OllamaTextRequest, OllamaVisionRequest {
}
public record OllamaTextRequest(
String model,
String prompt,
boolean stream) implements OllamaRequest {}
public record OllamaVisionRequest(
String model,
String prompt,
boolean stream,
List<String> images) implements OllamaRequest {
public OllamaVisionRequest {
images = images.stream()
.map(this::encodeImage)
.collect(Collectors.toList());
}
private String encodeImage(String path) {
try {
byte[] imageBytes = Files.readAllBytes(Paths.get(path));
return Base64.getEncoder()
.encodeToString(imageBytes);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
}
}
- In
OllamaService
, we can go back to when ourgenerate
method took anOllamaRequest
object as its only argument and let polymorphism distinguish between a text request and a vision request. We can then use pattern matching to determine which type of request we have and act accordingly. We had this before, but let's repeat it here for convenience:
public OllamaResponse generate(OllamaRequest ollamaRequest) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(URL + "/api/generate"))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(gson.toJson(ollamaRequest)))
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return gson.fromJson(response.body(), OllamaResponse.class);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
- Pattern matching for switch became GA in Java 21. If you have Java 21 available, let's use it to identify what type of request we are sending. First, add a system logger as an attribute to
OllamaService
:
private final System.Logger logger = System.getLogger(OllamaService.class.getName());
- With that available, we can now add a switch statement to the beginning of the
generate
method:
public OllamaResponse generate(OllamaRequest ollamaRequest) {
switch (ollamaRequest) {
case OllamaTextRequest textRequest -> {
logger.log(System.Logger.Level.INFO, "Text request to: {0}", textRequest.model());
logger.log(System.Logger.Level.INFO, "Prompt: {0}", textRequest.prompt());
}
case OllamaVisionRequest visionRequest -> {
logger.log(System.Logger.Level.INFO, "Vision request to: {0}", visionRequest.model());
logger.log(System.Logger.Level.INFO, "Size of uploaded image: {0}", visionRequest.images()
.getFirst()
.length());
logger.log(System.Logger.Level.INFO, "Prompt: {0}", visionRequest.prompt());
}
}
// ... rest as before ...
}
- Because we used a sealed interface, there are only two possible types of requests. Our switch expression is therefore exhaustive. We can add tests for each to verify that the logging works as expected.
@Test
void generate_with_text_request() {
var ollamaRequest = new OllamaTextRequest("gemma2", "Why is the sky blue?", false);
OllamaResponse ollamaResponse = service.generate(ollamaRequest);
System.out.println(ollamaResponse);
String answer = ollamaResponse.response();
System.out.println(answer);
assertTrue(answer.contains("scattering"));
assertNotNull(ollamaResponse.createdAt());
}
@Test
void generate_with_vision_request() {
var request = new OllamaVisionRequest("moondream",
"""
Generate a text description of this image
suitable for accessibility in HTML.
""",
false,
List.of("src/main/resources/cats_playing_cards.png"));
OllamaResponse response = service.generateVision(request);
assertNotNull(response);
System.out.println(response);
}
- Run the tests to verify that the logging works as expected. This combination of modeling the data as records, limiting the available entries with sealed interfaces, and selecting between them with switch expressions, is a form of data oriented programming, as described in the linked article by Brian Goetz.
- The
generate
endpoint we've been using is very limited. Ollama also supports a more complicated model, similar to those from OpenAI, Anthropic, Gemini, and Mistral. That model allows conversations, that connect messages together. You need to add the messages yourself in a list, alternating betweenuser
andassistant
messages, and the AI tool will respond to the complete sequence. - In Ollama, you send a POST request to
/api/chat
instead of/api/generate
when you're dealing with a list of messages rather than just a single one. - Here is a sample request (from the Ollama documentation):
{
"model": "llama3",
"messages": [
{
"role": "user",
"content": "why is the sky blue?"
},
{
"role": "assistant",
"content": "due to rayleigh scattering."
},
{
"role": "user",
"content": "how is that different than mie scattering?"
}
]
}
- Create a new record called
OllamaMessage
with the following fields:role
of typeString
content
of typeString
public record OllamaMessage(
String role,
String content) {}
- The value of
role
is restricted to:user
, for client questionsassistant
, for AI responsessystem
, for global context
- With that in mind, add a compact constructor to
OllamaMessage
that validates therole
field:
public record OllamaMessage(String role,
String content) {
public OllamaMessage {
if (!List.of("user", "assistant", "system").contains(role)) {
throw new IllegalArgumentException("Invalid role: " + role);
}
}
}
- Create a new record called
OllamaChatRequest
with the following fields:model
of typeString
messages
of typeList<OllamaMessage>
- 'temperature' of type
double
public record OllamaChatRequest(
String model,
List<OllamaMessage> messages,
boolean stream) {}
- Add a record called
OllamaChatResponse
with the following fields:model
of typeString
createdAt
of typeString
message
of typeOllamaMessage
done
of typeboolean
public record OllamaChatResponse(
String model,
String createdAt,
OllamaMessage message,
boolean done) {}
- Add a new method to
OllamaService
calledchat
that takes anOllamaChatRequest
object and returns aList<OllamaMessage>
object. - Add a test to try out the conversation model:
@Test
void test_chat() {
var request = new OllamaChatRequest("gemma2",
List.of(new OllamaMessage("user", "why is the sky blue?"),
new OllamaMessage("assistant", "due to rayleigh scattering."),
new OllamaMessage("user", "how is that different than mie scattering?")),
false);
OllamaChatResponse response = service.chat(request);
assertNotNull(response);
System.out.println(response);
}
- Update the
OllamaService
class to include thechat
method:
public OllamaChatResponse chat(OllamaChatRequest chatRequest) {
String json = gson.toJson(chatRequest);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(URL + "/api/chat"))
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return gson.fromJson(response.body(), OllamaChatResponse.class);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
- Run the test to see it in action. In practice, you would extract the
assistant
response from each message and add it to the next request. Note that all the major framework (Spring AI, LangChain4j, etc) have ways of doing that for you.
-
We now return to OpenAI to use it's DALL-E 3 image model. Generating images is straightforward. Again, we'll create an image request object that models the input JSON data, send it in a POST request, and process the results. It turns out we can retrieve the response as either a URL or a Base 64 encoded string.
-
Add a record called
DalleImageRequest
:
public record ImageRequest(
String model,
String prompt,
Integer n,
String quality,
String responseFormat,
String size,
String style
) {}
- The response wraps either a URL to the generated image, or the actual Base 64 encoded data. For this exercise, we'll get the URL. Add a record called
DalleImageResponse
:
public record ImageResponse(
long created,
List<Image> data) {
public record Image(
String url,
String revisedPrompt) {}
}
- Add a class called
DalleService
with constants for the endpoint, the API key, and aGson
object:
public class DalleService {
private static final String IMAGE_URL = "https://api.openai.com/v1/images/generations";
private static final String API_KEY = System.getenv("OPENAI_API_KEY");
private final Gson gson = new GsonBuilder()
.setPrettyPrinting()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
}
- Add a new method to
DalleService
calledgenerateImage
that takes aDalleImageRequest
object and returns aDalleImageResponse
object.
public ImageResponse generateImage(ImageRequest imageRequest) {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(IMAGE_URL))
.header("Authorization", "Bearer %s".formatted(API_KEY))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(gson.toJson(imageRequest)))
.build();
try (HttpClient client = HttpClient.newHttpClient()) {
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
return gson.fromJson(response.body(), ImageResponse.class);
} catch (IOException | InterruptedException e) {
throw new RuntimeException("Error sending prompt prompt", e);
}
}
- Add a test to try out the image generation model:
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Test;
import static com.kousenit.OpenAiRecords.*;
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class DalleServiceTest {
private final DalleService service = new DalleService();
@Test
void generate_image() {
var imageRequest = new ImageRequest(
"dall-e-3",
"Draw a picture of cats playing cards",
1,
"standard",
"url",
"1024x1024",
"natural");
ImageResponse imageResponse = service.generateImage(imageRequest);
System.out.println(imageResponse.data().getFirst().revisedPrompt());
System.out.println(imageResponse.data().getFirst().url());
}
}
- Run the test to see it in action. The response will contain a URL to the generated image, with you can either click on or copy and paste into a browser to download the image.