Skip to content

Commit 2c7fc4b

Browse files
authored
Merge branch 'main' into orchestration-convenience/typed-model-parameters
2 parents 3dfd323 + ebf749d commit 2c7fc4b

File tree

305 files changed

+7688
-5712
lines changed

Some content is hidden

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

305 files changed

+7688
-5712
lines changed

.github/workflows/e2e-test.yaml

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@ name: "End-to-end Tests"
22
on:
33
workflow_dispatch:
44
schedule:
5-
- cron: 0 2 * * *
5+
- cron: 15 2 * * MON-FRI
66

77
env:
88
MVN_MULTI_THREADED_ARGS: --batch-mode --no-transfer-progress --fail-at-end --show-version --threads 1C
99
JAVA_VERSION: 17
1010

1111
jobs:
12-
1312
end-to-end-tests:
14-
# https://wiki.one.int.sap/wiki/display/DevFw/SUGAR
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
environment: [ canary, production ]
17+
exclude:
18+
- environment: production
19+
# secret-name: AI_CORE_PRODUCTION
20+
include:
21+
- environment: canary
22+
secret-name: AI_CORE_CANARY
1523
runs-on: ubuntu-latest
1624
steps:
1725

@@ -33,10 +41,10 @@ jobs:
3341
- name: "Run tests"
3442
run: |
3543
MVN_ARGS="${{ env.MVN_MULTI_THREADED_ARGS }} surefire:test -pl :spring-app -DskipTests=false"
36-
mvn $MVN_ARGS
44+
mvn $MVN_ARGS "-Daicore.landscape=${{ matrix.environment }}"
3745
env:
3846
# See "End-to-end test application instructions" on the README.md to update the secret
39-
AICORE_SERVICE_KEY: ${{ secrets.AICORE_SERVICE_KEY }}
47+
AICORE_SERVICE_KEY: ${{ secrets[matrix.secret-name] }}
4048

4149
- name: "Start Application Locally"
4250
run: |
@@ -53,7 +61,7 @@ jobs:
5361
done
5462
env:
5563
# See "End-to-end test application instructions" on the README.md to update the secret
56-
AICORE_SERVICE_KEY: ${{ secrets.AICORE_SERVICE_KEY }}
64+
AICORE_SERVICE_KEY: ${{ secrets[matrix.secret-name] }}
5765

5866
- name: "Health Check"
5967
# print response body with headers to stdout. q:body only O:print -:stdout S:headers

.github/workflows/fosstars-report.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ jobs:
2222
distribution: "temurin"
2323
java-version: ${{ env.JAVA_VERSION }}
2424
cache: 'maven'
25+
- name: Restore CVE Database
26+
uses: actions/cache/restore@v4
27+
with:
28+
path: ${{ env.CVE_CACHE_DIR }}
29+
key: ${{ env.CVE_CACHE_KEY }}
30+
fail-on-cache-miss: true
2531

2632
- name: "Build SDK"
2733
run: |
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
name: Update Vulnerability Database
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: '42 03 * * MON-FRI' # 03:42 on weekdays, a somewhat random time to avoid producing load spikes on the GH actions infrastructure
7+
8+
env:
9+
CVE_CACHE_REF: refs/heads/main
10+
CVE_CACHE_KEY: cve-db
11+
CVE_CACHE_DIR: ~/.m2/repository/org/owasp/dependency-check-data
12+
13+
jobs:
14+
update-vulnerability-database:
15+
runs-on: ubuntu-latest
16+
permissions:
17+
contents: write
18+
steps:
19+
- uses: actions/checkout@v4
20+
with:
21+
ref: ${{ env.CVE_CACHE_REF }}
22+
- name: Restore Existing Cache
23+
uses: actions/cache/restore@v4
24+
with:
25+
path: ${{ env.CVE_CACHE_DIR }}
26+
key: ${{ env.CVE_CACHE_KEY }}
27+
28+
- name: Run Maven Plugin
29+
run: |
30+
mvn org.owasp:dependency-check-maven:10.0.4:update-only -DnvdMaxRetryCount=10 -DnvdApiDelay=15000 -DconnectionTimeout=60000
31+
env:
32+
NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
33+
34+
- name: Delete Cache
35+
run: |
36+
CACHE_IDS=$(gh cache list --key "${{ env.CVE_CACHE_KEY }}" --ref "${{ env.CVE_CACHE_REF }}" --json id | jq -r '.[] | .id')
37+
for CACHE_ID in $CACHE_IDS; do
38+
echo "Deleting cache with ID: $CACHE_ID"
39+
gh cache delete "${CACHE_ID}"
40+
done
41+
env:
42+
GH_TOKEN: ${{ secrets.CLOUD_SDK_AT_SAP_ALL_ACCESS_PAT }}
43+
44+
- name: Cache CVE Database
45+
uses: actions/cache/save@v4
46+
with:
47+
path: ${{ env.CVE_CACHE_DIR }}
48+
key: ${{ env.CVE_CACHE_KEY }}
49+
50+
# - name: "Slack Notification"
51+
# if: failure()
52+
# uses: slackapi/[email protected]
53+
# with:
54+
# payload: |
55+
# {
56+
# "text": "⚠️ OWASP Update Failed! 😬 Please inspect & fix by clicking <https://github.com/SAP/ai-sdk-java/actions/runs/${{ github.run_id }}|here>"
57+
# }
58+
# env:
59+
# SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
60+
# SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
61+
62+

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,21 @@ The SDK simplifies the setup and interaction with SAP AI Core, allowing you to f
3737

