Skip to content

Commit 6ab1c08

Browse files
TillK17I750911rpanackalnewtork
authored
feat: [Orchestration] Spec update 12a (#267)
* updated orchestration.yaml * regenerated model after spec update * Minimal integration of new features - convenience api does not support MultiChatMessage * Fix deserialization with custom addition - try-catch based approach on Subtype annotation mentions - Also pass custom list of candidates * Fix deserialization with custom addition - try-catch based approach on Subtype annotation mentions - Also pass custom list of candidates * Fix codestyle * release notes for spec update * Update release_notes.md remove version naming * Follow-up update on release_notes.md * update of release_notes.md implementing review feedback * Update docs/release-notes/release_notes.md Co-authored-by: Alexander Dümont <[email protected]> * Orchestration spec update 12a - Tests (#269) * Test - improve coverage on existing tests in addition - tests for failure cases of PolymorphicFallbackDeserializer * Unit test request with image (low level api) * Test calling multi chat response using convenience * minor test method name change * Replace url with a sample --------- Co-authored-by: Roshin Rajan Panackal <[email protected]> * Improve PolymorphicFallbackDeserializer - Generalize test case - add @SInCE and @beta annotation on custom deserializer (public) * lombok on PolymorphicFallbackDeserializer * make PolymorphicFallbackDeserializer AllArgsConstructor protected and use chaining * Add Beta annotation, modify access on deserializer, improve test - make PolymorphicFallbackDeserializer AllArgsConstructor protected and use chaining - Add beta annotation to MultiChatMessageContent and ChatMessagesInner - move request body constraint to stub * Revert @beta annotation (manual) addition on interfaces * Revert stub change --------- Co-authored-by: I750911 <[email protected]> Co-authored-by: Roshin Rajan Panackal <[email protected]> Co-authored-by: Alexander Dümont <[email protected]> Co-authored-by: Roshin Rajan Panackal <[email protected]>
1 parent 172bee8 commit 6ab1c08

File tree

66 files changed

+1927
-137
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1927
-137
lines changed

docs/release-notes/release_notes.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,19 @@
88

99
### 🔧 Compatibility Notes
1010

11+
- `ChatMessage`, as well as new `MultiChatMessage`, are now subtypes of new interface `ChatMessagesInner`.
12+
Most variables or methods previously typed as `ChatMessage` in `model` package are now typed as `ChatMessagesInner`.
1113
- Add missing `@Beta` annotations to all `com.sap.ai.sdk.core.client` and `com.sap.ai.sdk.core.model` classes.
1214

1315
### ✨ New Functionality
1416

15-
-
17+
- Orchestration supports images as input in newly introduced `MultiChatMessage`.
18+
- `MultiChatMessage` also allows for multiple content items (text or image) in one object.
19+
- Grounding input can be masked with `DPIConfig`.
1620

1721
### 📈 Improvements
1822

19-
-
23+
- Update Orchestration client to version 0.43.0 (2412a)
2024

2125
### 🐛 Fixed Issues
2226

orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.sap.ai.sdk.orchestration;
22

3-
import com.sap.ai.sdk.orchestration.model.ChatMessage;
3+
import com.sap.ai.sdk.orchestration.model.ChatMessagesInner;
44
import com.sap.ai.sdk.orchestration.model.CompletionPostRequest;
55
import com.sap.ai.sdk.orchestration.model.ModuleConfigs;
66
import com.sap.ai.sdk.orchestration.model.OrchestrationConfig;
@@ -32,7 +32,10 @@ static CompletionPostRequest toCompletionPostRequest(
3232
OrchestrationConfig.create().moduleConfigurations(toModuleConfigs(configCopy)))
3333
.inputParams(prompt.getTemplateParameters())
3434
.messagesHistory(
35-
prompt.getMessagesHistory().stream().map(Message::createChatMessage).toList());
35+
prompt.getMessagesHistory().stream()
36+
.map(Message::createChatMessage)
37+
.map(ChatMessagesInner.class::cast)
38+
.toList());
3639
}
3740

