Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
9614895
full implementation
n-o-u-r-h-a-n Feb 25, 2026
049ef67
Merge branch 'main' into support-partial-translation
n-o-u-r-h-a-n Feb 25, 2026
3aec367
release notes
n-o-u-r-h-a-n Feb 25, 2026
a429f37
Update sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/servic…
n-o-u-r-h-a-n Feb 25, 2026
46230db
Formatting
bot-sdk-js Feb 25, 2026
fdf7a5e
reverting
n-o-u-r-h-a-n Feb 25, 2026
5b8e536
Formatting
bot-sdk-js Feb 25, 2026
9a5a200
refactoring the new class name
n-o-u-r-h-a-n Feb 25, 2026
8d61683
Merge remote-tracking branch 'origin/support-partial-translation' int…
n-o-u-r-h-a-n Feb 25, 2026
d3cf79d
Formatting
bot-sdk-js Feb 25, 2026
dc21aea
reverting a comment
n-o-u-r-h-a-n Feb 25, 2026
212e506
Merge remote-tracking branch 'origin/support-partial-translation' int…
n-o-u-r-h-a-n Feb 25, 2026
0ecab09
Merge branch 'main' into support-partial-translation
n-o-u-r-h-a-n Feb 25, 2026
0fb1bc3
fixed test + reduced methods
n-o-u-r-h-a-n Feb 25, 2026
08e8d82
Merge remote-tracking branch 'origin/support-partial-translation' int…
n-o-u-r-h-a-n Feb 25, 2026
20b4c27
Formatting
bot-sdk-js Feb 25, 2026
7918494
further method reduction
n-o-u-r-h-a-n Feb 25, 2026
f2ed08d
Merge remote-tracking branch 'origin/support-partial-translation' int…
n-o-u-r-h-a-n Feb 25, 2026
2d3ef5b
Formatting
bot-sdk-js Feb 25, 2026
e6ebe2f
updating constructors
n-o-u-r-h-a-n Feb 25, 2026
bb9f590
Merge remote-tracking branch 'origin/support-partial-translation' int…
n-o-u-r-h-a-n Feb 25, 2026
df9db78
Merge branch 'refs/heads/main' into support-partial-translation
n-o-u-r-h-a-n Mar 2, 2026
aec7e6e
fix tests + mistake from old pr
n-o-u-r-h-a-n Mar 2, 2026
593d4c4
Formatting
bot-sdk-js Mar 2, 2026
4b13d42
formatting
n-o-u-r-h-a-n Mar 2, 2026
3ce80b1
fix source language
n-o-u-r-h-a-n Mar 2, 2026
b7f03e6
fix tests
n-o-u-r-h-a-n Mar 2, 2026
caf4fb0
Merge remote-tracking branch 'origin/support-partial-translation' int…
n-o-u-r-h-a-n Mar 2, 2026
7186853
Formatting
bot-sdk-js Mar 2, 2026
7011d6d
chore: retrigger CI
n-o-u-r-h-a-n Mar 2, 2026
20bf354
Merge remote-tracking branch 'origin/support-partial-translation' int…
n-o-u-r-h-a-n Mar 2, 2026
c4d0dff
some updates
n-o-u-r-h-a-n Mar 2, 2026
c9dd0e9
Merge branch 'main' into support-partial-translation
n-o-u-r-h-a-n Mar 3, 2026
f73443f
allowing apply_to to be null
n-o-u-r-h-a-n Mar 3, 2026
fece560
release notes update
n-o-u-r-h-a-n Mar 3, 2026
f0c43ad
Merge branch 'main' into support-partial-translation
n-o-u-r-h-a-n Mar 3, 2026
f891497
making withApplyTo private
n-o-u-r-h-a-n Mar 3, 2026
6894b5e
Merge remote-tracking branch 'origin/support-partial-translation' int…
n-o-u-r-h-a-n Mar 3, 2026
f681845
reverting pom formatting
n-o-u-r-h-a-n Mar 3, 2026
4083c44
Update orchestration/src/main/java/com/sap/ai/sdk/orchestration/Trans…
n-o-u-r-h-a-n Mar 3, 2026
8edc04f
Formatting
bot-sdk-js Mar 3, 2026
de9d424
fixing source language issue
n-o-u-r-h-a-n Mar 3, 2026
0896896
Update orchestration/src/main/java/com/sap/ai/sdk/orchestration/Trans…
n-o-u-r-h-a-n Mar 5, 2026
e6181e6
Formatting
bot-sdk-js Mar 5, 2026
1c7bda6
removing getter due to value presence
n-o-u-r-h-a-n Mar 5, 2026
4022b3a
Merge remote-tracking branch 'origin/support-partial-translation' int…
n-o-u-r-h-a-n Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

### 📈 Improvements

-
- [Orchestration] Added new API `TranslationConfig#applyToPlaceholders` and `TranslationConfig#applyToTemplateRoles` to support partial translation for a message.

