Skip to content

Commit

Permalink
Merge pull request #38 from sashirestela/release_1_4_0
Browse files Browse the repository at this point in the history
Release 1.4.0
  • Loading branch information
sashirestela authored Jan 29, 2024
2 parents cfa86a9 + 3d6d709 commit 9ca56a2
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 45 deletions.
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ public static class RunAlarm implements Functional {
}
```
#### Chat Completion Service with Vision
Example to call the Chat Completion service to allow the model to take in images and answer questions about them:
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")
Expand All @@ -270,6 +270,37 @@ chatResponse.filter(chatResp -> chatResp.firstContent() != null)
.forEach(System.out::print);
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")
.messages(List.of(
new ChatMsgUser(List.of(
new ContentPartText(
"What do you see in the image? Give in details in no more than 100 words."),
new ContentPartImage(loadImageAsBase64("src/demo/resources/machupicchu.jpg"))))))
.temperature(0.0)
.maxTokens(500)
.build();
var chatResponse = openai.chatCompletions().createStream(chatRequest).join();
chatResponse.filter(chatResp -> chatResp.firstContent() != null)
.map(chatResp -> chatResp.firstContent())
.forEach(System.out::print);
System.out.println();

private static ImageUrl loadImageAsBase64(String imagePath) {
try {
Path path = Paths.get(imagePath);
byte[] imageBytes = Files.readAllBytes(path);
String base64String = Base64.getEncoder().encodeToString(imageBytes);
var extension = imagePath.substring(imagePath.lastIndexOf(".") + 1);
var prefix = "data:image/" + extension + ";base64,";
return new ImageUrl(prefix + base64String);
} catch (Exception e) {
return null;
}
}
```

