Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@
### ✨ New Functionality

- [OpenAI] You can now add multiple custom headers to an `OpenAiClient` at once via `.withHeaders()`.
- [Orchestration] Added `GEMINI_3_1_PRO_PREVIEW_EA` to model list in `OrchestrationAiModel`.

### 📈 Improvements

-

### 🐛 Fixed Issues

-
- [Orchestration] Some `OrchestrationClientException` were reported as `OrchestrationFilterException.Input`.
4 changes: 2 additions & 2 deletions orchestration/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@
<coverage.complexity>82%</coverage.complexity>
<coverage.line>94%</coverage.line>
<coverage.instruction>93%</coverage.instruction>
<coverage.branch>75%</coverage.branch>
<coverage.method>95%</coverage.method>
<coverage.branch>74%</coverage.branch>
<coverage.method>94%</coverage.method>
Comment thread
CharlesDuboisSAP marked this conversation as resolved.
<coverage.class>100%</coverage.class>
</properties>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -27,17 +29,36 @@ public class OrchestrationClientException extends ClientException {

static final ClientExceptionFactory<OrchestrationClientException, OrchestrationError> 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(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(ROOT).contains("filter"))
.orElse(false);
}
return false;
}

@SuppressWarnings("unchecked")
@Nonnull
static Map<String, Object> extractInputFilterDetails(@Nullable final OrchestrationError error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -209,8 +210,8 @@ 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().toLowerCase(ROOT))
.contains("grounding");
var groundingData =
(Map<String, String>) result.getIntermediateResults().getGrounding().getData();
assertThat(groundingData.get("grounding_result")).contains("metadata");
Expand Down Expand Up @@ -244,8 +245,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,
Expand All @@ -269,7 +269,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
Expand Down Expand Up @@ -302,15 +302,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").containsAnyOf("passed", "skipped");
}

@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,
Expand All @@ -332,7 +331,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
Expand Down Expand Up @@ -550,7 +549,7 @@ void testTranslation() {
assertThat(inputTranslation).isNotNull();
assertThat(inputTranslation.getMessage())
.isNotNull()
.contains("Successfully translated placeholders:")
.contains("Successfully", " placeholders:")
.contains("exam_type")
.contains("topic");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)");
}

Expand All @@ -86,7 +85,7 @@ void testInputFilteringLenient() {
.getOriginalResponse()
.getIntermediateResults()
.getInputFiltering();
assertThat(filterResult.getMessage()).contains("skipped");
assertThat(filterResult.getMessage()).contains("Filtering").containsAnyOf("passed", "skipped");
}

@Test
Expand All @@ -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
Expand All @@ -123,7 +121,7 @@ void testOutputFilteringLenient() {
.getOriginalResponse()
.getIntermediateResults()
.getOutputFiltering();
assertThat(filterResult.getMessage()).contains("Choice 0: Filtering was skipped.");
assertThat(filterResult.getMessage()).contains("Filtering").containsAnyOf("passed", "skipped");
}

@Test
Expand Down