### 🐛 Fixed Issues

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
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 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.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.With;
Expand All @@ -21,23 +30,129 @@
* @since 1.14.0
*/
public interface TranslationConfig {
/**
* Supported values for {@code items[]} when {@code category=template_roles}.
*
* <p>These map to the roles used in prompt templates.
*/
@RequiredArgsConstructor
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;
}

/** Input configuration for translation. */
@Value
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
class Input implements TranslationConfig {
String targetLanguage;

@With String sourceLanguage;
String sourceLanguage;

Object ApplyTo; // Can be null
/**
* Optional selection(s) to translate. If empty or null, translation is applied to the whole
* message. If used, source language will be applied per selector.
*/
@Nullable List<SAPDocumentTranslationApplyToSelector> 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)
.applyTo(applyTo);

if (applyTo == null || applyTo.isEmpty()) {
conf.sourceLanguage(sourceLanguage);
}

return SAPDocumentTranslationInput.create().type(translationType).config(conf);
}

/**
* Start an {@code apply_to} selector for placeholder names in {@code placeholder_values}.
*
* @param name The first placeholder name to translate.
* @param additionalNames Additional placeholder names to translate.
* @return A selector with {@code category=placeholders} and the given items.
*/
@Nonnull
public Input applyToPlaceholders(
@Nonnull final String name, @Nonnull final String... additionalNames) {
final var selector =
SAPDocumentTranslationApplyToSelector.create()
.category(PLACEHOLDERS)
.items(Stream.concat(Stream.of(name), Stream.of(additionalNames)).toList());
return addApplyToSelector(selector);
}

/**
* Start an {@code apply_to} selector for prompt template message roles.
*
* @param role The first template role to translate.
* @param roles The template roles to translate.
* @return A selector with {@code category=template_roles} and the given items.
*/
@Nonnull
public Input applyToTemplateRoles(
@Nonnull final TranslationConfig.TemplateRole role,
@Nonnull final TranslationConfig.TemplateRole... roles) {
final var roleStrings = new ArrayList<String>(1 + roles.length);
roleStrings.add(role.getValue());
for (final var r : roles) {
if (r != null) {
roleStrings.add(r.getValue());
}
}

final var selector =
SAPDocumentTranslationApplyToSelector.create()
.category(TEMPLATE_ROLES)
.items(roleStrings);
return addApplyToSelector(selector);
}

/**
* Set the source language for this translation. <br>
* <strong>Important Note:</strong> If no selectors are used, this applies to the whole message.
* If selectors are used, this applies to the most recently added selector.
*
* @param sourceLanguage The source language code
* @return A new Input with the given source language applied.
*/
@Nonnull
public Input withSourceLanguage(@Nonnull final String sourceLanguage) {
if (applyTo != null) {
applyTo.get(applyTo.size() - 1).sourceLanguage(sourceLanguage);
}
return new Input(targetLanguage, sourceLanguage, applyTo);
}

private Input addApplyToSelector(
@Nonnull final SAPDocumentTranslationApplyToSelector selector) {
final var appended = new ArrayList<SAPDocumentTranslationApplyToSelector>();
if (applyTo != null && !applyTo.isEmpty()) {
appended.addAll(applyTo);
}
appended.add(selector);

return new TranslationConfig.Input(targetLanguage, sourceLanguage, appended);
}
}

