Skip to content

Commit dcbb7eb

Browse files
committed
Supporting DPICustomEntity and option field replacement_strategy
1 parent fe1669e commit dcbb7eb

File tree

3 files changed

+80
-6
lines changed

3 files changed

+80
-6
lines changed

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

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
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;
1214
import java.util.ArrayList;
1315
import java.util.Arrays;
@@ -33,6 +35,7 @@
3335
public class DpiMasking implements MaskingProvider {
3436
@Nonnull DPIConfig.MethodEnum maskingMethod;
3537
@Nonnull List<DPIEntities> entities;
38+
@Nonnull List<DPICustomEntity> customEntities;
3639
@With boolean maskGroundingInput;
3740
@Nonnull List<String> allowList;
3841

@@ -78,7 +81,27 @@ public DpiMasking withEntities(
7881
val entitiesList = new ArrayList<DPIEntities>();
7982
entitiesList.add(entity);
8083
entitiesList.addAll(Arrays.asList(entities));
81-
return new DpiMasking(maskingMethod, entitiesList, false, List.of());
84+
return new DpiMasking(maskingMethod, entitiesList, List.of(), false, List.of());
85+
}
86+
87+
/**
88+
* Specifies 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(), List.of(customEntity), false, List.of());
82105
}
83106
}
84107

@@ -90,14 +113,18 @@ public DpiMasking withEntities(
90113
*/
91114
@Nonnull
92115
public DpiMasking withAllowList(@Nonnull final List<String> allowList) {
93-
return new DpiMasking(maskingMethod, entities, maskGroundingInput, allowList);
116+
return new DpiMasking(maskingMethod, entities, customEntities, maskGroundingInput, allowList);
94117
}
95118

96119
@Nonnull
97120
@Override
98121
public DPIConfig createConfig() {
99122
val entitiesDTO =
100-
entities.stream().map(it -> (DPIEntityConfig) DPIStandardEntity.create().type(it)).toList();
123+
new ArrayList<>(
124+
entities.stream()
125+
.map(it -> (DPIEntityConfig) DPIStandardEntity.create().type(it))
126+
.toList());
127+
entitiesDTO.addAll(customEntities);
101128
return DPIConfig.create()
102129
.type(SAP_DATA_PRIVACY_INTEGRATION)
103130
.method(maskingMethod)

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

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

269269
val prompt = new OrchestrationPrompt(systemMessage, userMessage);
270270
val maskingConfig = DpiMasking.anonymization().withEntities(entity);
@@ -273,6 +273,34 @@ public OrchestrationChatResponse maskingAnonymization(@Nonnull final DPIEntities
273273
return client.chatCompletion(prompt, configWithMasking);
274274
}
275275

276+
/**
277+
* Let the orchestration service evaluate the feedback on the AI SDK provided by a hypothetical
278+
* user. Anonymize any patient IDs given as they are not relevant for judging the sentiment of the
279+
* feedback.
280+
*
281+
* @link <a
282+
* href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/data-masking">SAP AI
283+
* Core: Orchestration - Data Masking</a>
284+
* @return the assistant response object
285+
*/
286+
@Nonnull
287+
public OrchestrationChatResponse maskingCustomAnonymization() {
288+
val systemMessage = Message.system("Repeat this phrase");
289+
val userMessage =
290+
Message.user(
291+
"""
292+
The patient id is patient_id_123.
293+
""");
294+
295+
val prompt = new OrchestrationPrompt(systemMessage, userMessage);
296+
val regex = "patient_id_[0-9]+";
297+
val replacement = "REDACTED_ID";
298+
val maskingConfig = DpiMasking.anonymization().withRegex(regex, replacement);
299+
val configWithMasking = config.withMaskingConfig(maskingConfig);
300+
301+
return client.chatCompletion(prompt, configWithMasking);
302+
}
303+
276304
/**
277305
* Chat request to OpenAI through the Orchestration deployment under a specific resource group.
278306
*

sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,25 @@ void testMaskingAnonymization() {
148148
assertThat(result.getIntermediateResults().getOutputUnmasking()).isEmpty();
149149
}
150150

151+
@Test
152+
void testMaskingCustomAnonymization() {
153+
var response = service.maskingCustomAnonymization();
154+
var result = response.getOriginalResponse();
155+
var llmChoice = result.getFinalResult().getChoices().get(0);
156+
assertThat(llmChoice.getFinishReason()).isEqualTo("stop");
157+
158+
var maskingResult = result.getIntermediateResults().getInputMasking();
159+
assertThat(maskingResult.getMessage()).isNotEmpty();
160+
var data = (Map<String, Object>) maskingResult.getData();
161+
var maskedMessage = (String) data.get("masked_template");
162+
assertThat(maskedMessage)
163+
.describedAs("The masked input should replace patient IDs with REDACTED_ID")
164+
.doesNotContain("patient_id_123")
165+
.contains("REDACTED_ID");
166+
167+
assertThat(result.getIntermediateResults().getOutputUnmasking()).isEmpty();
168+
}
169+
151170
@SuppressWarnings("unchecked")
152171
@Test
153172
void testMaskingPseudonymization() {

0 commit comments

Comments
 (0)