Skip to content

Commit

Permalink
Add support for Upload API
Browse files Browse the repository at this point in the history
  • Loading branch information
sashirestela committed Jul 20, 2024
1 parent 5db4c2c commit ac55a9b
Show file tree
Hide file tree
Showing 17 changed files with 442 additions and 12 deletions.
20 changes: 11 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ A Java library to use the OpenAI Api in the simplest possible way.
- [Chat Completion with Streaming Example](#chat-completion-with-streaming-example)
- [Chat Completion with Functions Example](#chat-completion-with-functions-example)
- [Chat Completion with Vision Example](#chat-completion-with-vision-example)
- [Chat Completion Conversation Example](#chat-completion-conversation-example) **New**
- [Assistant v2 Conversation Example](#assistant-v2-conversation-example) **New**
- [Chat Completion Conversation Example](#chat-completion-conversation-example) 🍀
- [Assistant v2 Conversation Example](#assistant-v2-conversation-example) 🍀
- [Support for Additional OpenAI Providers](#-support-for-additional-openai-providers)
- [Azure OpenAI](#azure-openai)
- [Anyscale](#anyscale)
Expand All @@ -38,7 +38,7 @@ Simple-OpenAI uses the [CleverClient](https://github.com/sashirestela/cleverclie


## ✅ Supported Services
Simple-OpenAI seeks to stay up to date with the most recent changes in OpenAI. Currently, it supports all existing features until [Jun 6th, 2024](https://platform.openai.com/docs/changelog/jun-6th-2024) and will continue to update with future changes.
Simple-OpenAI seeks to stay up to date with the most recent changes in OpenAI. Currently, it supports all existing features until [Jul 18th, 2024](https://platform.openai.com/docs/changelog/jul-18th-2024) and will continue to update with future changes.

Full support for all of the OpenAI services:

Expand All @@ -52,6 +52,7 @@ Full support for all of the OpenAI services:
* Image (Generate, Edit, Variation)
* Models (List)
* Moderation (Check Harmful Text)
* Upload (Upload Large Files in Parts) <span style="color:red">**NEW**</span>
* Assistants Beta v2 (Assistants, Threads, Messages, Runs, Steps, Vector Stores, Streaming, Function Calling, Vision)

![OpenAI Services](media/openai_services.png)
Expand Down Expand Up @@ -172,7 +173,7 @@ imageResponse.stream().forEach(img -> System.out.println("\n" + img.getUrl()));
Example to call the Chat Completion service to ask a question and wait for a full answer. We are printing out it in the console:
```java
var chatRequest = ChatRequest.builder()
.model("gpt-3.5-turbo-1106")
.model("gpt-4o-mini")
.message(SystemMessage.of("You are an expert in AI."))
.message(UserMessage.of("Write a technical article about ChatGPT, no more than 100 words."))
.temperature(0.0)
Expand All @@ -186,7 +187,7 @@ System.out.println(chatResponse.firstContent());
Example to call the Chat Completion service to ask a question and wait for an answer in partial message deltas. We are printing out it in the console as soon as each delta is arriving:
```java
var chatRequest = ChatRequest.builder()
.model("gpt-3.5-turbo-1106")
.model("gpt-4o-mini")
.message(SystemMessage.of("You are an expert in AI."))
.message(UserMessage.of("Write a technical article about ChatGPT, no more than 100 words."))
.temperature(0.0)
Expand Down Expand Up @@ -225,7 +226,7 @@ public void demoCallChatWithFunctions() {
var messages = new ArrayList<ChatMessage>();
messages.add(UserMessage.of("What is the product of 123 and 456?"));
chatRequest = ChatRequest.builder()
.model(modelIdToUse)
.model("gpt-4o-mini")
.messages(messages)
.tools(functionExecutor.getToolFunctions())
.build();
Expand All @@ -237,7 +238,7 @@ public void demoCallChatWithFunctions() {
messages.add(chatMessage);
messages.add(ToolMessage.of(result.toString(), chatToolCall.getId()));
chatRequest = ChatRequest.builder()
.model(modelIdToUse)
.model("gpt-4o-mini")
.messages(messages)
.tools(functionExecutor.getToolFunctions())
.build();
Expand Down Expand Up @@ -292,7 +293,7 @@ public static class RunAlarm implements Functional {
Example to call the Chat Completion service to allow the model to take in external images and answer questions about them:
```java
var chatRequest = ChatRequest.builder()
.model("gpt-4-vision-preview")
.model("gpt-4o-mini")
.messages(List.of(
UserMessage.of(List.of(
ContentPartText.of(
Expand All @@ -311,7 +312,7 @@ System.out.println();
Example to call the Chat Completion service to allow the model to take in local images and answer questions about them:
```java
var chatRequest = ChatRequest.builder()
.model("gpt-4-vision-preview")
.model("gpt-4o-mini")
.messages(List.of(
UserMessage.of(List.of(
ContentPartText.of(
Expand Down Expand Up @@ -905,6 +906,7 @@ Examples for each OpenAI service have been created in the folder [demo](https://
* Image
* Model
* Moderation
* Upload
* Conversation
* AssistantV2
* ThreadV2
Expand Down
Binary file modified media/openai_services.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public class ChatDemo extends AbstractDemo {
private String modelIdToUse;

public ChatDemo() {
modelIdToUse = "gpt-3.5-turbo-1106";
modelIdToUse = "gpt-4o-mini";
chatRequest = ChatRequest.builder()
.model(modelIdToUse)
.message(SystemMessage.of("You are an expert in AI."))
Expand Down Expand Up @@ -96,7 +96,7 @@ public void demoCallChatWithFunctions() {

public void demoCallChatWithVisionExternalImage() {
var chatRequest = ChatRequest.builder()
.model("gpt-4-vision-preview")
.model(modelIdToUse)
.messages(List.of(
UserMessage.of(List.of(
ContentPartText.of(
Expand All @@ -113,7 +113,7 @@ public void demoCallChatWithVisionExternalImage() {

public void demoCallChatWithVisionLocalImage() {
var chatRequest = ChatRequest.builder()
.model("gpt-4-vision-preview")
.model(modelIdToUse)
.messages(List.of(
UserMessage.of(List.of(
ContentPartText.of(
Expand Down
121 changes: 121 additions & 0 deletions src/demo/java/io/github/sashirestela/openai/demo/UploadDemo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package io.github.sashirestela.openai.demo;

import io.github.sashirestela.openai.domain.file.FileRequest.PurposeType;
import io.github.sashirestela.openai.domain.upload.UploadCompleteRequest;
import io.github.sashirestela.openai.domain.upload.UploadPartRequest;
import io.github.sashirestela.openai.domain.upload.UploadRequest;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

public class UploadDemo extends AbstractDemo {

private String fileName;
private String splitPath;
private String mimeType;
private long totalBytes;
private int totalSplits;
private String uploadId;
private List<String> uploadPartIds;

public void splitSourceFile() throws IOException {
String fullFileName;
String chunkSizeMB;

while ((fullFileName = System.console().readLine("Enter the full file name to upload: ")).isBlank());
while ((chunkSizeMB = System.console().readLine("Enter the chunk size in MB: ")).isBlank());
while ((splitPath = System.console().readLine("Enter the path to put splittings: ")).isBlank());
File sourceFile = new File(fullFileName);
fileName = sourceFile.getName();
mimeType = Files.probeContentType(Path.of(fullFileName));
FileInputStream fis = new FileInputStream(sourceFile);
int chunkSize = Integer.parseInt(chunkSizeMB) * 1024 * 1024;
byte[] buffer = new byte[chunkSize];
int bytesRead = 0;
int chunkIndex = 1;
while ((bytesRead = fis.read(buffer)) > 0) {
File chunkFile = new File(splitPath, fileName + ".part" + chunkIndex);
try (FileOutputStream fos = new FileOutputStream(chunkFile)) {
fos.write(buffer, 0, bytesRead);
}
totalBytes += bytesRead;
chunkIndex++;
}
fis.close();
totalSplits = chunkIndex - 1;
}

public void demoCreateUpload() {
var uploadRequest = UploadRequest.builder()
.filename(fileName)
.purpose(PurposeType.ASSISTANTS)
.bytes(totalBytes)
.mimeType(mimeType)
.build();
var uploadResponse = openAI.uploads().create(uploadRequest).join();
uploadId = uploadResponse.getId();
System.out.println(uploadResponse);
}

public void demoAddPartsUpload() {
uploadPartIds = new ArrayList<>();
for (int i = 1; i <= totalSplits; i++) {
Path partPath = Path.of(splitPath, fileName + ".part" + i);
var uploadPartResponse = openAI.uploads()
.addPart(uploadId,
UploadPartRequest.builder().data(partPath).build())
.join();
uploadPartIds.add(uploadPartResponse.getId());
System.out.println(uploadPartResponse);
}
}

public void demoCompleteUpload() {
var uploadCompleteResponse = openAI.uploads()
.complete(uploadId,
UploadCompleteRequest.builder().partIds(uploadPartIds).build())
.join();
System.out.println(uploadCompleteResponse);

var fileId = uploadCompleteResponse.getFile().getId();
FileDemo fileServiceDemo = new FileDemo();
fileServiceDemo.waitUntilFileIsProcessed(fileId);
System.out.println("The file was processed.");
fileServiceDemo.deleteFile(fileId);
System.out.println("The file was deleted.");
}

public void demoCancelUpload() {
var uploadRequest = UploadRequest.builder()
.filename(fileName + ".tmp")
.purpose(PurposeType.ASSISTANTS)
.bytes(totalBytes)
.mimeType(mimeType)
.build();
var uploadResponse = openAI.uploads().create(uploadRequest).join();
var uploadIdToCancel = uploadResponse.getId();

var uploadCancelResponse = openAI.uploads().cancel(uploadIdToCancel).join();
System.out.println(uploadCancelResponse);
}

public static void main(String[] args) throws IOException {
var demo = new UploadDemo();

demo.splitSourceFile();

demo.addTitleAction("Call Upload Create", demo::demoCreateUpload);
demo.addTitleAction("Call Upload Add Parts", demo::demoAddPartsUpload);
demo.addTitleAction("Call Upload Complete", demo::demoCompleteUpload);
demo.addTitleAction("Call Upload Cancel", demo::demoCancelUpload);

demo.run();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public abstract class BaseSimpleOpenAI {
protected OpenAI.Images imageService;
protected OpenAI.Models modelService;
protected OpenAI.Moderations moderationService;
protected OpenAI.Uploads uploadService;
protected OpenAIBeta2.Assistants assistantService;
protected OpenAIBeta2.Threads threadService;
protected OpenAIBeta2.ThreadMessages threadMessageService;
Expand Down Expand Up @@ -141,6 +142,13 @@ public OpenAI.Moderations moderations() {
throw new UnsupportedOperationException(NOT_IMPLEMENTED);
}

/**
* Throw not implemented
*/
public OpenAI.Uploads uploads() {
throw new UnsupportedOperationException(NOT_IMPLEMENTED);
}

/**
* Throw not implemented
*/
Expand Down
57 changes: 57 additions & 0 deletions src/main/java/io/github/sashirestela/openai/OpenAI.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
import io.github.sashirestela.openai.domain.model.Model;
import io.github.sashirestela.openai.domain.moderation.Moderation;
import io.github.sashirestela.openai.domain.moderation.ModerationRequest;
import io.github.sashirestela.openai.domain.upload.Upload;
import io.github.sashirestela.openai.domain.upload.UploadCompleteRequest;
import io.github.sashirestela.openai.domain.upload.UploadPart;
import io.github.sashirestela.openai.domain.upload.UploadPartRequest;
import io.github.sashirestela.openai.domain.upload.UploadRequest;

import java.io.InputStream;
import java.util.EnumSet;
Expand Down Expand Up @@ -718,6 +723,58 @@ interface Moderations {

}

/**
* Allows you to upload large files in multiple parts.
*
* @see <a href= "https://platform.openai.com/docs/api-reference/uploads">OpenAI Upload</a>
*/
@Resource("/v1/uploads")
interface Uploads {

/**
* Creates an intermediate Upload object that you can add Parts to.
*
* @param uploadRequest The creation request.
* @return The Upload object with status pending.
*/
@POST
CompletableFuture<Upload> create(@Body UploadRequest uploadRequest);

/**
* Adds a Part to an Upload object. A Part represents a chunk of bytes from the file you are trying
* to upload.
*
* @param uploadId The ID of the Upload.
* @param uploadPartRequest The chunk of bytes for this Part.
* @return The upload Part object.
*/
@Multipart
@POST("/{uploadId}/parts")
CompletableFuture<UploadPart> addPart(@Path("uploadId") String uploadId,
@Body UploadPartRequest uploadPartRequest);

/**
* Completes the Upload.
*
* @param uploadId The ID of the Upload.
* @param uploadCompleteRequest The request including the ordered list of Part IDs.
* @return The Upload object with status completed.
*/
@POST("/{uploadId}/complete")
CompletableFuture<Upload> complete(@Path("uploadId") String uploadId,
@Body UploadCompleteRequest uploadCompleteRequest);

/**
* Cancels the Upload. No Parts may be added after an Upload is cancelled.
*
* @param uploadId The ID of the Upload.
* @return The Upload object with status cancelled.
*/
@POST("/{uploadId}/cancel")
CompletableFuture<Upload> cancel(@Path("uploadId") String uploadId);

}

static AudioResponseFormat getResponseFormat(AudioResponseFormat currValue, AudioResponseFormat orDefault,
String methodName) {
final var jsonEnumSet = EnumSet.of(AudioResponseFormat.JSON, AudioResponseFormat.VERBOSE_JSON);
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/io/github/sashirestela/openai/SimpleOpenAI.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,19 @@ public OpenAI.Moderations moderations() {
return moderationService;
}

/**
* Generates an implementation of the Uploads interface to handle requests.
*
* @return An instance of the interface.
*/
@Override
public OpenAI.Uploads uploads() {
if (uploadService == null) {
uploadService = cleverClient.create(OpenAI.Uploads.class);
}
return uploadService;
}

@Override
public OpenAIBeta2.Assistants assistants() {
if (assistantService == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.github.sashirestela.openai.domain.upload;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import io.github.sashirestela.openai.domain.file.FileResponse;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

@NoArgsConstructor
@Getter
@ToString
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class Upload {

private String id;
private Long createdAt;
private String filename;
private Long bytes;
private String purpose;
private UploadStatus status;
private Long expiresAt;
private String object;
private FileResponse file;

public enum UploadStatus {

@JsonProperty("pending")
PENDING,

@JsonProperty("completed")
COMPLETED,

@JsonProperty("cancelled")
CANCELLED,

@JsonProperty("expired")
EXPIRED;

}

}
Loading

0 comments on commit ac55a9b

Please sign in to comment.