3841
@Nonnull
@@ -45,7 +48,7 @@ static TemplatingModuleConfig toTemplateModuleConfig(
4548
* In this case, the request will fail, since the templating module will try to resolve the parameter.
4649
* To be fixed with https://github.tools.sap/AI/llm-orchestration/issues/662
4750
*/
48-
val messages = template instanceof Template t ? t.getTemplate() : List.<ChatMessage>of();
51+
val messages = template instanceof Template t ? t.getTemplate() : List.<ChatMessagesInner>of();
4952
val messagesWithPrompt = new ArrayList<>(messages);
5053
messagesWithPrompt.addAll(
5154
prompt.getMessages().stream().map(Message::createChatMessage).toList());

orchestration/src/main/java/com/sap/ai/sdk/orchestration/JacksonMixins.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,7 @@ interface LLMModuleResultMixIn {}
1818
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
1919
@JsonDeserialize(as = LLMChoice.class)
2020
interface ModuleResultsOutputUnmaskingInnerMixIn {}
21+
22+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
23+
interface NoneTypeInfoMixin {}
2124
}

orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationChatResponse.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static lombok.AccessLevel.PACKAGE;
44

55
import com.sap.ai.sdk.orchestration.model.ChatMessage;
6+
import com.sap.ai.sdk.orchestration.model.ChatMessagesInner;
67
import com.sap.ai.sdk.orchestration.model.CompletionPostResponse;
78
import com.sap.ai.sdk.orchestration.model.LLMChoice;
89
import com.sap.ai.sdk.orchestration.model.LLMModuleResultSynchronous;
@@ -50,21 +51,28 @@ public TokenUsage getTokenUsage() {
5051
/**
5152
* Get all messages. This can be used for subsequent prompts as a message history.
5253
*
54+
* @throws UnsupportedOperationException if the MultiChatMessage type message in chat.
5355
* @return A list of all messages.
5456
*/
5557
@Nonnull
56-
public List<Message> getAllMessages() {
58+
public List<Message> getAllMessages() throws UnsupportedOperationException {
5759
final var messages = new ArrayList<Message>();
5860

59-
for (final ChatMessage chatMessage : originalResponse.getModuleResults().getTemplating()) {
60-
final var message =
61-
switch (chatMessage.getRole()) {
62-
case "user" -> new UserMessage(chatMessage.getContent());
63-
case "assistant" -> new AssistantMessage(chatMessage.getContent());
64-
case "system" -> new SystemMessage(chatMessage.getContent());
65-
default -> throw new IllegalStateException("Unexpected role: " + chatMessage.getRole());
66-
};
67-
messages.add(message);
61+
for (final ChatMessagesInner chatMessage :
62+
originalResponse.getModuleResults().getTemplating()) {
63+
if (chatMessage instanceof ChatMessage simpleMsg) {
64+
final var message =
65+
switch (simpleMsg.getRole()) {
66+
case "user" -> new UserMessage(simpleMsg.getContent());
67+
case "assistant" -> new AssistantMessage(simpleMsg.getContent());
68+
case "system" -> new SystemMessage(simpleMsg.getContent());
69+
default -> throw new IllegalStateException("Unexpected role: " + simpleMsg.getRole());
70+
};
71+
messages.add(message);
72+
} else {
73+
throw new UnsupportedOperationException(
74+
"Messages of MultiChatMessage type not supported by convenience API");
75+
}
6876
}
6977

7078
messages.add(new AssistantMessage(getChoice().getMessage().getContent()));

orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55
import com.fasterxml.jackson.core.JsonProcessingException;
66
import com.fasterxml.jackson.databind.JsonNode;
77
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.fasterxml.jackson.databind.module.SimpleModule;
89
import com.fasterxml.jackson.databind.node.ObjectNode;
910
import com.google.common.annotations.Beta;
1011
import com.sap.ai.sdk.core.AiCoreService;
1112
import com.sap.ai.sdk.core.DeploymentResolutionException;
1213
import com.sap.ai.sdk.core.common.ClientResponseHandler;
1314
import com.sap.ai.sdk.core.common.ClientStreamingHandler;
1415
import com.sap.ai.sdk.core.common.StreamedDelta;
16+
import com.sap.ai.sdk.orchestration.model.ChatMessagesInner;
1517
import com.sap.ai.sdk.orchestration.model.CompletionPostRequest;
1618
import com.sap.ai.sdk.orchestration.model.CompletionPostResponse;
1719
import com.sap.ai.sdk.orchestration.model.LLMModuleResult;
@@ -49,6 +51,14 @@ public class OrchestrationClient {
4951
JACKSON.addMixIn(
5052
ModuleResultsOutputUnmaskingInner.class,
5153
JacksonMixins.ModuleResultsOutputUnmaskingInnerMixIn.class);
54+
55+
final var module =
56+
new SimpleModule()
57+
.addDeserializer(
58+
ChatMessagesInner.class,
59+
PolymorphicFallbackDeserializer.fromJsonSubTypes(ChatMessagesInner.class))
60+
.setMixInAnnotation(ChatMessagesInner.class, JacksonMixins.NoneTypeInfoMixin.class);
61+
JACKSON.registerModule(module);
5262
}
5363

5464
@Nonnull private final Supplier<HttpDestination> destinationSupplier;
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package com.sap.ai.sdk.orchestration;
2+
3+
import com.fasterxml.jackson.annotation.JsonSubTypes;
4+
import com.fasterxml.jackson.core.JsonParser;
5+
import com.fasterxml.jackson.databind.DeserializationContext;
6+
import com.fasterxml.jackson.databind.JsonDeserializer;
7+
import com.fasterxml.jackson.databind.JsonMappingException;
8+
import com.google.common.annotations.Beta;
9+
import java.io.IOException;
10+
import java.util.ArrayList;
11+
import java.util.List;
12+
import javax.annotation.Nonnull;
13+
import lombok.AccessLevel;
14+
import lombok.AllArgsConstructor;
15+
16+
/**
17+
* Handles polymorphic deserialization for a base class or interface.
18+
*
19+
* <p>This deserializer uses a fallback strategy by attempting to deserialize the JSON into known
20+
* subtypes. Subtypes are either discovered using the {@link JsonSubTypes} annotation or provided
21+
* explicitly. If deserialization fails for all candidates, a {@link JsonMappingException} is thrown
22+
* with suppressed exceptions.
23+
*
24+
* @since 1.2.0
25+
* @param <T> The base type for deserialization.
26+
*/
27+
@Beta
28+
@AllArgsConstructor(access = AccessLevel.PROTECTED)
29+
public class PolymorphicFallbackDeserializer<T> extends JsonDeserializer<T> {
30+
31+
@Nonnull private final Class<T> baseClass;
32+
@Nonnull private final List<Class<? extends T>> candidates;
33+
34+
/**
35+
* Constructs the deserializer using candidates inferred from the {@link JsonSubTypes} annotation.
36+
*
37+
* @param baseClass The base class or interface to be resolved.
38+
* @throws IllegalStateException If no subtypes are found.
39+
*/
40+
@Nonnull
41+
protected static <T> PolymorphicFallbackDeserializer<T> fromJsonSubTypes(
42+
@Nonnull final Class<T> baseClass) {
43+
final var subTypes = baseClass.getAnnotation(JsonSubTypes.class);
44+
if (subTypes == null || subTypes.value().length == 0) {
45+
throw new IllegalStateException("No subtypes found for " + baseClass.getName());
46+
}
47+
48+
final var candidates = new ArrayList<Class<? extends T>>();
49+
for (final var subType : subTypes.value()) {
50+
candidates.add((Class<? extends T>) subType.value());
51+
}
52+
53+
return PolymorphicFallbackDeserializer.fromCandidates(baseClass, candidates);
54+
}
55+
56+
/**
57+
* Constructs the deserializer with an explicit given list of candidate types.
58+
*
59+
* @param baseClass The base class or interface to be resolved.
60+
* @param candidates A list of candidate classes to try deserialization.
61+
*/
62+
@Nonnull
63+
protected static <T> PolymorphicFallbackDeserializer<T> fromCandidates(
64+
@Nonnull final Class<T> baseClass, @Nonnull final List<Class<? extends T>> candidates) {
65+
return new PolymorphicFallbackDeserializer<>(baseClass, candidates);
66+
}
67+
68+
/**
69+
* Deserializes the JSON into the first matching candidate type.
70+
*
71+
* @param jsonParser The parser providing the JSON.
72+
* @param deserializationContext The deserialization context.
73+
* @return The deserialized object of a matching candidate type.
74+
* @throws JsonMappingException If deserialization fails for all candidates.
75+
* @throws IOException If json content cannot be consumed.
76+
*/
77+
@Nonnull
78+
@Override
79+
public T deserialize(
80+
@Nonnull final JsonParser jsonParser,
81+
@Nonnull final DeserializationContext deserializationContext)
82+
throws IOException {
83+
84+
final var root = jsonParser.readValueAsTree();
85+
final var suppressed = new ArrayList<JsonMappingException>();
86+
87+
for (final var candidate : candidates) {
88+
try {
89+
return jsonParser.getCodec().treeToValue(root, candidate);
90+
} catch (JsonMappingException e) {
91+
suppressed.add(e);
92+
}
93+
}
94+
95+
final var aggregateException =
96+
JsonMappingException.from(
97+
jsonParser,
98+
"PolymorphicFallbackDeserializer failed to deserialize "
99+
+ this.baseClass.getName()
100+
+ ". Attempted candidates: "
101+
+ candidates.stream().map(Class::getName).toList());
102+
103+
suppressed.forEach(aggregateException::addSuppressed);
104+
throw aggregateException;
105+
}
106+
}

orchestration/src/main/java/com/sap/ai/sdk/orchestration/model/AzureContentSafety.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Orchestration
33
* Orchestration is an inference service which provides common additional capabilities for business AI scenarios, such as content filtering and data masking. At the core of the service is the LLM module which allows for an easy, harmonized access to the language models of gen AI hub. The service is designed to be modular and extensible, allowing for the addition of new modules in the future. Each module can be configured independently and at runtime, allowing for a high degree of flexibility in the orchestration of AI services.
44
*
5-
* The version of the OpenAPI document: 0.36.1
5+
* The version of the OpenAPI document: 0.43.0
66
*
77
*
88
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

orchestration/src/main/java/com/sap/ai/sdk/orchestration/model/AzureContentSafetyFilterConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Orchestration
33
* Orchestration is an inference service which provides common additional capabilities for business AI scenarios, such as content filtering and data masking. At the core of the service is the LLM module which allows for an easy, harmonized access to the language models of gen AI hub. The service is designed to be modular and extensible, allowing for the addition of new modules in the future. Each module can be configured independently and at runtime, allowing for a high degree of flexibility in the orchestration of AI services.
44
*
5-
* The version of the OpenAPI document: 0.36.1
5+
* The version of the OpenAPI document: 0.43.0
66
*
77
*
88
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

orchestration/src/main/java/com/sap/ai/sdk/orchestration/model/AzureThreshold.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Orchestration
33
* Orchestration is an inference service which provides common additional capabilities for business AI scenarios, such as content filtering and data masking. At the core of the service is the LLM module which allows for an easy, harmonized access to the language models of gen AI hub. The service is designed to be modular and extensible, allowing for the addition of new modules in the future. Each module can be configured independently and at runtime, allowing for a high degree of flexibility in the orchestration of AI services.
44
*
5-
* The version of the OpenAPI document: 0.36.1
5+
* The version of the OpenAPI document: 0.43.0
66
*
77
*
88
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -16,10 +16,7 @@
1616
import com.fasterxml.jackson.annotation.JsonValue;
1717
import javax.annotation.Nonnull;
1818

19-
/**
20-
* Threshold for the filter. Setting it to &#x60;0&#x60; blocks content with low severity, whereas
21-
* &#x60;6&#x60; is the most permissive and blocks only the highest severity
22-
*/
19+
/** Gets or Sets AzureThreshold */
2320
public enum AzureThreshold {
2421
NUMBER_0(0),
2522

orchestration/src/main/java/com/sap/ai/sdk/orchestration/model/ChatDelta.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Orchestration
33
* Orchestration is an inference service which provides common additional capabilities for business AI scenarios, such as content filtering and data masking. At the core of the service is the LLM module which allows for an easy, harmonized access to the language models of gen AI hub. The service is designed to be modular and extensible, allowing for the addition of new modules in the future. Each module can be configured independently and at runtime, allowing for a high degree of flexibility in the orchestration of AI services.
44
*
5-
* The version of the OpenAPI document: 0.36.1
5+
* The version of the OpenAPI document: 0.43.0
66
*
77
*
88
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

0 commit comments

Comments
 (0)