diff --git a/.gitignore b/.gitignore index 28c9baefe..e0219a487 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,8 @@ ### Environment ### *.env -.openapi-generator \ No newline at end of file +.openapi-generator + +# sub-module spotbugs +**/.pipeline/spotbugs.xml +**/.pipeline/spotbugs-exclusions.xml diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationController.java index 2440b0c38..4668bedde 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationController.java @@ -1,13 +1,20 @@ package com.sap.ai.sdk.app.controllers; import com.sap.ai.sdk.app.services.SpringAiOrchestrationService; +import com.sap.ai.sdk.orchestration.AzureFilterThreshold; +import com.sap.ai.sdk.orchestration.OrchestrationClientException; import com.sap.ai.sdk.orchestration.spring.OrchestrationSpringChatResponse; +import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import lombok.extern.slf4j.Slf4j; import lombok.val; import org.springframework.ai.chat.messages.AssistantMessage; +import org.springframework.ai.chat.model.ChatResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -15,6 +22,7 @@ @SuppressWarnings("unused") @RestController +@Slf4j @RequestMapping("/spring-ai-orchestration") class SpringAiOrchestrationController { @Autowired private SpringAiOrchestrationService service; @@ -52,6 +60,50 @@ Object template(@Nullable @RequestParam(value = "format", required = false) fina return response.getResult().getOutput().getText(); } + @GetMapping("/inputFiltering/{policy}") + @Nonnull + Object inputFiltering( + @Nullable @RequestParam(value = "format", required = false) final String format, + @Nonnull @PathVariable("policy") final AzureFilterThreshold policy) { + + final ChatResponse response; + try { + response = service.inputFiltering(policy); + } catch (OrchestrationClientException e) { + final var msg = "Failed to obtain a response as the content was flagged by input filter."; + log.debug(msg, e); + return ResponseEntity.internalServerError().body(msg); + } + + if ("json".equals(format)) { + return ((OrchestrationSpringChatResponse) response) + .getOrchestrationResponse() + .getOriginalResponse(); + } + return response.getResult().getOutput().getText(); + } + + @GetMapping("/outputFiltering/{policy}") + @Nonnull + Object outputFiltering( + @Nullable @RequestParam(value = "format", required = false) final String format, + @Nonnull @PathVariable("policy") final AzureFilterThreshold policy) { + + val response = service.outputFiltering(policy); + + if (response.hasFinishReasons(Set.of("content_filter"))) { + return ResponseEntity.internalServerError() + .body("Failed to obtain a response as the content was flagged by output filter."); + } + + if ("json".equals(format)) { + return ((OrchestrationSpringChatResponse) response) + .getOrchestrationResponse() + .getOriginalResponse(); + } + return response.getResult().getOutput().getText(); + } + @GetMapping("/masking") Object masking(@Nullable @RequestParam(value = "format", required = false) final String format) { val response = service.masking(); diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOrchestrationService.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOrchestrationService.java index d56c85994..101a48b23 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOrchestrationService.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOrchestrationService.java @@ -1,8 +1,12 @@ package com.sap.ai.sdk.app.services; +import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GEMINI_1_5_FLASH; import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GPT_4O_MINI; +import com.sap.ai.sdk.orchestration.AzureContentFilter; +import com.sap.ai.sdk.orchestration.AzureFilterThreshold; import com.sap.ai.sdk.orchestration.DpiMasking; +import com.sap.ai.sdk.orchestration.OrchestrationClientException; import com.sap.ai.sdk.orchestration.OrchestrationModuleConfig; import com.sap.ai.sdk.orchestration.model.DPIEntities; import com.sap.ai.sdk.orchestration.spring.OrchestrationChatModel; @@ -94,6 +98,57 @@ public ChatResponse masking() { return client.call(prompt); } + /** + * Apply input filtering for a request to orchestration using the SpringAI integration. + * + * @link SAP + * AI Core: Orchestration - Input Filtering + * @param policy the explicitness of content that should be allowed through the filter + * @return the assistant response object + */ + @Nonnull + public ChatResponse inputFiltering(@Nonnull final AzureFilterThreshold policy) + throws OrchestrationClientException { + val filterConfig = + new AzureContentFilter().hate(policy).selfHarm(policy).sexual(policy).violence(policy); + val opts = + new OrchestrationChatOptions( + config.withLlmConfig(GEMINI_1_5_FLASH).withInputFiltering(filterConfig)); + + val prompt = + new Prompt( + "Please rephrase the following sentence for me: 'We shall spill blood tonight', said the operator in-charge.", + opts); + + return client.call(prompt); + } + + /** + * Apply output filtering for a request to orchestration using the SpringAI integration. + * + * @link SAP + * AI Core: Orchestration - Output Filtering + * @param policy the explicitness of content that should be allowed through the filter + * @return the assistant response object + */ + @Nonnull + public ChatResponse outputFiltering(@Nonnull final AzureFilterThreshold policy) { + val filterConfig = + new AzureContentFilter().hate(policy).selfHarm(policy).sexual(policy).violence(policy); + val opts = + new OrchestrationChatOptions( + config.withLlmConfig(GEMINI_1_5_FLASH).withOutputFiltering(filterConfig)); + + val prompt = + new Prompt( + "Please rephrase the following sentence for me: 'We shall spill blood tonight', said the operator in-charge.", + opts); + + return client.call(prompt); + } + /** * Turn a method into a tool by annotating it with @Tool. Spring AI diff --git a/sample-code/spring-app/src/main/resources/static/index.html b/sample-code/spring-app/src/main/resources/static/index.html index 0019ecada..34d8d0a93 100644 --- a/sample-code/spring-app/src/main/resources/static/index.html +++ b/sample-code/spring-app/src/main/resources/static/index.html @@ -691,6 +691,30 @@
Orchestration Integration
+
  • +
    + +
    + Apply strict input filtering for a request to orchestration using the SpringAI integration. +
    +
    +
  • +
  • +
    + +
    + Apply strict output filtering for a request to orchestration using the SpringAI integration. +
    +
    +