Skip to content

Commit cc7e833

Browse files
committed
Fix sample application; Improve code quality; Format
1 parent 0620966 commit cc7e833

File tree

5 files changed

+147
-83
lines changed

5 files changed

+147
-83
lines changed

foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiChatCompletionDelta.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package com.sap.ai.sdk.foundationmodels.openai;
22

33
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
45
import com.google.common.annotations.Beta;
56
import com.sap.ai.sdk.core.common.StreamedDelta;
67
import com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionsCreate200Response;
8+
import com.sap.ai.sdk.foundationmodels.openai.model2.CompletionUsage;
79
import com.sap.ai.sdk.foundationmodels.openai.model2.CreateChatCompletionResponse;
810
import com.sap.ai.sdk.foundationmodels.openai.model2.CreateChatCompletionStreamResponse;
11+
import java.util.Map;
912
import java.util.Objects;
1013
import javax.annotation.Nonnull;
1114
import javax.annotation.Nullable;
@@ -66,4 +69,23 @@ public String getFinishReason() {
6669
}
6770
return null;
6871
}
72+
73+
/**
74+
* Get the completion usage from the response, or null if it is not available.
75+
*
76+
* @param objectMapper The object mapper to use for conversion.
77+
* @return The completion usage or null.
78+
*/
79+
@Nullable
80+
public CompletionUsage getCompletionUsage(@Nonnull final ObjectMapper objectMapper) {
81+
if (getOriginalResponse() instanceof CreateChatCompletionStreamResponse response
82+
&& response.getCustomFieldNames().contains("usage")
83+
&& response.getCustomField("usage") instanceof Map<?, ?> usage) {
84+
return objectMapper.convertValue(usage, CompletionUsage.class);
85+
}
86+
if (getOriginalResponse() instanceof CreateChatCompletionResponse response) {
87+
return response.getUsage();
88+
}
89+
return null;
90+
}
6991
}

foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,14 @@ public CreateChatCompletionResponse chatCompletion(@Nonnull final String prompt)
141141
.role(ChatCompletionRequestSystemMessage.RoleEnum.SYSTEM)
142142
.content(ChatCompletionRequestSystemMessageContent.create(systemPrompt)));
143143
}
144-
parameters.addMessagesItem(
145-
new ChatCompletionRequestUserMessage()
146-
.role(ChatCompletionRequestUserMessage.RoleEnum.USER)
147-
.content(ChatCompletionRequestUserMessageContent.create(prompt)));
144+
parameters
145+
.addMessagesItem(
146+
new ChatCompletionRequestUserMessage()
147+
.role(ChatCompletionRequestUserMessage.RoleEnum.USER)
148+
.content(ChatCompletionRequestUserMessageContent.create(prompt)))
149+
.functions(null)
150+
.tools(null)
151+
.parallelToolCalls(null);
148152
return chatCompletion(parameters);
149153
}
150154

@@ -165,7 +169,7 @@ public CreateChatCompletionResponse chatCompletion(
165169
/**
166170
* Stream a completion for the given prompt. Returns a <b>lazily</b> populated stream of text
167171
* chunks. To access more details about the individual chunks, use {@link
168-
* #streamChatCompletionDeltas(OpenAiChatCompletionParameters)}.
172+
* #streamChatCompletionDeltas(CreateChatCompletionRequest)}.
169173
*
170174
* <p>The stream should be consumed using a try-with-resources block to ensure that the underlying
171175
* HTTP connection is closed.
@@ -185,7 +189,7 @@ public CreateChatCompletionResponse chatCompletion(
185189
* @param prompt a text message.
186190
* @return A stream of message deltas
187191
* @throws OpenAiClientException if the request fails or if the finish reason is content_filter
188-
* @see #streamChatCompletionDeltas(OpenAiChatCompletionParameters)
192+
* @see #streamChatCompletionDeltas(CreateChatCompletionRequest)
189193
*/
190194
@Nonnull
191195
public Stream<String> streamChatCompletion(@Nonnull final String prompt)
@@ -198,15 +202,16 @@ public Stream<String> streamChatCompletion(@Nonnull final String prompt)
198202
.role(ChatCompletionRequestSystemMessage.RoleEnum.SYSTEM)
199203
.content(ChatCompletionRequestSystemMessageContent.create(systemPrompt)));
200204
}
201-
parameters.addMessagesItem(
205+
final var userMessage =
202206
new ChatCompletionRequestUserMessage()
203207
.role(ChatCompletionRequestUserMessage.RoleEnum.USER)
204-
.content(ChatCompletionRequestUserMessageContent.create(prompt)));
208+
.content(ChatCompletionRequestUserMessageContent.create(prompt));
209+
parameters.addMessagesItem(userMessage).tools(null).functions(null).parallelToolCalls(null);
205210

