Skip to content

Commit e03ac0e

Browse files
committed
Refactor Jackson into central mapper config
1 parent 0c77064 commit e03ac0e

File tree

17 files changed

+134
-134
lines changed

17 files changed

+134
-134
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: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
package com.sap.ai.sdk.core;
22

3+
import static com.fasterxml.jackson.databind.MapperFeature.DEFAULT_VIEW_INCLUSION;
4+
35
import com.fasterxml.jackson.annotation.JsonAutoDetect;
46
import com.fasterxml.jackson.annotation.JsonInclude;
57
import com.fasterxml.jackson.annotation.PropertyAccessor;
8+
import com.fasterxml.jackson.databind.DeserializationFeature;
9+
import com.fasterxml.jackson.databind.json.JsonMapper;
610
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
11+
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
12+
import com.google.common.annotations.Beta;
713
import com.google.common.collect.Iterables;
814
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
915
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
@@ -21,7 +27,6 @@
2127
import lombok.val;
2228
import org.springframework.http.client.BufferingClientHttpRequestFactory;
2329
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
24-
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
2530
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
2631
import org.springframework.web.client.RestTemplate;
2732

@@ -33,6 +38,7 @@ public class AiCoreService implements AiCoreDestination {
3338
static final String AI_RESOURCE_GROUP = "URL.headers.AI-Resource-Group";
3439

3540
private static final DeploymentCache DEPLOYMENT_CACHE = new DeploymentCache();
41+
private static final JsonMapper objectMapper = getDefaultObjectMapper();
3642

3743
@Nonnull private final DestinationResolver destinationResolver;
3844

@@ -198,14 +204,6 @@ protected DefaultHttpDestination.Builder getDestinationBuilder(
198204
*/
199205
@Nonnull
200206
protected ApiClient buildApiClient(@Nonnull final Destination destination) {
201-
val objectMapper =
202-
new Jackson2ObjectMapperBuilder()
203-
.modules(new JavaTimeModule())
204-
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
205-
.visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
206-
.serializationInclusion(JsonInclude.Include.NON_NULL) // THIS STOPS `null` serialization
207-
.build();
208-
209207
val httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
210208
httpRequestFactory.setHttpClient(ApacheHttpClient5Accessor.getHttpClient(destination));
211209

@@ -227,4 +225,29 @@ protected ApiClient buildApiClient(@Nonnull final Destination destination) {
227225
public void reloadCachedDeployments(@Nonnull final String resourceGroup) {
228226
DEPLOYMENT_CACHE.resetCache(this, resourceGroup);
229227
}
228+
229+
/**
230+
* Default object mapper used for JSON de-/serialization. <b>Only intended for internal usage
231+
* within this SDK</b>. Largely follows the defaults set by Spring.
232+
*
233+
* @return A new object mapper with the default configuration.
234+
* @see <a
235+
* href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/converter/json/Jackson2ObjectMapperBuilder.html">Jackson2ObjectMapperBuilder</a>
236+
*/
237+
@Nonnull
238+
@Beta
239+
public static JsonMapper getDefaultObjectMapper() {
240+
return JsonMapper.builder()
241+
.addModule(new JavaTimeModule())
242+
.addModule(new ParameterNamesModule())
243+
// Disable automatic detection of getters and setters
244+
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
245+
.visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
246+
.serializationInclusion(JsonInclude.Include.NON_NULL)
247+
// for generated code unknown properties should always be stored as custom fields
248+
// still added for any non-generated code and additional safety
249+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
250+
.disable(DEFAULT_VIEW_INCLUSION)
251+
.build();
252+
}
230253
}

foundation-models/openai/pom.xml

Lines changed: 4 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>
@@ -87,6 +79,10 @@
8779
<groupId>org.slf4j</groupId>
8880
<artifactId>slf4j-api</artifactId>
8981
</dependency>
82+
<dependency>
83+
<groupId>com.google.guava</groupId>
84+
<artifactId>guava</artifactId>
85+
</dependency>
9086
<!-- scope "provided" -->
9187
<dependency>
9288
<groupId>org.projectlombok</groupId>

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.AiCoreService.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.sap.ai.sdk.core.AiCoreService;
108
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionDelta;
119
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionOutput;
@@ -21,32 +19,22 @@
2119
import java.io.IOException;
2220
import java.util.stream.Stream;
2321
import javax.annotation.Nonnull;
22+
import javax.annotation.Nullable;
2423
import lombok.AccessLevel;
2524
import lombok.RequiredArgsConstructor;
2625
import lombok.extern.slf4j.Slf4j;
2726
import org.apache.hc.client5.http.classic.methods.HttpPost;
2827
import org.apache.hc.core5.http.ContentType;
2928
import org.apache.hc.core5.http.io.entity.StringEntity;
3029
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
31-
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
3230

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

5139
@Nonnull private final Destination destination;
5240

@@ -147,11 +135,29 @@ public OpenAiChatCompletionOutput chatCompletion(
147135
}
148136

149137
/**
150-
* Generate a completion for the given prompt.
138+
* Stream a completion for the given prompt. Returns a <b>lazily</b> populated stream of text
139+
* chunks. To access more details about the individual chunks, use {@link
140+
* #streamChatCompletionDeltas(OpenAiChatCompletionParameters)}.
141+
*
142+
* <p>The stream should be consumed using a try-with-resources block to ensure that the underlying
143+
* HTTP connection is closed.
144+
*
145+
* <p>Example:
146+
*
147+
* <pre>{@code
148+
* try (var stream = client.streamChatCompletion("...")) {
149+
* stream.forEach(System.out::println);
150+
* }
151+
* }</pre>
152+
*
153+
* <p>Please keep in mind that using a terminal stream operation like {@link Stream#forEach} will
154+
* block until all chunks are consumed. Also, for obvious reasons, invoking {@link
155+
* Stream#parallel()} on this stream is not supported.
151156
*
152157
* @param prompt a text message.
153158
* @return A stream of message deltas
154159
* @throws OpenAiClientException if the request fails or if the finish reason is content_filter
160+
* @see #streamChatCompletionDeltas(OpenAiChatCompletionParameters)
155161
*/
156162
@Nonnull
157163
public Stream<String> streamChatCompletion(@Nonnull final String prompt)
@@ -174,11 +180,31 @@ private static void throwOnContentFilter(@Nonnull final OpenAiChatCompletionDelt
174180
}
175181

176182
/**
177-
* Generate a completion for the given prompt.
183+
* Stream a completion for the given prompt. Returns a <b>lazily</b> populated stream of delta
184+
* objects. To simply stream the text chunks use {@link #streamChatCompletion(String)}
178185
*
179-
* @param parameters the prompt, including messages and other parameters.
180-
* @return A stream of chat completion delta elements.
181-
* @throws OpenAiClientException if the request fails
186+
* <p>The stream should be consumed using a try-with-resources block to ensure that the underlying
187+
* HTTP connection is closed.
188+
*
189+
* <p>Example:
190+
*
191+
* <pre>{@code
192+
* try (var stream = client.streamChatCompletionDeltas(params)) {
193+
* stream
194+
* .peek(delta -> System.out.println(delta.getUsage()))
195+
* .map(OpenAiChatCompletionDelta::getDeltaContent)
196+
* .forEach(System.out::println);
197+
* }
198+
* }</pre>
199+
*
200+
* <p>Please keep in mind that using a terminal stream operation like {@link Stream#forEach} will
201+
* block until all chunks are consumed. Also, for obvious reasons, invoking {@link
202+
* Stream#parallel()} on this stream is not supported.
203+
*
204+
* @param parameters The prompt, including messages and other parameters.
205+
* @return A stream of message deltas
206+
* @throws OpenAiClientException if the request fails or if the finish reason is content_filter
207+
* @see #streamChatCompletion(String)
182208
*/
183209
@Nonnull
184210
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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public String getContent() throws OpenAiClientException {
5252
*
5353
* @param delta the delta to add.
5454
*/
55+
@Override
5556
public void addDelta(@Nonnull final OpenAiChatCompletionDelta delta) {
5657
super.addDelta(delta);
5758

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
/**

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class OpenAiContentFilterPromptResults extends OpenAiContentFilterResults
1717
@Getter(onMethod_ = @Nullable)
1818
private OpenAiContentFilterDetectedResult jailbreak;
1919

20+
@Override
2021
void addDelta(@Nonnull final OpenAiContentFilterPromptResults delta) {
2122
super.addDelta(delta);
2223

0 commit comments

Comments
 (0)