Skip to content

Commit e7406f5

Browse files
authored
Orchestration spec update 12a - Tests (#269)
* Test - improve coverage on existing tests in addition - tests for failure cases of PolymorphicFallbackDeserializer * Unit test request with image (low level api) * Test calling multi chat response using convenience * minor test method name change * Replace url with a sample --------- Co-authored-by: Roshin Rajan Panackal <[email protected]>
1 parent 5e3b8d3 commit e7406f5

File tree

6 files changed

+336
-7
lines changed

6 files changed

+336
-7
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public List<Message> getAllMessages() throws UnsupportedOperationException {
7171
messages.add(message);
7272
} else {
7373
throw new UnsupportedOperationException(
74-
"Currently MultiChatMessage type not supported by convenience API");
74+
"Messages of MultiChatMessage type not supported by convenience API");
7575
}
7676
}
7777

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

Lines changed: 157 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
import static com.sap.ai.sdk.orchestration.AzureFilterThreshold.ALLOW_SAFE;
1818
import static com.sap.ai.sdk.orchestration.AzureFilterThreshold.ALLOW_SAFE_LOW_MEDIUM;
1919
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GPT_35_TURBO_16K;
20+
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GPT_4O_MINI;
2021
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.Parameter.*;
2122
import static org.apache.hc.core5.http.HttpStatus.SC_BAD_REQUEST;
23+
import static org.apache.hc.core5.http.HttpStatus.SC_OK;
2224
import static org.assertj.core.api.Assertions.assertThat;
2325
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2426
import static org.mockito.ArgumentMatchers.any;
@@ -29,13 +31,27 @@
2931
import static org.mockito.Mockito.when;
3032

