Skip to content

Commit 2987f66

Browse files
authored
chore: Code Cleanup Before GA Release (#212)
* Some code cleanup * Remove Spring Dependency * Refactor Jackson into central mapper config * Minor update * Revert HTML changes * Move Jackson to separate class * Remove redundant javadoc
1 parent c9443a2 commit 2987f66

File tree

25 files changed

+202
-194
lines changed

25 files changed

+202
-194
lines changed

core/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@
8686
<groupId>com.fasterxml.jackson.datatype</groupId>
8787
<artifactId>jackson-datatype-jsr310</artifactId>
8888
</dependency>
89+
<dependency>
90+
<groupId>com.fasterxml.jackson.module</groupId>
91+
<artifactId>jackson-module-parameter-names</artifactId>
92+
</dependency>
8993
<dependency>
9094
<groupId>com.google.guava</groupId>
9195
<artifactId>guava</artifactId>

core/src/main/java/com/sap/ai/sdk/core/AiCoreService.java

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package com.sap.ai.sdk.core;
22

3-
import com.fasterxml.jackson.annotation.JsonAutoDetect;
4-
import com.fasterxml.jackson.annotation.JsonInclude;
5-
import com.fasterxml.jackson.annotation.PropertyAccessor;
6-
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
3+
import static com.sap.ai.sdk.core.JacksonConfiguration.getDefaultObjectMapper;
4+
5+
import com.fasterxml.jackson.databind.json.JsonMapper;
76
import com.google.common.annotations.Beta;
87
import com.google.common.collect.Iterables;
98
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
@@ -21,7 +20,6 @@
2120
import lombok.val;
2221
import org.springframework.http.client.BufferingClientHttpRequestFactory;
2322
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
24-
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
2523
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
2624
import org.springframework.web.client.RestTemplate;
2725

@@ -37,6 +35,7 @@ public class AiCoreService {
3735
/** The default resource group. */
3836
public static final String DEFAULT_RESOURCE_GROUP = "default";
3937

38+
private static final JsonMapper objectMapper = getDefaultObjectMapper();
4039
private static final String RESOURCE_GROUP_HEADER_PROPERTY = "URL.headers.AI-Resource-Group";
4140

4241
@Nonnull private final Supplier<HttpDestination> baseDestinationResolver;
@@ -131,14 +130,6 @@ public ApiClient getApiClient() {
131130
httpRequestFactory.setHttpClient(ApacheHttpClient5Accessor.getHttpClient(destination));
132131

133132
val rt = new RestTemplate();
134-
val objectMapper =
135-
new Jackson2ObjectMapperBuilder()
136-
.modules(new JavaTimeModule())
137-
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
138-
.visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
139-
.serializationInclusion(JsonInclude.Include.NON_NULL) // THIS STOPS `null` serialization
140-
.build();
141-
142133
Iterables.filter(rt.getMessageConverters(), MappingJackson2HttpMessageConverter.class)
143134
.forEach(converter -> converter.setObjectMapper(objectMapper));
144135
rt.setRequestFactory(new BufferingClientHttpRequestFactory(httpRequestFactory));
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.sap.ai.sdk.core;
2+
3+
import static com.fasterxml.jackson.databind.MapperFeature.DEFAULT_VIEW_INCLUSION;
4+
5+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
6+
import com.fasterxml.jackson.annotation.JsonInclude;
7+
import com.fasterxml.jackson.annotation.PropertyAccessor;
8+
import com.fasterxml.jackson.databind.DeserializationFeature;
9+
import com.fasterxml.jackson.databind.json.JsonMapper;
10+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
11+
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
12+
import com.google.common.annotations.Beta;
13+
import javax.annotation.Nonnull;
14+
import lombok.AccessLevel;
15+
import lombok.NoArgsConstructor;
16+
17+
/** Internal utility class for getting a default object mapper with preset configuration. */
18+
@NoArgsConstructor(access = AccessLevel.NONE)
19+
public final class JacksonConfiguration {
20+
21+
/**
22+
* Default object mapper used for JSON de-/serialization. <b>Only intended for internal usage
23+
* within this SDK</b>. Largely follows the defaults set by Spring.
24+
*
25+
* @return A new object mapper with the default configuration.
26+
* @see <a
27+
* href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.html">Jackson2ObjectMapperBuilder</a>
28+
*/
29+
@Nonnull
30+
@Beta
31+
public static JsonMapper getDefaultObjectMapper() {
32+
return JsonMapper.builder()
33+
.addModule(new JavaTimeModule())
34+
.addModule(new ParameterNamesModule())
35+
// Disable automatic detection of getters and setters
36+
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
37+
.visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
38+
.serializationInclusion(JsonInclude.Include.NON_NULL)
39+
// for generated code unknown properties should always be stored as custom fields
40+
// still added for any non-generated code and additional safety
41+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
42+
.disable(DEFAULT_VIEW_INCLUSION)
43+
.build();
44+
}
45+
}

foundation-models/openai/pom.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@
3535
<project.rootdir>${project.basedir}/../../</project.rootdir>
3636
</properties>
3737
<dependencies>
38-
<dependency>
39-
<groupId>org.springframework</groupId>
40-
<artifactId>spring-web</artifactId>
41-
</dependency>
4238
<dependency>
4339
<groupId>com.sap.cloud.sdk.cloudplatform</groupId>
4440
<artifactId>cloudplatform-connectivity</artifactId>
@@ -75,10 +71,6 @@
7571
<groupId>com.fasterxml.jackson.core</groupId>
7672
<artifactId>jackson-annotations</artifactId>
7773
</dependency>
78-
<dependency>
79-
<groupId>com.fasterxml.jackson.datatype</groupId>
80-
<artifactId>jackson-datatype-jsr310</artifactId>
81-
</dependency>
8274
<dependency>
8375
<groupId>io.vavr</groupId>
8476
<artifactId>vavr</artifactId>

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

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

3-
import com.fasterxml.jackson.annotation.JsonAutoDetect;
4-
import com.fasterxml.jackson.annotation.JsonInclude;
5-
import com.fasterxml.jackson.annotation.PropertyAccessor;
3+
import static com.sap.ai.sdk.core.JacksonConfiguration.getDefaultObjectMapper;
4+
65
import com.fasterxml.jackson.core.JsonProcessingException;
76
import com.fasterxml.jackson.databind.ObjectMapper;
8-
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
97
import com.google.common.annotations.Beta;
108
import com.sap.ai.sdk.core.AiCoreService;
119
import com.sap.ai.sdk.core.DeploymentResolutionException;
@@ -24,32 +22,22 @@
2422
import java.io.IOException;
2523
import java.util.stream.Stream;
2624
import javax.annotation.Nonnull;
25+
import javax.annotation.Nullable;
2726
import lombok.AccessLevel;
2827
import lombok.RequiredArgsConstructor;
2928
import lombok.extern.slf4j.Slf4j;
3029
import org.apache.hc.client5.http.classic.methods.HttpPost;
3130
import org.apache.hc.core5.http.ContentType;
3231
import org.apache.hc.core5.http.io.entity.StringEntity;
3332
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
34-
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
3533

3634
/** Client for interacting with OpenAI models. */
3735
@Slf4j
3836
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
3937
public final class OpenAiClient {
4038
private static final String DEFAULT_API_VERSION = "2024-02-01";
41-
static final ObjectMapper JACKSON;
42-
private String systemPrompt = null;
43-
44-
static {
45-
JACKSON =
46-
new Jackson2ObjectMapperBuilder()
47-
.modules(new JavaTimeModule())
48-
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
49-
.visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
50-
.serializationInclusion(JsonInclude.Include.NON_NULL)
51-
.build();
52-
}
39+
static final ObjectMapper JACKSON = getDefaultObjectMapper();
40+
@Nullable private String systemPrompt = null;
5341

5442
@Nonnull private final Destination destination;
5543

@@ -154,11 +142,29 @@ public OpenAiChatCompletionOutput chatCompletion(
154142
}
155143

156144
/**
157-
* Generate a completion for the given prompt.
145+
* Stream a completion for the given prompt. Returns a <b>lazily</b> populated stream of text
146+
* chunks. To access more details about the individual chunks, use {@link
147+
* #streamChatCompletionDeltas(OpenAiChatCompletionParameters)}.
148+
*
149+
* <p>The stream should be consumed using a try-with-resources block to ensure that the underlying
150+
* HTTP connection is closed.
151+
*
152+
* <p>Example:
153+
*
154+
* <pre>{@code
155+
* try (var stream = client.streamChatCompletion("...")) {
156+
* stream.forEach(System.out::println);
157+
* }
158+
* }</pre>
159+
*
160+
* <p>Please keep in mind that using a terminal stream operation like {@link Stream#forEach} will
161+
* block until all chunks are consumed. Also, for obvious reasons, invoking {@link
162+
* Stream#parallel()} on this stream is not supported.
158163
*
159164
* @param prompt a text message.
160165
* @return A stream of message deltas
161166
* @throws OpenAiClientException if the request fails or if the finish reason is content_filter
167+
* @see #streamChatCompletionDeltas(OpenAiChatCompletionParameters)
162168
*/
163169
@Nonnull
164170
public Stream<String> streamChatCompletion(@Nonnull final String prompt)
@@ -181,11 +187,31 @@ private static void throwOnContentFilter(@Nonnull final OpenAiChatCompletionDelt
181187
}
182188

183189
/**
184-
* Generate a completion for the given prompt.
190+
* Stream a completion for the given prompt. Returns a <b>lazily</b> populated stream of delta
191+
* objects. To simply stream the text chunks use {@link #streamChatCompletion(String)}
185192
*
186-
* @param parameters the prompt, including messages and other parameters.
187-
* @return A stream of chat completion delta elements.
188-
* @throws OpenAiClientException if the request fails
193+
* <p>The stream should be consumed using a try-with-resources block to ensure that the underlying
194+
* HTTP connection is closed.
195+
*
196+
* <p>Example:
197+
*
198+
* <pre>{@code
199+
* try (var stream = client.streamChatCompletionDeltas(params)) {
200+
* stream
201+
* .peek(delta -> System.out.println(delta.getUsage()))
202+
* .map(OpenAiChatCompletionDelta::getDeltaContent)
203+
* .forEach(System.out::println);
204+
* }
205+
* }</pre>
206+
*
207+
* <p>Please keep in mind that using a terminal stream operation like {@link Stream#forEach} will
208+
* block until all chunks are consumed. Also, for obvious reasons, invoking {@link
209+
* Stream#parallel()} on this stream is not supported.
210+
*
211+
* @param parameters The prompt, including messages and other parameters.
212+
* @return A stream of message deltas
213+
* @throws OpenAiClientException if the request fails or if the finish reason is content_filter
214+
* @see #streamChatCompletion(String)
189215
*/
190216
@Nonnull
191217
public Stream<OpenAiChatCompletionDelta> streamChatCompletionDeltas(
Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,7 @@
11
package com.sap.ai.sdk.foundationmodels.openai;
22

3-
import java.io.Serial;
4-
import javax.annotation.Nonnull;
3+
import lombok.experimental.StandardException;
54

65
/** Generic exception for errors occurring when using OpenAI foundation models. */
7-
public class OpenAiClientException extends RuntimeException {
8-
@Serial private static final long serialVersionUID = -7345541120979974432L;
9-
10-
/**
11-
* Create a new exception with the given message.
12-
*
13-
* @param message the message
14-
*/
15-
public OpenAiClientException(@Nonnull final String message) {
16-
super(message);
17-
}
18-
19-
/**
20-
* Create a new exception with the given message and cause.
21-
*
22-
* @param message the message
23-
* @param e the cause
24-
*/
25-
public OpenAiClientException(@Nonnull final String message, @Nonnull final Exception e) {
26-
super(message, e);
27-
}
28-
}
6+
@StandardException
7+
public class OpenAiClientException extends RuntimeException {}

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

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

3+
import com.google.common.annotations.Beta;
34
import javax.annotation.Nonnull;
45

56
/**
@@ -9,6 +10,7 @@
910
*
1011
* @param <D> the delta type.
1112
*/
13+
@Beta
1214
public interface DeltaAggregatable<D> {
1315

1416
/**

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,7 @@ public String getContent() throws OpenAiClientException {
4747
return Objects.requireNonNullElse(getChoices().get(0).getMessage().getContent(), "");
4848
}
4949

50-
/**
51-
* Add a streamed delta to the total output.
52-
*
53-
* @param delta the delta to add.
54-
*/
50+
@Override
5551
public void addDelta(@Nonnull final OpenAiChatCompletionDelta delta) {
5652
super.addDelta(delta);
5753

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

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -84,18 +84,6 @@ public class OpenAiChatCompletionParameters extends OpenAiCompletionParameters {
8484
@Nullable
8585
private ToolChoice toolChoice;
8686

87-
/**
88-
* <b>NOTE:</b> This method is currently not supported. Therefore, it stays protected.<br>
89-
* <br>
90-
* The configuration entries for Azure OpenAI chat extensions that use them. This additional
91-
* specification is only compatible with Azure OpenAI.
92-
*/
93-
@JsonProperty("data_sources")
94-
@Setter(onParam_ = @Nullable, value = AccessLevel.PROTECTED)
95-
private List<Object> dataSources; // TODO for implementation details, please find
96-
97-
// https://github.com/Azure/azure-rest-api-specs/blob/3cb1b51638616435470fc10ea00de92512186ece/specification/cognitiveservices/data-plane/AzureOpenAI/inference/stable/2024-02-01/inference.json#L1223
98-
9987
/** "response_format": { "type": "json_object" } */
10088
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
10189
@RequiredArgsConstructor

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public class OpenAiChatCompletionTool {
2525
private OpenAiChatCompletionFunction function;
2626

2727
/** The type of the tool. Currently, only `function` is supported. */
28-
// JSON payload sent: { "type": "function" }
2928
@RequiredArgsConstructor
3029
public enum ToolType {
3130
/**

0 commit comments

Comments
 (0)