From 81a953ed6ba4063917191b7aefcdb5859ecebd76 Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 15 Jul 2026 14:22:04 +0200 Subject: [PATCH 1/6] chore: [DevOps] Fix e2e tests --- docs/release_notes.md | 3 +- .../orchestration/OrchestrationAiModel.java | 4 +++ .../OrchestrationClientException.java | 35 ++++++++++++++----- .../app/controllers/OrchestrationTest.java | 13 +++---- .../SpringAiOrchestrationTest.java | 8 ++--- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/docs/release_notes.md b/docs/release_notes.md index 7d44802f2..adb2d2a4d 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -13,6 +13,7 @@ ### ✨ New Functionality - [OpenAI] You can now add multiple custom headers to an `OpenAiClient` at once via `.withHeaders()`. +- [Orchestration] Added `OrchestrationAiModel.GEMINI_3_1_PRO_PREVIEW_EA` ### 📈 Improvements @@ -20,4 +21,4 @@ ### 🐛 Fixed Issues -- +- [Orchestration] Some `OrchestrationClientException` were reported as `OrchestrationFilterException.Input`. diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationAiModel.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationAiModel.java index 41064c274..5145e9174 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationAiModel.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationAiModel.java @@ -429,6 +429,10 @@ public class OrchestrationAiModel { public static final OrchestrationAiModel GEMINI_3_5_FLASH = new OrchestrationAiModel("gemini-3.5-flash"); + /** Google Cloud Platform Gemini 3.1 Pro preview early access model */ + public static final OrchestrationAiModel GEMINI_3_1_PRO_PREVIEW_EA = + new OrchestrationAiModel("gemini-3.1-pro-preview-ea"); + /** * Alephalpha-pharia-1-7b-control model * diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java index 857d1e46f..80559b8f5 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java @@ -27,17 +27,36 @@ public class OrchestrationClientException extends ClientException { static final ClientExceptionFactory FACTORY = (message, clientError, cause) -> { - final var details = extractInputFilterDetails(clientError); - if (details.isEmpty()) { - if (message.contains("No Prompt Template found in the Prompt Registry.")) { - message += - "\n Please make sure to provide a resource group id and verify that it matches the provided template reference details if the template is referenced from a resource-group scope, otherwise use the tenant scope without providing resource group id."; - } - return new OrchestrationClientException(message, cause).setClientError(clientError); + if (isInputFilterError(clientError)) { + return new Input(message, cause) + .setFilterDetails(extractInputFilterDetails(clientError)) + .setClientError(clientError); + } else if (message.contains("No Prompt Template found in the Prompt Registry.")) { + message += + "\n Please make sure to provide a resource group id and verify that it matches the provided template reference details if the template is referenced from a resource-group scope, otherwise use the tenant scope without providing resource group id."; } - return new Input(message, cause).setFilterDetails(details).setClientError(clientError); + return new OrchestrationClientException(message, cause).setClientError(clientError); }; + static boolean isInputFilterError(@Nullable final OrchestrationError error) { + if (error instanceof OrchestrationError.Synchronous synchronousError) { + return Optional.of(synchronousError.getErrorResponse()) + .map(ErrorResponse::getError) + .flatMap(OrchestrationClientException::lastError) + .map(Error::getLocation) + .map(filter -> filter.toLowerCase().contains("filter")) + .orElse(false); + } else if (error instanceof OrchestrationError.Streaming streamingError) { + return Optional.of(streamingError.getErrorResponse()) + .map(ErrorResponseStreaming::getError) + .flatMap(OrchestrationClientException::lastErrorStreaming) + .map(ErrorStreaming::getLocation) + .map(filter -> filter.toLowerCase().contains("filter")) + .orElse(false); + } + return false; + } + @SuppressWarnings("unchecked") @Nonnull static Map extractInputFilterDetails(@Nullable final OrchestrationError error) { 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 92d27ee15..3d7123dd2 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 @@ -209,8 +209,7 @@ void testGrounding() { assertThat(llmChoice.getFinishReason()).isEqualTo("stop"); assertThat(result.getIntermediateResults().getGrounding()).isNotNull(); assertThat(result.getIntermediateResults().getGrounding().getData()).isNotNull(); - assertThat(result.getIntermediateResults().getGrounding().getMessage()) - .isEqualTo("grounding result"); + assertThat(result.getIntermediateResults().getGrounding().getMessage()).contains("Grounding"); var groundingData = (Map) result.getIntermediateResults().getGrounding().getData(); assertThat(groundingData.get("grounding_result")).contains("metadata"); @@ -244,8 +243,7 @@ void testInputFilteringStrict() { var policy = AzureFilterThreshold.ALLOW_SAFE; assertThatThrownBy(() -> service.inputFiltering(policy)) - .hasMessageContaining( - "Content filtered due to safety violations. Please modify the prompt and try again.") + .hasMessageContainingAll("Filtering", "blocked") .hasMessageContaining("400 (Bad Request)") .isInstanceOfSatisfying( OrchestrationFilterException.Input.class, @@ -269,7 +267,7 @@ void testInputFilteringLenient() { assertThat(response.getContent()).isNotEmpty(); var filterResult = response.getOriginalResponse().getIntermediateResults().getInputFiltering(); - assertThat(filterResult.getMessage()).contains("passed"); // prompt shield is a filter + assertThat(filterResult.getMessage()).isNotEmpty(); // prompt shield is a filter } @Test @@ -302,15 +300,14 @@ void testOutputFilteringLenient() { assertThat(response.getContent()).isNotEmpty(); var filterResult = response.getOriginalResponse().getIntermediateResults().getOutputFiltering(); - assertThat(filterResult.getMessage()).containsPattern("Choice 0: Filtering was skipped."); + assertThat(filterResult.getMessage()).contains("Filtering", "blocked"); } @Test void testLlamaGuardEnabled() { assertThatThrownBy(() -> service.llamaGuardInputFilter(true)) .isInstanceOf(OrchestrationFilterException.Input.class) - .hasMessageContaining( - "Content filtered due to safety violations. Please modify the prompt and try again.") + .hasMessageContainingAll("Filtering", "blocked") .hasMessageContaining("400 (Bad Request)") .isInstanceOfSatisfying( OrchestrationFilterException.Input.class, diff --git a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationTest.java b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationTest.java index 4727d0ad4..739a113e6 100644 --- a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationTest.java +++ b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationTest.java @@ -66,8 +66,7 @@ void testInputFilteringStrict() { assertThatThrownBy(() -> service.inputFiltering(policy)) .isInstanceOf(OrchestrationClientException.class) - .hasMessageContaining( - "Content filtered due to safety violations. Please modify the prompt and try again.") + .hasMessageContainingAll("Filtering", "blocked") .hasMessageContaining("400 (Bad Request)"); } @@ -104,8 +103,7 @@ void testOutputFilteringStrict() { .getOriginalResponse() .getIntermediateResults() .getOutputFiltering(); - assertThat(filterResult.getMessage()) - .contains("Choice 0: Content filtered due to safety violations."); + assertThat(filterResult.getMessage()).contains("Filtering", "blocked"); } @Test @@ -123,7 +121,7 @@ void testOutputFilteringLenient() { .getOriginalResponse() .getIntermediateResults() .getOutputFiltering(); - assertThat(filterResult.getMessage()).contains("Choice 0: Filtering was skipped."); + assertThat(filterResult.getMessage()).contains("Filtering", "skipped"); } @Test From d1c6b58ec60c4786bb2f3a5a600f8a189e763290 Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 15 Jul 2026 14:38:29 +0200 Subject: [PATCH 2/6] more fixes --- .../com/sap/ai/sdk/app/controllers/OrchestrationTest.java | 6 +++--- .../ai/sdk/app/controllers/SpringAiOrchestrationTest.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) 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 3d7123dd2..16761ff86 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()).contains("Filtering", "blocked"); + assertThat(filterResult.getMessage()).contains("Filtering").containsAnyOf("passed", "skipped"); } @Test @@ -329,7 +329,7 @@ void testLlamaGuardDisabled() { assertThat(response.getContent()).isNotEmpty(); var filterResult = response.getOriginalResponse().getIntermediateResults().getInputFiltering(); - assertThat(filterResult.getMessage()).contains("skipped"); + assertThat(filterResult.getMessage()).contains("Filtering").containsAnyOf("passed", "skipped"); } @Test @@ -547,7 +547,7 @@ void testTranslation() { assertThat(inputTranslation).isNotNull(); assertThat(inputTranslation.getMessage()) .isNotNull() - .contains("Successfully translated placeholders:") + .contains("Successfully", " placeholders:") .contains("exam_type") .contains("topic"); diff --git a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationTest.java b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationTest.java index 739a113e6..b100b951f 100644 --- a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationTest.java +++ b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationTest.java @@ -85,7 +85,7 @@ void testInputFilteringLenient() { .getOriginalResponse() .getIntermediateResults() .getInputFiltering(); - assertThat(filterResult.getMessage()).contains("skipped"); + assertThat(filterResult.getMessage()).contains("Filtering").containsAnyOf("passed", "skipped"); } @Test @@ -121,7 +121,7 @@ void testOutputFilteringLenient() { .getOriginalResponse() .getIntermediateResults() .getOutputFiltering(); - assertThat(filterResult.getMessage()).contains("Filtering", "skipped"); + assertThat(filterResult.getMessage()).contains("Filtering").containsAnyOf("passed", "skipped"); } @Test From df7d33772343846ebf8bbb86b6691c7c695f69cc Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 15 Jul 2026 15:20:05 +0200 Subject: [PATCH 3/6] more fixes --- docs/release_notes.md | 2 +- .../java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/release_notes.md b/docs/release_notes.md index adb2d2a4d..29e8b1652 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -13,7 +13,7 @@ ### ✨ New Functionality - [OpenAI] You can now add multiple custom headers to an `OpenAiClient` at once via `.withHeaders()`. -- [Orchestration] Added `OrchestrationAiModel.GEMINI_3_1_PRO_PREVIEW_EA` +- [Orchestration] Added `GEMINI_3_1_PRO_PREVIEW_EA` to model list in `OrchestrationAiModel`. ### 📈 Improvements 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 16761ff86..fb1341dd7 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 @@ -209,7 +209,8 @@ void testGrounding() { assertThat(llmChoice.getFinishReason()).isEqualTo("stop"); assertThat(result.getIntermediateResults().getGrounding()).isNotNull(); assertThat(result.getIntermediateResults().getGrounding().getData()).isNotNull(); - assertThat(result.getIntermediateResults().getGrounding().getMessage()).contains("Grounding"); + assertThat(result.getIntermediateResults().getGrounding().getMessage().toLowerCase()) + .contains("grounding"); var groundingData = (Map) result.getIntermediateResults().getGrounding().getData(); assertThat(groundingData.get("grounding_result")).contains("metadata"); From 1d9b67fd6c6d34232d339c15200da922fba6e0a0 Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 15 Jul 2026 16:09:21 +0200 Subject: [PATCH 4/6] locale.root --- .../ai/sdk/orchestration/OrchestrationClientException.java | 7 +++++-- .../com/sap/ai/sdk/app/controllers/OrchestrationTest.java | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java index 80559b8f5..78e5234e1 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java @@ -14,6 +14,7 @@ import com.sap.ai.sdk.orchestration.model.ModuleResults; import com.sap.ai.sdk.orchestration.model.ModuleResultsStreaming; import java.util.Collections; +import java.util.Locale; import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; @@ -21,6 +22,8 @@ import lombok.experimental.StandardException; import lombok.val; +import static java.util.Locale.ROOT; + /** Exception thrown by the {@link OrchestrationClient} in case of an error. */ @StandardException public class OrchestrationClientException extends ClientException { @@ -44,14 +47,14 @@ static boolean isInputFilterError(@Nullable final OrchestrationError error) { .map(ErrorResponse::getError) .flatMap(OrchestrationClientException::lastError) .map(Error::getLocation) - .map(filter -> filter.toLowerCase().contains("filter")) + .map(filter -> filter.toLowerCase(ROOT).contains("filter")) .orElse(false); } else if (error instanceof OrchestrationError.Streaming streamingError) { return Optional.of(streamingError.getErrorResponse()) .map(ErrorResponseStreaming::getError) .flatMap(OrchestrationClientException::lastErrorStreaming) .map(ErrorStreaming::getLocation) - .map(filter -> filter.toLowerCase().contains("filter")) + .map(filter -> filter.toLowerCase(ROOT).contains("filter")) .orElse(false); } return false; 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 fb1341dd7..3e21116ee 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 @@ -5,6 +5,7 @@ import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.Parameter.TEMPERATURE; import static com.sap.ai.sdk.orchestration.model.AzureThreshold.*; import static com.sap.ai.sdk.orchestration.model.ResponseChatMessage.RoleEnum.ASSISTANT; +import static java.util.Locale.ROOT; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -209,7 +210,7 @@ void testGrounding() { assertThat(llmChoice.getFinishReason()).isEqualTo("stop"); assertThat(result.getIntermediateResults().getGrounding()).isNotNull(); assertThat(result.getIntermediateResults().getGrounding().getData()).isNotNull(); - assertThat(result.getIntermediateResults().getGrounding().getMessage().toLowerCase()) + assertThat(result.getIntermediateResults().getGrounding().getMessage().toLowerCase(ROOT)) .contains("grounding"); var groundingData = (Map) result.getIntermediateResults().getGrounding().getData(); From 1c2b6a09e9bb02a3e79f97962254269a86ecc0b0 Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 15 Jul 2026 16:09:35 +0200 Subject: [PATCH 5/6] locale.root --- .../ai/sdk/orchestration/OrchestrationClientException.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java index 78e5234e1..1177e68a0 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClientException.java @@ -1,5 +1,7 @@ package com.sap.ai.sdk.orchestration; +import static java.util.Locale.ROOT; + import com.google.common.annotations.Beta; import com.sap.ai.sdk.core.common.ClientException; import com.sap.ai.sdk.core.common.ClientExceptionFactory; @@ -14,7 +16,6 @@ import com.sap.ai.sdk.orchestration.model.ModuleResults; import com.sap.ai.sdk.orchestration.model.ModuleResultsStreaming; import java.util.Collections; -import java.util.Locale; import java.util.Map; import java.util.Optional; import javax.annotation.Nonnull; @@ -22,8 +23,6 @@ import lombok.experimental.StandardException; import lombok.val; -import static java.util.Locale.ROOT; - /** Exception thrown by the {@link OrchestrationClient} in case of an error. */ @StandardException public class OrchestrationClientException extends ClientException { From cf53b9cd253b314dceb8b22cb70b9da0d5150c07 Mon Sep 17 00:00:00 2001 From: I538344 Date: Thu, 16 Jul 2026 16:55:38 +0200 Subject: [PATCH 6/6] coverage --- orchestration/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/orchestration/pom.xml b/orchestration/pom.xml index 8b8d7f0f1..b41927f4b 100644 --- a/orchestration/pom.xml +++ b/orchestration/pom.xml @@ -39,8 +39,8 @@ 82% 94% 93% - 75% - 95% + 74% + 94% 100%