1717import static com .sap .ai .sdk .orchestration .AzureFilterThreshold .ALLOW_SAFE ;
1818import static com .sap .ai .sdk .orchestration .AzureFilterThreshold .ALLOW_SAFE_LOW_MEDIUM ;
1919import static com .sap .ai .sdk .orchestration .OrchestrationAiModel .GPT_35_TURBO_16K ;
20+ import static com .sap .ai .sdk .orchestration .OrchestrationAiModel .GPT_4O_MINI ;
2021import static com .sap .ai .sdk .orchestration .OrchestrationAiModel .Parameter .*;
2122import static org .apache .hc .core5 .http .HttpStatus .SC_BAD_REQUEST ;
23+ import static org .apache .hc .core5 .http .HttpStatus .SC_OK ;
2224import static org .assertj .core .api .Assertions .assertThat ;
2325import static org .assertj .core .api .Assertions .assertThatThrownBy ;
2426import static org .mockito .ArgumentMatchers .any ;
2931import static org .mockito .Mockito .when ;
3032
3133import com .fasterxml .jackson .core .JsonParseException ;
34+ import com .fasterxml .jackson .databind .ObjectMapper ;
35+ import com .fasterxml .jackson .databind .module .SimpleModule ;
3236import com .github .tomakehurst .wiremock .junit5 .WireMockRuntimeInfo ;
3337import com .github .tomakehurst .wiremock .junit5 .WireMockTest ;
3438import com .github .tomakehurst .wiremock .stubbing .Scenario ;
3539import 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 ;
3643import com .sap .ai .sdk .orchestration .model .DPIEntities ;
3744import 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 ;
3849import 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 ;
3955import com .sap .cloud .sdk .cloudplatform .connectivity .ApacheHttpClient5Accessor ;
4056import com .sap .cloud .sdk .cloudplatform .connectivity .ApacheHttpClient5Cache ;
4157import com .sap .cloud .sdk .cloudplatform .connectivity .DefaultHttpDestination ;
4763import java .util .function .Function ;
4864import java .util .stream .Stream ;
4965import javax .annotation .Nonnull ;
66+ import lombok .SneakyThrows ;
5067import org .apache .hc .client5 .http .classic .HttpClient ;
5168import org .apache .hc .core5 .http .ContentType ;
5269import 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}
0 commit comments