Skip to content

Commit 9ca56a2

Browse files
authored
Merge pull request #38 from sashirestela/release_1_4_0
Release 1.4.0
2 parents cfa86a9 + 3d6d709 commit 9ca56a2

File tree

6 files changed

+113
-45
lines changed

6 files changed

+113
-45
lines changed

README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ public static class RunAlarm implements Functional {
251251
}
252252
```
253253
#### Chat Completion Service with Vision
254-
Example to call the Chat Completion service to allow the model to take in images and answer questions about them:
254+
Example to call the Chat Completion service to allow the model to take in external images and answer questions about them:
255255
```java
256256
var chatRequest = ChatRequest.builder()
257257
.model("gpt-4-vision-preview")
@@ -270,6 +270,37 @@ chatResponse.filter(chatResp -> chatResp.firstContent() != null)
270270
.forEach(System.out::print);
271271
System.out.println();
272272
```
273+
Example to call the Chat Completion service to allow the model to take in local images and answer questions about them:
274+
```java
275+
var chatRequest = ChatRequest.builder()
276+
.model("gpt-4-vision-preview")
277+
.messages(List.of(
278+
new ChatMsgUser(List.of(
279+
new ContentPartText(
280+
"What do you see in the image? Give in details in no more than 100 words."),
281+
new ContentPartImage(loadImageAsBase64("src/demo/resources/machupicchu.jpg"))))))
282+
.temperature(0.0)
283+
.maxTokens(500)
284+
.build();
285+
var chatResponse = openai.chatCompletions().createStream(chatRequest).join();
286+
chatResponse.filter(chatResp -> chatResp.firstContent() != null)
287+
.map(chatResp -> chatResp.firstContent())
288+
.forEach(System.out::print);
289+
System.out.println();
290+
291+
private static ImageUrl loadImageAsBase64(String imagePath) {
292+
try {
293+
Path path = Paths.get(imagePath);
294+
byte[] imageBytes = Files.readAllBytes(path);
295+
String base64String = Base64.getEncoder().encodeToString(imageBytes);
296+
var extension = imagePath.substring(imagePath.lastIndexOf(".") + 1);
297+
var prefix = "data:image/" + extension + ";base64,";
298+
return new ImageUrl(prefix + base64String);
299+
} catch (Exception e) {
300+
return null;
301+
}
302+
}
303+
```
273304

274305
## ✳ Run Examples
275306
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:

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>io.github.sashirestela</groupId>
88
<artifactId>simple-openai</artifactId>
9-
<version>1.3.0</version>
9+
<version>1.4.0</version>
1010
<packaging>jar</packaging>
1111

1212
<name>simple-openai</name>

src/demo/java/io/github/sashirestela/openai/demo/ChatServiceDemo.java

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package io.github.sashirestela.openai.demo;
22

3+
import java.nio.file.Files;
4+
import java.nio.file.Path;
5+
import java.nio.file.Paths;
36
import java.util.ArrayList;
7+
import java.util.Base64;
48
import java.util.List;
59

610
import com.fasterxml.jackson.annotation.JsonProperty;
@@ -94,7 +98,7 @@ public void demoCallChatWithFunctions() {
9498
System.out.println(chatResponse.firstContent());
9599
}
96100

97-
public void demoCallChatWithVision() {
101+
public void demoCallChatWithVisionExternalImage() {
98102
var chatRequest = ChatRequest.builder()
99103
.model("gpt-4-vision-preview")
100104
.messages(List.of(
@@ -113,6 +117,38 @@ public void demoCallChatWithVision() {
113117
System.out.println();
114118
}
115119

120+
public void demoCallChatWithVisionLocalImage() {
121+
var chatRequest = ChatRequest.builder()
122+
.model("gpt-4-vision-preview")
123+
.messages(List.of(
124+
new ChatMsgUser(List.of(
125+
new ContentPartText(
126+
"What do you see in the image? Give in details in no more than 100 words."),
127+
new ContentPartImage(loadImageAsBase64("src/demo/resources/machupicchu.jpg"))))))
128+
.temperature(0.0)
129+
.maxTokens(500)
130+
.build();
131+
var chatResponse = openAI.chatCompletions().createStream(chatRequest).join();
132+
chatResponse.filter(chatResp -> chatResp.firstContent() != null)
133+
.map(chatResp -> chatResp.firstContent())
134+
.forEach(System.out::print);
135+
System.out.println();
136+
}
137+
138+
private static ImageUrl loadImageAsBase64(String imagePath) {
139+
try {
140+
Path path = Paths.get(imagePath);
141+
byte[] imageBytes = Files.readAllBytes(path);
142+
String base64String = Base64.getEncoder().encodeToString(imageBytes);
143+
var extension = imagePath.substring(imagePath.lastIndexOf(".") + 1);
144+
var prefix = "data:image/" + extension + ";base64,";
145+
return new ImageUrl(prefix + base64String);
146+
} catch (Exception e) {
147+
e.printStackTrace();
148+
return null;
149+
}
150+
}
151+
116152
public static class Weather implements Functional {
117153
@JsonPropertyDescription("City and state, for example: León, Guanajuato")
118154
public String location;
@@ -155,7 +191,8 @@ public static void main(String[] args) {
155191
demo.addTitleAction("Call Chat (Streaming Approach)", demo::demoCallChatStreaming);
156192
demo.addTitleAction("Call Chat (Blocking Approach)", demo::demoCallChatBlocking);
157193
demo.addTitleAction("Call Chat with Functions", demo::demoCallChatWithFunctions);
158-
demo.addTitleAction("Call Chat with Vision", demo::demoCallChatWithVision);
194+
demo.addTitleAction("Call Chat with Vision (External image)", demo::demoCallChatWithVisionExternalImage);
195+
demo.addTitleAction("Call Chat with Vision (Local image)", demo::demoCallChatWithVisionLocalImage);
159196

160197
demo.run();
161198
}

src/demo/resources/machupicchu.jpg

813 KB
Loading

src/main/java/io/github/sashirestela/openai/SimpleOpenAI.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public SimpleOpenAI(
9090
this.apiKey = apiKey;
9191
this.organizationId = organizationId;
9292
this.baseUrl = Optional.ofNullable(baseUrl)
93-
.orElse(Optional.ofNullable(urlBase).orElse(OPENAI_BASE_URL));
93+
.orElse(Optional.ofNullable(urlBase).orElse(OPENAI_BASE_URL));
9494

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

@@ -102,11 +102,11 @@ public SimpleOpenAI(
102102
headers.add(organizationId);
103103
}
104104
this.cleverClient = CleverClient.builder()
105-
.httpClient(this.httpClient)
106-
.baseUrl(this.baseUrl)
107-
.headers(headers)
108-
.endOfStream(END_OF_STREAM)
109-
.build();
105+
.httpClient(this.httpClient)
106+
.baseUrl(this.baseUrl)
107+
.headers(headers)
108+
.endOfStream(END_OF_STREAM)
109+
.build();
110110
}
111111

112112
public void setCleverClient(CleverClient cleverClient) {

src/test/java/io/github/sashirestela/openai/SimpleOpenAITest.java

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -62,17 +62,17 @@ void shouldSetPropertiesWhenBuilderIsCalledWithThoseProperties() {
6262
void shouldSetBaseUrlWhenBuilderIsCalledWithBaseUrlOnly() {
6363
var someUrl = "https://exmaple.org/api";
6464
var openAI = SimpleOpenAI.builder()
65-
.baseUrl(someUrl)
66-
.build();
65+
.baseUrl(someUrl)
66+
.build();
6767
assertEquals(someUrl, openAI.getBaseUrl());
6868
}
6969

7070
@Test
7171
void shouldSetBaseUrlWhenBuilderIsCalledWithUrlBaseOnly() {
7272
var someUrl = "https://exmaple.org/api";
7373
var openAI = SimpleOpenAI.builder()
74-
.urlBase(someUrl)
75-
.build();
74+
.urlBase(someUrl)
75+
.build();
7676
assertEquals(someUrl, openAI.getBaseUrl());
7777
}
7878

@@ -81,16 +81,16 @@ void shouldSetBaseUrlWhenBuilderIsCalledWithBothBaseUrlAndUrlBase() {
8181
var someUrl = "https://exmaple.org/api";
8282
var otherUrl = "https://exmaple.org/other-api";
8383
var openAI = SimpleOpenAI.builder()
84-
.baseUrl(someUrl)
85-
.urlBase(otherUrl)
86-
.build();
84+
.baseUrl(someUrl)
85+
.urlBase(otherUrl)
86+
.build();
8787
assertEquals(someUrl, openAI.getBaseUrl());
8888
}
8989

9090
@Test
9191
void shouldSetDefaultBaseUrlWhenBuilderIsCalledWithoutBaseUrlOrUrlBase() {
9292
var openAI = SimpleOpenAI.builder()
93-
.build();
93+
.build();
9494
assertEquals(OPENAI_BASE_URL, openAI.getBaseUrl());
9595
}
9696

@@ -163,89 +163,89 @@ void init() {
163163
@Test
164164
void shouldInstanceAudioServiceOnlyOnceWhenItIsCalledSeveralTimes() {
165165
when(cleverClient.create(any()))
166-
.thenReturn(ReflectUtil.createProxy(
167-
OpenAI.Audios.class,
168-
new HttpProcessor(null, null, null)));
166+
.thenReturn(ReflectUtil.createProxy(
167+
OpenAI.Audios.class,
168+
new HttpProcessor(null, null, null)));
169169
repeat(NUMBER_CALLINGS, () -> openAI.audios());
170170
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
171171
}
172172

173173
@Test
174174
void shouldInstanceChatCompletionServiceOnlyOnceWhenItIsCalledSeveralTimes() {
175175
when(cleverClient.create(any()))
176-
.thenReturn(ReflectUtil.createProxy(
177-
OpenAI.ChatCompletions.class,
178-
new HttpProcessor(null, null, null)));
176+
.thenReturn(ReflectUtil.createProxy(
177+
OpenAI.ChatCompletions.class,
178+
new HttpProcessor(null, null, null)));
179179
repeat(NUMBER_CALLINGS, () -> openAI.chatCompletions());
180180
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
181181
}
182182

183183
@Test
184184
void shouldInstanceCompletionServiceOnlyOnceWhenItIsCalledSeveralTimes() {
185185
when(cleverClient.create(any()))
186-
.thenReturn(ReflectUtil.createProxy(
187-
OpenAI.Completions.class,
188-
new HttpProcessor(null, null, null)));
186+
.thenReturn(ReflectUtil.createProxy(
187+
OpenAI.Completions.class,
188+
new HttpProcessor(null, null, null)));
189189
repeat(NUMBER_CALLINGS, () -> openAI.completions());
190190
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
191191
}
192192

193193
@Test
194194
void shouldInstanceEmbeddingServiceOnlyOnceWhenItIsCalledSeveralTimes() {
195195
when(cleverClient.create(any()))
196-
.thenReturn(ReflectUtil.createProxy(
197-
OpenAI.Embeddings.class,
198-
new HttpProcessor(null, null, null)));
196+
.thenReturn(ReflectUtil.createProxy(
197+
OpenAI.Embeddings.class,
198+
new HttpProcessor(null, null, null)));
199199
repeat(NUMBER_CALLINGS, () -> openAI.embeddings());
200200
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
201201
}
202202

203203
@Test
204204
void shouldInstanceFilesServiceOnlyOnceWhenItIsCalledSeveralTimes() {
205205
when(cleverClient.create(any()))
206-
.thenReturn(ReflectUtil.createProxy(
207-
OpenAI.Files.class,
208-
new HttpProcessor(null, null, null)));
206+
.thenReturn(ReflectUtil.createProxy(
207+
OpenAI.Files.class,
208+
new HttpProcessor(null, null, null)));
209209
repeat(NUMBER_CALLINGS, () -> openAI.files());
210210
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
211211
}
212212

213213
@Test
214214
void shouldInstanceFineTunningServiceOnlyOnceWhenItIsCalledSeveralTimes() {
215215
when(cleverClient.create(any()))
216-
.thenReturn(ReflectUtil.createProxy(
217-
OpenAI.FineTunings.class,
218-
new HttpProcessor(null, null, null)));
216+
.thenReturn(ReflectUtil.createProxy(
217+
OpenAI.FineTunings.class,
218+
new HttpProcessor(null, null, null)));
219219
repeat(NUMBER_CALLINGS, () -> openAI.fineTunings());
220220
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
221221
}
222222

223223
@Test
224224
void shouldInstanceImageServiceOnlyOnceWhenItIsCalledSeveralTimes() {
225225
when(cleverClient.create(any()))
226-
.thenReturn(ReflectUtil.createProxy(
227-
OpenAI.Images.class,
228-
new HttpProcessor(null, null, null)));
226+
.thenReturn(ReflectUtil.createProxy(
227+
OpenAI.Images.class,
228+
new HttpProcessor(null, null, null)));
229229
repeat(NUMBER_CALLINGS, () -> openAI.images());
230230
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
231231
}
232232

233233
@Test
234234
void shouldInstanceModelsServiceOnlyOnceWhenItIsCalledSeveralTimes() {
235235
when(cleverClient.create(any()))
236-
.thenReturn(ReflectUtil.createProxy(
237-
OpenAI.Models.class,
238-
new HttpProcessor(null, null, null)));
236+
.thenReturn(ReflectUtil.createProxy(
237+
OpenAI.Models.class,
238+
new HttpProcessor(null, null, null)));
239239
repeat(NUMBER_CALLINGS, () -> openAI.models());
240240
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
241241
}
242242

243243
@Test
244244
void shouldInstanceModerationServiceOnlyOnceWhenItIsCalledSeveralTimes() {
245245
when(cleverClient.create(any()))
246-
.thenReturn(ReflectUtil.createProxy(
247-
OpenAI.Moderations.class,
248-
new HttpProcessor(null, null, null)));
246+
.thenReturn(ReflectUtil.createProxy(
247+
OpenAI.Moderations.class,
248+
new HttpProcessor(null, null, null)));
249249
repeat(NUMBER_CALLINGS, () -> openAI.moderations());
250250
verify(cleverClient, times(NUMBER_INVOCATIONS)).create(any());
251251
}

0 commit comments

Comments
 (0)