Skip to content

Commit 223ab51

Browse files
Merge branch 'refs/heads/main' into llama🦙guard
# Conflicts: # sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java # sample-code/spring-app/src/main/resources/static/index.html
2 parents 18f5e0b + 68ebf91 commit 223ab51

File tree

17 files changed

+284
-760
lines changed

17 files changed

+284
-760
lines changed

docs/guides/OPENAI_CHAT_COMPLETION.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ var result = OpenAiClient.forModel(GPT_35_TURBO).chatCompletion(request);
122122
String resultMessage = result.getContent();
123123
```
124124

125-
See [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java)
125+
See [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OpenAiService.java)
126126

127127
## Chat Completion with Specific Model Version
128128

@@ -205,7 +205,7 @@ Integer tokensUsed = totalOutput.getUsage().getCompletionTokens();
205205
System.out.println("Tokens used: " + tokensUsed);
206206
```
207207

208-
Please find [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java). It shows the usage of Spring
208+
Please find [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OpenAiService.java). It shows the usage of Spring
209209
Boot's `ResponseBodyEmitter` to stream the chat completion delta messages to the frontend in real-time.
210210

211211
## Embedding
@@ -218,4 +218,4 @@ var request = new OpenAiEmbeddingParameters().setInput("Hello World");
218218
OpenAiEmbeddingOutput embedding = OpenAiClient.forModel(TEXT_EMBEDDING_ADA_002).embedding(request);
219219
```
220220

221-
See [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java)
221+
See [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OpenAiService.java)

docs/guides/ORCHESTRATION_CHAT_COMPLETION.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ var config = new OrchestrationModuleConfig()
8383
.withLlmConfig(OrchestrationAiModel.GPT_4O);
8484
```
8585

86-
Please also refer to [our sample code](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java) for this and all following code examples.
86+
Please also refer to [our sample code](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java) for this and all following code examples.
8787

8888
## Chat Completion
8989

@@ -179,7 +179,7 @@ var result =
179179
The convenience method `getContent()` on the resulting object will throw an `OrchestrationClientException` upon invocation.
180180
The low level API under `getOriginalResponse()` will not throw an exception.
181181

182-
You will find [some examples](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java) in our Spring Boot application demonstrating response handling with filters.
182+
You will find [some examples](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java) in our Spring Boot application demonstrating response handling with filters.
183183

184184
## Data masking
185185

@@ -256,7 +256,7 @@ try (Stream<String> stream = client.streamChatCompletion(prompt, config)) {
256256
}
257257
```
258258

259-
Please find [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java).
259+
Please find [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java).
260260
It shows the usage of Spring Boot's `ResponseBodyEmitter` to stream the chat completion delta messages to the frontend in real-time.
261261

262262
## Set model parameters

docs/guides/SPRING_AI_INTEGRATION.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Prompt prompt = new Prompt("What is the capital of France?", opts);
5050
ChatResponse response = client.call(prompt);
5151
```
5252

53-
Please find [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationController.java).
53+
Please find [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOrchestrationService.java).
5454

5555
## Orchestration Masking
5656