3838
## General Requirements
3939

40-
To use the SAP AI SDK for Java, the following general prerequisites must be met:
40+
To use the SDK in a Java application, it is necessary to understand the technical prerequisites and required versions for common dependencies.
4141

42-
- **Java Development Kit (JDK) 17** or higher installed.
43-
- **Apache Maven 3.9** or higher installed.
4442
- **SAP AI Core Service** enabled in your SAP BTP account.
4543
- [How to enable the AI Core service](https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/initial-setup)
4644
- **SAP AI Core Credentials** to access the AI Core service.
4745
- [Connecting to SAP AI Core](#connecting-to-sap-ai-core)
48-
- **(Optional) Spring Boot** version 3 or higher if you are using Spring Boot.
46+
47+
The following table lists the required versions, based on the latest release:
48+
49+
| Dependency | Minimum Version | Recommended Version |
50+
| --- | --- | --- |
51+
| JDK | 17 (LTS) | 21 (LTS) |
52+
| SAP Cloud SDK | 5.6.0 | latest |
53+
| (optional) CAP Java | 3.0.0 | latest |
54+
| (optional) Spring Boot | 3.0 | latest |
4955

5056
See [an example `pom.xml` in our Spring Boot application](sample-code/spring-app/pom.xml).
5157

core/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@
157157
<groupId>com.sap.cloud.sdk.datamodel</groupId>
158158
<artifactId>openapi-generator-maven-plugin</artifactId>
159159
<configuration>
160+
<skip>true</skip>
161+
<!-- TODO: remove this once Cloud SDK 5.15.0 is released -->
160162
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
161163
<apiMaturity>released</apiMaturity>
162164
<enableOneOfAnyOfGeneration>true</enableOneOfAnyOfGeneration>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.sap.ai.sdk.core;
2+
3+
import lombok.experimental.StandardException;
4+
5+
/** Exception thrown when the JSON AI Core service key is invalid. */
6+
@StandardException
7+
class AiCoreCredentialsInvalidException extends RuntimeException {}

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

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

3-
import com.sap.cloud.sdk.cloudplatform.connectivity.Destination;
3+
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination;
44
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
55
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException;
66
import com.sap.cloud.sdk.services.openapi.apiclient.ApiClient;
@@ -30,7 +30,8 @@ public AiCoreDeployment withResourceGroup(@Nonnull final String resourceGroup) {
3030

3131
@Nonnull
3232
@Override
33-
public Destination destination() throws DestinationAccessException, DestinationNotFoundException {
33+
public HttpDestination destination()
34+
throws DestinationAccessException, DestinationNotFoundException {
3435
aiCoreService.deploymentId = deploymentId.get();
3536
return aiCoreService.destination();
3637
}

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

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

3-
import static com.sap.ai.sdk.core.DestinationResolver.AI_CLIENT_TYPE_KEY;
4-
import static com.sap.ai.sdk.core.DestinationResolver.AI_CLIENT_TYPE_VALUE;
5-
63
import com.fasterxml.jackson.annotation.JsonAutoDetect;
74
import com.fasterxml.jackson.annotation.JsonInclude;
85
import com.fasterxml.jackson.annotation.PropertyAccessor;
@@ -12,15 +9,14 @@
129
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
1310
import com.sap.cloud.sdk.cloudplatform.connectivity.Destination;
1411
import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationProperty;
12+
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination;
1513
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
1614
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException;
1715
import com.sap.cloud.sdk.services.openapi.apiclient.ApiClient;
18-
import io.github.cdimascio.dotenv.Dotenv;
1916
import java.util.NoSuchElementException;
2017
import java.util.function.BiFunction;
2118
import java.util.function.Function;
2219
import javax.annotation.Nonnull;
23-
import lombok.RequiredArgsConstructor;
2420
import lombok.extern.slf4j.Slf4j;
2521
import lombok.val;
2622
import org.springframework.http.client.BufferingClientHttpRequestFactory;
@@ -31,19 +27,21 @@
3127

3228
/** Connectivity convenience methods for AI Core. */
3329
@Slf4j
34-
@RequiredArgsConstructor
3530
public class AiCoreService implements AiCoreDestination {
36-
37-
Function<AiCoreService, Destination> baseDestinationHandler;
38-
final BiFunction<AiCoreService, Destination, ApiClient> clientHandler;
39-
final BiFunction<AiCoreService, Destination, DefaultHttpDestination.Builder> builderHandler;
31+
static final String AI_CLIENT_TYPE_KEY = "URL.headers.AI-Client-Type";
32+
static final String AI_CLIENT_TYPE_VALUE = "AI SDK Java";
33+
static final String AI_RESOURCE_GROUP = "URL.headers.AI-Resource-Group";
4034

4135
private static final DeploymentCache DEPLOYMENT_CACHE = new DeploymentCache();
4236

43-
private static final String AI_RESOURCE_GROUP = "URL.headers.AI-Resource-Group";
37+
@Nonnull private final DestinationResolver destinationResolver;
4438

45-
/** loads the .env file from the root of the project */
46-
private static final Dotenv DOTENV = Dotenv.configure().ignoreIfMissing().load();
39+
@Nonnull private Function<AiCoreService, HttpDestination> baseDestinationHandler;
40+
@Nonnull private final BiFunction<AiCoreService, HttpDestination, ApiClient> clientHandler;
41+
42+
@Nonnull
43+
private final BiFunction<AiCoreService, HttpDestination, DefaultHttpDestination.Builder>
44+
builderHandler;
4745

4846
/** The resource group is defined by AiCoreDeployment.withResourceGroup(). */
4947
@Nonnull String resourceGroup;
@@ -53,8 +51,16 @@ public class AiCoreService implements AiCoreDestination {
5351

5452
/** The default constructor. */
5553
public AiCoreService() {
56-
this(AiCoreService::getApiClient, AiCoreService::getDestinationBuilder, "default", "");
54+
this(new DestinationResolver());
55+
}
56+
57+
AiCoreService(@Nonnull final DestinationResolver destinationResolver) {
58+
this.destinationResolver = destinationResolver;
5759
baseDestinationHandler = AiCoreService::getBaseDestination;
60+
clientHandler = AiCoreService::buildApiClient;
61+
builderHandler = AiCoreService::getDestinationBuilder;
62+
resourceGroup = "default";
63+
deploymentId = "";
5864
}
5965

6066
@Nonnull
@@ -66,7 +72,7 @@ public ApiClient client() {
6672

6773
@Nonnull
6874
@Override
69-
public Destination destination() {
75+
public HttpDestination destination() {
7076
val dest = baseDestinationHandler.apply(this);
7177
val builder = builderHandler.apply(this, dest);
7278
if (!deploymentId.isEmpty()) {
@@ -107,7 +113,7 @@ protected void destinationSetHeaders(@Nonnull final DefaultHttpDestination.Build
107113
* @return The AI Core Service based on the provided destination.
108114
*/
109115
@Nonnull
110-
public AiCoreService withDestination(@Nonnull final Destination destination) {
116+
public AiCoreService withDestination(@Nonnull final HttpDestination destination) {
111117
baseDestinationHandler = service -> destination;
112118
return this;
113119
}
@@ -161,10 +167,9 @@ public AiCoreDeployment forDeploymentByScenario(@Nonnull final String scenarioId
161167
* @throws DestinationNotFoundException If the destination cannot be found.
162168
*/
163169
@Nonnull
164-
protected Destination getBaseDestination()
170+
protected HttpDestination getBaseDestination()
165171
throws DestinationAccessException, DestinationNotFoundException {
166-
val serviceKey = DOTENV.get("AICORE_SERVICE_KEY");
167-
return DestinationResolver.getDestination(serviceKey);
172+
return destinationResolver.getDestination();
168173
}
169174

170175
/**
@@ -186,14 +191,13 @@ protected DefaultHttpDestination.Builder getDestinationBuilder(
186191
}
187192

188193
/**
189-
* Get a destination using the default service binding loading logic.
194+
* Build an {@link ApiClient} that can be used for executing plain REST HTTP calls.
190195
*
191-
* @return The destination.
192-
* @throws DestinationAccessException If the destination cannot be accessed.
193-
* @throws DestinationNotFoundException If the destination cannot be found.
196+
* @param destination The destination to use as basis for the client.
197+
* @return The new API client.
194198
*/
195199
@Nonnull
196-
protected ApiClient getApiClient(@Nonnull final Destination destination) {
200+
protected ApiClient buildApiClient(@Nonnull final Destination destination) {
197201
val objectMapper =
198202
new Jackson2ObjectMapperBuilder()
199203
.modules(new JavaTimeModule())

0 commit comments

Comments
 (0)