[Spring Boot] How to use OpenAI ChatGPT APIs in a Spring Boot Application
Chat Completions API is described as follows (link):
Chat models take a list of messages as input and return a model-generated message as output. Although the chat format is designed to make multi-turn conversations easy, it’s just as useful for single-turn tasks without any conversation.
There are libraries available to integrate with ChatGPT but this article shows how to use ChatGPT APIs without any external dependencies.
Implementation
WebClient
is used to call the ChatGPT APIs, that’s why spring-boot-starter-webflux
dependency is added to build.gradle.kts
.
plugins {
java
id("org.springframework.boot") version "3.1.2"
id("io.spring.dependency-management") version "1.1.2"
}
group = "io.jay"
version = "0.0.1-SNAPSHOT"
java {
sourceCompatibility = JavaVersion.VERSION_17
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-webflux")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.withType<Test> {
useJUnitPlatform()
}
Place your API key in the application.yml
like below. The API key will be sent as Authorization
HTTP header in the request. API keys can be created from https://platform.openai.com/account/api-keys.
chatgpt:
api-key: your-api-key
In order to keep things simple, the controller endpoint receives a question or a prompt in the request body and sends to ChatGPT directly. You could do more robust things to come up with a prompt.
Note that you could change and play around with the gpt model
and temperature
in the ChatRequest
constructor. Also, you could use different role
for Message
.
package io.jay.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.annotation.PostExchange;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
import reactor.netty.http.client.HttpClient;
import java.time.Duration;
import java.util.List;
import java.util.stream.Collectors;
interface GptClient {
@PostExchange("/v1/chat/completions")
ChatResponse chat(@RequestBody ChatRequest request);
}
@SpringBootApplication
public class MainApplication {
@Value("${chatgpt.api-key}")
private String apiKey;
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
@Bean
GptClient client(WebClient.Builder builder) {
var httpClient = HttpClient.newConnection()
.responseTimeout(Duration.ofSeconds(60))
.keepAlive(false);
var wc = builder.baseUrl("https://api.openai.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + apiKey)
.clientConnector(new ReactorClientHttpConnector(httpClient))
.build();
var wca = WebClientAdapter.forClient(wc);
return HttpServiceProxyFactory.builder()
.blockTimeout(Duration.ofSeconds(60))
.clientAdapter(wca)
.build()
.createClient(GptClient.class);
}
}
class ChatRequest {
private String model;
private List<Message> messages;
private double temperature;
public ChatRequest() {
}
public ChatRequest(String prompt) {
this.model = "gpt-3.5-turbo";
this.messages = List.of(new Message("user", prompt));
this.temperature = 0.0;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
public List<Message> getMessages() {
return messages;
}
public void setMessages(List<Message> messages) {
this.messages = messages;
}
public double getTemperature() {
return temperature;
}
public void setTemperature(double temperature) {
this.temperature = temperature;
}
}
record Message(String role, String content) {
}
record Choice(int index, Message message) {
}
record ChatResponse(List<Choice> choices) {
}
@Controller
@ResponseBody
@RequestMapping("/api")
class ChatController {
private final GptClient gpt;
public ChatController(GptClient gpt) {
this.gpt = gpt;
}
@PostMapping("/chat")
public String chat(@RequestBody String prompt) {
var response = gpt.chat(new ChatRequest(prompt));
System.out.println(response);
return response.choices().stream()
.map(c -> c.message().content())
.collect(Collectors.joining("\n"));
}
}