@@ -74,7 +74,7 @@ ChatResponse response = client.call(prompt);
7474
```
7575

7676
Please
77-
find [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationController.java).
77+
find [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOrchestrationService.java).
7878

7979
## Stream chat completion
8080

@@ -98,4 +98,4 @@ Flux<String> responseFlux =
9898

9999
_Note: A Spring endpoint can return `Flux` instead of `ResponseEntity`._
100100

101-
Please find [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/SpringAiOrchestrationController.java).
101+
Please find [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/SpringAiOrchestrationService.java).

docs/release-notes/release_notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
- Grounding input can be masked with `DPIConfig`.
2323
- [LLama Guard can now be used for content filtering.](../guides/ORCHESTRATION_CHAT_COMPLETION.md#chat-completion-filter)
2424
- Support for tool calling and response format
25+
- Updated the list for supported models (e.g., added amazon nova models).
2526

2627
### 📈 Improvements
2728

orchestration/src/main/java/com/sap/ai/sdk/orchestration/spring/OrchestrationChatModel.java

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,12 @@
66
import com.sap.ai.sdk.orchestration.AssistantMessage;
77
import com.sap.ai.sdk.orchestration.OrchestrationChatCompletionDelta;
88
import com.sap.ai.sdk.orchestration.OrchestrationClient;
9-
import com.sap.ai.sdk.orchestration.OrchestrationClientException;
109
import com.sap.ai.sdk.orchestration.OrchestrationPrompt;
1110
import com.sap.ai.sdk.orchestration.SystemMessage;
1211
import com.sap.ai.sdk.orchestration.UserMessage;
1312
import java.util.List;
1413
import java.util.Map;
1514
import java.util.function.Function;
16-
import java.util.stream.Stream;
1715
import javax.annotation.Nonnull;
1816
import lombok.RequiredArgsConstructor;
1917
import lombok.extern.slf4j.Slf4j;
@@ -79,26 +77,12 @@ public Flux<ChatResponse> stream(@Nonnull final Prompt prompt) {
7977
}
8078
return iterator;
8179
});
82-
return flux.map(
83-
delta -> {
84-
throwOnContentFilter(stream, delta);
85-
return new OrchestrationSpringChatDelta(delta);
86-
});
80+
return flux.map(OrchestrationSpringChatDelta::new);
8781
}
8882
throw new IllegalArgumentException(
8983
"Please add OrchestrationChatOptions to the Prompt: new Prompt(\"message\", new OrchestrationChatOptions(config))");
9084
}
9185

92-
private static void throwOnContentFilter(
93-
@Nonnull final Stream<OrchestrationChatCompletionDelta> stream,
94-
@Nonnull final OrchestrationChatCompletionDelta delta) {
95-
final String finishReason = delta.getFinishReason();
96-
if (finishReason != null && finishReason.equals("content_filter")) {
97-
stream.close();
98-
throw new OrchestrationClientException("Content filter filtered the output.");
99-
}
100-
}
101-
10286
@Nonnull
10387
private OrchestrationPrompt toOrchestrationPrompt(@Nonnull final Prompt prompt) {
10488
val messages = toOrchestrationMessages(prompt.getInstructions());

orchestration/src/test/java/com/sap/ai/sdk/orchestration/spring/OrchestrationChatModelTest.java

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
1717
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
1818
import com.sap.ai.sdk.orchestration.OrchestrationClient;
19-
import com.sap.ai.sdk.orchestration.OrchestrationClientException;
2019
import com.sap.ai.sdk.orchestration.OrchestrationModuleConfig;
2120
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
2221
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Cache;
@@ -102,31 +101,6 @@ void testThrowsOnMissingLlmConfig() {
102101
.hasMessageContaining("LLM config is required");
103102
}
104103

105-
@Test
106-
void streamChatCompletionOutputFilterErrorHandling() throws IOException {
107-
try (var inputStream = spy(fileLoader.apply("streamChatCompletionOutputFilter.txt"))) {
108-
109-
final var httpClient = mock(HttpClient.class);
110-
ApacheHttpClient5Accessor.setHttpClientFactory(destination -> httpClient);
111-
112-
// Create a mock response
113-
final var mockResponse = new BasicClassicHttpResponse(200, "OK");
114-
final var inputStreamEntity = new InputStreamEntity(inputStream, ContentType.TEXT_PLAIN);
115-
mockResponse.setEntity(inputStreamEntity);
116-
mockResponse.setHeader("Content-Type", "text/event-stream");
117-
118-
// Configure the HttpClient mock to return the mock response
119-
doReturn(mockResponse).when(httpClient).executeOpen(any(), any(), any());
120-
121-
Flux<ChatResponse> flux = client.stream(prompt);
122-
assertThatThrownBy(() -> flux.toStream().forEach(System.out::println))
123-
.isInstanceOf(OrchestrationClientException.class)
124-
.hasMessage("Content filter filtered the output.");
125-
126-
Mockito.verify(inputStream, times(1)).close();
127-
}
128-
}
129-
130104
@Test
131105
void testStreamCompletion() throws IOException {
132106
try (var inputStream = spy(fileLoader.apply("streamChatCompletion.txt"))) {

sample-code/spring-app/pom.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,6 @@
118118
<groupId>com.fasterxml.jackson.core</groupId>
119119
<artifactId>jackson-core</artifactId>
120120
</dependency>
121-
<dependency>
122-
<groupId>com.fasterxml.jackson.core</groupId>
123-
<artifactId>jackson-annotations</artifactId>
124-
</dependency>
125121
<!-- scope "runtime" -->
126122
<dependency>
127123
<groupId>ch.qos.logback</groupId>

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,34 @@
11
package com.sap.ai.sdk.app;
22

3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.sap.ai.sdk.orchestration.OrchestrationJacksonConfiguration;
5+
import javax.annotation.Nonnull;
36
import org.springframework.boot.SpringApplication;
47
import org.springframework.boot.autoconfigure.SpringBootApplication;
58
import org.springframework.boot.web.servlet.ServletComponentScan;
9+
import org.springframework.context.annotation.Bean;
610
import org.springframework.context.annotation.ComponentScan;
11+
import org.springframework.context.annotation.Primary;
712

813
/** Main class to start the Spring Boot application. */
914
@SpringBootApplication
1015
@ComponentScan({"com.sap.cloud.sdk", "com.sap.ai.sdk.app"})
1116
@ServletComponentScan({"com.sap.cloud.sdk", "com.sap.ai.sdk.app"})
1217
public class Application {
18+
19+
/**
20+
* Temporary workaround to fix the issue with the Orchestration spec.
21+
*
22+
* @return a modified object mapper that works for Orchestration.
23+
*/
24+
@Bean
25+
@Primary
26+
@SuppressWarnings("unused")
27+
@Nonnull
28+
public ObjectMapper objectMapper() {
29+
return OrchestrationJacksonConfiguration.getOrchestrationObjectMapper();
30+
}
31+
1332
/**
1433
* Main method to start the Spring Boot application.
1534
*

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
class ControllerExceptionHandler {
1515
/** Exceptions thrown by the Spring Boot controllers are turned into a readable text response. */
1616
@ExceptionHandler(Exception.class)
17-
ResponseEntity<String> handleError(final Exception ex) {
17+
Object handleError(final Exception ex) {
1818
final var headers = new HttpHeaders();
1919
headers.setContentType(MediaType.TEXT_PLAIN);
2020

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

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@
44
import com.sap.ai.sdk.core.model.AiConfiguration;
55
import java.util.stream.Collectors;
66
import javax.annotation.Nullable;
7-
import org.springframework.http.ResponseEntity;
87
import org.springframework.web.bind.annotation.GetMapping;
9-
import org.springframework.web.bind.annotation.RequestHeader;
8+
import org.springframework.web.bind.annotation.RequestParam;
109
import org.springframework.web.bind.annotation.RestController;
1110

1211
/** Endpoint for Configuration operations */
@@ -16,23 +15,17 @@ class ConfigurationController {
1615

1716
private static final ConfigurationApi CLIENT = new ConfigurationApi();
1817

19-
/**
20-
* Get the list of configurations.
21-
*
22-
* @param accept the accept header
23-
* @return a response entity with a string representation of the list of configurations
24-
*/
2518
@GetMapping("/configurations")
26-
ResponseEntity<Object> getConfigurations(
27-
@Nullable @RequestHeader(value = "accept", required = false) final String accept) {
19+
Object getConfigurations(
20+
@Nullable @RequestParam(value = "format", required = false) final String format) {
2821
final var configList = CLIENT.query("default");
29-
if ("application/json".equals(accept)) {
30-
return ResponseEntity.ok().body(configList);
22+
if ("json".equals(format)) {
23+
return configList;
3124
}
3225
final var items =
3326
configList.getResources().stream()
3427
.map(AiConfiguration::getName)
3528
.collect(Collectors.joining(", "));
36-
return ResponseEntity.ok("The following configurations are available: %s.".formatted(items));
29+
return "The following configurations are available: %s.".formatted(items);
3730
}
3831
}

0 commit comments

Comments
 (0)