Skip to content

Commit 6862111

Browse files
committed
Merge branch 'main' into orchestration-llm-config
# Conflicts: # orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java # sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java # sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java
2 parents 50f2a78 + 173530d commit 6862111

File tree

15 files changed

+201
-95
lines changed

15 files changed

+201
-95
lines changed

.github/workflows/continuous-integration.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ jobs:
2222
with:
2323
fetch-depth: 1
2424
ref: ${{ github.event.pull_request.head.ref }}
25+
repository: ${{ github.event.pull_request.head.repo.full_name }}
2526

2627
- name: "Setup java"
2728
uses: actions/setup-java@v4

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ After restarting your application, you should see an "aicore" entry in the `VCAP
165165

166166
- **Name**: `my-aicore`
167167
- **Type**: `HTTP`
168-
- **URL**: `[serviceurls.AI_API_URL]/v2` (append `/v2` to the URL)
168+
- **URL**: `[serviceurls.AI_API_URL]`
169169
- **Proxy Type**: `Internet`
170170
- **Authentication**: `OAuth2ClientCredentials`
171171
- **Client ID**: `[clientid]`

docs/guides/ORCHESTRATION_CHAT_COMPLETION.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,7 @@ var prompt = new OrchestrationPrompt("Hello world! Why is this phrase so famous?
9191

9292
var result = client.chatCompletion(prompt, config);
9393

94-
String messageResult =
95-
result.getOrchestrationResult().getChoices().get(0).getMessage().getContent();
94+
String messageResult = result.getContent();
9695
```
9796

9897
In this example, the Orchestration service generates a response to the user message "Hello world! Why is this phrase so famous?".
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.sap.ai.sdk.orchestration;
2+
3+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
4+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
5+
import com.sap.ai.sdk.orchestration.client.model.LLMChoice;
6+
import com.sap.ai.sdk.orchestration.client.model.LLMModuleResultSynchronous;
7+
import lombok.AccessLevel;
8+
import lombok.NoArgsConstructor;
9+
10+
@NoArgsConstructor(access = AccessLevel.PRIVATE)
11+
final class JacksonMixins {
12+
/** Mixin to enforce a specific subtype to be deserialized always. */
13+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
14+
@JsonDeserialize(as = LLMModuleResultSynchronous.class)
15+
interface LLMModuleResultMixIn {}
16+
17+
/** Mixin to enforce a specific subtype to be deserialized always. */
18+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
19+
@JsonDeserialize(as = LLMChoice.class)
20+
interface ModuleResultsOutputUnmaskingInnerMixIn {}
21+
22+
/** Mixin to suppress @JsonTypeInfo for oneOf interfaces. */
23+
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
24+
interface NoTypeInfoMixin {}
25+
}

orchestration/src/main/java/com/sap/ai/sdk/orchestration/LLMModuleResultMixIn.java

Lines changed: 0 additions & 10 deletions
This file was deleted.

orchestration/src/main/java/com/sap/ai/sdk/orchestration/NoTypeInfoMixin.java

Lines changed: 0 additions & 7 deletions
This file was deleted.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.sap.ai.sdk.orchestration;
2+
3+
import static lombok.AccessLevel.PACKAGE;
4+
5+
import com.sap.ai.sdk.orchestration.client.model.CompletionPostResponse;
6+
import com.sap.ai.sdk.orchestration.client.model.LLMModuleResultSynchronous;
7+
import javax.annotation.Nonnull;
8+
import lombok.RequiredArgsConstructor;
9+
import lombok.Value;
10+
11+
/** Orchestration chat completion output. */
12+
@Value
13+
@RequiredArgsConstructor(access = PACKAGE)
14+
public class OrchestrationChatResponse {
15+
CompletionPostResponse originalResponse;
16+
17+
/**
18+
* Get the message content from the output.
19+
*
20+
* <p>Note: If there are multiple choices only the first one is returned
21+
*
22+
* @return the message content or empty string.
23+
* @throws OrchestrationClientException if the content filter filtered the output.
24+
*/
25+
@Nonnull
26+
public String getContent() throws OrchestrationClientException {
27+
final var choices =
28+
((LLMModuleResultSynchronous) originalResponse.getOrchestrationResult()).getChoices();
29+
30+
if (choices.isEmpty()) {
31+
return "";
32+
}
33+
34+
final var choice = choices.get(0);
35+
36+
if ("content_filter".equals(choice.getFinishReason())) {
37+
throw new OrchestrationClientException("Content filter filtered the output.");
38+
}
39+
return choice.getMessage().getContent();
40+
}
41+
}

orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,13 @@ public class OrchestrationClient {
4545
.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE)
4646
.visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE)
4747
.serializationInclusion(JsonInclude.Include.NON_NULL)
48-
.mixIn(LLMModuleResult.class, LLMModuleResultMixIn.class)
49-
.mixIn(ModuleResultsOutputUnmaskingInner.class, NoTypeInfoMixin.class)
50-
.mixIn(FilterConfig.class, NoTypeInfoMixin.class)
51-
.mixIn(MaskingProviderConfig.class, NoTypeInfoMixin.class)
52-
.mixIn(TemplatingModuleConfig.class, NoTypeInfoMixin.class)
48+
.mixIn(LLMModuleResult.class, JacksonMixins.LLMModuleResultMixIn.class)
49+
.mixIn(
50+
ModuleResultsOutputUnmaskingInner.class,
51+
JacksonMixins.ModuleResultsOutputUnmaskingInnerMixIn.class)
52+
.mixIn(FilterConfig.class, JacksonMixins.NoTypeInfoMixin.class)
53+
.mixIn(MaskingProviderConfig.class, JacksonMixins.NoTypeInfoMixin.class)
54+
.mixIn(TemplatingModuleConfig.class, JacksonMixins.NoTypeInfoMixin.class)
5355
.build();
5456
}
5557

