Skip to content

Commit 2da40a0

Browse files
committed
Merge branch 'refs/heads/main' into feat/openai/tool-definition-and-call-parsing-v2
2 parents f8e3645 + 4af893f commit 2da40a0

File tree

11 files changed

+209
-7
lines changed

11 files changed

+209
-7
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@
2828
### Environment ###
2929
*.env
3030

31-
.openapi-generator
31+
.openapi-generator
32+
33+
# sub-module spotbugs
34+
**/.pipeline/spotbugs.xml
35+
**/.pipeline/spotbugs-exclusions.xml

core-services/document-grounding/src/main/java/com/sap/ai/sdk/grounding/model/DocumentKeyValueListPair.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public String toString() {
8585
* @return The enum value of type DocumentKeyValueListPair
8686
*/
8787
@JsonCreator
88-
@Nonnull
88+
@Nullable
8989
public static MatchModeEnum fromValue(@Nonnull final String value) {
9090
for (MatchModeEnum b : MatchModeEnum.values()) {
9191
if (b.value.equals(value)) {

core/src/main/java/com/sap/ai/sdk/core/model/AiDeployment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public String toString() {
170170
* @return The enum value of type AiDeployment
171171
*/
172172
@JsonCreator
173-
@Nonnull
173+
@Nullable
174174
public static LastOperationEnum fromValue(@Nonnull final String value) {
175175
for (LastOperationEnum b : LastOperationEnum.values()) {
176176
if (b.value.equals(value)) {

core/src/main/java/com/sap/ai/sdk/core/model/AiDeploymentResponseWithDetails.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public String toString() {
170170
* @return The enum value of type AiDeploymentResponseWithDetails
171171
*/
172172
@JsonCreator
173-
@Nonnull
173+
@Nullable
174174
public static LastOperationEnum fromValue(@Nonnull final String value) {
175175
for (LastOperationEnum b : LastOperationEnum.values()) {
176176
if (b.value.equals(value)) {

orchestration/src/main/java/com/sap/ai/sdk/orchestration/model/GroundingModuleConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public String toString() {
7373
* @return The enum value of type GroundingModuleConfig
7474
*/
7575
@JsonCreator
76-
@Nonnull
76+
@Nullable
7777
public static TypeEnum fromValue(@Nonnull final String value) {
7878
for (TypeEnum b : TypeEnum.values()) {
7979
if (b.value.equals(value)) {

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
<maven.compiler.proc>full</maven.compiler.proc>
5959
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
6060
<project.build.outputTimestamp>2025-04-03T13:23:00Z</project.build.outputTimestamp>
61-
<cloud-sdk.version>5.17.0</cloud-sdk.version>
61+
<cloud-sdk.version>5.18.0</cloud-sdk.version>
6262
<junit-jupiter.version>5.12.2</junit-jupiter.version>
6363
<wiremock.version>3.12.1</wiremock.version>
6464
<assertj-core.version>3.27.3</assertj-core.version>

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationController.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
11
package com.sap.ai.sdk.app.controllers;
22

33
import com.sap.ai.sdk.app.services.SpringAiOrchestrationService;
4+
import com.sap.ai.sdk.orchestration.AzureFilterThreshold;
5+
import com.sap.ai.sdk.orchestration.OrchestrationClientException;
46
import com.sap.ai.sdk.orchestration.spring.OrchestrationSpringChatResponse;
7+
import java.util.Set;
58
import javax.annotation.Nonnull;
69
import javax.annotation.Nullable;
10+
import lombok.extern.slf4j.Slf4j;
711
import lombok.val;
812
import org.springframework.ai.chat.messages.AssistantMessage;
13+
import org.springframework.ai.chat.model.ChatResponse;
914
import org.springframework.beans.factory.annotation.Autowired;
15+
import org.springframework.http.ResponseEntity;
1016
import org.springframework.web.bind.annotation.GetMapping;
17+
import org.springframework.web.bind.annotation.PathVariable;
1118
import org.springframework.web.bind.annotation.RequestMapping;
1219
import org.springframework.web.bind.annotation.RequestParam;
1320
import org.springframework.web.bind.annotation.RestController;
1421
import reactor.core.publisher.Flux;
1522

1623
@SuppressWarnings("unused")
1724
@RestController
25+
@Slf4j
1826
@RequestMapping("/spring-ai-orchestration")
1927
class SpringAiOrchestrationController {
2028
@Autowired private SpringAiOrchestrationService service;
@@ -52,6 +60,50 @@ Object template(@Nullable @RequestParam(value = "format", required = false) fina
5260
return response.getResult().getOutput().getText();
5361
}
5462

63+
@GetMapping("/inputFiltering/{policy}")
64+
@Nonnull
65+
Object inputFiltering(
66+
@Nullable @RequestParam(value = "format", required = false) final String format,
67+
@Nonnull @PathVariable("policy") final AzureFilterThreshold policy) {
68+
69+
final ChatResponse response;
70+
try {
71+
response = service.inputFiltering(policy);
72+
} catch (OrchestrationClientException e) {
73+
final var msg = "Failed to obtain a response as the content was flagged by input filter.";
74+
log.debug(msg, e);
75+
return ResponseEntity.internalServerError().body(msg);
76+
}
77+
78+
if ("json".equals(format)) {
79+
return ((OrchestrationSpringChatResponse) response)
80+
.getOrchestrationResponse()
81+
.getOriginalResponse();
82+
}
83+
return response.getResult().getOutput().getText();
84+
}
85+
86+
@GetMapping("/outputFiltering/{policy}")
87+
@Nonnull
88+
Object outputFiltering(
89+
@Nullable @RequestParam(value = "format", required = false) final String format,
90+
@Nonnull @PathVariable("policy") final AzureFilterThreshold policy) {
91+
92+
val response = service.outputFiltering(policy);
93+
94+
if (response.hasFinishReasons(Set.of("content_filter"))) {
95+
return ResponseEntity.internalServerError()
96+
.body("Failed to obtain a response as the content was flagged by output filter.");
97+
}
98+
99+
if ("json".equals(format)) {
100+
return ((OrchestrationSpringChatResponse) response)
101+
.getOrchestrationResponse()
102+
.getOriginalResponse();
103+
}
104+
return response.getResult().getOutput().getText();
105+
}
106+
55107
@GetMapping("/masking")
56108
Object masking(@Nullable @RequestParam(value = "format", required = false) final String format) {
57109
val response = service.masking();

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.sap.ai.sdk.app.services;
22

3+
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GEMINI_1_5_FLASH;
34
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GPT_4O_MINI;
45

6+
import com.sap.ai.sdk.orchestration.AzureContentFilter;
7+
import com.sap.ai.sdk.orchestration.AzureFilterThreshold;
58
import com.sap.ai.sdk.orchestration.DpiMasking;
9+
import com.sap.ai.sdk.orchestration.OrchestrationClientException;
610
import com.sap.ai.sdk.orchestration.OrchestrationModuleConfig;
711
import com.sap.ai.sdk.orchestration.model.DPIEntities;
812
import com.sap.ai.sdk.orchestration.spring.OrchestrationChatModel;
@@ -94,6 +98,57 @@ public ChatResponse masking() {
9498
return client.call(prompt);
9599
}
96100

101+
/**
102+
* Apply input filtering for a request to orchestration using the SpringAI integration.
103+
*
104+
* @link <a
105+
* href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/input-filtering">SAP
106+
* AI Core: Orchestration - Input Filtering</a>
107+
* @param policy the explicitness of content that should be allowed through the filter
108+
* @return the assistant response object
109+
*/
110+
@Nonnull
111+
public ChatResponse inputFiltering(@Nonnull final AzureFilterThreshold policy)
112+
throws OrchestrationClientException {
113+
val filterConfig =
114+
new AzureContentFilter().hate(policy).selfHarm(policy).sexual(policy).violence(policy);
115+
val opts =
116+
new OrchestrationChatOptions(
117+
config.withLlmConfig(GEMINI_1_5_FLASH).withInputFiltering(filterConfig));
118+
119+
val prompt =
120+
new Prompt(
121+
"Please rephrase the following sentence for me: 'We shall spill blood tonight', said the operator in-charge.",
122+
opts);
123+
124+
return client.call(prompt);
125+
}
126+
127+
/**
128+
* Apply output filtering for a request to orchestration using the SpringAI integration.
129+
*
130+
* @link <a
131+
* href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/output-filtering">SAP
132+
* AI Core: Orchestration - Output Filtering</a>
133+
* @param policy the explicitness of content that should be allowed through the filter
134+
* @return the assistant response object
135+
*/
136+
@Nonnull
137+
public ChatResponse outputFiltering(@Nonnull final AzureFilterThreshold policy) {
138+
val filterConfig =
139+
new AzureContentFilter().hate(policy).selfHarm(policy).sexual(policy).violence(policy);
140+
val opts =
141+
new OrchestrationChatOptions(
142+
config.withLlmConfig(GEMINI_1_5_FLASH).withOutputFiltering(filterConfig));
143+
144+
val prompt =
145+
new Prompt(
146+
"Please rephrase the following sentence for me: 'We shall spill blood tonight', said the operator in-charge.",
147+
opts);
148+
149+
return client.call(prompt);
150+
}
151+
97152
/**
98153
* Turn a method into a tool by annotating it with @Tool. <a
99154
* href="https://docs.spring.io/spring-ai/reference/api/tools.html#_methods_as_tools">Spring AI

sample-code/spring-app/src/main/resources/static/index.html

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,30 @@ <h5 class="mb-1">Orchestration Integration</h5>
691691
</div>
692692
</div>
693693
</li>
694+
<li class="list-group-item">
695+
<div class="info-tooltip">
696+
<button type="submit"
697+
formaction="/spring-ai-orchestration/inputFiltering/ALLOW_SAFE"
698+
class="link-offset-2-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover endpoint">
699+
<code>/spring-ai-orchestration/inputFiltering/ALLOW_SAFE</code>
700+
</button>
701+
<div class="tooltip-content">
702+
Apply strict input filtering for a request to orchestration using the SpringAI integration.
703+
</div>
704+
</div>
705+
</li>
706+
<li class="list-group-item">
707+
<div class="info-tooltip">
708+
<button type="submit"
709+
formaction="/spring-ai-orchestration/outputFiltering/ALLOW_SAFE"
710+
class="link-offset-2-hover link-underline link-underline-opacity-0 link-underline-opacity-75-hover endpoint">
711+
<code>/spring-ai-orchestration/outputFiltering/ALLOW_SAFE</code>
712+
</button>
713+
<div class="tooltip-content">
714+
Apply strict output filtering for a request to orchestration using the SpringAI integration.
715+
</div>
716+
</div>
717+
</li>
694718
<li class="list-group-item">
695719
<div class="info-tooltip">
696720
<button type="submit"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ void history() {
7878
PromptTemplateListResponse history = controller.history();
7979
// Bug that doesn't delete prompts fast enough. Should be equal to 2
8080
assertThat(history.getCount()).isGreaterThanOrEqualTo(2);
81-
assertThat(history.getResources()).hasSizeGreaterThan(2);
81+
assertThat(history.getResources()).hasSizeGreaterThanOrEqualTo(2);
8282

8383
// cleanup
8484
List<PromptTemplateDeleteResponse> deletedTemplate = controller.deleteTemplate();

0 commit comments

Comments
 (0)