From 5c08adcdaffb932fbc2c9431bc01ba7f7b026438 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Wed, 4 Feb 2026 16:25:35 +0100 Subject: [PATCH 1/7] supporting protected_material_code filtering module --- .../sap/ai/sdk/orchestration/AzureContentFilter.java | 12 ++++++++++-- .../ai/sdk/app/services/OrchestrationService.java | 11 +++++++++-- .../ai/sdk/app/controllers/OrchestrationTest.java | 1 + 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/AzureContentFilter.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/AzureContentFilter.java index 7620fd54c..cf4c6c73b 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/AzureContentFilter.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/AzureContentFilter.java @@ -48,6 +48,9 @@ public class AzureContentFilter implements ContentFilter { /** The filter category for violence content. */ @Nullable AzureFilterThreshold violence; + /** The filter category for protected material content. */ + boolean protectedMaterialCode; + /** * A flag to set prompt shield on input filer. * @@ -90,7 +93,11 @@ public AzureContentSafetyInputFilterConfig createInputFilterConfig() { @Override @Nonnull public AzureContentSafetyOutputFilterConfig createOutputFilterConfig() { - if (hate == null && selfHarm == null && sexual == null && violence == null) { + if (hate == null + && selfHarm == null + && sexual == null + && violence == null + && !protectedMaterialCode) { throw new IllegalArgumentException("At least one filter category must be set"); } @@ -101,6 +108,7 @@ public AzureContentSafetyOutputFilterConfig createOutputFilterConfig() { .hate(hate != null ? hate.getAzureThreshold() : null) .selfHarm(selfHarm != null ? selfHarm.getAzureThreshold() : null) .sexual(sexual != null ? sexual.getAzureThreshold() : null) - .violence(violence != null ? violence.getAzureThreshold() : null)); + .violence(violence != null ? violence.getAzureThreshold() : null) + .protectedMaterialCode(true)); } } diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java index 0ecc9b3cc..1a009f063 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java @@ -203,7 +203,9 @@ public OrchestrationChatResponse inputFiltering(@Nonnull final AzureFilterThresh * @return the assistant response object */ @Nonnull - public OrchestrationChatResponse outputFiltering(@Nonnull final AzureFilterThreshold policy) { + public OrchestrationChatResponse outputFiltering( + @Nonnull final AzureFilterThreshold policy) { + val isProtected = true; // enforce protected material code filtering val systemMessage = Message.system("Give three paraphrases for the following sentence"); // Reliably triggering the content filter of models fine-tuned for ethical compliance @@ -212,7 +214,12 @@ public OrchestrationChatResponse outputFiltering(@Nonnull final AzureFilterThres new OrchestrationPrompt("'We shall spill blood tonight', said the operation in-charge.") .messageHistory(List.of(systemMessage)); val filterConfig = - new AzureContentFilter().hate(policy).selfHarm(policy).sexual(policy).violence(policy); + new AzureContentFilter() + .hate(policy) + .selfHarm(policy) + .sexual(policy) + .violence(policy) + .protectedMaterialCode(isProtected); val configWithFilter = config.withOutputFiltering(filterConfig); return client.chatCompletion(prompt, configWithFilter); diff --git a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java index 5051e091c..a4f3f9b93 100644 --- a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java +++ b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java @@ -286,6 +286,7 @@ void testOutputFilteringStrict() { assertThat(actualAzureContentSafety.getSelfHarm()).isEqualTo(NUMBER_0); assertThat(actualAzureContentSafety.getSexual()).isEqualTo(NUMBER_0); assertThat(actualAzureContentSafety.getHate()).isEqualTo(NUMBER_0); + assertThat(actualAzureContentSafety.isProtectedMaterialCode()).isFalse(); }); } From ebb4a10ac65f177a6a15a84c7b78bedd2a769513 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Wed, 4 Feb 2026 18:29:14 +0100 Subject: [PATCH 2/7] fixing orchestration test --- .../java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java index a4f3f9b93..5c3ec3e04 100644 --- a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java +++ b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java @@ -300,7 +300,7 @@ void testOutputFilteringLenient() { assertThat(response.getContent()).isNotEmpty(); var filterResult = response.getOriginalResponse().getIntermediateResults().getOutputFiltering(); - assertThat(filterResult.getMessage()).containsPattern("Choice 0: Filtering was skipped."); + assertThat(filterResult.getMessage()).containsPattern("Choice 0: Filtering passed successfully."); } @Test From b938bc016f05202ba62a475c678f4153c78af830 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Wed, 4 Feb 2026 19:10:38 +0100 Subject: [PATCH 3/7] unit test initial implementation --- .../orchestration/OrchestrationUnitTest.java | 21 ++++++++++++------- .../__files/outputFilteringStrict.json | 3 ++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java index a3dec5a90..93ef944ff 100644 --- a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java +++ b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java @@ -438,18 +438,21 @@ void filteringLoose() throws IOException { .withBodyFile("filteringLooseResponse.json") .withHeader("Content-Type", "application/json"))); - final var azureFilter = + final var azureInputFilter = new AzureContentFilter() .hate(ALLOW_SAFE_LOW_MEDIUM) .selfHarm(ALLOW_SAFE_LOW_MEDIUM) .sexual(ALLOW_SAFE_LOW_MEDIUM) .violence(ALLOW_SAFE_LOW_MEDIUM); + final var azureOutputFilter = azureInputFilter.protectedMaterialCode(true); final var llamaFilter = new LlamaGuardFilter().config(LlamaGuard38b.create().selfHarm(true)); client.chatCompletion( prompt, - config.withInputFiltering(azureFilter, llamaFilter).withOutputFiltering(azureFilter)); + config + .withInputFiltering(azureInputFilter, llamaFilter) + .withOutputFiltering(azureOutputFilter)); // the result is asserted in the verify step below // verify that null fields are absent from the sent request @@ -464,19 +467,20 @@ void filteringLooseStream() throws IOException { post(anyUrl()) .willReturn(aResponse().withBody(res).withHeader("Content-Type", "application/json"))); - final var azureFilter = + final var azureInputFilter = new AzureContentFilter() .hate(ALLOW_SAFE_LOW_MEDIUM) .selfHarm(ALLOW_SAFE_LOW_MEDIUM) .sexual(ALLOW_SAFE_LOW_MEDIUM) .violence(ALLOW_SAFE_LOW_MEDIUM); + final var azureOutputFilter = azureInputFilter.protectedMaterialCode(true); final var llamaFilter = new LlamaGuardFilter().config(LlamaGuard38b.create().selfHarm(true)); OrchestrationModuleConfig myConfig = config - .withInputFiltering(azureFilter, llamaFilter) - .withOutputFiltering(azureFilter) + .withInputFiltering(azureInputFilter, llamaFilter) + .withOutputFiltering(azureOutputFilter) .withOutputFilteringStreamOptions(FilteringStreamOptions.create().overlap(1_000)); Stream result = client.streamChatCompletion(prompt, myConfig); @@ -580,7 +584,8 @@ void outputFilteringStrict() { .hate(ALLOW_SAFE) .selfHarm(ALLOW_SAFE) .sexual(ALLOW_SAFE) - .violence(ALLOW_SAFE); + .violence(ALLOW_SAFE) + .protectedMaterialCode(true); final var llamaFilter = new LlamaGuardFilter().config(LlamaGuard38b.create().violentCrimes(true)); @@ -601,7 +606,8 @@ void outputFilteringStrict() { "Hate", 6, "SelfHarm", 0, "Sexual", 0, - "Violence", 6), + "Violence", 6, + "protectedMaterialCode", false), "llama_guard_3_8b", Map.of("violent_crimes", true))); assertThat(e.getErrorResponse()).isNull(); @@ -612,6 +618,7 @@ void outputFilteringStrict() { assertThat(e.getAzureContentSafetyOutput().getSelfHarm()).isEqualTo(NUMBER_0); assertThat(e.getAzureContentSafetyOutput().getSexual()).isEqualTo(NUMBER_0); assertThat(e.getAzureContentSafetyOutput().getViolence()).isEqualTo(NUMBER_6); + assertThat(e.getAzureContentSafetyOutput().isProtectedMaterialCode()).isFalse(); assertThat(e.getLlamaGuard38b()).isNotNull(); assertThat(e.getLlamaGuard38b().isViolentCrimes()).isTrue(); diff --git a/orchestration/src/test/resources/__files/outputFilteringStrict.json b/orchestration/src/test/resources/__files/outputFilteringStrict.json index e34f2bce6..9439fe893 100644 --- a/orchestration/src/test/resources/__files/outputFilteringStrict.json +++ b/orchestration/src/test/resources/__files/outputFilteringStrict.json @@ -21,7 +21,8 @@ "Hate": 6, "SelfHarm": 0, "Sexual": 0, - "Violence": 6 + "Violence": 6, + "ProtectedMaterialCode": false }, "llama_guard_3_8b": { "violent_crimes": true From 2c21a658f41b61cbf8eb9d9586b89597f7acbacf Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Fri, 6 Feb 2026 16:35:16 +0100 Subject: [PATCH 4/7] fixing unit tests --- .../com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java | 2 +- .../src/test/resources/filteringLooseRequestStream.json | 2 +- .../java/com/sap/ai/sdk/app/services/OrchestrationService.java | 3 +-- .../java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java | 3 ++- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java index 93ef944ff..a3abaf540 100644 --- a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java +++ b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java @@ -607,7 +607,7 @@ void outputFilteringStrict() { "SelfHarm", 0, "Sexual", 0, "Violence", 6, - "protectedMaterialCode", false), + "ProtectedMaterialCode", false), "llama_guard_3_8b", Map.of("violent_crimes", true))); assertThat(e.getErrorResponse()).isNull(); diff --git a/orchestration/src/test/resources/filteringLooseRequestStream.json b/orchestration/src/test/resources/filteringLooseRequestStream.json index f359c5659..68c9bfe8d 100644 --- a/orchestration/src/test/resources/filteringLooseRequestStream.json +++ b/orchestration/src/test/resources/filteringLooseRequestStream.json @@ -56,7 +56,7 @@ "self_harm": 4, "sexual": 4, "violence": 4, - "protected_material_code" : false + "protected_material_code" : true } } ], diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java index 1a009f063..ed43c9258 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java @@ -203,8 +203,7 @@ public OrchestrationChatResponse inputFiltering(@Nonnull final AzureFilterThresh * @return the assistant response object */ @Nonnull - public OrchestrationChatResponse outputFiltering( - @Nonnull final AzureFilterThreshold policy) { + public OrchestrationChatResponse outputFiltering(@Nonnull final AzureFilterThreshold policy) { val isProtected = true; // enforce protected material code filtering val systemMessage = Message.system("Give three paraphrases for the following sentence"); diff --git a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java index 5c3ec3e04..8108728d3 100644 --- a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java +++ b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java @@ -300,7 +300,8 @@ void testOutputFilteringLenient() { assertThat(response.getContent()).isNotEmpty(); var filterResult = response.getOriginalResponse().getIntermediateResults().getOutputFiltering(); - assertThat(filterResult.getMessage()).containsPattern("Choice 0: Filtering passed successfully."); + assertThat(filterResult.getMessage()) + .containsPattern("Choice 0: Filtering passed successfully."); } @Test From e63d208577787e6f79c4d94e4abf3abeda4a5584 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Fri, 6 Feb 2026 16:59:56 +0100 Subject: [PATCH 5/7] fixing more tests --- .../sdk/orchestration/OrchestrationModuleConfigTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java index fcbeaed33..f9d6a2740 100644 --- a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java +++ b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java @@ -50,18 +50,19 @@ static class InsideTestClass { void testStackingInputAndOutputFilter() { final var config = new OrchestrationModuleConfig().withLlmConfig(GPT_4O); - final var filter = + final var inputFilter = new AzureContentFilter() .hate(ALLOW_SAFE_LOW_MEDIUM) .selfHarm(ALLOW_SAFE_LOW_MEDIUM) .sexual(ALLOW_SAFE_LOW_MEDIUM) .violence(ALLOW_SAFE_LOW_MEDIUM); + final var outputFilter = inputFilter.protectedMaterialCode(true); - final var configWithInputFirst = config.withInputFiltering(filter).withOutputFiltering(filter); + final var configWithInputFirst = config.withInputFiltering(inputFilter).withOutputFiltering(outputFilter); assertThat(configWithInputFirst.getFilteringConfig()).isNotNull(); assertThat(configWithInputFirst.getFilteringConfig().getInput()).isNotNull(); - final var configWithOutputFirst = config.withOutputFiltering(filter).withInputFiltering(filter); + final var configWithOutputFirst = config.withOutputFiltering(outputFilter).withInputFiltering(inputFilter); assertThat(configWithOutputFirst.getFilteringConfig()).isNotNull(); assertThat(configWithOutputFirst.getFilteringConfig().getOutput()).isNotNull(); } From 81fea09ed07090b46e865fc3e17be930df02ec9c Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Fri, 6 Feb 2026 17:11:01 +0100 Subject: [PATCH 6/7] update release notes --- docs/release_notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release_notes.md b/docs/release_notes.md index e16ac20e9..6bbe03f34 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -18,6 +18,7 @@ - [Orchestration] Added new API `OrchestrationTemplateReference#withScope` to support prompt templates with resource-group scope. - [Orchestration] Chat completion calls now can have multiple module configs to support [fallback modules](https://sap.github.io/ai-sdk/docs/java/orchestration/chat-completion). +- [Orchestration] `protected_material_code` is now supported as an output content filtering module . ### 🐛 Fixed Issues From 623cce88495cca36621e67e2dd3d797d9732c0d8 Mon Sep 17 00:00:00 2001 From: SAP Cloud SDK Bot Date: Fri, 6 Feb 2026 16:15:37 +0000 Subject: [PATCH 7/7] Formatting --- .../ai/sdk/orchestration/OrchestrationModuleConfigTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java index f9d6a2740..a325c446e 100644 --- a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java +++ b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java @@ -58,11 +58,13 @@ void testStackingInputAndOutputFilter() { .violence(ALLOW_SAFE_LOW_MEDIUM); final var outputFilter = inputFilter.protectedMaterialCode(true); - final var configWithInputFirst = config.withInputFiltering(inputFilter).withOutputFiltering(outputFilter); + final var configWithInputFirst = + config.withInputFiltering(inputFilter).withOutputFiltering(outputFilter); assertThat(configWithInputFirst.getFilteringConfig()).isNotNull(); assertThat(configWithInputFirst.getFilteringConfig().getInput()).isNotNull(); - final var configWithOutputFirst = config.withOutputFiltering(outputFilter).withInputFiltering(inputFilter); + final var configWithOutputFirst = + config.withOutputFiltering(outputFilter).withInputFiltering(inputFilter); assertThat(configWithOutputFirst.getFilteringConfig()).isNotNull(); assertThat(configWithOutputFirst.getFilteringConfig().getOutput()).isNotNull(); }