@@ -99,12 +101,12 @@ public static CompletionPostRequest toCompletionPostRequest(
99101
* @throws OrchestrationClientException if the request fails.
100102
*/
101103
@Nonnull
102-
public CompletionPostResponse chatCompletion(
104+
public OrchestrationChatResponse chatCompletion(
103105
@Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config)
104106
throws OrchestrationClientException {
105107

106108
val request = toCompletionPostRequest(prompt, config);
107-
return executeRequest(request);
109+
return new OrchestrationChatResponse(executeRequest(request));
108110
}
109111

110112
/**

orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,7 @@ void testCompletion() {
122122
final var result = client.chatCompletion(prompt, config);
123123

124124
assertThat(result).isNotNull();
125-
var orchestrationResult = (LLMModuleResultSynchronous) result.getOrchestrationResult();
126-
assertThat(orchestrationResult.getChoices().get(0).getMessage().getContent()).isNotEmpty();
125+
assertThat(result.getContent()).isNotEmpty();
127126
}
128127

129128
@Test
@@ -142,11 +141,12 @@ void testTemplating() throws IOException {
142141
final var result =
143142
client.chatCompletion(new OrchestrationPrompt(inputParams, template), config);
144143

145-
assertThat(result.getRequestId()).isEqualTo("26ea36b5-c196-4806-a9a6-a686f0c6ad91");
146-
assertThat(result.getModuleResults().getTemplating().get(0).getContent())
144+
final var response = result.getOriginalResponse();
145+
assertThat(response.getRequestId()).isEqualTo("26ea36b5-c196-4806-a9a6-a686f0c6ad91");
146+
assertThat(response.getModuleResults().getTemplating().get(0).getContent())
147147
.isEqualTo("Reply with 'Orchestration Service is working!' in German");
148-
assertThat(result.getModuleResults().getTemplating().get(0).getRole()).isEqualTo("user");
149-
var llm = (LLMModuleResultSynchronous) result.getModuleResults().getLlm();
148+
assertThat(response.getModuleResults().getTemplating().get(0).getRole()).isEqualTo("user");
149+
var llm = (LLMModuleResultSynchronous) response.getModuleResults().getLlm();
150150
assertThat(llm).isNotNull();
151151
assertThat(llm.getId()).isEqualTo("chatcmpl-9lzPV4kLrXjFckOp2yY454wksWBoj");
152152
assertThat(llm.getObject()).isEqualTo("chat.completion");
@@ -162,7 +162,7 @@ void testTemplating() throws IOException {
162162
assertThat(usage.getCompletionTokens()).isEqualTo(7);
163163
assertThat(usage.getPromptTokens()).isEqualTo(19);
164164
assertThat(usage.getTotalTokens()).isEqualTo(26);
165-
var orchestrationResult = (LLMModuleResultSynchronous) result.getOrchestrationResult();
165+
var orchestrationResult = (LLMModuleResultSynchronous) response.getOrchestrationResult();
166166
assertThat(orchestrationResult.getId()).isEqualTo("chatcmpl-9lzPV4kLrXjFckOp2yY454wksWBoj");
167167
assertThat(orchestrationResult.getObject()).isEqualTo("chat.completion");
168168
assertThat(orchestrationResult.getCreated()).isEqualTo(1721224505);
@@ -288,7 +288,8 @@ void messagesHistory() throws IOException {
288288

289289
final var result = client.chatCompletion(prompt, config);
290290

291-
assertThat(result.getRequestId()).isEqualTo("26ea36b5-c196-4806-a9a6-a686f0c6ad91");
291+
assertThat(result.getOriginalResponse().getRequestId())
292+
.isEqualTo("26ea36b5-c196-4806-a9a6-a686f0c6ad91");
292293

293294
// verify that the history is sent correctly
294295
try (var requestInputStream = fileLoader.apply("messagesHistoryRequest.json")) {
@@ -300,7 +301,7 @@ void messagesHistory() throws IOException {
300301
}
301302

302303
@Test
303-
void maskingAnonymization() throws IOException {
304+
void maskingPseudonymization() throws IOException {
304305
stubFor(
305306
post(urlPathEqualTo("/v2/inference/deployments/abcdef0123456789/completion"))
306307
.willReturn(
@@ -309,19 +310,17 @@ void maskingAnonymization() throws IOException {
309310
.withHeader("Content-Type", "application/json")));
310311

311312
final var maskingConfig =
312-
createMaskingConfig(DPIConfig.MethodEnum.ANONYMIZATION, DPIEntities.PHONE);
313+
createMaskingConfig(DPIConfig.MethodEnum.PSEUDONYMIZATION, DPIEntities.PHONE);
313314

314315
final var result = client.chatCompletion(prompt, config.withMaskingConfig(maskingConfig));
316+
final var response = result.getOriginalResponse();
315317

316-
assertThat(result).isNotNull();
317-
GenericModuleResult inputMasking = result.getModuleResults().getInputMasking();
318+
assertThat(response).isNotNull();
319+
GenericModuleResult inputMasking = response.getModuleResults().getInputMasking();
318320
assertThat(inputMasking).isNotNull();
319321
assertThat(inputMasking.getMessage()).isEqualTo("Input to LLM is masked successfully.");
320322
assertThat(inputMasking.getData()).isNotNull();
321-
final var choices = ((LLMModuleResultSynchronous) result.getOrchestrationResult()).getChoices();
322-
assertThat(choices.get(0).getMessage().getContent())
323-
.isEqualTo(
324-
"I'm sorry, I cannot provide information about specific individuals, including their nationality.");
323+
assertThat(result.getContent()).contains("Hi Mallory");
325324

326325
// verify that the request is sent correctly
327326
try (var requestInputStream = fileLoader.apply("maskingRequest.json")) {
@@ -417,4 +416,17 @@ void testErrorHandling() {
417416

418417
softly.assertAll();
419418
}
419+
420+
@Test
421+
void testEmptyChoicesResponse() {
422+
stubFor(
423+
post(urlPathEqualTo("/v2/inference/deployments/abcdef0123456789/completion"))
424+
.willReturn(
425+
aResponse()
426+
.withBodyFile("emptyChoicesResponse.json")
427+
.withHeader("Content-Type", "application/json")));
428+
final var result = client.chatCompletion(prompt, config);
429+
430+
assertThat(result.getContent()).isEmpty();
431+
}
420432
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"request_id": "26ea36b5-c196-4806-a9a6-a686f0c6ad91",
3+
"module_results": {
4+
"templating": [
5+
{
6+
"role": "user",
7+
"content": "Reply with 'Orchestration Service is working!' in German"
8+
}
9+
],
10+
"llm": {
11+
"id": "chatcmpl-9lzPV4kLrXjFckOp2yY454wksWBoj",
12+
"object": "chat.completion",
13+
"created": 1721224505,
14+
"model": "gpt-35-turbo-16k",
15+
"choices": [],
16+
"usage": {
17+
"completion_tokens": 7,
18+
"prompt_tokens": 19,
19+
"total_tokens": 26
20+
}
21+
}
22+
},
23+
"orchestration_result": {
24+
"id": "chatcmpl-9lzPV4kLrXjFckOp2yY454wksWBoj",
25+
"object": "chat.completion",
26+
"created": 1721224505,
27+
"model": "gpt-35-turbo-16k",
28+
"choices": [],
29+
"usage": {
30+
"completion_tokens": 7,
31+
"prompt_tokens": 19,
32+
"total_tokens": 26
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)