Skip to content

Commit d1bad94

Browse files
authored
Merge branch 'main' into workflow/compatibility-version
2 parents d1025ff + cf661ad commit d1bad94

File tree

16 files changed

+224
-99
lines changed

16 files changed

+224
-99
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

.github/workflows/perform-release.yml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,6 @@ jobs:
8888
with:
8989
distribution: "sapmachine"
9090
java-version: ${{ env.JAVA_VERSION }}
91-
server-id: ossrh
92-
server-username: MAVEN_CENTRAL_USER # env variable for username in deploy
93-
server-password: MAVEN_CENTRAL_PASSWORD # env variable for token in deploy
9491

9592
- name: "Download Release Asset"
9693
id: download-asset
@@ -113,8 +110,8 @@ jobs:
113110
114111
- name: "Deploy"
115112
run: |
116-
MVN_ARGS="${{ env.MVN_CLI_ARGS }} deploy -Drelease -s settings.xml"
117-
mvn $MVN_ARGS
113+
MVN_ARGS="${{ env.MVN_CLI_ARGS }} -Drelease -s settings.xml"
114+
mvn deploy $MVN_ARGS
118115
env:
119116
MAVEN_GPG_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
120117

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
@@ -121,8 +121,7 @@ void testCompletion() {
121121
final var result = client.chatCompletion(prompt, config);
122122

123123
assertThat(result).isNotNull();
124-
var orchestrationResult = (LLMModuleResultSynchronous) result.getOrchestrationResult();
125-
assertThat(orchestrationResult.getChoices().get(0).getMessage().getContent()).isNotEmpty();
124+
assertThat(result.getContent()).isNotEmpty();
126125
}
127126

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

144-
assertThat(result.getRequestId()).isEqualTo("26ea36b5-c196-4806-a9a6-a686f0c6ad91");
145-
assertThat(result.getModuleResults().getTemplating().get(0).getContent())
143+
final var response = result.getOriginalResponse();
144+
assertThat(response.getRequestId()).isEqualTo("26ea36b5-c196-4806-a9a6-a686f0c6ad91");
145+
assertThat(response.getModuleResults().getTemplating().get(0).getContent())
146146
.isEqualTo("Reply with 'Orchestration Service is working!' in German");
147-
assertThat(result.getModuleResults().getTemplating().get(0).getRole()).isEqualTo("user");
148-
var llm = (LLMModuleResultSynchronous) result.getModuleResults().getLlm();
147+
assertThat(response.getModuleResults().getTemplating().get(0).getRole()).isEqualTo("user");
148+
var llm = (LLMModuleResultSynchronous) response.getModuleResults().getLlm();
149149
assertThat(llm.getId()).isEqualTo("chatcmpl-9lzPV4kLrXjFckOp2yY454wksWBoj");
150150
assertThat(llm.getObject()).isEqualTo("chat.completion");
151151
assertThat(llm.getCreated()).isEqualTo(1721224505);
@@ -160,7 +160,7 @@ void testTemplating() throws IOException {
160160
assertThat(usage.getCompletionTokens()).isEqualTo(7);
161161
assertThat(usage.getPromptTokens()).isEqualTo(19);
162162
assertThat(usage.getTotalTokens()).isEqualTo(26);
163-
var orchestrationResult = (LLMModuleResultSynchronous) result.getOrchestrationResult();
163+
var orchestrationResult = (LLMModuleResultSynchronous) response.getOrchestrationResult();
164164
assertThat(orchestrationResult.getId()).isEqualTo("chatcmpl-9lzPV4kLrXjFckOp2yY454wksWBoj");
165165
assertThat(orchestrationResult.getObject()).isEqualTo("chat.completion");
166166
assertThat(orchestrationResult.getCreated()).isEqualTo(1721224505);
@@ -286,7 +286,8 @@ void messagesHistory() throws IOException {
286286

287287
final var result = client.chatCompletion(prompt, config);
288288

289-
assertThat(result.getRequestId()).isEqualTo("26ea36b5-c196-4806-a9a6-a686f0c6ad91");
289+
assertThat(result.getOriginalResponse().getRequestId())
290+
.isEqualTo("26ea36b5-c196-4806-a9a6-a686f0c6ad91");
290291

291292
// verify that the history is sent correctly
292293
try (var requestInputStream = fileLoader.apply("messagesHistoryRequest.json")) {
@@ -298,7 +299,7 @@ void messagesHistory() throws IOException {
298299
}
299300

300301
@Test
301-
void maskingAnonymization() throws IOException {
302+
void maskingPseudonymization() throws IOException {
302303
stubFor(
303304
post(urlPathEqualTo("/v2/inference/deployments/abcdef0123456789/completion"))
304305
.willReturn(
@@ -307,18 +308,16 @@ void maskingAnonymization() throws IOException {
307308
.withHeader("Content-Type", "application/json")));
308309

309310
final var maskingConfig =
310-
createMaskingConfig(DPIConfig.MethodEnum.ANONYMIZATION, DPIEntities.PHONE);
311+
createMaskingConfig(DPIConfig.MethodEnum.PSEUDONYMIZATION, DPIEntities.PHONE);
311312

312313
final var result = client.chatCompletion(prompt, config.withMaskingConfig(maskingConfig));
314+
final var response = result.getOriginalResponse();
313315

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

323322
// verify that the request is sent correctly
324323
try (var requestInputStream = fileLoader.apply("maskingRequest.json")) {
@@ -414,4 +413,17 @@ void testErrorHandling() {
414413

415414
softly.assertAll();
416415
}
416+
417+
@Test
418+
void testEmptyChoicesResponse() {
419+
stubFor(
420+
post(urlPathEqualTo("/v2/inference/deployments/abcdef0123456789/completion"))
421+
.willReturn(
422+
aResponse()
423+
.withBodyFile("emptyChoicesResponse.json")
424+
.withHeader("Content-Type", "application/json")));
425+
final var result = client.chatCompletion(prompt, config);
426+
427+
assertThat(result.getContent()).isEmpty();
428+
}
417429
}

0 commit comments

Comments
 (0)