3133
import com.fasterxml.jackson.core.JsonParseException;
34+
import com.fasterxml.jackson.databind.ObjectMapper;
35+
import com.fasterxml.jackson.databind.module.SimpleModule;
3236
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
3337
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
3438
import com.github.tomakehurst.wiremock.stubbing.Scenario;
3539
import com.sap.ai.sdk.orchestration.model.ChatMessage;
40+
import com.sap.ai.sdk.orchestration.model.ChatMessagesInner;
41+
import com.sap.ai.sdk.orchestration.model.CompletionPostRequest;
42+
import com.sap.ai.sdk.orchestration.model.CompletionPostResponse;
3643
import com.sap.ai.sdk.orchestration.model.DPIEntities;
3744
import com.sap.ai.sdk.orchestration.model.GenericModuleResult;
45+
import com.sap.ai.sdk.orchestration.model.ImageContent;
46+
import com.sap.ai.sdk.orchestration.model.ImageContentImageUrl;
47+
import com.sap.ai.sdk.orchestration.model.LLMModuleConfig;
48+
import com.sap.ai.sdk.orchestration.model.LLMModuleResult;
3849
import com.sap.ai.sdk.orchestration.model.LLMModuleResultSynchronous;
50+
import com.sap.ai.sdk.orchestration.model.ModuleConfigs;
51+
import com.sap.ai.sdk.orchestration.model.MultiChatMessage;
52+
import com.sap.ai.sdk.orchestration.model.OrchestrationConfig;
53+
import com.sap.ai.sdk.orchestration.model.Template;
54+
import com.sap.ai.sdk.orchestration.model.TextContent;
3955
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
4056
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Cache;
4157
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
@@ -47,6 +63,7 @@
4763
import java.util.function.Function;
4864
import java.util.stream.Stream;
4965
import javax.annotation.Nonnull;
66+
import lombok.SneakyThrows;
5067
import org.apache.hc.client5.http.classic.HttpClient;
5168
import org.apache.hc.core5.http.ContentType;
5269
import org.apache.hc.core5.http.io.entity.InputStreamEntity;
@@ -160,9 +177,16 @@ void testTemplating() throws IOException {
160177

161178
final var response = result.getOriginalResponse();
162179
assertThat(response.getRequestId()).isEqualTo("26ea36b5-c196-4806-a9a6-a686f0c6ad91");
163-
assertThat(result.getAllMessages().get(0).content())
180+
final var messageList = result.getAllMessages();
181+
182+
assertThat(messageList.get(0).content()).isEqualTo("You are a multi language translator");
183+
assertThat(messageList.get(0).role()).isEqualTo("system");
184+
assertThat(messageList.get(1).content())
164185
.isEqualTo("Reply with 'Orchestration Service is working!' in German");
165-
assertThat(result.getAllMessages().get(0).role()).isEqualTo("user");
186+
assertThat(messageList.get(1).role()).isEqualTo("user");
187+
assertThat(messageList.get(2).content()).isEqualTo("Orchestration Service funktioniert!");
188+
assertThat(messageList.get(2).role()).isEqualTo("assistant");
189+
166190
var llm = (LLMModuleResultSynchronous) response.getModuleResults().getLlm();
167191
assertThat(llm).isNotNull();
168192
assertThat(llm.getId()).isEqualTo("chatcmpl-9lzPV4kLrXjFckOp2yY454wksWBoj");
@@ -172,7 +196,7 @@ void testTemplating() throws IOException {
172196
var choices = llm.getChoices();
173197
assertThat(choices.get(0).getIndex()).isZero();
174198
assertThat(choices.get(0).getMessage().getContent())
175-
.isEqualTo("Orchestration Service funktioniert!");
199+
.isEqualTo("Le service d'orchestration fonctionne!");
176200
assertThat(choices.get(0).getMessage().getRole()).isEqualTo("assistant");
177201
assertThat(choices.get(0).getFinishReason()).isEqualTo("stop");
178202
var usage = result.getTokenUsage();
@@ -187,7 +211,7 @@ void testTemplating() throws IOException {
187211
choices = orchestrationResult.getChoices();
188212
assertThat(choices.get(0).getIndex()).isZero();
189213
assertThat(choices.get(0).getMessage().getContent())
190-
.isEqualTo("Orchestration Service funktioniert!");
214+
.isEqualTo("Le service d'orchestration fonctionne!");
191215
assertThat(choices.get(0).getMessage().getRole()).isEqualTo("assistant");
192216
assertThat(choices.get(0).getFinishReason()).isEqualTo("stop");
193217
usage = result.getTokenUsage();
@@ -618,4 +642,133 @@ void streamChatCompletionDeltas() throws IOException {
618642
Mockito.verify(inputStream, times(1)).close();
619643
}
620644
}
645+
646+
@Test
647+
void testRequestWithMultiChatMessage() throws IOException {
648+
649+
stubFor(
650+
post("/completion")
651+
.willReturn(
652+
aResponse().withStatus(SC_OK).withBodyFile("multiChatMessageResponse.json")));
653+
654+
var multiChatMessage =
655+
MultiChatMessage.create()
656+
.role("user")
657+
.content(
658+
List.of(
659+
TextContent.create()
660+
.type(TextContent.TypeEnum.TEXT)
661+
.text("Can you solve this captcha? Please help me prove my humanity!"),
662+
ImageContent.create()
663+
.type(ImageContent.TypeEnum.IMAGE_URL)
664+
.imageUrl(
665+
ImageContentImageUrl.create().url("https://sample.sap.com/image"))));
666+
667+
var llmWithImageSupportConfig =
668+
LLMModuleConfig.create()
669+
.modelName(GPT_4O_MINI.getName())
670+
.modelParams(Map.of())
671+
.modelVersion(GPT_4O_MINI.getVersion());
672+
673+
var templatingModuleConfig = Template.create().template(List.of(multiChatMessage));
674+
675+
CompletionPostRequest completionPostRequest =
676+
CompletionPostRequest.create()
677+
.orchestrationConfig(
678+
OrchestrationConfig.create()
679+
.moduleConfigurations(
680+
ModuleConfigs.create()
681+
.llmModuleConfig(llmWithImageSupportConfig)
682+
.templatingModuleConfig(templatingModuleConfig)));
683+
684+
var response = client.executeRequest(completionPostRequest);
685+
686+
assertThat(response).isNotNull();
687+
assertThat(response.getRequestId()).isEqualTo("2547cb86-a143-4064-bf40-45461c6a7ed9");
688+
689+
assertThat(response.getModuleResults()).isNotNull();
690+
assertThat(response.getModuleResults().getTemplating()).hasSize(1);
691+
692+
var multiChatMessageResponse =
693+
(MultiChatMessage) response.getModuleResults().getTemplating().get(0);
694+
assertThat(((TextContent) multiChatMessageResponse.getContent().get(0)).getText())
695+
.isEqualTo("Can you solve this captcha? Please help me prove my humanity!");
696+
assertThat(((TextContent) multiChatMessageResponse.getContent().get(0)).getType())
697+
.isEqualTo(TextContent.TypeEnum.TEXT);
698+
assertThat(((ImageContent) multiChatMessageResponse.getContent().get(1)).getType())
699+
.isEqualTo(ImageContent.TypeEnum.IMAGE_URL);
700+
assertThat(((ImageContent) multiChatMessageResponse.getContent().get(1)).getImageUrl().getUrl())
701+
.isEqualTo("https://sample.sap.com/image");
702+
703+
var llmResults = (LLMModuleResultSynchronous) response.getModuleResults().getLlm();
704+
assertThat(llmResults).isNotNull();
705+
assertThat(llmResults.getId()).isEqualTo("chatcmpl-Annjjf8T5LfLh7PRJPbaUlcC48DdE");
706+
assertThat(llmResults.getObject()).isEqualTo("chat.completion");
707+
assertThat(llmResults.getCreated()).isEqualTo(1736432623);
708+
assertThat(llmResults.getModel()).isEqualTo("gpt-4o-mini-2024-07-18");
709+
assertThat(llmResults.getSystemFingerprint()).isEqualTo("fp_5154047bf2");
710+
711+
assertThat(llmResults.getChoices()).hasSize(1);
712+
assertThat(llmResults.getChoices().get(0).getMessage().getContent())
713+
.isEqualTo(
714+
"Of course! Just let me put on my human glasses... Oh wait, I left them in the matrix");
715+
assertThat(llmResults.getChoices().get(0).getFinishReason()).isEqualTo("stop");
716+
assertThat(llmResults.getChoices().get(0).getMessage().getRole()).isEqualTo("assistant");
717+
assertThat(llmResults.getChoices().get(0).getIndex()).isZero();
718+
719+
assertThat(llmResults.getUsage().getCompletionTokens()).isEqualTo(31);
720+
assertThat(llmResults.getUsage().getPromptTokens()).isEqualTo(928);
721+
assertThat(llmResults.getUsage().getTotalTokens()).isEqualTo(959);
722+
723+
var orchestrationResult = (LLMModuleResultSynchronous) response.getOrchestrationResult();
724+
assertThat(orchestrationResult).isNotNull();
725+
assertThat(orchestrationResult.getId()).isEqualTo("chatcmpl-Annjjf8T5LfLh7PRJPbaUlcC48DdE");
726+
assertThat(orchestrationResult.getObject()).isEqualTo("chat.completion");
727+
assertThat(orchestrationResult.getCreated()).isEqualTo(1736432623);
728+
assertThat(orchestrationResult.getModel()).isEqualTo("gpt-4o-mini-2024-07-18");
729+
assertThat(orchestrationResult.getSystemFingerprint()).isEqualTo("fp_5154047bf2");
730+
assertThat(orchestrationResult.getChoices()).hasSize(1);
731+
assertThat(orchestrationResult.getChoices().get(0).getMessage().getContent())
732+
.isEqualTo(
733+
"Of course! Just let me put on my human glasses... Oh wait, I left them in the matrix");
734+
assertThat(orchestrationResult.getChoices().get(0).getFinishReason()).isEqualTo("stop");
735+
assertThat(orchestrationResult.getChoices().get(0).getMessage().getRole())
736+
.isEqualTo("assistant");
737+
assertThat(orchestrationResult.getChoices().get(0).getIndex()).isZero();
738+
assertThat(orchestrationResult.getUsage().getCompletionTokens()).isEqualTo(31);
739+
assertThat(orchestrationResult.getUsage().getPromptTokens()).isEqualTo(928);
740+
assertThat(orchestrationResult.getUsage().getTotalTokens()).isEqualTo(959);
741+
742+
try (var requestInputStream = fileLoader.apply("multiChatMessageRequest.json")) {
743+
final String requestBody = new String(requestInputStream.readAllBytes());
744+
verify(
745+
postRequestedFor(urlPathEqualTo("/completion"))
746+
.withRequestBody(equalToJson(requestBody)));
747+
}
748+
}
749+
750+
@SneakyThrows
751+
@Test
752+
void testOrchestrationChatResponseWithMultiChatMessage() {
753+
var module = new SimpleModule();
754+
module.setMixInAnnotation(LLMModuleResult.class, JacksonMixins.NoneTypeInfoMixin.class);
755+
module.addDeserializer(
756+
LLMModuleResult.class, new PolymorphicFallbackDeserializer<>(LLMModuleResult.class));
757+
module.setMixInAnnotation(ChatMessagesInner.class, JacksonMixins.NoneTypeInfoMixin.class);
758+
module.addDeserializer(
759+
ChatMessagesInner.class, new PolymorphicFallbackDeserializer<>(ChatMessagesInner.class));
760+
761+
var orchestrationChatResponse =
762+
new OrchestrationChatResponse(
763+
new ObjectMapper()
764+
.registerModule(module)
765+
.readValue(
766+
new String(
767+
fileLoader.apply("__files/multiChatMessageResponse.json").readAllBytes()),
768+
CompletionPostResponse.class));
769+
770+
assertThatThrownBy(orchestrationChatResponse::getAllMessages)
771+
.isInstanceOf(UnsupportedOperationException.class)
772+
.hasMessage("Messages of MultiChatMessage type not supported by convenience API");
773+
}
621774
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.sap.ai.sdk.orchestration;
2+
3+
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
4+
5+
import com.fasterxml.jackson.databind.JsonMappingException;
6+
import com.fasterxml.jackson.databind.json.JsonMapper;
7+
import com.fasterxml.jackson.databind.module.SimpleModule;
8+
import com.sap.ai.sdk.orchestration.model.ChatMessage;
9+
import com.sap.ai.sdk.orchestration.model.ChatMessagesInner;
10+
import java.util.List;
11+
import lombok.SneakyThrows;
12+
import org.junit.jupiter.api.Test;
13+
14+
class PolymorphicFallbackDeserializerTest {
15+
16+
@SneakyThrows
17+
@Test
18+
void testValueTypeResolutionFailure() {
19+
20+
final String multiChatMessageJson =
21+
"""
22+
{
23+
"role": "user",
24+
"content": [
25+
{
26+
"type": "text",
27+
"text": "What’s in this image?"
28+
},
29+
{
30+
"type": "image_url",
31+
"image_url": {
32+
"url": "https://sample.page.org/an-image.jpg"
33+
}
34+
}
35+
]
36+
}
37+
""";
38+
39+
final var module =
40+
new SimpleModule()
41+
.addDeserializer(
42+
ChatMessagesInner.class,
43+
new PolymorphicFallbackDeserializer<>(
44+
ChatMessagesInner.class, List.of(ChatMessage.class)))
45+
.setMixInAnnotation(ChatMessagesInner.class, JacksonMixins.NoneTypeInfoMixin.class);
46+
47+
final var mapper = new JsonMapper().registerModule(module);
48+
49+
assertThatThrownBy(() -> mapper.readValue(multiChatMessageJson, ChatMessagesInner.class))
50+
.isInstanceOf(JsonMappingException.class)
51+
.hasMessageContaining(
52+
"PolymorphicFallbackDeserializer failed to deserialize "
53+
+ ChatMessagesInner.class.getName());
54+
}
55+
56+
@Test
57+
void testMissingSubtypeAnnotation() {
58+
interface NoSubTypesInterface {}
59+
60+
assertThatThrownBy(() -> new PolymorphicFallbackDeserializer<>(NoSubTypesInterface.class))
61+
.isInstanceOf(IllegalStateException.class)
62+
.hasMessageContaining("No subtypes found for " + NoSubTypesInterface.class.getName());
63+
}
64+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"request_id": "2547cb86-a143-4064-bf40-45461c6a7ed9",
3+
"module_results": {
4+
"templating": [
5+
{
6+
"role": "user",
7+
"content": [
8+
{
9+
"type": "text",
10+
"text": "Can you solve this captcha? Please help me prove my humanity!"
11+
},
12+
{
13+
"type": "image_url",
14+
"image_url": {
15+
"url": "https://sample.sap.com/image",
16+
"detail": "auto"
17+
}
18+
}
19+
]
20+
}
21+
],
22+
"llm": {
23+
"id": "chatcmpl-Annjjf8T5LfLh7PRJPbaUlcC48DdE",
24+
"object": "chat.completion",
25+
"created": 1736432623,
26+
"model": "gpt-4o-mini-2024-07-18",
27+
"system_fingerprint": "fp_5154047bf2",
28+
"choices": [
29+
{
30+
"index": 0,
31+
"message": {
32+
"role": "assistant",
33+
"content": "Of course! Just let me put on my human glasses... Oh wait, I left them in the matrix"
34+
},
35+
"finish_reason": "stop"
36+
}
37+
],
38+
"usage": {
39+
"completion_tokens": 31,
40+
"prompt_tokens": 928,
41+
"total_tokens": 959
42+
}
43+
}
44+
},
45+
"orchestration_result": {
46+
"id": "chatcmpl-Annjjf8T5LfLh7PRJPbaUlcC48DdE",
47+
"object": "chat.completion",
48+
"created": 1736432623,
49+
"model": "gpt-4o-mini-2024-07-18",
50+
"system_fingerprint": "fp_5154047bf2",
51+
"choices": [
52+
{
53+
"index": 0,
54+
"message": {
55+
"role": "assistant",
56+
"content": "Of course! Just let me put on my human glasses... Oh wait, I left them in the matrix"
57+
},
58+
"finish_reason": "stop"
59+
}
60+
],
61+
"usage": {
62+
"completion_tokens": 31,
63+
"prompt_tokens": 928,
64+
"total_tokens": 959
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)