206211
return streamChatCompletionDeltas(parameters)
207-
.map(com.sap.ai.sdk.foundationmodels.openai.OpenAiChatCompletionDelta.class::cast)
212+
.map(OpenAiChatCompletionDelta.class::cast)
208213
.peek(OpenAiClient::throwOnContentFilter)
209-
.map(com.sap.ai.sdk.foundationmodels.openai.OpenAiChatCompletionDelta::getDeltaContent);
214+
.map(OpenAiChatCompletionDelta::getDeltaContent);
210215
}
211216

212217
private static void throwOnContentFilter(

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
import com.fasterxml.jackson.core.JsonProcessingException;
66
import com.fasterxml.jackson.databind.ObjectMapper;
77
import com.sap.ai.sdk.app.services.OpenAiService;
8-
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionOutput;
8+
import com.sap.ai.sdk.foundationmodels.openai.model2.CompletionUsage;
99
import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutors;
1010
import java.io.IOException;
1111
import java.util.Arrays;
12+
import java.util.concurrent.atomic.AtomicReference;
1213
import javax.annotation.Nonnull;
1314
import lombok.extern.slf4j.Slf4j;
1415
import org.springframework.beans.factory.annotation.Autowired;
@@ -45,7 +46,7 @@ ResponseEntity<String> chatCompletion(
4546
.contentType(MediaType.APPLICATION_JSON)
4647
.body(mapper.writeValueAsString(response));
4748
}
48-
return ResponseEntity.ok(response.getContent());
49+
return ResponseEntity.ok(response.getChoices().get(0).getMessage().getContent());
4950
}
5051