/** Output configuration for translation. */
Expand Down Expand Up @@ -71,7 +186,6 @@ SAPDocumentTranslationOutput createSAPDocumentTranslationOutput() {
*/
@Nonnull
static TranslationConfig.Input translateInputTo(@Nonnull final String targetLanguage) {

return new TranslationConfig.Input(targetLanguage, null, null);
}

Expand All @@ -86,7 +200,6 @@ static TranslationConfig.Input translateInputTo(@Nonnull final String targetLang
*/
@Nonnull
static TranslationConfig.Output translateOutputTo(@Nonnull final String targetLanguage) {

return new TranslationConfig.Output(targetLanguage, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
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;
import static com.sap.ai.sdk.orchestration.TranslationConfig.TemplateRole.ASSISTANT;
import static com.sap.ai.sdk.orchestration.TranslationConfig.TemplateRole.USER;
import static com.sap.ai.sdk.orchestration.model.DataRepositoryType.VECTOR;
import static com.sap.ai.sdk.orchestration.model.GroundingModuleConfig.TypeEnum.DOCUMENT_GROUNDING_SERVICE;
import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -20,6 +22,7 @@
import com.sap.ai.sdk.orchestration.model.MaskingModuleConfigProviders;
import com.sap.ai.sdk.orchestration.model.ResponseFormatJsonObject;
import com.sap.ai.sdk.orchestration.model.ResponseFormatJsonSchema;
import com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationOutputTargetLanguage;
import com.sap.ai.sdk.orchestration.model.Template;
import com.sap.ai.sdk.orchestration.model.TemplateRef;
import com.sap.ai.sdk.orchestration.model.TemplateRefByID;
Expand Down Expand Up @@ -145,35 +148,85 @@ void testTranslationConfig() {
TranslationConfig.translateInputTo("en-US").withSourceLanguage("de-DE");
var outputTranslationConfig =
TranslationConfig.translateOutputTo("de-DE").withSourceLanguage("en-US");
var config =

var sapInput = inputTranslationConfig.createSAPDocumentTranslationInput();
var sapOutput = outputTranslationConfig.createSAPDocumentTranslationOutput();

assertThat(sapInput.getConfig()).isNotNull();
assertThat(sapInput.getConfig().getTargetLanguage()).isEqualTo("en-US");
assertThat(sapInput.getConfig().getSourceLanguage())
.isEqualTo(inputTranslationConfig.getSourceLanguage());

assertThat(sapOutput.getConfig()).isNotNull();
assertThat(
((SAPDocumentTranslationOutputTargetLanguage.InnerString)
sapOutput.getConfig().getTargetLanguage())
.value())
.isEqualTo("de-DE");
assertThat(sapOutput.getConfig().getSourceLanguage())
.isEqualTo(outputTranslationConfig.getSourceLanguage());
}

@Test
void testTranslationConfigApplyToSelectors() {
var inputTranslationConfig =
TranslationConfig.translateInputTo("en-US")
.applyToPlaceholders("exam_type", "topic")
.withSourceLanguage("de-DE")
.applyToTemplateRoles(USER, ASSISTANT)
.withSourceLanguage("en-US");

var sapInput = inputTranslationConfig.createSAPDocumentTranslationInput();
assertThat(sapInput.getConfig().getTargetLanguage()).isEqualTo("en-US");
assertThat(sapInput.getConfig().getSourceLanguage()).isNull();
assertThat(sapInput.getConfig().getApplyTo().get(0).getSourceLanguage()).isEqualTo("de-DE");
assertThat(sapInput.getConfig().getApplyTo().get(1).getSourceLanguage()).isEqualTo("en-US");

assertThat(sapInput.getConfig().getApplyTo()).hasSize(2);
assertThat(sapInput.getConfig().getApplyTo().get(0).getCategory().getValue())
.isEqualTo("placeholders");
assertThat(sapInput.getConfig().getApplyTo().get(0).getItems())
.containsExactly("exam_type", "topic");
assertThat(sapInput.getConfig().getApplyTo().get(1).getCategory().getValue())
.isEqualTo("template_roles");
assertThat(sapInput.getConfig().getApplyTo().get(1).getItems())
.containsExactly("user", "assistant");

// applyTo == null list
final var inputNull = TranslationConfig.translateInputTo("en-US").withSourceLanguage("de-DE");
final var sapNull = inputNull.createSAPDocumentTranslationInput();
assertThat(sapNull.getConfig().getTargetLanguage()).isEqualTo("en-US");
assertThat(sapNull.getConfig().getSourceLanguage()).isEqualTo("de-DE");
assertThat(sapNull.getConfig().getApplyTo()).isNull();
}

@Test
void testTranslationConfigViaModuleConfig() {
final var inputTranslation =
TranslationConfig.translateInputTo("en-US").withSourceLanguage("de-DE");
final var outputTranslation =
TranslationConfig.translateOutputTo("de-DE").withSourceLanguage("en-US");

final var config =
new OrchestrationModuleConfig()
.withLlmConfig(GPT_4O)
.withInputTranslationConfig(inputTranslationConfig)
.withOutputTranslationConfig(outputTranslationConfig);
.withInputTranslationConfig(inputTranslation)
.withOutputTranslationConfig(outputTranslation);

assertThat(config.getInputTranslationConfig()).isNotNull();
assertThat(config.getInputTranslationConfig().getConfig().getTargetLanguage())
.isEqualTo("en-US");
assertThat(config.getInputTranslationConfig().getConfig().getSourceLanguage())
.isEqualTo(
inputTranslationConfig
.createSAPDocumentTranslationInput()
.getConfig()
.getSourceLanguage());
.isEqualTo("de-DE");

assertThat(config.getOutputTranslationConfig()).isNotNull();
assertThat(config.getOutputTranslationConfig().getConfig().getTargetLanguage())
.isEqualTo(
outputTranslationConfig
.createSAPDocumentTranslationOutput()
.getConfig()
.getTargetLanguage());
assertThat(
((SAPDocumentTranslationOutputTargetLanguage.InnerString)
config.getOutputTranslationConfig().getConfig().getTargetLanguage())
.value())
.isEqualTo("de-DE");
assertThat(config.getOutputTranslationConfig().getConfig().getSourceLanguage())
.isEqualTo(
outputTranslationConfig
.createSAPDocumentTranslationOutput()
.getConfig()
.getSourceLanguage());
.isEqualTo("en-US");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -690,17 +690,30 @@ 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")
.applyToPlaceholders("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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
}

Expand Down