Skip to content

Commit 4a3f363

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/orchestration/springai-embed
# Conflicts: # foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/spring/OpenAiSpringEmbeddingModel.java
2 parents fc29a39 + f42b195 commit 4a3f363

File tree

12 files changed

+161
-38
lines changed

12 files changed

+161
-38
lines changed

.github/ISSUE_TEMPLATE/bug-report.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Bug Report
1+
name: Problem/Bug Report
22
description: Released feature does not work as expected in the SAP Cloud SDK for AI (Java).
33
labels:
44
- bug
@@ -11,8 +11,8 @@ body:
1111
**Missing information may result in delayed response**
1212
- type: textarea
1313
attributes:
14-
label: "Describe the Bug"
15-
description: "A clear and concise description of what the bug is."
14+
label: "Describe the Problem/Bug"
15+
description: "A clear and concise description of what the problem/bug is."
1616
validations:
1717
required: true
1818
- type: textarea

.github/ISSUE_TEMPLATE/question.yml

Lines changed: 0 additions & 15 deletions
This file was deleted.

.github/workflows/prepare-release.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,12 @@ jobs:
201201
run: |
202202
PR_TITLE="Java: Add release notes for release ${{ needs.bump-version.outputs.release-version }}"
203203
204-
# if the minor version is a multiple of 15, then change the PR_BODY to "# ⚠️Update the `docs-java/release-notes/index.jsx` file⚠️"
204+
# if the minor version is a multiple of 15, then change the PR_BODY to "# ⚠️Update the `docs-java/release-notes.mdx` file⚠️"
205205
# else the PR_BODY will be "Add the SAP Cloud SDK ${{ needs.bump-version.outputs.release-version }} release notes"
206206
207207
minor_version=$(echo ${{ needs.bump-version.outputs.release-version }} | cut -d '.' -f 2)
208208
if [[ $((minor_version % 15)) -eq 0 ]]; then
209-
PR_BODY="# ⚠️Update the \`docs-java/release-notes/index.jsx\` file⚠️"
209+
PR_BODY="# ⚠️Update the \`docs-java/release-notes.mdx\` file⚠️"
210210
else
211211
PR_BODY="Add the SAP Cloud SDK ${{ needs.bump-version.outputs.release-version }} release notes"
212212
fi

docs/release_notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757

5858
### 📈 Improvements
5959

60-
-
60+
- [Orchestration] Added new API `DpiMasking#withRegex` to apply custom masking patterns.
6161

6262
### 🐛 Fixed Issues
6363

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

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66

