├── src └── main │ ├── java │ └── it │ │ └── gdorsi │ │ └── openaiclient │ │ ├── dto │ │ ├── ThreadDTO.java │ │ ├── MessageDTO.java │ │ ├── AssistantRequestDTO.java │ │ ├── RunRequestDTO.java │ │ ├── ThreadResponseDTO.java │ │ ├── MessagesListResponseDTO.java │ │ ├── AssistantResponseDTO.java │ │ ├── MessageResponseDTO.java │ │ └── RunResponseDTO.java │ │ ├── model │ │ └── GPTModel.java │ │ ├── Main.java │ │ └── AssistantAIClient.java │ └── resources │ └── application.properties ├── .gitignore ├── pom.xml └── README.md /src/main/java/it/gdorsi/openaiclient/dto/ThreadDTO.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public record ThreadDTO() {} 7 | -------------------------------------------------------------------------------- /src/main/java/it/gdorsi/openaiclient/dto/MessageDTO.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public record MessageDTO(String role, String content) {} 7 | -------------------------------------------------------------------------------- /src/main/java/it/gdorsi/openaiclient/dto/AssistantRequestDTO.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | 5 | @JsonIgnoreProperties(ignoreUnknown = true) 6 | public record AssistantRequestDTO(String model, String instructions) {} 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | openai.api.key=//TODO: make sure that is never comitted to repo... 2 | openai.threads.url=https://api.openai.com/v1/threads 3 | openai.assistants.url=https://api.openai.com/v1/assistants 4 | openai.assistant.instructions="You are an expert in geography, be helpful and concise." 5 | openai.user.prompt="What is the capital of Italy?" -------------------------------------------------------------------------------- /src/main/java/it/gdorsi/openaiclient/dto/RunRequestDTO.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | @JsonIgnoreProperties(ignoreUnknown = true) 7 | public record RunRequestDTO ( 8 | @JsonProperty("assistant_id") 9 | String assistantId) {} 10 | -------------------------------------------------------------------------------- /src/main/java/it/gdorsi/openaiclient/dto/ThreadResponseDTO.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.Map; 7 | 8 | @JsonIgnoreProperties(ignoreUnknown = true) 9 | public record ThreadResponseDTO( 10 | String id, 11 | String object, 12 | @JsonProperty("created_at") 13 | long createdAt, 14 | Map metadata) {} -------------------------------------------------------------------------------- /src/main/java/it/gdorsi/openaiclient/model/GPTModel.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient.model; 2 | 3 | public enum GPTModel { 4 | @SuppressWarnings("unused") GPT3_5_TURBO("gpt-3.5-turbo"), //balance between cost and efficiency 5 | @SuppressWarnings("unused") GPT4_1106_PREVIEW("gpt-4-1106-preview"); //best capabilities to date, but almost 15x more expensive than 3.5-turbo 6 | 7 | private final String modelName; 8 | 9 | GPTModel(String modelName) { 10 | this.modelName = modelName; 11 | } 12 | 13 | public String getName() { 14 | return modelName; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/it/gdorsi/openaiclient/dto/MessagesListResponseDTO.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.List; 7 | 8 | @JsonIgnoreProperties(ignoreUnknown = true) 9 | public record MessagesListResponseDTO( 10 | String object, 11 | List data, 12 | @JsonProperty("first_id") 13 | String firstId, 14 | @JsonProperty("last_id") 15 | String lastId, 16 | @JsonProperty("has_more") 17 | 18 | boolean hasMore) {} 19 | -------------------------------------------------------------------------------- /src/main/java/it/gdorsi/openaiclient/dto/AssistantResponseDTO.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | @JsonIgnoreProperties(ignoreUnknown = true) 10 | public record AssistantResponseDTO( 11 | String id, 12 | String object, 13 | @JsonProperty("created_at") 14 | long createdAt, 15 | String name, 16 | String description, 17 | String model, 18 | String instructions, 19 | List tools, 20 | @JsonProperty("file_ids") 21 | List fileIds, 22 | Map metadata) {} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled java files 2 | *.class 3 | 4 | # Log files 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Package files 11 | *.jar 12 | *.war 13 | *.ear 14 | *.zip 15 | *.tar.gz 16 | *.rar 17 | 18 | # Virtual machine crash logs 19 | hs_err_pid* 20 | 21 | # Maven 22 | target/ 23 | pom.xml.tag 24 | pom.xml.releaseBackup 25 | pom.xml.versionsBackup 26 | pom.xml.next 27 | release.properties 28 | dependency-reduced-pom.xml 29 | buildNumber.properties 30 | .mvn/wrapper/maven-wrapper.jar 31 | .mvn/wrapper/maven-wrapper.properties 32 | !**/src/main/**/maven-wrapper.jar 33 | !**/src/main/**/maven-wrapper.properties 34 | 35 | # IDEs 36 | .idea/ 37 | *.iml 38 | *.iws 39 | *.ipr 40 | .classpath 41 | .project 42 | .settings/ 43 | nbactions.xml 44 | nb-configuration.xml 45 | nbproject/private/ 46 | build/ 47 | nbbuild/ 48 | dist/ 49 | nbdist/ 50 | 51 | # OS files 52 | .DS_Store 53 | Thumbs.db 54 | -------------------------------------------------------------------------------- /src/main/java/it/gdorsi/openaiclient/dto/MessageResponseDTO.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | @JsonIgnoreProperties(ignoreUnknown = true) 10 | public record MessageResponseDTO( 11 | String id, 12 | String object, 13 | @JsonProperty("created_at") 14 | long createdAt, 15 | @JsonProperty("thread_id") 16 | String threadId, 17 | String role, 18 | List content, 19 | @JsonProperty("file_ids") 20 | List fileIds, 21 | @JsonProperty("assistant_id") 22 | String assistantId, 23 | @JsonProperty("run_id") 24 | String runId, 25 | Map metadata) { 26 | 27 | public static record Content( 28 | String type, 29 | Text text) { 30 | 31 | public static record Text( 32 | String value, 33 | List annotations) {} 34 | } 35 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | it.gdorsi 8 | assistantapi-client 9 | 0.1 10 | 11 | 12 | 13 | 14 | com.fasterxml.jackson.core 15 | jackson-databind 16 | 2.13.5 17 | 18 | 19 | 20 | 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-compiler-plugin 25 | 26 | 17 27 | 17 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/it/gdorsi/openaiclient/dto/RunResponseDTO.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient.dto; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 | import com.fasterxml.jackson.annotation.JsonProperty; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | @JsonIgnoreProperties(ignoreUnknown = true) 11 | public record RunResponseDTO( 12 | String id, 13 | String object, 14 | @JsonProperty("created_at") 15 | long createdAt, 16 | @JsonProperty("assistant_id") 17 | String assistantId, 18 | @JsonProperty("thread_id") 19 | String threadId, 20 | 21 | String status, 22 | @JsonProperty("started_at") 23 | Long startedAt, // Using Long to handle null values 24 | @JsonProperty("expires_at") 25 | Long expiresAt, 26 | @JsonProperty("cancelled_at") 27 | Long cancelledAt, 28 | @JsonProperty("failed_at") 29 | Long failedAt, 30 | @JsonProperty("completed_at") 31 | Long completedAt, 32 | @JsonProperty("last_error") 33 | String lastError, 34 | String model, 35 | String instructions, 36 | List tools, 37 | @JsonProperty("file_ids") 38 | List fileIds, 39 | Map metadata) {} 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # OpenAI Assistant API for Java 3 | 4 | This project offers a lightweight and straightforward Java client for interacting with the OpenAI Assistant API. It's specifically designed to facilitate the integration of Generative AI technology into Java applications. 5 | 6 | ## Important considerations 7 | 8 | This repository uses Assistant API to intelligently address user problems. This process may involve multiple calls to the OpenAI API. Please be aware that in the event of an error during processing a request, there is a risk of rapidly escalating API calls, which could dramatically increase usage costs. This is especially important if you plan to use it as an unsupervised service performing tasks in the background. This note is relevant for any usage of Assistant API, not just this repository. Always use Assistant API with caution. 9 | 10 | ## Features 11 | 12 | - Create an assistant. 13 | - Create a thread. 14 | - Send messages in a thread. 15 | - Fetch messages from a thread. 16 | - Handle responses for assistants, threads, and messages. 17 | 18 | For detailed information on the OpenAI API and its capabilities, 19 | visit OpenAI's official documentation. 20 | https://platform.openai.com/docs/assistants/overview 21 | 22 | ## Requirements 23 | 24 | - Java 17 (for now) 25 | - Maven 26 | 27 | ## Setup and Usage 28 | 29 | 1. Clone the repository. 30 | 2. Add your OpenAI API key to `src/main/resources/application.properties`. 31 | 3. Build the project using Maven: `mvn clean install`. 32 | 4. Run the `Main.java` class to see the client in action. 33 | 5. 34 | 35 | ## Contributing 36 | 37 | Contributions to the project are welcome! Feel free to fork the repository, make changes, and submit pull requests. 38 | 39 | ## License 40 | 41 | This project is open-sourced under the MIT License. 42 | -------------------------------------------------------------------------------- /src/main/java/it/gdorsi/openaiclient/Main.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient; 2 | 3 | import it.gdorsi.openaiclient.dto.*; 4 | 5 | import java.io.InputStream; 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.Properties; 9 | 10 | import static java.lang.Thread.sleep; 11 | 12 | public class Main { 13 | public static void main(String[] args) { 14 | 15 | //LOAD YOUR API KEY 16 | Properties properties = new Properties(); 17 | long DELAY = 3;// wait for a bit between status checks 18 | 19 | try (InputStream input = Main.class.getClassLoader().getResourceAsStream("application.properties")) { 20 | if (input == null) { 21 | System.out.println("Sorry, unable to find application.properties"); 22 | return; 23 | } 24 | properties.load(input); 25 | 26 | AssistantAIClient client = new AssistantAIClient(properties); 27 | AssistantResponseDTO assistant = client.createAssistant(properties.getProperty("openai.assistant.instructions")); 28 | ThreadResponseDTO thread = client.createThread(); 29 | client.sendMessage(thread.id(), "user", properties.getProperty("openai.user.prompt")); 30 | RunResponseDTO run = client.runMessage(thread.id(), assistant.id()); 31 | 32 | waitUntilRunIsFinished(client, thread, run, DELAY); 33 | 34 | MessagesListResponseDTO allResponses = client.getMessages(thread.id()); 35 | System.out.println("These are all the messages and you will be billed by OpenAI for every single one of them:"); 36 | log(allResponses); 37 | 38 | } catch (Exception ex) { 39 | ex.printStackTrace(); 40 | } 41 | } 42 | 43 | private static void waitUntilRunIsFinished(AssistantAIClient client, ThreadResponseDTO thread, RunResponseDTO run, long DELAY) throws InterruptedException { 44 | while (!isRunDone(client, thread.id(), run.id())) { 45 | superviseWorkInProgress(client, thread); 46 | sleep(DELAY * 1000); 47 | } 48 | } 49 | 50 | private static void superviseWorkInProgress(AssistantAIClient client, ThreadResponseDTO thread) { 51 | try { 52 | System.out.println("Checking messages to supervise assistant's work"); 53 | MessagesListResponseDTO messages = client.getMessages(thread.id()); 54 | log(messages); 55 | } catch (Exception e) { 56 | throw new RuntimeException(e); 57 | } 58 | } 59 | 60 | private static void log(MessagesListResponseDTO messages) { 61 | messages.data().forEach(System.out::println); 62 | } 63 | 64 | private static boolean isRunDone(AssistantAIClient client, String threadId, String runId) { 65 | RunResponseDTO status; 66 | try { 67 | status = client.getRunStatus(threadId, runId); 68 | System.out.println("Status of your run is currently " + status); 69 | return isRunStateFinal(status); 70 | } catch (Exception e) { 71 | System.err.println("Failed to get run state, will retry..." + e); 72 | e.printStackTrace(); 73 | return false; 74 | } 75 | } 76 | 77 | private static boolean isRunStateFinal(RunResponseDTO runResponseDTO) { 78 | List finalStates = List.of("cancelled", "failed", "completed", "expired"); //I consider these states as final 79 | String runStatus = Optional.of(runResponseDTO).map(RunResponseDTO::status).orElse("unknown").toLowerCase(); 80 | return finalStates.contains(runStatus); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/it/gdorsi/openaiclient/AssistantAIClient.java: -------------------------------------------------------------------------------- 1 | package it.gdorsi.openaiclient; 2 | 3 | import java.net.http.HttpClient; 4 | import java.net.http.HttpRequest; 5 | import java.net.http.HttpResponse; 6 | import java.net.URI; 7 | import java.util.Properties; 8 | 9 | import com.fasterxml.jackson.databind.ObjectMapper; 10 | import it.gdorsi.openaiclient.dto.*; 11 | 12 | import static it.gdorsi.openaiclient.model.GPTModel.GPT3_5_TURBO; 13 | 14 | public class AssistantAIClient { 15 | private final String assistantsUrl; 16 | private final String threadsUrl; 17 | private final String apiKey; 18 | private final HttpClient httpClient; 19 | private final ObjectMapper objectMapper; 20 | private static final String SLASH = "/"; 21 | 22 | public AssistantAIClient(Properties properties) { 23 | this.apiKey = properties.getProperty("openai.api.key"); 24 | this.assistantsUrl = properties.getProperty("openai.assistants.url"); 25 | this.threadsUrl = properties.getProperty("openai.threads.url"); 26 | this.httpClient = HttpClient.newHttpClient(); 27 | this.objectMapper = new ObjectMapper(); 28 | } 29 | 30 | private String post(String url, Object body) throws Exception { 31 | String jsonBody = objectMapper.writeValueAsString(body); 32 | 33 | HttpRequest request = getRequestWithHeaders(url).POST(HttpRequest.BodyPublishers.ofString(jsonBody)).build(); 34 | 35 | HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); 36 | System.out.println("POST " + url + " status " + response.statusCode()); 37 | return response.body(); 38 | } 39 | 40 | public AssistantResponseDTO createAssistant(String initialPrompt) throws Exception { 41 | AssistantRequestDTO dto = new AssistantRequestDTO(GPT3_5_TURBO.getName(), initialPrompt); 42 | String response = post(assistantsUrl, dto); 43 | return objectMapper.readValue(response, AssistantResponseDTO.class); 44 | } 45 | 46 | public ThreadResponseDTO createThread() throws Exception { 47 | String response = post(threadsUrl, ""); 48 | return objectMapper.readValue(response, ThreadResponseDTO.class); 49 | } 50 | 51 | public void sendMessage(String threadId, String role, String content) throws Exception { 52 | String url = threadsUrl + SLASH + threadId + "/messages"; 53 | MessageDTO dto = new MessageDTO(role, content); 54 | 55 | String response = post(url, dto); 56 | objectMapper.readValue(response, MessageResponseDTO.class); 57 | } 58 | 59 | public RunResponseDTO runMessage(String threadId, String assistantId) throws Exception { 60 | String url = threadsUrl + SLASH + threadId + "/runs"; 61 | RunRequestDTO dto = new RunRequestDTO(assistantId); 62 | 63 | String response = post(url, dto); 64 | return objectMapper.readValue(response, RunResponseDTO.class); 65 | } 66 | 67 | public RunResponseDTO getRunStatus(String threadId, String runId) throws Exception { 68 | String url = threadsUrl + SLASH + threadId + "/runs/" + runId; 69 | 70 | HttpRequest request = getRequestWithHeaders(url).GET().build(); 71 | HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); 72 | 73 | return objectMapper.readValue(response.body(), RunResponseDTO.class); 74 | } 75 | 76 | private HttpRequest.Builder getRequestWithHeaders(String url) { 77 | return HttpRequest.newBuilder() 78 | .uri(URI.create(url)) 79 | .header("Authorization", "Bearer " + apiKey) 80 | .header("OpenAI-Beta", "assistants=v1") 81 | .header("Content-Type", "application/json"); 82 | } 83 | 84 | 85 | public MessagesListResponseDTO getMessages(String threadId) throws Exception { 86 | String url = threadsUrl + SLASH + threadId + "/messages"; 87 | 88 | HttpRequest request = getRequestWithHeaders(url).GET().build(); 89 | System.out.println("Threads link for manual verification of requests: " + url); 90 | HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); 91 | 92 | return objectMapper.readValue(response.body(), MessagesListResponseDTO.class); 93 | // Assuming the response is a JSON array of MessageResponseDTO 94 | //return objectMapper.readValue(response.body(), new TypeReference>() {}); 95 | } 96 | } 97 | --------------------------------------------------------------------------------