Skip to content

Commit 15f4347

Browse files
Merge branch 'refs/heads/main' into bean
# Conflicts: # docs/release-notes/release_notes.md
2 parents 2d20234 + 4ba7069 commit 15f4347

File tree

69 files changed

+2032
-156
lines changed

Some content is hidden

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

69 files changed

+2032
-156
lines changed

.github/workflows/deploy-snapshot.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,14 @@ jobs:
3737
env:
3838
DEPLOYMENT_USER: ${{ secrets.ARTIFACTORY_COMMON_USER }}
3939
DEPLOYMENT_PASS: ${{ secrets.ARTIFACTORY_COMMON_PASSWORD }}
40+
41+
- name: "Slack Notification"
42+
if: failure()
43+
uses: slackapi/slack-github-action@v2.0.0
44+
with:
45+
webhook: ${{ secrets.SLACK_WEBHOOK }}
46+
webhook-type: incoming-webhook
47+
payload: |
48+
{
49+
"text": "⚠️ Snapshot Deployment failed! 😬 Please inspect & fix by clicking <https://github.com/SAP/ai-sdk-java/actions/runs/${{ github.run_id }}|here>"
50+
}

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ The SDK simplifies the setup and interaction with SAP AI Core, allowing you to f
2121
- [Run the Application Locally](#run-the-application-locally)
2222
- [Explore Further Capabilities](#explore-further-capabilities)
2323
- [Documentation](#documentation)
24+
- [Build the Project](#build-the-project)
2425
- [FAQs](#faqs)
2526
- [Contribute, Support and Feedback](#contribute-support-and-feedback)
2627
- [Security / Disclosure](#security--disclosure)
@@ -137,6 +138,18 @@ For more detailed information and advanced usage, please refer to the following:
137138

138139
For updating versions, please refer to the [**Release Notes**](docs/release-notes/release-notes-0-to-14.md).
139140

141+
## Build the Project
142+
143+
You can build the project using Maven:
144+
145+
```shell
146+
mvn clean install -DskipTests
147+
```
148+
149+
This will install the current `SNAPSHOT` version of the project into your local Maven repository.
150+
151+
For SAP internal development, you can also use `SNAPSHOT` builds from the [internal](https://int.repositories.cloud.sap/ui/repos/tree/General/proxy-build-snapshots-cloudsdk/com/sap/ai/sdk) and [internet-facing](https://common.repositories.cloud.sap/artifactory/build-snapshots-cloudsdk/com/sap/ai/sdk/) Artifactory.
152+
140153
## FAQs
141154

142155
### _"How to add a custom header to AI Core requests?"_

docs/adrs/005-release-strategy.md

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ Since most new features will rely on generated code, the options for Java are li
2727
Testing can be performed against multiple landscapes and can vary based on the type of tests.
2828

2929
- Unit tests may use test data from any landscape (including development landscapes) manually tested with e.g. Bruno.
30-
- E2E tests may use canary or production landscapes.
30+
- E2E tests may use integration, canary or production landscapes.
3131
In case of multiple landscapes:
3232
- GitHub matrix builds can be used to easily testing against multiple landscapes
3333
- For any differences between landscapes, test toggles need to be considered (e.g. `@EnabledIfSystemProperty`)
3434
- Such toggles come with a bit of additional maintenance cost, as they need to be removed once the feature is released to all landscapes
35+
- Alternatively, E2E tests may be executed manually during development against any landscape
3536

3637
## Release
3738

@@ -56,47 +57,45 @@ However, here there would be more freedom in when a feature toggle is enabled fo
5657

5758
We decide as follows:
5859

59-
> 1. New AI SDK versions are released roughly every 2 weeks, shortly after new AI Core versions have been released to _production EU10_.
60-
> 2. Any _publicly available release_ of the AI SDKs must only contain _public API_ for AI Core features available in _production EU10_ under the service plan _extended_.
61-
> 3. E2E tests run against canary EU12 and production EU10 using test toggles.
60+
> 1. New AI SDK versions are released roughly every 2 weeks, shortly after new AI Core versions have been released to **all** landscapes.
61+
> 2. Any _publicly available release_ of the AI SDKs must only contain _public API_ for AI Core features available in **any** landscape under the service plan _extended_.
62+
> 3. E2E tests run automatically against canary only (EU12 for Java and JS, EU10 for Python). Production EU10 can be used for manual test runs.
6263
6364
Further explanations and notes:
6465

6566
- Any features released exclusively under the `sap-internal` plan are not supported.
66-
Similarly, any features released only to specific landscapes (other than prod EU10) are not supported.
67-
- There will be no releases of the SDKs to internal artifactory.
67+
- There will be no stable releases of the SDKs to internal artifactory.
68+
For Java, we deliver SNAPSHOT versions to Artifactory, for JS we deliver canary releases to NPM.
6869
- Please note that the following is allowed:
6970
- Public API in an unreleased SDK version for unreleased AI Core features.
7071
Notably, this will **block** the release of the SDK until the AI Core feature is released publicly.
7172
- Internal code in a released SDK version for unreleased AI Core features.
73+
- Testing against additional landscapes is possible, but requires maintaining test toggles.
7274

7375
### An Example Development Lifecycle Iteration
7476

7577
The following depicts a development flow where the AI SDK development steps are performed as soon as possible.
7678

7779
1. A new AI Core feature is being developed.
78-
2. A PR is raised on the AI SDK with a corresponding implementation, but so far not E2E-tested.
79-
- Potentially aided by unit tests based on test data manually copied from e.g. Bruno.
80+
- (optional) A preview of the changes is created on-demand on the AI SDK.
8081
- Generated code is created from a development version of the relevant spec file.
81-
3. (+2 weeks later) The feature is released to EU12 canary landscape.
82-
4. The AI SDK PR is enhanced:
83-
- With an updated spec file.
84-
- With an E2E test against canary.
85-
5. The AI SDK PR is merged.
86-
6. (+1 week later) The feature is released to EU10 production landscape.
87-
7. The E2E test is updated to now also run against production.
88-
8. The AI SDK is released publicly.
82+
- Potentially aided by unit tests based on test data manually copied from e.g. Bruno.
83+
2. (+2 weeks later) The feature is released to EU12 canary landscape.
84+
3. An AI SDK PR is automatically raised with an updated spec file.
85+
- If required, E2E tests are enhanced to cover the additional feature scope.
86+
4. The AI SDK PR is merged.
87+
5. (+1 week later) The feature is released to EU10 production landscape.
88+
6. The AI SDK is released publicly.
8989

9090
In case of delays in the release process of AI Core:
9191

92-
- If step (3) is delayed, the open PR may be closed and re-opened later
93-
- If step (5) is delayed, we consider 3 options:
92+
- If step (6) is delayed, we consider 3 options:
9493
1. The PR is reverted, together with potentially other related PRs
9594
2. The AI SDK release is delayed equally
9695
3. The AI SDK is released anyway with exceptional PO approval
9796

9897
## Further Links
9998

10099
- The single source of truth for all landscapes is in [mlf-gitops](https://github.tools.sap/MLF-prod/mlf-gitops-prod)
101-
- In particular, we care about the [version of orchestration in Prod EU10](https://github.tools.sap/MLF-prod/mlf-gitops-prod/blob/aws.eu-central-1.prod-eu/current/services/llm-orchestration/source/Chart.yaml)
102-
- [This JIRA ticket](https://jira.tools.sap/browse/AI-44024) tracks releases
100+
- In particular, we care about the [version of orchestration](https://github.tools.sap/MLF-prod/mlf-gitops-prod/blob/aws.eu-central-1.prod-eu/current/services/llm-orchestration/source/Chart.yaml)
101+
- [This JIRA ticket](https://jira.tools.sap/browse/AI-44024) tracks releases

docs/release-notes/release_notes.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,20 @@
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

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`.
1520
- [Integrated the Orchestration client in Spring AI.](../guides/ORCHESTRATION_CHAT_COMPLETION.md#spring-ai-integration)
1621

1722
### 📈 Improvements
1823

19-
-
24+
- Update Orchestration client to version 0.43.0 (2412a)
2025

2126
### 🐛 Fixed Issues
2227

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+
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).

0 commit comments

Comments
 (0)