77
import com.sap.ai.sdk.orchestration.model.DPIConfig;
88
import com.sap.ai.sdk.orchestration.model.DPIConfigMaskGroundingInput;
9+
import com.sap.ai.sdk.orchestration.model.DPICustomEntity;
910
import com.sap.ai.sdk.orchestration.model.DPIEntities;
1011
import com.sap.ai.sdk.orchestration.model.DPIEntityConfig;
12+
import com.sap.ai.sdk.orchestration.model.DPIMethodConstant;
1113
import com.sap.ai.sdk.orchestration.model.DPIStandardEntity;
12-
import java.util.ArrayList;
1314
import java.util.Arrays;
1415
import java.util.List;
16+
import java.util.stream.Stream;
1517
import javax.annotation.Nonnull;
1618
import lombok.AccessLevel;
1719
import lombok.Getter;
@@ -32,7 +34,7 @@
3234
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
3335
public class DpiMasking implements MaskingProvider {
3436
@Nonnull DPIConfig.MethodEnum maskingMethod;
35-
@Nonnull List<DPIEntities> entities;
37+
@Nonnull List<DPIEntityConfig> entitiesConfig;
3638
@With boolean maskGroundingInput;
3739
@Nonnull List<String> allowList;
3840

@@ -75,11 +77,53 @@ public static class Builder {
7577
@Nonnull
7678
public DpiMasking withEntities(
7779
@Nonnull final DPIEntities entity, @Nonnull final DPIEntities... entities) {
78-
val entitiesList = new ArrayList<DPIEntities>();
79-
entitiesList.add(entity);
80-
entitiesList.addAll(Arrays.asList(entities));
81-
return new DpiMasking(maskingMethod, entitiesList, false, List.of());
80+
val entitiesConfig =
81+
Stream.concat(Stream.of(entity), Arrays.stream(entities))
82+
.map(it -> (DPIEntityConfig) DPIStandardEntity.create().type(it))
83+
.toList();
84+
return new DpiMasking(maskingMethod, entitiesConfig, false, List.of());
8285
}
86+
87+
/**
88+
* Adds a custom regex pattern for masking.
89+
*
90+
* @param regex The regex pattern to match
91+
* @param replacement The replacement string
92+
* @return A new {@link DpiMasking} instance
93+
*/
94+
@Nonnull
95+
public DpiMasking withRegex(@Nonnull final String regex, @Nonnull final String replacement) {
96+
val customEntity =
97+
DPICustomEntity.create()
98+
.regex(regex)
99+
.replacementStrategy(
100+
DPIMethodConstant.create()
101+
.method(DPIMethodConstant.MethodEnum.CONSTANT)
102+
.value(replacement));
103+
104+
return new DpiMasking(maskingMethod, List.of(customEntity), false, List.of());
105+
}
106+
}
107+
108+
/**
109+
* Specifies a custom regex pattern for masking.
110+
*
111+
* @param regex The regex pattern to match
112+
* @param replacement The replacement string
113+
* @return A new {@link DpiMasking} instance
114+
*/
115+
@Nonnull
116+
public DpiMasking withRegex(@Nonnull final String regex, @Nonnull final String replacement) {
117+
val customEntity =
118+
DPICustomEntity.create()
119+
.regex(regex)
120+
.replacementStrategy(
121+
DPIMethodConstant.create()
122+
.method(DPIMethodConstant.MethodEnum.CONSTANT)
123+
.value(replacement));
124+
val newEntities = new java.util.ArrayList<>(entitiesConfig);
125+
newEntities.add(customEntity);
126+
return new DpiMasking(maskingMethod, newEntities, maskGroundingInput, allowList);
83127
}
84128

85129
/**
@@ -90,18 +134,16 @@ public DpiMasking withEntities(
90134
*/
91135
@Nonnull
92136
public DpiMasking withAllowList(@Nonnull final List<String> allowList) {
93-
return new DpiMasking(maskingMethod, entities, maskGroundingInput, allowList);
137+
return new DpiMasking(maskingMethod, entitiesConfig, maskGroundingInput, allowList);
94138
}
95139

96140
@Nonnull
97141
@Override
98142
public DPIConfig createConfig() {
99-
val entitiesDTO =
100-
entities.stream().map(it -> (DPIEntityConfig) DPIStandardEntity.create().type(it)).toList();
101143
return DPIConfig.create()
102144
.type(SAP_DATA_PRIVACY_INTEGRATION)
103145
.method(maskingMethod)
104-
.entities(entitiesDTO)
146+
.entities(entitiesConfig)
105147
.maskGroundingInput(DPIConfigMaskGroundingInput.create().enabled(maskGroundingInput))
106148
.allowlist(allowList);
107149
}

orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010

1111
import com.fasterxml.jackson.annotation.JsonProperty;
1212
import com.sap.ai.sdk.orchestration.model.DPIConfig;
13+
import com.sap.ai.sdk.orchestration.model.DPICustomEntity;
1314
import com.sap.ai.sdk.orchestration.model.DPIEntities;
15+
import com.sap.ai.sdk.orchestration.model.DPIMethodConstant;
1416
import com.sap.ai.sdk.orchestration.model.DPIStandardEntity;
1517
import com.sap.ai.sdk.orchestration.model.DocumentGroundingFilter;
1618
import com.sap.ai.sdk.orchestration.model.GroundingModuleConfigConfig;
@@ -106,6 +108,36 @@ void testDpiMaskingConfig() {
106108
.hasSize(1);
107109
}
108110

111+
@Test
112+
void testDpiMaskingRegex() {
113+
var masking =
114+
DpiMasking.anonymization()
115+
.withRegex("\\d{3}-\\d{2}-\\d{4}", "***-**-****")
116+
.withRegex("\\d{2}-\\d{2}-\\d{5}", "**-**-*****");
117+
var config = new OrchestrationModuleConfig().withLlmConfig(GPT_4O).withMaskingConfig(masking);
118+
assertThat(config.getMaskingConfig()).isNotNull();
119+
assertThat(((MaskingModuleConfigProviders) config.getMaskingConfig()).getProviders())
120+
.hasSize(1);
121+
DPIConfig dpiConfig =
122+
((MaskingModuleConfigProviders) config.getMaskingConfig()).getProviders().get(0);
123+
assertThat(dpiConfig.getMethod()).isEqualTo(DPIConfig.MethodEnum.ANONYMIZATION);
124+
assertThat(dpiConfig.getEntities()).hasSize(2);
125+
assertThat(((DPICustomEntity) dpiConfig.getEntities().get(0)).getRegex())
126+
.isEqualTo("\\d{3}-\\d{2}-\\d{4}");
127+
assertThat(((DPICustomEntity) dpiConfig.getEntities().get(0)).getReplacementStrategy())
128+
.isEqualTo(
129+
DPIMethodConstant.create()
130+
.method(DPIMethodConstant.MethodEnum.CONSTANT)
131+
.value("***-**-****"));
132+
assertThat(((DPICustomEntity) dpiConfig.getEntities().get(1)).getRegex())
133+
.isEqualTo("\\d{2}-\\d{2}-\\d{5}");
134+
assertThat(((DPICustomEntity) dpiConfig.getEntities().get(1)).getReplacementStrategy())
135+
.isEqualTo(
136+
DPIMethodConstant.create()
137+
.method(DPIMethodConstant.MethodEnum.CONSTANT)
138+
.value("**-**-*****"));
139+
}
140+
109141
@Test
110142
void testParams() {
111143
// test withParams(Map<String, Object>)

pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
<wiremock.version>3.13.1</wiremock.version>
6161
<assertj-core.version>3.27.6</assertj-core.version>
6262
<slf4j.version>2.0.17</slf4j.version>
63-
<checkstyle.version>11.1.0</checkstyle.version>
63+
<checkstyle.version>12.0.1</checkstyle.version>
6464
<system-stubs.version>2.1.3</system-stubs.version>
6565
<surefire.version>3.5.4</surefire.version>
6666
<springframework.version>6.2.11</springframework.version>
@@ -632,7 +632,7 @@
632632
<plugin>
633633
<groupId>org.apache.maven.plugins</groupId>
634634
<artifactId>maven-pmd-plugin</artifactId>
635-
<version>3.27.0</version>
635+
<version>3.28.0</version>
636636
<configuration>
637637
<rulesets>
638638
<ruleset>${project.rootdir}/.pipeline/pmd.xml</ruleset>
@@ -667,7 +667,7 @@ https://gitbox.apache.org/repos/asf?p=maven-pmd-plugin.git;a=blob_plain;f=src/ma
667667
<plugin>
668668
<groupId>org.jacoco</groupId>
669669
<artifactId>jacoco-maven-plugin</artifactId>
670-
<version>0.8.13</version>
670+
<version>0.8.14</version>
671671
<configuration>
672672
<excludes>
673673
<!-- Exclude generated clients and end to end application from test coverage -->

sample-code/spring-app/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<spring-boot.version>3.5.6</spring-boot.version>
3737
<logback.version>1.5.19</logback.version>
3838
<cf-logging.version>3.8.6</cf-logging.version>
39-
<apache-tomcat-embed.version>11.0.11</apache-tomcat-embed.version>
39+
<apache-tomcat-embed.version>11.0.12</apache-tomcat-embed.version>
4040
<!-- Skip end-to-end tests by default, can be overridden with -DskipTests=false -->
4141
<skipTests>true</skipTests>
4242
<!-- Allow logging frameworks because this module is not released -->

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,17 @@ Object maskingAnonymization(
216216
return response.getContent();
217217
}
218218

219+
@GetMapping("/maskingRegex")
220+
@Nonnull
221+
Object maskingRegex(
222+
@Nullable @RequestParam(value = "format", required = false) final String format) {
223+
final var response = service.maskingRegex();
224+
if ("json".equals(format)) {
225+
return response;
226+
}
227+
return response.getContent();
228+
}
229+
219230
@GetMapping("/completion/{resourceGroup}")
220231
@Nonnull
221232
Object completionWithResourceGroup(

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,9 @@ public OrchestrationChatResponse maskingAnonymization(@Nonnull final DPIEntities
267267
val userMessage =
268268
Message.user(
269269
"""
270-
I think the SDK is good, but could use some further enhancements.
271-
My architect Alice and manager Bob pointed out that we need the grounding capabilities, which aren't supported yet.
272-
""");
270+
I think the SDK is good, but could use some further enhancements.
271+
My architect Alice and manager Bob pointed out that we need the grounding capabilities, which aren't supported yet.
272+
""");
273273

274274
val prompt = new OrchestrationPrompt(systemMessage, userMessage);
275275
val maskingConfig = DpiMasking.anonymization().withEntities(entity);
@@ -278,6 +278,28 @@ public OrchestrationChatResponse maskingAnonymization(@Nonnull final DPIEntities
278278
return client.chatCompletion(prompt, configWithMasking);
279279
}
280280

281+
/**
282+
* Let the LLM respond with a masked repeated phrase of patient IDs.
283+
*
284+
* @link <a
285+
* href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/data-masking">SAP AI
286+
* Core: Orchestration - Data Masking</a>
287+
* @return the assistant response object
288+
*/
289+
@Nonnull
290+
public OrchestrationChatResponse maskingRegex() {
291+
val systemMessage = Message.system("Repeat following messages");
292+
val userMessage = Message.user("The patient id is patient_id_123.");
293+
294+
val prompt = new OrchestrationPrompt(systemMessage, userMessage);
295+
val regex = "patient_id_[0-9]+";
296+
val replacement = "REDACTED_ID";
297+
val maskingConfig = DpiMasking.anonymization().withRegex(regex, replacement);
298+
val configWithMasking = config.withMaskingConfig(maskingConfig);
299+
300+
return client.chatCompletion(prompt, configWithMasking);
301+
}
302+
281303
/**
282304
* Chat request to OpenAI through the Orchestration deployment under a specific resource group.
283305
*

0 commit comments

Comments
 (0)