## ✳ Run Examples
Examples for each OpenAI service have been created in the folder [demo](https://github.com/sashirestela/simple-openai/tree/main/src/demo/java/io/github/sashirestela/openai/demo) and you can follow the next steps to execute them:
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.sashirestela</groupId>
<artifactId>simple-openai</artifactId>
<version>1.3.0</version>
<version>1.4.0</version>
<packaging>jar</packaging>

<name>simple-openai</name>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package io.github.sashirestela.openai.demo;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

import com.fasterxml.jackson.annotation.JsonProperty;
Expand Down Expand Up @@ -94,7 +98,7 @@ public void demoCallChatWithFunctions() {
System.out.println(chatResponse.firstContent());
}

public void demoCallChatWithVision() {
public void demoCallChatWithVisionExternalImage() {
var chatRequest = ChatRequest.builder()
.model("gpt-4-vision-preview")
.messages(List.of(
Expand All @@ -113,6 +117,38 @@ public void demoCallChatWithVision() {
System.out.println();
}

public void demoCallChatWithVisionLocalImage() {
var chatRequest = ChatRequest.builder()
.model("gpt-4-vision-preview")
.messages(List.of(
new ChatMsgUser(List.of(
new ContentPartText(
"What do you see in the image? Give in details in no more than 100 words."),
new ContentPartImage(loadImageAsBase64("src/demo/resources/machupicchu.jpg"))))))
.temperature(0.0)
.maxTokens(500)
.build();
var chatResponse = openAI.chatCompletions().createStream(chatRequest).join();
chatResponse.filter(chatResp -> chatResp.firstContent() != null)
.map(chatResp -> chatResp.firstContent())
.forEach(System.out::print);
System.out.println();
}

private static ImageUrl loadImageAsBase64(String imagePath) {
try {
Path path = Paths.get(imagePath);
byte[] imageBytes = Files.readAllBytes(path);
String base64String = Base64.getEncoder().encodeToString(imageBytes);
var extension = imagePath.substring(imagePath.lastIndexOf(".") + 1);
var prefix = "data:image/" + extension + ";base64,";
return new ImageUrl(prefix + base64String);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

public static class Weather implements Functional {
@JsonPropertyDescription("City and state, for example: León, Guanajuato")
public String location;
Expand Down Expand Up @@ -155,7 +191,8 @@ public static void main(String[] args) {
demo.addTitleAction("Call Chat (Streaming Approach)", demo::demoCallChatStreaming);
demo.addTitleAction("Call Chat (Blocking Approach)", demo::demoCallChatBlocking);
demo.addTitleAction("Call Chat with Functions", demo::demoCallChatWithFunctions);
demo.addTitleAction("Call Chat with Vision", demo::demoCallChatWithVision);
demo.addTitleAction("Call Chat with Vision (External image)", demo::demoCallChatWithVisionExternalImage);
demo.addTitleAction("Call Chat with Vision (Local image)", demo::demoCallChatWithVisionLocalImage);

demo.run();
}
Expand Down
Binary file added src/demo/resources/machupicchu.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions src/main/java/io/github/sashirestela/openai/SimpleOpenAI.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public SimpleOpenAI(
this.apiKey = apiKey;
this.organizationId = organizationId;
this.baseUrl = Optional.ofNullable(baseUrl)
.orElse(Optional.ofNullable(urlBase).orElse(OPENAI_BASE_URL));
.orElse(Optional.ofNullable(urlBase).orElse(OPENAI_BASE_URL));

this.httpClient = Optional.ofNullable(httpClient).orElse(HttpClient.newHttpClient());

Expand All @@ -102,11 +102,11 @@ public SimpleOpenAI(
headers.add(organizationId);
}
this.cleverClient = CleverClient.builder()
.httpClient(this.httpClient)
.baseUrl(this.baseUrl)
.headers(headers)
.endOfStream(END_OF_STREAM)
.build();
.httpClient(this.httpClient)
.baseUrl(this.baseUrl)
.headers(headers)
.endOfStream(END_OF_STREAM)
.build();
}

public void setCleverClient(CleverClient cleverClient) {
Expand Down
70 changes: 35 additions & 35 deletions src/test/java/io/github/sashirestela/openai/SimpleOpenAITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,17 @@ void shouldSetPropertiesWhenBuilderIsCalledWithThoseProperties() {
void shouldSetBaseUrlWhenBuilderIsCalledWithBaseUrlOnly() {
var someUrl = "https://exmaple.org/api";
var openAI = SimpleOpenAI.builder()
.baseUrl(someUrl)
.build();
.baseUrl(someUrl)
.build();
assertEquals(someUrl, openAI.getBaseUrl());
}

@Test
void shouldSetBaseUrlWhenBuilderIsCalledWithUrlBaseOnly() {
var someUrl = "https://exmaple.org/api";
var openAI = SimpleOpenAI.builder()
.urlBase(someUrl)
.build();
.urlBase(someUrl)
.build();
assertEquals(someUrl, openAI.getBaseUrl());
}

Expand All @@ -81,16 +81,16 @@ void shouldSetBaseUrlWhenBuilderIsCalledWithBothBaseUrlAndUrlBase() {
var someUrl = "https://exmaple.org/api";
var otherUrl = "https://exmaple.org/other-api";
var openAI = SimpleOpenAI.builder()
.baseUrl(someUrl)
.urlBase(otherUrl)
.build();
.baseUrl(someUrl)
.urlBase(otherUrl)
.build();
assertEquals(someUrl, openAI.getBaseUrl());
}

@Test
void shouldSetDefaultBaseUrlWhenBuilderIsCalledWithoutBaseUrlOrUrlBase() {
var openAI = SimpleOpenAI.builder()
.build();
.build();
assertEquals(OPENAI_BASE_URL, openAI.getBaseUrl());
}

Expand Down Expand Up @@ -163,89 +163,89 @@ void init() {
@Test
void shouldInstanceAudioServiceOnlyOnceWhenItIsCalledSeveralTimes() {
when(cleverClient.create(any()))
.thenReturn(ReflectUtil.createProxy(
OpenAI.Audios.class,
new HttpProcessor(null, null, null)));
.thenReturn(ReflectUtil.createProxy(
OpenAI.Audios.class,
new HttpProcessor(null, null, null)));
repeat(NUMBER_CALLINGS, () -> openAI.audios());
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
}

@Test
void shouldInstanceChatCompletionServiceOnlyOnceWhenItIsCalledSeveralTimes() {
when(cleverClient.create(any()))
.thenReturn(ReflectUtil.createProxy(
OpenAI.ChatCompletions.class,
new HttpProcessor(null, null, null)));
.thenReturn(ReflectUtil.createProxy(
OpenAI.ChatCompletions.class,
new HttpProcessor(null, null, null)));
repeat(NUMBER_CALLINGS, () -> openAI.chatCompletions());
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
}

@Test
void shouldInstanceCompletionServiceOnlyOnceWhenItIsCalledSeveralTimes() {
when(cleverClient.create(any()))
.thenReturn(ReflectUtil.createProxy(
OpenAI.Completions.class,
new HttpProcessor(null, null, null)));
.thenReturn(ReflectUtil.createProxy(
OpenAI.Completions.class,
new HttpProcessor(null, null, null)));
repeat(NUMBER_CALLINGS, () -> openAI.completions());
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
}

@Test
void shouldInstanceEmbeddingServiceOnlyOnceWhenItIsCalledSeveralTimes() {
when(cleverClient.create(any()))
.thenReturn(ReflectUtil.createProxy(
OpenAI.Embeddings.class,
new HttpProcessor(null, null, null)));
.thenReturn(ReflectUtil.createProxy(
OpenAI.Embeddings.class,
new HttpProcessor(null, null, null)));
repeat(NUMBER_CALLINGS, () -> openAI.embeddings());
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
}

@Test
void shouldInstanceFilesServiceOnlyOnceWhenItIsCalledSeveralTimes() {
when(cleverClient.create(any()))
.thenReturn(ReflectUtil.createProxy(
OpenAI.Files.class,
new HttpProcessor(null, null, null)));
.thenReturn(ReflectUtil.createProxy(
OpenAI.Files.class,
new HttpProcessor(null, null, null)));
repeat(NUMBER_CALLINGS, () -> openAI.files());
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
}

@Test
void shouldInstanceFineTunningServiceOnlyOnceWhenItIsCalledSeveralTimes() {
when(cleverClient.create(any()))
.thenReturn(ReflectUtil.createProxy(
OpenAI.FineTunings.class,
new HttpProcessor(null, null, null)));
.thenReturn(ReflectUtil.createProxy(
OpenAI.FineTunings.class,
new HttpProcessor(null, null, null)));
repeat(NUMBER_CALLINGS, () -> openAI.fineTunings());
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
}

@Test
void shouldInstanceImageServiceOnlyOnceWhenItIsCalledSeveralTimes() {
when(cleverClient.create(any()))
.thenReturn(ReflectUtil.createProxy(
OpenAI.Images.class,
new HttpProcessor(null, null, null)));
.thenReturn(ReflectUtil.createProxy(
OpenAI.Images.class,
new HttpProcessor(null, null, null)));
repeat(NUMBER_CALLINGS, () -> openAI.images());
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
}

@Test
void shouldInstanceModelsServiceOnlyOnceWhenItIsCalledSeveralTimes() {
when(cleverClient.create(any()))
.thenReturn(ReflectUtil.createProxy(
OpenAI.Models.class,
new HttpProcessor(null, null, null)));
.thenReturn(ReflectUtil.createProxy(
OpenAI.Models.class,
new HttpProcessor(null, null, null)));
repeat(NUMBER_CALLINGS, () -> openAI.models());
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
}

@Test
void shouldInstanceModerationServiceOnlyOnceWhenItIsCalledSeveralTimes() {
when(cleverClient.create(any()))
.thenReturn(ReflectUtil.createProxy(
OpenAI.Moderations.class,
new HttpProcessor(null, null, null)));
.thenReturn(ReflectUtil.createProxy(
OpenAI.Moderations.class,
new HttpProcessor(null, null, null)));
repeat(NUMBER_CALLINGS, () -> openAI.moderations());
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
}
Expand Down

0 comments on commit 9ca56a2

Please sign in to comment.