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 =