OpenAi4J
OpenAi4J is an unofficial Java library tailored to facilitate the interaction with OpenAI’s GPT models, including the
newest additions such as gpt4-turbo vision,assistant-v2. Originally forked from TheoKanning/openai-java, this library
continues development to incorporate latest API features after the original project’s maintenance was discontinued.
中文介绍☕
Features
- Full support for all OpenAI API models including Completions, Chat, Edits, Embeddings, Audio, Files, Assistants-v2,
Images, Moderations, Batch, and Fine-tuning.
- Easy-to-use client setup with Retrofit for immediate API interaction.
- Extensive examples and documentation to help you start quickly.
- Customizable setup with environment variable integration for API keys and base URLs.
- Supports synchronous and asynchronous API calls.
This library aims to provide Java developers with a robust tool to integrate OpenAI’s powerful capabilities into their
applications effortlessly.
Quick Start
Import
Gradle
implementation 'io.github.lambdua:<api|client|service>:0.22.5'
Maven
<dependency>
<groupId>io.github.lambdua</groupId>
<artifactId>service</artifactId>
<version>0.22.5</version>
</dependency>
chat with OpenAi model
static void simpleChat() {
//api-key get from environment variable OPENAI_API_KEY
OpenAiService service = new OpenAiService(Duration.ofSeconds(30));
List<ChatMessage> messages = new ArrayList<>();
ChatMessage systemMessage = new SystemMessage("You are a cute cat and will speak as such.");
messages.add(systemMessage);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model("gpt-4o-mini")
.messages(messages)
.n(1)
.maxTokens(50)
.build();
ChatCompletionResult chatCompletion = service.createChatCompletion(chatCompletionRequest);
System.out.println(chatCompletion.getChoices().get(0).getMessage().getContent());
}
Just Using POJO
If you wish to develop your own client, simply import POJOs from the api module.</br>
Ensure your client adopts snake case naming for compatibility with the OpenAI API.
To utilize pojos, import the api module:
<dependency>
<groupId>io.github.lambdua</groupId>
<artifactId>api</artifactId>
<version>0.22.5</version>
</dependency>
other examples:
The sample code is all in the example
package, which includes most of the functional usage. </br>
You can refer to the code in the example package. Below are some commonly used feature usage examples
gpt-vision image recognition
```java
static void gptVision() {
OpenAiService service = new OpenAiService(Duration.ofSeconds(20));
final List messages = new ArrayList<>();
final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant.");
//Here, the imageMessage is intended for image recognition
final ChatMessage imageMessage = UserMessage.buildImageMessage("What's in this image?",
"https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/Gfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg");
messages.add(systemMessage);
messages.add(imageMessage);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model("gpt-4-turbo")
.messages(messages)
.n(1)
.maxTokens(200)
.build();
ChatCompletionChoice choice = service.createChatCompletion(chatCompletionRequest).getChoices().get(0);
System.out.println(choice.getMessage().getContent());
}
```
</details>
Customizing OpenAiService
OpenAiService is versatile in its setup options, as demonstrated in the `example.ServiceCreateExample` within the example package.
```java
//0 Using the default configuration, read the environment variables OPENAI-API_KEY, OPENAI-API_BASE-URL as the default API_KEY and BASE-URL,
//encourage the use of environment variables to load the OpenAI API key
OpenAiService openAiService0 = new OpenAiService();
//1.Use the default base URL and configure service by default. Here, the base URL (key: OPENAI API BASE URL) will be obtained from the environment variable by default. If not, the default URL will be used“ https://api.openai.com/v 1/";
OpenAiService openAiService = new OpenAiService(API_KEY);
//2. Use custom base Url with default configuration of service
OpenAiService openAiService1 = new OpenAiService(API_KEY, BASE_URL);
//3.Custom expiration time
OpenAiService openAiService2 = new OpenAiService(API_KEY, Duration.ofSeconds(10));
//4. More flexible customization
//4.1. customize okHttpClient
OkHttpClient client = new OkHttpClient.Builder()
//connection pool
.connectionPool(new ConnectionPool(Runtime.getRuntime().availableProcessors() * 2, 30, TimeUnit.SECONDS))
//Customized interceptors, such as retry interceptors, log interceptors, load balancing interceptors, etc
// .addInterceptor(new RetryInterceptor())
// .addInterceptor(new LogInterceptor())
// .addInterceptor(new LoadBalanceInterceptor())
// .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxyHost", 8080)))
.connectTimeout(2, TimeUnit.SECONDS)
.writeTimeout(3, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))
.build();
//4.2 Customizing Retorfit Configuration
Retrofit retrofit = OpenAiService.defaultRetrofit(client, OpenAiService.defaultObjectMapper(), BASE_URL);
OpenAiApi openAiApi = retrofit.create(OpenAiApi.class);
OpenAiService openAiService3 = new OpenAiService(openAiApi);
```
stream chat
```java
static void streamChat() {
//api-key get from environment variable OPENAI_API_KEY
OpenAiService service = new OpenAiService(Duration.ofSeconds(30));
List messages = new ArrayList<>();
ChatMessage systemMessage = new SystemMessage("You are a cute cat and will speak as such.");
messages.add(systemMessage);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model("gpt-4o-mini")
.messages(messages)
.n(1)
.maxTokens(50)
.build();
service.streamChatCompletion(chatCompletionRequest).blockingForEach(System.out::println);
}
```
</details>
Tools
This library supports both the outdated method of function calls and the current tool-based approach.
First, we define a function object. The definition of a function object is flexible; you can use POJO to define it (
automatically serialized by JSON schema) or use methods like `map` and `FunctionDefinition` to define it. You can refer
to the code in the example package. Here, we define a weather query function object:
```java
public class Weather {
@JsonPropertyDescription("City and state, for example: León, Guanajuato")
public String location;
@JsonPropertyDescription("The temperature unit, can be 'celsius' or 'fahrenheit'")
@JsonProperty(required = true)
public WeatherUnit unit;
}
public enum WeatherUnit {
CELSIUS, FAHRENHEIT;
}
public static class WeatherResponse {
public String location;
public WeatherUnit unit;
public int temperature;
public String description;
// constructor
}
```
Next, we declare the function and associate it with an executor, here simulating an API response:
```java
//First, a function to fetch the weather
public static FunctionDefinition weatherFunction() {
return FunctionDefinition.builder()
.name("get_weather")
.description("Get the current weather in a given location")
.parametersDefinitionByClass(Weather.class)
//The executor here is a lambda expression that accepts a Weather object and returns a Weather Response object
.executor(w -> new WeatherResponse(w.location, w.unit, 25, "sunny"))
.build();
}
```
Then, the service is used for a chatCompletion request, incorporating the tool:
```java
static void toolChat() {
OpenAiService service = new OpenAiService(Duration.ofSeconds(30));
final ChatTool tool = new ChatTool(ToolUtil.weatherFunction());
final List messages = new ArrayList<>();
final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant.");
final ChatMessage userMessage = new UserMessage("What is the weather in BeiJin?");
messages.add(systemMessage);
messages.add(userMessage);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest.builder()
.model("gpt-4o-mini")
.messages(messages)
//Tools is a list; multiple tools can be included
.tools(Collections.singletonList(tool))
.toolChoice(ToolChoice.AUTO)
.n(1)
.maxTokens(100)
.build();
//Request is sent
ChatCompletionChoice choice = service.createChatCompletion(chatCompletionRequest).getChoices().get(0);
AssistantMessage toolCallMsg = choice.getMessage();
ChatToolCall toolCall = toolCallMsg.getToolCalls().get(0);
System.out.println(toolCall.getFunction());
messages.add(toolCallMsg);
messages.add(new ToolMessage("the weather is fine today.", toolCall.getId()));
//submit tool call
ChatCompletionRequest toolCallRequest = ChatCompletionRequest.builder()
.model("gpt-4o-mini")
.messages(messages)
.n(1)
.maxTokens(100)
.build();
ChatCompletionChoice toolCallChoice = service.createChatCompletion(toolCallRequest).getChoices().get(0);
System.out.println(toolCallChoice.getMessage().getContent());
}
```
</details>
stream chat with tool call (support Concurrent tool call)
```java
void streamChatMultipleToolCalls() {
final List functions = Arrays.asList(
//1. weather query
FunctionDefinition.builder()
.name("get_weather")
.description("Get the current weather in a given location")
.parametersDefinitionByClass(ToolUtil.Weather.class)
.executor( w -> {
switch (w.location) {
case "tokyo":
return new ToolUtil.WeatherResponse(w.location, w.unit, 10, "cloudy");
case "san francisco":
return new ToolUtil.WeatherResponse(w.location, w.unit, 72, "sunny");
case "paris":
return new ToolUtil.WeatherResponse(w.location, w.unit, 22, "sunny");
default:
return new ToolUtil.WeatherResponse(w.location, w.unit, 0, "unknown");
}
}).build(),
//2. city query
FunctionDefinition.builder().name("getCities").description("Get a list of cities by time").parametersDefinitionByClass(ToolUtil.City.class).executor(v -> Arrays.asList("tokyo", "paris")).build()
);
final FunctionExecutorManager toolExecutor = new FunctionExecutorManager(functions);
List tools = new ArrayList<>();
tools.add(new ChatTool(functions.get(0)));
tools.add(new ChatTool(functions.get(1)));
final List messages = new ArrayList<>();
final ChatMessage systemMessage = new SystemMessage("You are a helpful assistant.");
final ChatMessage userMessage = new UserMessage("What is the weather like in cities with weather on 2022-12-01 ?");
messages.add(systemMessage);
messages.add(userMessage);
ChatCompletionRequest chatCompletionRequest = ChatCompletionRequest
.builder()
.model("gpt-4o-mini")
.messages(messages)
.tools(tools)
.toolChoice(ToolChoice.AUTO)
.n(1)
.maxTokens(200)
.build();
AssistantMessage accumulatedMessage = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest))
.blockingLast()
.getAccumulatedMessage();
List toolCalls = accumulatedMessage.getToolCalls();
ChatToolCall toolCall = toolCalls.get(0);
ChatFunctionCall function = toolCall.getFunction();
JsonNode jsonNode = toolExecutor.executeAndConvertToJson(function.getName(), function.getArguments());
ToolMessage toolMessage = toolExecutor.executeAndConvertToChatMessage(function.getName(),function.getArguments(), toolCall.getId());
messages.add(accumulatedMessage);
messages.add(toolMessage);
ChatCompletionRequest chatCompletionRequest2 = ChatCompletionRequest
.builder()
.model("gpt-4o-mini")
.messages(messages)
.tools(tools)
.toolChoice(ToolChoice.AUTO)
.n(1)
.maxTokens(100)
.logitBias(new HashMap<>())
.build();
// ChatCompletionChoice choice2 = service.createChatCompletion(chatCompletionRequest2).getChoices().get(0);
AssistantMessage accumulatedMessage2 = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest2))
.blockingLast()
.getAccumulatedMessage();
messages.add(accumulatedMessage2);
for (ChatToolCall weatherToolCall : accumulatedMessage2.getToolCalls()) {
ChatFunctionCall call2 = weatherToolCall.getFunction();
Object itemResult = toolExecutor.execute(call2.getName(), call2.getArguments());
messages.add(toolExecutor.executeAndConvertToChatMessage(call2.getName(),call2.getArguments(), weatherToolCall.getId()));
}
ChatCompletionRequest chatCompletionRequest3 = ChatCompletionRequest
.builder()
.model("gpt-4o-mini")
.messages(messages)
.tools(tools)
.toolChoice(ToolChoice.AUTO)
.n(1)
.maxTokens(100)
.logitBias(new HashMap<>())
.build();
AssistantMessage accumulatedMessage3 = service.mapStreamToAccumulator(service.streamChatCompletion(chatCompletionRequest3))
.blockingLast()
.getAccumulatedMessage();
}
```
</details>
Token usage calculate
```java
public static void main(String... args) {
List messages = new ArrayList<>();
messages.add(new SystemMessage("Hello OpenAI 1."));
messages.add(new SystemMessage("Hello OpenAI 2. "));
messages.add(new UserMessage(Arrays.asList(new ImageContent("text", "", new ImageUrl("dddd")))));
int tokens_1 = TikTokensUtil.tokens(TikTokensUtil.ModelEnum.GPT_3_5_TURBO.getName(), messages);
int tokens_2 = TikTokensUtil.tokens(TikTokensUtil.ModelEnum.GPT_3_5_TURBO.getName(), "Hello OpenAI 1.");
int tokens_3 = TikTokensUtil.tokens(TikTokensUtil.ModelEnum.GPT_4_TURBO.getName(), messages);
}
```
</details>
Assistant Tool Call
```java
static void assistantToolCall() {
OpenAiService service = new OpenAiService();
FunctionExecutorManager executor = new FunctionExecutorManager(Collections.singletonList(ToolUtil.weatherFunction()));
AssistantRequest assistantRequest = AssistantRequest.builder()
.model("gpt-4o-mini").name("weather assistant")
.instructions("You are a weather assistant responsible for calling the weather API to return weather information based on the location entered by the user")
.tools(Collections.singletonList(new FunctionTool(ToolUtil.weatherFunction())))
.temperature(0D)
.build();
Assistant assistant = service.createAssistant(assistantRequest);
String assistantId = assistant.getId();
ThreadRequest threadRequest = ThreadRequest.builder().build();
Thread thread = service.createThread(threadRequest);
String threadId = thread.getId();
MessageRequest messageRequest = MessageRequest.builder()
.content("What's the weather of Xiamen?")
.build();
//add message to thread
service.createMessage(threadId, messageRequest);
RunCreateRequest runCreateRequest = RunCreateRequest.builder().assistantId(assistantId).build();
Run run = service.createRun(threadId, runCreateRequest);
Run retrievedRun = service.retrieveRun(threadId, run.getId());
while (!(retrievedRun.getStatus().equals("completed"))
&& !(retrievedRun.getStatus().equals("failed"))
&& !(retrievedRun.getStatus().equals("expired"))
&& !(retrievedRun.getStatus().equals("incomplete"))
&& !(retrievedRun.getStatus().equals("requires_action"))) {
retrievedRun = service.retrieveRun(threadId, run.getId());
}
System.out.println(retrievedRun);
RequiredAction requiredAction = retrievedRun.getRequiredAction();
List toolCalls = requiredAction.getSubmitToolOutputs().getToolCalls();
ToolCall toolCall = toolCalls.get(0);
ToolCallFunction function = toolCall.getFunction();
String toolCallId = toolCall.getId();
SubmitToolOutputsRequest submitToolOutputsRequest = SubmitToolOutputsRequest.ofSingletonToolOutput(toolCallId, executor.executeAndConvertToJson(function.getName(),function.getArguments()).toPrettyString());
retrievedRun = service.submitToolOutputs(threadId, retrievedRun.getId(), submitToolOutputsRequest);
while (!(retrievedRun.getStatus().equals("completed"))
&& !(retrievedRun.getStatus().equals("failed"))
&& !(retrievedRun.getStatus().equals("expired"))
&& !(retrievedRun.getStatus().equals("incomplete"))
&& !(retrievedRun.getStatus().equals("requires_action"))) {
retrievedRun = service.retrieveRun(threadId, run.getId());
}
System.out.println(retrievedRun);
OpenAiResponse response = service.listMessages(threadId, MessageListSearchParameters.builder()
.runId(retrievedRun.getId()).build());
List messages = response.getData();
messages.forEach(message -> {
System.out.println(message.getContent());
});
}
```
</details>
Assistant Stream
```java
static void assistantStream() throws JsonProcessingException {
OpenAiService service = new OpenAiService();
String assistantId;
String threadId;
AssistantRequest assistantRequest = AssistantRequest.builder()
.model("gpt-4o-mini").name("weather assistant")
.instructions("You are a weather assistant responsible for calling the weather API to return weather information based on the location entered by the user")
.tools(Collections.singletonList(new FunctionTool(ToolUtil.weatherFunction())))
.temperature(0D)
.build();
Assistant assistant = service.createAssistant(assistantRequest);
assistantId = assistant.getId();
//general response
Flowable threadAndRunStream = service.createThreadAndRunStream(
CreateThreadAndRunRequest.builder()
.assistantId(assistantId)
//no tools are used here
.toolChoice(ToolChoice.NONE)
.thread(ThreadRequest.builder()
.messages(Collections.singletonList(
MessageRequest.builder()
.content("hello what can you help me with?")
.build()
))
.build())
.build()
);
ObjectMapper objectMapper = new ObjectMapper();
TestSubscriber subscriber1 = new TestSubscriber<>();
threadAndRunStream
.doOnNext(System.out::println)
.blockingSubscribe(subscriber1);
Optional runStepCompletion = subscriber1.values().stream().filter(item -> item.getEvent().equals(StreamEvent.THREAD_RUN_STEP_COMPLETED)).findFirst();
RunStep runStep = objectMapper.readValue(runStepCompletion.get().getData(), RunStep.class);
System.out.println(runStep.getStepDetails());
// Function call stream
threadId = runStep.getThreadId();
service.createMessage(threadId, MessageRequest.builder().content("Please help me check the weather in Beijing").build());
Flowable getWeatherFlowable = service.createRunStream(threadId, RunCreateRequest.builder()
//Force the use of the get weather function here
.assistantId(assistantId)
.toolChoice(new ToolChoice(new Function("get_weather")))
.build()
);
TestSubscriber subscriber2 = new TestSubscriber<>();
getWeatherFlowable
.doOnNext(System.out::println)
.blockingSubscribe(subscriber2);
AssistantSSE requireActionSse = subscriber2.values().get(subscriber2.values().size() - 2);
Run requireActionRun = objectMapper.readValue(requireActionSse.getData(), Run.class);
RequiredAction requiredAction = requireActionRun.getRequiredAction();
List toolCalls = requiredAction.getSubmitToolOutputs().getToolCalls();
ToolCall toolCall = toolCalls.get(0);
String callId = toolCall.getId();
System.out.println(toolCall.getFunction());
// Submit function call results
Flowable toolCallResponseFlowable = service.submitToolOutputsStream(threadId, requireActionRun.getId(), SubmitToolOutputsRequest.ofSingletonToolOutput(callId, "The weather in Beijing is sunny"));
TestSubscriber subscriber3 = new TestSubscriber<>();
toolCallResponseFlowable
.doOnNext(System.out::println)
.blockingSubscribe(subscriber3);
Optional msgSse = subscriber3.values().stream().filter(item -> StreamEvent.THREAD_MESSAGE_COMPLETED.equals(item.getEvent())).findFirst();
Message message = objectMapper.readValue(msgSse.get().getData(), Message.class);
String responseContent = message.getContent().get(0).getText().getValue();
System.out.println(responseContent);
}
```
</details>
Assistant Stream Manager
By using the `AssistantEventHandler` class and the `AssistantStreamManager` class, it is easier to manage the streaming
calls of Assistant `AssistantEventHandler` contains all Assistant stream event callback hooks, and you can implement
different events as needed:
```java
/**
* You can implement various event callbacks for Assistant Event Handlers according to your own needs, making it convenient for you to handle various events related to Assistant
*/
private static class LogHandler implements AssistantEventHandler {
@Override
public void onEvent(AssistantSSE sse) {
//every event will call this method
}
@Override
public void onRunCreated(Run run) {
System.out.println("start run: " + run.getId());
}
@Override
public void onEnd() {
System.out.println("stream end");
}
@Override
public void onMessageDelta(MessageDelta messageDelta) {
System.out.println(messageDelta.getDelta().getContent().get(0).getText());
}
@Override
public void onMessageCompleted(Message message) {
System.out.println("message completed");
}
@Override
public void onMessageInComplete(Message message) {
System.out.println("message in complete");
}
@Override
public void onError(Throwable error) {
System.out.println("error:" + error.getMessage());
}
}
```
`AssistantStreamManager` arranges and manages various events in the stream, supporting synchronous/asynchronous
retrieval of content from the stream,
which can be obtained through the manager. Below is a usage example, for more examples, please refer
to `AssistantStreamManagerTest.java`.
```java
static void streamTest() {
OpenAiService service = new OpenAiService();
//1. create assistant
AssistantRequest assistantRequest = AssistantRequest.builder()
.model("gpt-4o-mini").name("weather assistant")
.instructions("You are a weather assistant responsible for calling the weather API to return weather information based on the location entered by the user")
.tools(Collections.singletonList(new FunctionTool(ToolUtil.weatherFunction())))
.temperature(0D)
.build();
Assistant assistant = service.createAssistant(assistantRequest);
String assistantId = assistant.getId();
System.out.println("assistantId:" + assistantId);
ThreadRequest threadRequest = ThreadRequest.builder()
.build();
Thread thread = service.createThread(threadRequest);
String threadId = thread.getId();
System.out.println("threadId:" + threadId);
MessageRequest messageRequest = MessageRequest.builder()
.content("What can you help me with?")
.build();
service.createMessage(threadId, messageRequest);
RunCreateRequest runCreateRequest = RunCreateRequest.builder()
.assistantId(assistantId)
.toolChoice(ToolChoice.AUTO)
.build();
//blocking
// AssistantStreamManager blockedManagere = AssistantStreamManager.syncStart(service.createRunStream(threadId, runCreateRequest), new LogHandler());
//async
AssistantStreamManager streamManager = AssistantStreamManager.start(service.createRunStream(threadId, runCreateRequest), new LogHandler());
//Other operations can be performed here...
boolean completed = streamManager.isCompleted();
// you can shut down the streamManager if you want to stop the stream
streamManager.shutDown();
//waiting for completion
streamManager.waitForCompletion();
// all of flowable events
List eventMsgsHolder = streamManager.getEventMsgsHolder();
Optional currentRun = streamManager.getCurrentRun();
// get the accumulated message
streamManager.getAccumulatedMsg().ifPresent(msg -> {
System.out.println("accumulatedMsg:" + msg);
});
service.deleteAssistant(assistantId);
service.deleteThread(threadId);
}
```
</details>
- [Assistant iamge chat](/openai4j/service/src/test/java/com/theokanning/openai/service/assistants/AssistantImageTest.java#L65-L90)
# FAQs
Is it possible to customize the OpenAI URL or use a proxy URL?
Yes, you can specify a URL when constructing OpenAiService, which will serve as the base URL.But we recommend using the
environment variable OPENAI_API_BASE_URL and OPENAI_API_KEY to load the OpenAI API key.
Why am I experiencing connection timeouts?
Ensure your network is stable and your OpenAI server is accessible. If you face network instability, consider increasing the timeout duration.
# Contributing to OpenAi4J
We welcome contributions from the community and are always looking for ways to make our project better. If you're
interested in helping improve OpenAi4J, here are some ways you can contribute:
## Reporting Issues
Please use the GitHub Issues page to report issues. Be as specific as possible about how to reproduce the problem you
are having, and include details like your operating system, Java version, and any relevant stack traces.
## Submitting Pull Requests
1. Fork the repository and create your branch from `main`.
2. If you've added code that should be tested, add tests.
3. Ensure your code lints and adheres to the existing style guidelines.
4. Write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should
have detailed descriptions.
5. Complete the pull request form, linking to any issues your PR addresses.
## Support Us
We hope you find this library useful! If you do, consider giving it a star on library❤️❤️❤️. Your support helps us keep
the project alive and continuously improve it. Stay tuned for updates and feel free to contribute to the project or
suggest new features.
Thank you for supporting OpenAi4J!
# Contributors
# License
Released under the MIT License