diff --git a/docs/release_notes.md b/docs/release_notes.md
index 80e2325c3..b9af822c7 100644
--- a/docs/release_notes.md
+++ b/docs/release_notes.md
@@ -16,7 +16,7 @@
### 📈 Improvements
--
+- [Orchestration] Added new API `TranslationConfig#withApplyTo` to support partial translation for user's input.
### 🐛 Fixed Issues
diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ApplyTo.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ApplyTo.java
new file mode 100644
index 000000000..15554ce35
--- /dev/null
+++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ApplyTo.java
@@ -0,0 +1,71 @@
+package com.sap.ai.sdk.orchestration;
+
+import static com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationApplyToSelector.CategoryEnum.PLACEHOLDERS;
+import static com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationApplyToSelector.CategoryEnum.TEMPLATE_ROLES;
+
+import com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationApplyToSelector;
+import java.util.Objects;
+import java.util.stream.Stream;
+import javax.annotation.Nonnull;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * Convenience builder for {@link SAPDocumentTranslationApplyToSelector}.
+ *
+ *
This avoids passing raw strings for template roles and keeps sample-code readable.
+ */
+public final class ApplyTo {
+ /**
+ * Supported values for {@code items[]} when {@code category=template_roles}.
+ *
+ *
These map to the roles used in prompt templates.
+ */
+ @RequiredArgsConstructor
+ public enum TemplateRole {
+ /** Template role for user messages. */
+ USER("user"),
+
+ /** Template role for system messages. */
+ SYSTEM("system"),
+
+ /** Template role for assistant messages. */
+ ASSISTANT("assistant"),
+
+ /** Template role for developer messages. */
+ DEVELOPER("developer"),
+
+ /** Template role for tool messages. */
+ TOOL("tool");
+
+ @Getter private final String value;
+ }
+
+ /**
+ * Start an {@code apply_to} selector for placeholder names in {@code placeholder_values}.
+ *
+ * @param names The placeholder keys to translate.
+ * @return A selector with {@code category=placeholders} and the given items.
+ */
+ @Nonnull
+ public static SAPDocumentTranslationApplyToSelector placeholders(@Nonnull final String... names) {
+ return SAPDocumentTranslationApplyToSelector.create().category(PLACEHOLDERS).items(names);
+ }
+
+ /**
+ * Start an {@code apply_to} selector for prompt template message roles.
+ *
+ * @param roles The template roles to translate.
+ * @return A selector with {@code category=template_roles} and the given items.
+ */
+ @Nonnull
+ public static SAPDocumentTranslationApplyToSelector templateRoles(
+ @Nonnull final TemplateRole... roles) {
+ final var roleStrings =
+ Stream.of(roles).filter(Objects::nonNull).map(TemplateRole::getValue).toList();
+
+ return SAPDocumentTranslationApplyToSelector.create()
+ .category(TEMPLATE_ROLES)
+ .items(roleStrings);
+ }
+}
diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/TranslationConfig.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/TranslationConfig.java
index 6210f6f7a..f830bd895 100644
--- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/TranslationConfig.java
+++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/TranslationConfig.java
@@ -1,10 +1,12 @@
package com.sap.ai.sdk.orchestration;
+import com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationApplyToSelector;
import com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationInput;
import com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationInputConfig;
import com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationOutput;
import com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationOutputConfig;
import com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationOutputTargetLanguage;
+import java.util.List;
import javax.annotation.Nonnull;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
@@ -29,13 +31,21 @@ class Input implements TranslationConfig {
@With String sourceLanguage;
- Object ApplyTo; // Can be null
+ /**
+ * Optional selection(s) to translate. If empty or null, translation is applied to the whole
+ * user input.
+ */
+ @With List applyTo;
@Nonnull
SAPDocumentTranslationInput createSAPDocumentTranslationInput() {
val translationType = SAPDocumentTranslationInput.TypeEnum.SAP_DOCUMENT_TRANSLATION;
- val conf =
- SAPDocumentTranslationInputConfig.create().targetLanguage(targetLanguage).applyTo(null);
+ final var conf = SAPDocumentTranslationInputConfig.create().targetLanguage(targetLanguage);
+
+ if (applyTo != null && !applyTo.isEmpty()) {
+ conf.applyTo(applyTo);
+ }
+
return SAPDocumentTranslationInput.create().type(translationType).config(conf);
}
}
@@ -71,7 +81,6 @@ SAPDocumentTranslationOutput createSAPDocumentTranslationOutput() {
*/
@Nonnull
static TranslationConfig.Input translateInputTo(@Nonnull final String targetLanguage) {
-
return new TranslationConfig.Input(targetLanguage, null, null);
}
@@ -86,7 +95,6 @@ static TranslationConfig.Input translateInputTo(@Nonnull final String targetLang
*/
@Nonnull
static TranslationConfig.Output translateOutputTo(@Nonnull final String targetLanguage) {
-
return new TranslationConfig.Output(targetLanguage, null);
}
}
diff --git a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java
index 6e321cd7c..639e288e5 100644
--- a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java
+++ b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigTest.java
@@ -1,5 +1,10 @@
package com.sap.ai.sdk.orchestration;
+import static com.sap.ai.sdk.orchestration.ApplyTo.TemplateRole.ASSISTANT;
+import static com.sap.ai.sdk.orchestration.ApplyTo.TemplateRole.DEVELOPER;
+import static com.sap.ai.sdk.orchestration.ApplyTo.TemplateRole.SYSTEM;
+import static com.sap.ai.sdk.orchestration.ApplyTo.TemplateRole.TOOL;
+import static com.sap.ai.sdk.orchestration.ApplyTo.TemplateRole.USER;
import static com.sap.ai.sdk.orchestration.AzureFilterThreshold.ALLOW_SAFE_LOW_MEDIUM;
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GPT_4O;
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.Parameter.MAX_TOKENS;
@@ -176,6 +181,39 @@ void testTranslationConfig() {
.getSourceLanguage());
}
+ @Test
+ void testTranslationConfigApplyToSelectors() {
+ var selector = ApplyTo.placeholders("exam_type", "topic").sourceLanguage("de-DE");
+
+ final var inputTranslationConfig =
+ TranslationConfig.translateInputTo("en-US").withApplyTo(List.of(selector));
+
+ final var sapInput = inputTranslationConfig.createSAPDocumentTranslationInput();
+ assertThat(sapInput.getConfig().getTargetLanguage()).isEqualTo("en-US");
+ assertThat(sapInput.getConfig().getApplyTo()).hasSize(1);
+ assertThat(sapInput.getConfig().getApplyTo().get(0).getCategory().getValue())
+ .isEqualTo("placeholders");
+ assertThat(sapInput.getConfig().getApplyTo().get(0).getItems())
+ .containsExactly("exam_type", "topic");
+
+ final var inputNull = TranslationConfig.translateInputTo("en-US");
+ final var sapNull = inputNull.createSAPDocumentTranslationInput();
+ assertThat(sapNull.getConfig().getApplyTo()).isEmpty();
+
+ // applyTo == empty list
+ final var inputEmpty = TranslationConfig.translateInputTo("en-US").withApplyTo(List.of());
+ final var sapEmpty = inputEmpty.createSAPDocumentTranslationInput();
+ assertThat(sapEmpty.getConfig().getApplyTo()).isEmpty();
+
+ selector =
+ ApplyTo.templateRoles(USER, SYSTEM, ASSISTANT, DEVELOPER, TOOL).sourceLanguage("de-DE");
+
+ assertThat(selector.getCategory().getValue()).isEqualTo("template_roles");
+ assertThat(selector.getItems())
+ .containsExactly("user", "system", "assistant", "developer", "tool");
+ assertThat(selector.getSourceLanguage()).isEqualTo("de-DE");
+ }
+
@Test
void testParams() {
// test withParams(Map)
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 45ed6797b..fdbe3c326 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
@@ -9,6 +9,7 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.sap.ai.sdk.core.AiCoreService;
+import com.sap.ai.sdk.orchestration.ApplyTo;
import com.sap.ai.sdk.orchestration.AzureContentFilter;
import com.sap.ai.sdk.orchestration.AzureFilterThreshold;
import com.sap.ai.sdk.orchestration.DpiMasking;
@@ -688,17 +689,33 @@ public OrchestrationChatResponse localPromptTemplate(@Nonnull final String promp
*/
@Nonnull
public OrchestrationChatResponse translation() {
- val prompt =
- new OrchestrationPrompt(
- "Quelle est la couleur de la tour Eiffel? Et en quelle langue tu me parles maintenant?");
+ val inputParams =
+ Map.of("exam_type", "Abitur", "topic", "Deutsche Literatur", "num_questions", "5");
+
+ val systemMessage =
+ Message.system(
+ "You are an expert study coach creating clear, concise exam notes and practice questions.");
+ val userMessage =
+ Message.user(
+ "Generate a study guide for the {{?exam_type}} exam on {{?topic}}.\n\nInclude {{?num_questions}} practice questions.");
+ val templatingConfig = TemplateConfig.create().withMessages(systemMessage, userMessage);
+
+ val prompt = new OrchestrationPrompt(inputParams);
// list of supported language pairs
// https://help.sap.com/docs/translation-hub/sap-translation-hub/supported-languages?version=Cloud#translation-provider-sap-machine-translation
+
val configWithTranslation =
config
- .withInputTranslationConfig(TranslationConfig.translateInputTo("en-US"))
+ .withTemplateConfig(templatingConfig)
+ .withInputTranslationConfig(
+ TranslationConfig.translateInputTo("en-US")
+ .withApplyTo(
+ List.of(
+ // Translate only selected placeholder values from German to English
+ ApplyTo.placeholders("exam_type", "topic")))
+ .withSourceLanguage("de-DE"))
.withOutputTranslationConfig(
- TranslationConfig.translateOutputTo("de-DE")
- .withSourceLanguage("en-US")); // optional source language
+ TranslationConfig.translateOutputTo("de-DE").withSourceLanguage("en-US"));
return client.chatCompletion(prompt, configWithTranslation);
}
diff --git a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java
index 9f12ba226..0b2ada3c4 100644
--- a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java
+++ b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java
@@ -22,7 +22,6 @@
import com.sap.ai.sdk.orchestration.TemplateConfig;
import com.sap.ai.sdk.orchestration.TextItem;
import com.sap.ai.sdk.orchestration.model.DPIEntities;
-import com.sap.ai.sdk.orchestration.model.GenericModuleResult;
import com.sap.ai.sdk.orchestration.model.InputTranslationModuleResult;
import java.io.IOException;
import java.io.InputStream;
@@ -497,18 +496,22 @@ void testStreamingErrorHandlingMasking() {
void testTranslation() {
val result = service.translation();
val content = result.getContent();
- // English translated to German
- assertThat(content).contains("Englisch");
- assertThat(content).contains("Der", "ist");
+ // Output translation turns the model response back to German
+ assertThat(content)
+ .containsAnyOf("Abitur", "Deutsche", "Literatur", "Lern", "Übungs", "Fragen");
InputTranslationModuleResult inputTranslation =
result.getOriginalResponse().getIntermediateResults().getInputTranslation();
- GenericModuleResult outputTranslation =
- result.getOriginalResponse().getIntermediateResults().getOutputTranslation();
assertThat(inputTranslation).isNotNull();
- assertThat(outputTranslation).isNotNull();
assertThat(inputTranslation.getMessage())
- .isEqualTo("Translated messages with roles: ['user']. ");
+ .isNotNull()
+ .contains("Successfully translated placeholders:")
+ .contains("exam_type")
+ .contains("topic");
+
+ val outputTranslation =
+ result.getOriginalResponse().getIntermediateResults().getOutputTranslation();
+ assertThat(outputTranslation).isNotNull();
assertThat(outputTranslation.getMessage()).isEqualTo("Output Translation successful");
}