Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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#withApplyTo` to support partial translation for user's input.

### 🐛 Fixed Issues

Expand Down
Original file line number Diff line number Diff line change
@@ -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}.
*
* <p>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}.
*
* <p>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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -29,13 +31,21 @@ class Input implements TranslationConfig {

@With String sourceLanguage;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mistake from your old PR:
sourceLanguage isn't being used and applied.
You should fix it and improve the unit test to make sure it is being used and applied.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used in OrchestrationService class and all other test classes.What do you mean exactly?


Object ApplyTo; // Can be null
/**
* Optional selection(s) to translate. If empty or null, translation is applied to the whole
* user input.
*/
@With List<SAPDocumentTranslationApplyToSelector> applyTo;
Copy link
Contributor

@CharlesDuboisSAP CharlesDuboisSAP Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather have this:

.applyToPlaceholders("exam_type", "topic")
.applyToTemplateRoles(TemplateRole.ASSISTANT)

than what we have currently:

.withApplyTo(
  List.of(
    TranslationApplyToSelector.placeholders(List.of("exam_type", "topic")),
    TranslationApplyToSelector.templateRoles(TemplateRole.ASSISTANT)))


@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);
}
}
Expand Down Expand Up @@ -71,7 +81,6 @@ SAPDocumentTranslationOutput createSAPDocumentTranslationOutput() {
*/
@Nonnull
static TranslationConfig.Input translateInputTo(@Nonnull final String targetLanguage) {

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

Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<String, Object>)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
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
Loading