AI-Powered Text Generation in Java: Using OpenAI's API for Structured Outputs
- Published on
AI-Powered Text Generation in Java: Using OpenAI's API for Structured Outputs
The OpenAI API offers powerful capabilities for natural language understanding and generation. One of its standout features is the ability to produce structured outputs that conform to a specified JSON schema. In this blog post, we'll explore how to integrate the OpenAI API into a Java Spring Boot application to generate structured data. We'll walk through a practical example of creating a daily schedule based on user input, demonstrating how to harness the API's structured output feature for reliable and predictable responses.
Benefits of Using OpenAI API for Structured Outputs
- Reliable Data Formatting: Ensures that API responses adhere to a predefined JSON schema.
- Ease of Parsing: Structured outputs are easier to parse and integrate into applications.
- Reduced Error Handling: Minimizes the need for extensive validation and error checking.
- Flexibility: Allows dynamic generation of structured data based on user input.
Setting Up the Java Spring Boot Project
Before we begin, ensure you have the following:
- Java 17 installed
- Maven for dependency management.
- An OpenAI API key. You can obtain one by signing up at OpenAI's website.
Including Necessary Dependencies
Create a new Spring Boot project or add the following dependencies to your pom.xml:
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Spring Web (includes RestTemplate) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Jackson Databind for JSON processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
Implementing the OpenAI API Client with RestTemplate
Here's a Spring service that interacts with the OpenAI API to generate a daily schedule based on user input, utilizing structured outputs.
Important Update
When working with the OpenAI API's structured outputs, it's crucial to include all required fields in your JSON schema. In particular, the json_schema object within the response_format must include the name field; otherwise, you'll encounter an error like:
"message": "Missing required parameter: 'response_format.json_schema.name'.",
Let's ensure our code includes this field and that our JSON schema is valid.
package com.example.openai;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class OpenAIService {
private static final String OPENAI_API_KEY = "YOUR_OPENAI_API_KEY";
private static final String OPENAI_API_URL = "https://api.openai.com/v1/chat/completions";
private static final String MODEL_NAME = "gpt-4o-mini"; // Ensure the model supports structured outputs
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
public OpenAIService() {
this.restTemplate = new RestTemplate();
this.objectMapper = new ObjectMapper();
}
public String generateDailyPlan(String userPreferences) throws Exception {
// Build the request payload
Map<String, Object> payload = new HashMap<>();
payload.put("model", MODEL_NAME);
// Messages
List<Map<String, String>> messages = new ArrayList<>();
messages.add(Map.of("role", "system", "content",
"You are a helpful assistant that creates a structured daily plan in JSON format."));
messages.add(Map.of("role", "user", "content",
"Create a daily plan for me based on the following preferences:\n" + userPreferences));
payload.put("messages", messages);
// Define the JSON schema for structured output
Map<String, Object> jsonSchema = new HashMap<>();
jsonSchema.put("type", "object");
Map<String, Object> properties = new HashMap<>();
Map<String, Object> scheduleProperty = new HashMap<>();
scheduleProperty.put("type", "array");
Map<String, Object> items = new HashMap<>();
items.put("type", "object");
Map<String, Object> itemProperties = new HashMap<>();
itemProperties.put("time", Map.of("type", "string"));
itemProperties.put("activity", Map.of("type", "string"));
items.put("properties", itemProperties);
items.put("required", List.of("time", "activity"));
items.put("additionalProperties", false);
scheduleProperty.put("items", items);
properties.put("schedule", scheduleProperty);
jsonSchema.put("properties", properties);
jsonSchema.put("required", List.of("schedule"));
jsonSchema.put("additionalProperties", false);
// Add response format with JSON schema
Map<String, Object> responseFormat = new HashMap<>();
responseFormat.put("type", "json_schema");
// Include 'name' and 'strict' in 'json_schema' as required by the API
Map<String, Object> jsonSchemaFormat = new HashMap<>();
jsonSchemaFormat.put("name", "daily_plan_response");
jsonSchemaFormat.put("schema", jsonSchema);
jsonSchemaFormat.put("strict", true);
responseFormat.put("json_schema", jsonSchemaFormat);
payload.put("response_format", responseFormat);
// Set headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(OPENAI_API_KEY);
// Create the request entity
HttpEntity<String> requestEntity = new HttpEntity<>(
objectMapper.writeValueAsString(payload), headers);
// Send the request and receive the response
ResponseEntity<String> responseEntity = restTemplate.exchange(
OPENAI_API_URL, HttpMethod.POST, requestEntity, String.class);
// Parse the assistant's response
JsonNode responseJson = objectMapper.readTree(responseEntity.getBody());
String assistantContent = responseJson
.path("choices")
.get(0)
.path("message")
.path("content")
.asText();
return assistantContent; // This is your structured JSON output
}
}
Creating a Controller to Use the Service
To expose an endpoint that uses the OpenAIService, you can create a simple REST controller.
package com.example.openai;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class OpenAIController {
private final OpenAIService openAIService;
public OpenAIController(OpenAIService openAIService) {
this.openAIService = openAIService;
}
@PostMapping("/generate-daily-plan")
public String generateDailyPlan(@RequestBody UserPreferencesDto preferencesDto) {
try {
String result = openAIService.generateDailyPlan(preferencesDto.getPreferences());
return result;
} catch (Exception e) {
e.printStackTrace();
return "{\"error\": \"An error occurred while generating the daily plan.\"}";
}
}
}
Create a DTO class to represent the user's preferences:
package com.example.openai;
public class UserPreferencesDto {
private String preferences;
public String getPreferences() {
return preferences;
}
public void setPreferences(String preferences) {
this.preferences = preferences;
}
}
Explanation of the Code
- RestTemplate: Used to make HTTP requests to the OpenAI API.
- ObjectMapper: From Jackson library, used for JSON processing.
- Building the Request Payload: We create a map representing the JSON payload, including the messages and the JSON schema.
- Defining the JSON Schema: The schema specifies that the expected output is an object with a
schedule
array, where each item in the array is an object containingtime
andactivity
fields. - Including Required Fields: The
json_schema
object within theresponse_format
now includes the requiredname
andstrict
fields. - Handling the Response: We parse the assistant's response to extract the structured JSON content.
Sample Request and Output
Sample Request
Send a POST request to http://localhost:8080/api/generate-daily-plan with the following JSON body:
{
"preferences": "I wake up at 6 AM, prefer jogging in the morning, and work from 8 AM to 4 PM."
}
Sample Output
{
"schedule":[
{
"activity":"Wake Up",
"time":"6:00 AM"
},
{
"activity":"Morning Jog",
"time":"6:15 AM"
},
{
"activity":"Shower and Get Ready",
"time":"7:00 AM"
},
{
"activity":"Breakfast",
"time":"7:30 AM"
},
{
"activity":"Commute to Work",
"time":"7:45 AM"
},
{
"activity":"Start Work",
"time":"8:00 AM"
},
{
"activity":"Work",
"time":"8:00 AM - 12:00 PM"
},
{
"activity":"Lunch Break",
"time":"12:00 PM"
},
{
"activity":"Work",
"time":"1:00 PM - 4:00 PM"
},
{
"activity":"Commute Home",
"time":"4:00 PM"
},
{
"activity":"Relax/Free Time",
"time":"4:30 PM"
},
{
"activity":"Dinner",
"time":"6:30 PM"
},
{
"activity":"Evening Activity (Reading/TV/Exercise)",
"time":"7:00 PM"
},
{
"activity":"Prepare for Next Day",
"time":"8:30 PM"
},
{
"activity":"Wind Down/Bedtime Routine",
"time":"9:30 PM"
},
{
"activity":"Sleep",
"time":"10:00 PM"
}
]
}
Conclusion
By integrating OpenAI's API into your Java Spring Boot applications, you can generate structured outputs that conform to a specified JSON schema. This ensures that the data is reliable, easy to parse, and reduces the need for extensive validation. Including all required fields in your JSON schema, such as the name and strict fields within json_schema, is essential for the API to process your request correctly.
Whether you're creating daily plans, generating reports, or transforming unstructured text into structured data, the OpenAI API provides a robust solution for your Java applications.