diff --git a/docs/release_notes.md b/docs/release_notes.md index 669af0b4e..6b59193cf 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -18,6 +18,7 @@ Interfaces with only one implementation were reduced. As a result, the accessors for fields `OrchestrationModuleConfig.inputTranslationConfig` and `OrchestrationModuleConfig.outputTranslationConfig` now handle the implementing class explicitly. The same applies to helper methods `DpiMasking#createConfig()` and `MaskingProvider#createConfig()`. +- [Orchestration] `OrchestrationTemplate.withTemplate()` has been deprecated. Please use `OrchestrationTemplate.withTemplateMessages()` instead. ### ✨ New Functionality diff --git a/orchestration/pom.xml b/orchestration/pom.xml index f5157eac7..f33d425a3 100644 --- a/orchestration/pom.xml +++ b/orchestration/pom.xml @@ -31,10 +31,10 @@ ${project.basedir}/../ - 84% + 83% 94% 95% - 79% + 78% 94% 100% diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/AssistantMessage.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/AssistantMessage.java index 540bf1bef..9548162de 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/AssistantMessage.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/AssistantMessage.java @@ -66,9 +66,14 @@ public AssistantMessage(@Nonnull final List toolCalls) { @Nonnull @Override public ChatMessage createChatMessage() { - if (toolCalls() != null) { + if (toolCalls != null) { return AssistantChatMessage.create().role(ASSISTANT).toolCalls(toolCalls); } + if (content.items().size() == 1 && content.items().get(0) instanceof TextItem textItem) { + return AssistantChatMessage.create() + .role(ASSISTANT) + .content(ChatMessageContent.create(textItem.text())); + } val texts = content.items().stream() .filter(item -> item instanceof TextItem) diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplate.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplate.java index 1d9015e22..2a56ee619 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplate.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplate.java @@ -19,6 +19,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.AccessLevel; @@ -41,8 +42,11 @@ @NoArgsConstructor(force = true, access = AccessLevel.PACKAGE) @Beta public class OrchestrationTemplate extends TemplateConfig { + + /** Please use {@link #withMessages(Message...)} instead. */ @JsonProperty("template") @Nullable + @With(onMethod_ = {@Deprecated}) List template; @JsonProperty("defaults") @@ -58,6 +62,17 @@ public class OrchestrationTemplate extends TemplateConfig { @Nullable List tools; + /** + * Create a new template with the given messages. + * + * @param messages The messages to use in the template. + * @return The updated template. + */ + @Nonnull + public OrchestrationTemplate withMessages(@Nonnull final Message... messages) { + return this.withTemplate(Stream.of(messages).map(Message::createChatMessage).toList()); + } + /** * Create a low-level representation of the template. * diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/SystemMessage.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/SystemMessage.java index 0322a770c..3f1c03224 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/SystemMessage.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/SystemMessage.java @@ -59,6 +59,11 @@ public SystemMessage withText(@Nonnull final String message) { @Nonnull @Override public ChatMessage createChatMessage() { + if (content.items().size() == 1 && content.items().get(0) instanceof TextItem textItem) { + return SystemChatMessage.create() + .role(SYSTEM) + .content(ChatMessageContent.create(textItem.text())); + } val texts = content.items().stream() .filter(item -> item instanceof TextItem) diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/UserMessage.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/UserMessage.java index 159841b38..4d07f86a9 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/UserMessage.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/UserMessage.java @@ -93,7 +93,12 @@ public UserMessage withImage(@Nonnull final String imageUrl) { public ChatMessage createChatMessage() { final var contentList = new LinkedList(); - for (final ContentItem item : this.content().items()) { + if (content.items().size() == 1 && content.items().get(0) instanceof TextItem textItem) { + return UserChatMessage.create() + .content(UserChatMessageContent.create(textItem.text())) + .role(USER); + } + for (final ContentItem item : content.items()) { if (item instanceof TextItem textItem) { contentList.add(UserChatMessageContentItem.create().type(TEXT).text(textItem.text())); } else if (item instanceof ImageItem imageItem) { diff --git a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationConvenienceUnitTest.java b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationConvenienceUnitTest.java index 9c2b78a7e..6ac9a7e83 100644 --- a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationConvenienceUnitTest.java +++ b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationConvenienceUnitTest.java @@ -4,15 +4,14 @@ import static org.assertj.core.api.Assertions.assertThat; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.sap.ai.sdk.orchestration.model.ChatCompletionTool; import com.sap.ai.sdk.orchestration.model.ChatMessage; -import com.sap.ai.sdk.orchestration.model.ChatMessageContent; import com.sap.ai.sdk.orchestration.model.FunctionObject; import com.sap.ai.sdk.orchestration.model.ResponseFormatJsonObject; import com.sap.ai.sdk.orchestration.model.ResponseFormatJsonSchema; import com.sap.ai.sdk.orchestration.model.ResponseFormatJsonSchemaJsonSchema; -import com.sap.ai.sdk.orchestration.model.SystemChatMessage; -import com.sap.ai.sdk.orchestration.model.SystemChatMessage.RoleEnum; import com.sap.ai.sdk.orchestration.model.Template; import com.sap.ai.sdk.orchestration.model.TemplateRef; import com.sap.ai.sdk.orchestration.model.TemplateRefByID; @@ -129,7 +128,8 @@ void testConfigWithResponseSchema() { @Test void testTemplateConstruction() { - List templateMessages = + Message templateMessages = Message.user("message"); + List templateMessagesLowLevel = List.of( UserChatMessage.create().content(UserChatMessageContent.create("message")).role(USER)); var defaults = Map.of("key", "value"); @@ -140,14 +140,14 @@ void testTemplateConstruction() { .function(FunctionObject.create().name("func"))); var template = TemplateConfig.create() - .withTemplate(templateMessages) + .withMessages(templateMessages) .withDefaults(defaults) .withTools(tools) .withJsonResponse(); var templateLowLevel = Template.create() - .template(templateMessages) + .template(templateMessagesLowLevel) .defaults(defaults) .responseFormat( ResponseFormatJsonObject.create() @@ -206,15 +206,9 @@ void testTemplateFromLocalFileWithJsonSchemaAndTools() throws IOException { false); var expectedTemplateWithJsonSchemaTools = OrchestrationTemplate.create() - .withTemplate( - List.of( - SystemChatMessage.create() - .role(RoleEnum.SYSTEM) - .content(ChatMessageContent.create("You are a language translator.")), - UserChatMessage.create() - .content( - UserChatMessageContent.create("Whats {{ ?word }} in {{ ?language }}?")) - .role(USER))) + .withMessages( + Message.system("You are a language translator."), + Message.user("Whats {{ ?word }} in {{ ?language }}?")) .withDefaults(Map.of("word", "apple")) .withJsonSchemaResponse( ResponseJsonSchema.fromMap(schema, "translation-schema") @@ -241,7 +235,12 @@ void testTemplateFromLocalFileWithJsonSchemaAndTools() throws IOException { "wordToTranslate", Map.of("type", "string")))) .description("Translate a word.") .strict(true)))); - assertThat(templateWithJsonSchemaTools).isEqualTo(expectedTemplateWithJsonSchemaTools); + + var jackson = new ObjectMapper(); + JsonNode template = jackson.readTree(jackson.writeValueAsString(templateWithJsonSchemaTools)); + JsonNode expectedTemplate = + jackson.readTree(jackson.writeValueAsString(expectedTemplateWithJsonSchemaTools)); + assertThat(template).isEqualTo(expectedTemplate); } @Test @@ -267,17 +266,16 @@ void testTemplateFromLocalFileWithJsonObject() throws IOException { var templateWithJsonObject = TemplateConfig.create().fromYaml(promptTemplateWithJsonObject); var expectedTemplateWithJsonObject = OrchestrationTemplate.create() - .withTemplate( - List.of( - SystemChatMessage.create() - .role(RoleEnum.SYSTEM) - .content(ChatMessageContent.create("You are a language translator.")), - UserChatMessage.create() - .content( - UserChatMessageContent.create("Whats {{ ?word }} in {{ ?language }}?")) - .role(USER))) + .withMessages( + Message.system("You are a language translator."), + Message.user("Whats {{ ?word }} in {{ ?language }}?")) .withDefaults(Map.of("word", "apple")) .withJsonResponse(); - assertThat(templateWithJsonObject).isEqualTo(expectedTemplateWithJsonObject); + + var jackson = new ObjectMapper(); + JsonNode template = jackson.readTree(jackson.writeValueAsString(templateWithJsonObject)); + JsonNode expectedTemplate = + jackson.readTree(jackson.writeValueAsString(expectedTemplateWithJsonObject)); + assertThat(template).isEqualTo(expectedTemplate); } } diff --git a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java index 06ff230da..6c8d86512 100644 --- a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java +++ b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java @@ -413,7 +413,11 @@ void messagesHistory() throws IOException { final List messagesHistory = List.of( new UserMessage("What is the capital of France?"), - new AssistantMessage("The capital of France is Paris.")); + new AssistantMessage( + new MessageContent( + List.of( + new TextItem("The capital of France is Paris."), + new TextItem("Paris is known for its art, fashion, and culture."))))); final var message = new UserMessage("What is the typical food there?"); prompt = new OrchestrationPrompt(message).messageHistory(messagesHistory); @@ -565,10 +569,7 @@ void testExecuteRequestFromJson() { { "messages_history": [{ "role" : "user", - "content" : [ { - "type" : "text", - "text" : "Hello World!" - } ] + "content" : "Hello World!" }], "input_params": { "foo" : "bar" diff --git a/orchestration/src/test/resources/chatMemory.json b/orchestration/src/test/resources/chatMemory.json index 32a1176b3..bcff50015 100644 --- a/orchestration/src/test/resources/chatMemory.json +++ b/orchestration/src/test/resources/chatMemory.json @@ -10,25 +10,16 @@ "template": [ { "role": "user", - "content": [ { - "type" : "text", - "text" : "What is the capital of France?" - } ] + "content": "What is the capital of France?" }, { "role": "assistant", - "content" : [ { - "type" : "text", - "text" : "Le service d'orchestration fonctionne!" - } ], + "content" : "Le service d'orchestration fonctionne!", "tool_calls" : [ ] }, { "role": "user", - "content": [ { - "type" : "text", - "text" : "And what is the typical food there?" - } ] + "content": "And what is the typical food there?" } ], "defaults": {}, diff --git a/orchestration/src/test/resources/filteringLooseRequest.json b/orchestration/src/test/resources/filteringLooseRequest.json index 3fad0a625..eb0f49f32 100644 --- a/orchestration/src/test/resources/filteringLooseRequest.json +++ b/orchestration/src/test/resources/filteringLooseRequest.json @@ -17,10 +17,7 @@ "template": [ { "role": "user", - "content": [ { - "type" : "text", - "text" : "Hello World! Why is this phrase so famous?" - } ] + "content": "Hello World! Why is this phrase so famous?" } ], "defaults" : { }, diff --git a/orchestration/src/test/resources/groundingHelpSapComRequest.json b/orchestration/src/test/resources/groundingHelpSapComRequest.json index 198c864cc..37b5fee37 100644 --- a/orchestration/src/test/resources/groundingHelpSapComRequest.json +++ b/orchestration/src/test/resources/groundingHelpSapComRequest.json @@ -16,10 +16,7 @@ "templating_module_config" : { "template" : [ { "role" : "user", - "content" : [ { - "type" : "text", - "text" : "{{?userMessage}} Use the following information as additional context: {{?groundingContext}}" - } ] + "content" : "{{?userMessage}} Use the following information as additional context: {{?groundingContext}}" } ], "defaults" : { }, "tools" : [ ] diff --git a/orchestration/src/test/resources/groundingRequest.json b/orchestration/src/test/resources/groundingRequest.json index a188147d6..e22194d9e 100644 --- a/orchestration/src/test/resources/groundingRequest.json +++ b/orchestration/src/test/resources/groundingRequest.json @@ -16,10 +16,7 @@ "templating_module_config" : { "template" : [ { "role" : "system", - "content" : [ { - "type" : "text", - "text" : "Context message with embedded grounding results. {{?results}}" - } ] + "content" : "Context message with embedded grounding results. {{?results}}" } ], "defaults" : { }, "tools" : [ ] diff --git a/orchestration/src/test/resources/jsonObjectRequest.json b/orchestration/src/test/resources/jsonObjectRequest.json index 15b062e26..b386108c8 100644 --- a/orchestration/src/test/resources/jsonObjectRequest.json +++ b/orchestration/src/test/resources/jsonObjectRequest.json @@ -9,16 +9,10 @@ "templating_module_config" : { "template" : [ { "role" : "user", - "content" : [ { - "type" : "text", - "text" : "What is 'apple' in German?" - } ] + "content" : "What is 'apple' in German?" }, { "role" : "system", - "content" : [ { - "type" : "text", - "text" : "You are a language translator. Answer using the following JSON format: {\"language\": ..., \"translation\": ...}" - } ] + "content" : "You are a language translator. Answer using the following JSON format: {\"language\": ..., \"translation\": ...}" } ], "defaults" : { }, "response_format" : { diff --git a/orchestration/src/test/resources/jsonSchemaRequest.json b/orchestration/src/test/resources/jsonSchemaRequest.json index f1119a7a0..c2c697672 100644 --- a/orchestration/src/test/resources/jsonSchemaRequest.json +++ b/orchestration/src/test/resources/jsonSchemaRequest.json @@ -9,16 +9,10 @@ "templating_module_config" : { "template" : [ { "role" : "user", - "content" : [ { - "type" : "text", - "text" : "Whats 'apple' in German?" - } ] + "content" : "Whats 'apple' in German?" }, { "role" : "system", - "content" : [ { - "type" : "text", - "text" : "You are a language translator." - } ] + "content" : "You are a language translator." } ], "defaults" : { }, "response_format" : { diff --git a/orchestration/src/test/resources/maskingRequest.json b/orchestration/src/test/resources/maskingRequest.json index 6d99a2214..ad4e4748e 100644 --- a/orchestration/src/test/resources/maskingRequest.json +++ b/orchestration/src/test/resources/maskingRequest.json @@ -17,10 +17,7 @@ "template": [ { "role": "user", - "content": [ { - "type" : "text", - "text" : "Hello World! Why is this phrase so famous?" - } ] + "content": "Hello World! Why is this phrase so famous?" } ], "defaults" : { }, diff --git a/orchestration/src/test/resources/messagesHistoryRequest.json b/orchestration/src/test/resources/messagesHistoryRequest.json index 1c8d7f849..42d860930 100644 --- a/orchestration/src/test/resources/messagesHistoryRequest.json +++ b/orchestration/src/test/resources/messagesHistoryRequest.json @@ -8,8 +8,8 @@ "frequency_penalty": 0, "max_tokens": 50, "temperature": 0.1, - "top_p" : 1, - "n" : 1 + "top_p": 1, + "n": 1 }, "model_version": "latest" }, @@ -17,14 +17,11 @@ "template": [ { "role": "user", - "content": [ { - "type" : "text", - "text" : "What is the typical food there?" - } ] + "content": "What is the typical food there?" } ], - "defaults" : { }, - "tools" : [ ] + "defaults": {}, + "tools": [] } }, "stream": false @@ -33,18 +30,21 @@ "messages_history": [ { "role": "user", - "content": [ { - "type" : "text", - "text" : "What is the capital of France?" - } ] + "content": "What is the capital of France?" }, { "role": "assistant", - "content": [ { - "type" : "text", - "text" : "The capital of France is Paris." - } ], - "tool_calls" : [ ] + "content": [ + { + "type": "text", + "text": "The capital of France is Paris." + }, + { + "type": "text", + "text": "Paris is known for its art, fashion, and culture." + } + ], + "tool_calls": [] } ] } diff --git a/orchestration/src/test/resources/promptTemplateExample.yaml b/orchestration/src/test/resources/promptTemplateExample.yaml index 78ac2670b..93c042c25 100644 --- a/orchestration/src/test/resources/promptTemplateExample.yaml +++ b/orchestration/src/test/resources/promptTemplateExample.yaml @@ -46,4 +46,4 @@ spec: translation: type: string additionalFields: - this: will get ignored \ No newline at end of file + this: will get ignored diff --git a/orchestration/src/test/resources/responseFormatTextRequest.json b/orchestration/src/test/resources/responseFormatTextRequest.json index 74efa233d..8c50cb6da 100644 --- a/orchestration/src/test/resources/responseFormatTextRequest.json +++ b/orchestration/src/test/resources/responseFormatTextRequest.json @@ -9,16 +9,10 @@ "templating_module_config" : { "template" : [ { "role" : "user", - "content" : [ { - "type" : "text", - "text" : "What is 'apple' in German?" - } ] + "content" : "What is 'apple' in German?" }, { "role" : "system", - "content" : [ { - "type" : "text", - "text" : "You are a language translator. Answer using JSON." - } ] + "content" : "You are a language translator. Answer using JSON." } ], "defaults" : { }, "response_format" : { diff --git a/orchestration/src/test/resources/templatingRequest.json b/orchestration/src/test/resources/templatingRequest.json index 71ebb95c5..45a90724e 100644 --- a/orchestration/src/test/resources/templatingRequest.json +++ b/orchestration/src/test/resources/templatingRequest.json @@ -17,10 +17,7 @@ "template": [ { "role": "user", - "content": [ { - "type" : "text", - "text" : "{{?input}}" - } ] + "content": "{{?input}}" } ], "defaults" : { }, diff --git a/orchestration/src/test/resources/toolCallsRequest.json b/orchestration/src/test/resources/toolCallsRequest.json index 9e5894366..6347b3cee 100644 --- a/orchestration/src/test/resources/toolCallsRequest.json +++ b/orchestration/src/test/resources/toolCallsRequest.json @@ -10,10 +10,7 @@ "template": [ { "role": "user", - "content": [ { - "type" : "text", - "text" : "What is the weather in Potsdam and in Toulouse?" - } ] + "content": "What is the weather in Potsdam and in Toulouse?" } ], "defaults": {}, diff --git a/orchestration/src/test/resources/toolCallsRequest2.json b/orchestration/src/test/resources/toolCallsRequest2.json index 8df334ee1..33882e448 100644 --- a/orchestration/src/test/resources/toolCallsRequest2.json +++ b/orchestration/src/test/resources/toolCallsRequest2.json @@ -10,12 +10,7 @@ "template": [ { "role": "user", - "content": [ - { - "type": "text", - "text": "What is the weather in Potsdam and in Toulouse?" - } - ] + "content": "What is the weather in Potsdam and in Toulouse?" }, { "role": "assistant", diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java index 125bf4195..c92a1b77d 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java @@ -120,8 +120,7 @@ public Stream streamChatCompletion(@Nonnull final String topic) { @Nonnull public OrchestrationChatResponse template(@Nonnull final String language) { val template = Message.user("Reply with 'Orchestration Service is working!' in {{?language}}"); - val templatingConfig = - TemplateConfig.create().withTemplate(List.of(template.createChatMessage())); + val templatingConfig = TemplateConfig.create().withMessages(template); val configWithTemplate = config.withTemplateConfig(templatingConfig); val inputParams = Map.of("language", language); @@ -462,10 +461,7 @@ public OrchestrationChatResponse responseFormatJsonSchema( @Nonnull public OrchestrationChatResponse responseFormatJsonObject(@Nonnull final String word) { val template = Message.user("What is '%s' in German?".formatted(word)); - val templatingConfig = - TemplateConfig.create() - .withTemplate(List.of(template.createChatMessage())) - .withJsonResponse(); + val templatingConfig = TemplateConfig.create().withMessages(template).withJsonResponse(); val configWithTemplate = config.withTemplateConfig(templatingConfig); val prompt =