5152
/**
@@ -62,14 +63,17 @@ ResponseEntity<ResponseBodyEmitter> streamChatCompletionDeltas() {
6263
final var emitter = new ResponseBodyEmitter();
6364
final Runnable consumeStream =
6465
() -> {
65-
final var totalOutput = new OpenAiChatCompletionOutput();
66+
final var totalOutput = new AtomicReference<CompletionUsage>();
6667
// try-with-resources ensures the stream is closed
6768
try (stream) {
68-
stream
69-
.peek(totalOutput::addDelta)
70-
.forEach(delta -> send(emitter, delta.getDeltaContent()));
69+
stream.forEach(
70+
delta -> {
71+
final var usage = delta.getCompletionUsage(mapper);
72+
totalOutput.compareAndExchange(null, usage);
73+
send(emitter, delta.getDeltaContent());
74+
});
7175
} finally {
72-
send(emitter, "\n\n-----Total Output-----\n\n" + objectToJson(totalOutput));
76+
send(emitter, "\n\n-----Total Output-----\n\n" + totalOutput);
7377
emitter.complete();
7478
}
7579
};
@@ -121,20 +125,6 @@ public static void send(@Nonnull final ResponseBodyEmitter emitter, @Nonnull fin
121125
}
122126
}
123127

124-
/**
125-
* Convert an object to JSON
126-
*
127-
* @param obj The object to convert
128-
* @return The JSON representation of the object
129-
*/
130-
private static String objectToJson(@Nonnull final Object obj) {
131-
try {
132-
return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(obj);
133-
} catch (final JsonProcessingException ignored) {
134-
return "Could not parse object to JSON";
135-
}
136-
}
137-
138128
/**
139129
* Chat request to OpenAI with an image
140130
*
@@ -153,7 +143,7 @@ ResponseEntity<String> chatCompletionImage(
153143
.contentType(MediaType.APPLICATION_JSON)
154144
.body(mapper.writeValueAsString(response));
155145
}
156-
return ResponseEntity.ok(response.getContent());
146+
return ResponseEntity.ok(response.getChoices().get(0).getMessage().getContent());
157147
}
158148

159149
/**
@@ -173,7 +163,7 @@ ResponseEntity<String> chatCompletionTools(
173163
.contentType(MediaType.APPLICATION_JSON)
174164
.body(mapper.writeValueAsString(response));
175165
}
176-
return ResponseEntity.ok(response.getContent());
166+
return ResponseEntity.ok(response.getChoices().get(0).getMessage().getContent());
177167
}
178168

179169
/**
@@ -209,6 +199,6 @@ ResponseEntity<String> chatCompletionWithResource(
209199
.contentType(MediaType.APPLICATION_JSON)
210200
.body(mapper.writeValueAsString(response));
211201
}
212-
return ResponseEntity.ok(response.getContent());
202+
return ResponseEntity.ok(response.getChoices().get(0).getMessage().getContent());
213203
}
214204
}

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OpenAiService.java

Lines changed: 70 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,30 @@
33
import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.GPT_35_TURBO;
44
import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.GPT_4O;
55
import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.TEXT_EMBEDDING_ADA_002;
6-
import static com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionTool.ToolType.FUNCTION;
6+
import static com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionRequestMessageContentPartImage.TypeEnum.IMAGE_URL;
7+
import static com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionRequestMessageContentPartImageImageUrl.DetailEnum.HIGH;
8+
import static com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionRequestMessageContentPartText.TypeEnum.TEXT;
9+
import static com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionRequestUserMessage.RoleEnum.USER;
710

811
import com.sap.ai.sdk.core.AiCoreService;
12+
import com.sap.ai.sdk.foundationmodels.openai.OpenAiChatCompletionDelta;
913
import com.sap.ai.sdk.foundationmodels.openai.OpenAiClient;
10-
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionDelta;
11-
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionFunction;
12-
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionOutput;
13-
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionParameters;
14-
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionTool;
15-
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatMessage;
16-
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingOutput;
17-
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingParameters;
14+
import com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionNamedToolChoice;
15+
import com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionNamedToolChoiceFunction;
16+
import com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionRequestMessageContentPartImage;
17+
import com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionRequestMessageContentPartImageImageUrl;
18+
import com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionRequestMessageContentPartText;
19+
import com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionRequestUserMessage;
20+
import com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionRequestUserMessageContent;
21+
import com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionTool;
22+
import com.sap.ai.sdk.foundationmodels.openai.model2.ChatCompletionToolChoiceOption;
23+
import com.sap.ai.sdk.foundationmodels.openai.model2.CreateChatCompletionRequest;
24+
import com.sap.ai.sdk.foundationmodels.openai.model2.CreateChatCompletionResponse;
25+
import com.sap.ai.sdk.foundationmodels.openai.model2.EmbeddingsCreate200Response;
26+
import com.sap.ai.sdk.foundationmodels.openai.model2.EmbeddingsCreateRequest;
27+
import com.sap.ai.sdk.foundationmodels.openai.model2.EmbeddingsCreateRequestInput;
28+
import com.sap.ai.sdk.foundationmodels.openai.model2.FunctionObject;
29+
import java.net.URI;
1830
import java.util.List;
1931
import java.util.Map;
2032
import java.util.stream.Stream;
@@ -34,7 +46,7 @@ public class OpenAiService {
3446
* @return the assistant message response
3547
*/
3648
@Nonnull
37-
public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) {
49+
public CreateChatCompletionResponse chatCompletion(@Nonnull final String prompt) {
3850
return OpenAiClient.forModel(GPT_35_TURBO).chatCompletion(prompt);
3951
}
4052

@@ -46,9 +58,12 @@ public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) {
4658
@Nonnull
4759
public Stream<OpenAiChatCompletionDelta> streamChatCompletionDeltas(
4860
@Nonnull final String message) {
61+
final var userMessage =
62+
new ChatCompletionRequestUserMessage()
63+
.role(USER)
64+
.content(ChatCompletionRequestUserMessageContent.create(message));
4965
final var request =
50-
new OpenAiChatCompletionParameters()
51-
.addMessages(new OpenAiChatMessage.OpenAiChatUserMessage().addText(message));
66+
new CreateChatCompletionRequest().addMessagesItem(userMessage).functions(null).tools(null);
5267

5368
return OpenAiClient.forModel(GPT_35_TURBO).streamChatCompletionDeltas(request);
5469
}
@@ -72,15 +87,27 @@ public Stream<String> streamChatCompletion(@Nonnull final String message) {
7287
* @return the assistant message response
7388
*/
7489
@Nonnull
75-
public OpenAiChatCompletionOutput chatCompletionImage(@Nonnull final String linkToImage) {
90+
public CreateChatCompletionResponse chatCompletionImage(@Nonnull final String linkToImage) {
91+
final var partText =
92+
new ChatCompletionRequestMessageContentPartText()
93+
.type(TEXT)
94+
.text("Describe the following image.");
95+
final var partImageUrl =
96+
new ChatCompletionRequestMessageContentPartImageImageUrl()
97+
.url(URI.create(linkToImage))
98+
.detail(HIGH);
99+
final var partImage =
100+
new ChatCompletionRequestMessageContentPartImage().type(IMAGE_URL).imageUrl(partImageUrl);
101+
final var userMessage =
102+
new ChatCompletionRequestUserMessage()
103+
.role(USER)
104+
.content(ChatCompletionRequestUserMessageContent.create(List.of(partText, partImage)));
76105
final var request =
77-
new OpenAiChatCompletionParameters()
78-
.addMessages(
79-
new OpenAiChatMessage.OpenAiChatUserMessage()
80-
.addText("Describe the following image.")
81-
.addImage(
82-
linkToImage,
83-
OpenAiChatMessage.OpenAiChatUserMessage.ImageDetailLevel.HIGH));
106+
new CreateChatCompletionRequest()
107+
.addMessagesItem(userMessage)
108+
.functions(null)
109+
.tools(null)
110+
.parallelToolCalls(null);
84111

85112
return OpenAiClient.forModel(GPT_4O).chatCompletion(request);
86113
}
@@ -92,21 +119,29 @@ public OpenAiChatCompletionOutput chatCompletionImage(@Nonnull final String link
92119
* @return the assistant message response
93120
*/
94121
@Nonnull
95-
public OpenAiChatCompletionOutput chatCompletionTools(@Nonnull final String prompt) {
122+
public CreateChatCompletionResponse chatCompletionTools(@Nonnull final String prompt) {
96123
final var question =
97124
"A pair of rabbits is placed in a field. Each month, every pair produces one new pair, starting from the second month. How many rabbits will there be after 12 months?";
98125
final var par = Map.of("type", "object", "properties", Map.of("N", Map.of("type", "integer")));
99-
final var function =
100-
new OpenAiChatCompletionFunction()
101-
.setName("fibonacci")
102-
.setDescription(prompt)
103-
.setParameters(par);
104-
final var tool = new OpenAiChatCompletionTool().setType(FUNCTION).setFunction(function);
126+
final var function = new FunctionObject().name("fibonacci").description(prompt).parameters(par);
127+
final var tool =
128+
new ChatCompletionTool().type(ChatCompletionTool.TypeEnum.FUNCTION).function(function);
129+
final var userMessage =
130+
new ChatCompletionRequestUserMessage()
131+
.role(USER)
132+
.content(ChatCompletionRequestUserMessageContent.create(question));
133+
final var toolChoice =
134+
ChatCompletionToolChoiceOption.create(
135+
new ChatCompletionNamedToolChoice()
136+
.type(ChatCompletionNamedToolChoice.TypeEnum.FUNCTION)
137+
.function(new ChatCompletionNamedToolChoiceFunction().name("fibonacci")));
105138
final var request =
106-
new OpenAiChatCompletionParameters()
107-
.addMessages(new OpenAiChatMessage.OpenAiChatUserMessage().addText(question))
108-
.setTools(List.of(tool))
109-
.setToolChoiceFunction("fibonacci");
139+
new CreateChatCompletionRequest()
140+
.addMessagesItem(userMessage)
141+
.tools(List.of(tool))
142+
.toolChoice(toolChoice)
143+
.functions(null)
144+
.parallelToolCalls(null);
110145

111146
return OpenAiClient.forModel(GPT_35_TURBO).chatCompletion(request);
112147
}
@@ -118,8 +153,9 @@ public OpenAiChatCompletionOutput chatCompletionTools(@Nonnull final String prom
118153
* @return the embedding response
119154
*/
120155
@Nonnull
121-
public OpenAiEmbeddingOutput embedding(@Nonnull final String input) {
122-
final var request = new OpenAiEmbeddingParameters().setInput(input);
156+
public EmbeddingsCreate200Response embedding(@Nonnull final String input) {
157+
final var request =
158+
new EmbeddingsCreateRequest().input(EmbeddingsCreateRequestInput.create(input));
123159

124160
return OpenAiClient.forModel(TEXT_EMBEDDING_ADA_002).embedding(request);
125161
}
@@ -132,7 +168,7 @@ public OpenAiEmbeddingOutput embedding(@Nonnull final String input) {
132168
* @return the assistant message response
133169
*/
134170
@Nonnull
135-
public OpenAiChatCompletionOutput chatCompletionWithResource(
171+
public CreateChatCompletionResponse chatCompletionWithResource(
136172
@Nonnull final String resourceGroup, @Nonnull final String prompt) {
137173

138174
final var destination =

0 commit comments

Comments
 (0)