From 23ff0d23eb11a60da241b9313bfc8ac8912c3c33 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Wed, 16 Sep 2026 11:43:10 +0200 Subject: [PATCH 1/9] v2 API restrict inputing a prompt with a prompt reference --- .../ConfigToRequestTransformer.java | 27 +++- .../orchestration/OrchestrationClient.java | 21 ++- .../OrchestrationModuleConfig.java | 15 ++ .../OrchestrationModuleConfigWithRef.java | 153 ++++++++++++++++++ .../OrchestrationTemplateReference.java | 45 ++++++ .../app/services/OrchestrationService.java | 42 ++--- 6 files changed, 277 insertions(+), 26 deletions(-) create mode 100644 orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRef.java diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java index 4806489ce..218db8cc2 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java @@ -14,7 +14,6 @@ import com.sap.ai.sdk.orchestration.model.PromptTemplatingModuleConfig; import com.sap.ai.sdk.orchestration.model.PromptTemplatingModuleConfigPrompt; import com.sap.ai.sdk.orchestration.model.Template; -import com.sap.ai.sdk.orchestration.model.TemplateRef; import com.sap.ai.sdk.orchestration.model.TranslationModuleConfig; import io.vavr.control.Option; import java.util.ArrayList; @@ -32,7 +31,7 @@ @NoArgsConstructor(access = AccessLevel.NONE) final class ConfigToRequestTransformer { @Nonnull - static CompletionRequestConfiguration toCompletionPostRequest( + static CompletionRequestConfiguration fromTemplateRefToCompletionPostRequest( @Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config, @Nonnull final OrchestrationModuleConfig... fallbackConfigs) { @@ -81,9 +80,6 @@ static PromptTemplatingModuleConfigPrompt toTemplateModuleConfig( * In this case, the request will fail, since the templating module will try to resolve the parameter. * To be fixed with https://github.tools.sap/AI/llm-orchestration/issues/662 */ - if (config instanceof TemplateRef) { - return config; - } val template = config instanceof Template t ? t : Template.create().template(); val messages = template.getTemplate(); @@ -249,4 +245,25 @@ static CompletionPostRequest fromReferenceToCompletionPostRequest( return request; } } + + @Nonnull + static CompletionRequestConfiguration fromTemplateRefToCompletionPostRequest( + @Nonnull final OrchestrationModuleConfigWithRef configWithRef) { + final OrchestrationTemplateReference templateRef = configWithRef.getTemplateRef(); + final var messageHistory = + templateRef.getMessagesHistory().stream().map(Message::createChatMessage).toList(); + final var placeholders = templateRef.getTemplateParameters(); + + final OrchestrationModuleConfig inner = + configWithRef.getInner().withTemplateConfig(templateRef.toLowLevel()); + + val requestConfig = + OrchestrationConfig.create().modules(toModuleConfigs(inner)).stream( + configWithRef.getInner().getGlobalStreamOptions()); + + return CompletionRequestConfiguration.create() + .config(requestConfig) + .placeholderValues(placeholders) + .messagesHistory(messageHistory); + } } diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java index 955a04def..53036e8ac 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java @@ -84,7 +84,8 @@ public static CompletionRequestConfiguration toCompletionPostRequest( @Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config, @Nonnull final OrchestrationModuleConfig... fallbackConfigs) { - return ConfigToRequestTransformer.toCompletionPostRequest(prompt, config, fallbackConfigs); + return ConfigToRequestTransformer.fromTemplateRefToCompletionPostRequest( + prompt, config, fallbackConfigs); } /** @@ -192,6 +193,24 @@ public OrchestrationChatResponse chatCompletionUsingReference( return new OrchestrationChatResponse(response); } + /** + * Generate a completion using a module configuration containing a template reference Per-request + * history and parameters must be set on the template reference via {@link + * OrchestrationTemplateReference#withMessageHistory} and {@link + * OrchestrationTemplateReference#withTemplateParameters}. + * + * @param config A module configuration wrapping an {@link OrchestrationTemplateReference}. + * @return The completion output. + * @since 1.26.0 + */ + @Nonnull + public OrchestrationChatResponse chatCompletion( + @Nonnull final OrchestrationModuleConfigWithRef config) { + val request = ConfigToRequestTransformer.fromTemplateRefToCompletionPostRequest(config); + val response = executeRequest(request); + return new OrchestrationChatResponse(response); + } + /** * Perform a request to the orchestration service using a module configuration provided as JSON * string. This can be useful when building a configuration in the AI Launchpad UI and exporting diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java index 82f4c6754..97f5f3a62 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java @@ -340,6 +340,21 @@ public OrchestrationModuleConfig withTemplateConfig( return this.withTemplateConfig(templateConfig.toLowLevel()); } + /** + * Creates a new configuration with the given template reference. The template reference is the + * only source of prompt input + * + * @param templateRef The template reference to use. + * @return A new {@link OrchestrationModuleConfigWithRef} wrapping this config. + * @since 1.26.0 + */ + @Tolerate + @Nonnull + public OrchestrationModuleConfigWithRef withTemplateConfig( + @Nonnull final OrchestrationTemplateReference templateRef) { + return new OrchestrationModuleConfigWithRef(this, templateRef); + } + /** * Configure input translation using a high-level TranslationConfig. * diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRef.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRef.java new file mode 100644 index 000000000..b7c951781 --- /dev/null +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRef.java @@ -0,0 +1,153 @@ +package com.sap.ai.sdk.orchestration; + +import com.sap.ai.sdk.orchestration.model.LLMModelDetails; +import javax.annotation.Nonnull; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * An {@link OrchestrationModuleConfig} that carries an {@link OrchestrationTemplateReference}. The + * template reference is the only source of prompt input — no free-form messages are accepted. + * Obtain instances via {@link + * OrchestrationModuleConfig#withTemplateConfig(OrchestrationTemplateReference)}. + * + * @since 1.26.0 + */ +@AllArgsConstructor(access = AccessLevel.PACKAGE) +public class OrchestrationModuleConfigWithRef { + + @Getter(AccessLevel.PACKAGE) + @Nonnull + private final OrchestrationModuleConfig inner; + + @Getter(AccessLevel.PACKAGE) + @Nonnull + private final OrchestrationTemplateReference templateRef; + + /** + * Creates a new configuration with the given LLM configuration. + * + * @param llm The LLM configuration to use. + * @return A new configuration with the given LLM configuration. + * @see OrchestrationModuleConfig#withLlmConfig(LLMModelDetails) + */ + @SuppressWarnings("PMD.PublicApiExposesModelType") + @Nonnull + public OrchestrationModuleConfigWithRef withLlmConfig(@Nonnull final LLMModelDetails llm) { + return new OrchestrationModuleConfigWithRef(inner.withLlmConfig(llm), templateRef); + } + + /** + * Creates a new configuration with the given LLM configuration. + * + * @param model The LLM configuration to use. + * @return A new configuration with the given LLM configuration. + * @see OrchestrationModuleConfig#withLlmConfig(OrchestrationAiModel) + */ + @Nonnull + public OrchestrationModuleConfigWithRef withLlmConfig(@Nonnull final OrchestrationAiModel model) { + return new OrchestrationModuleConfigWithRef(inner.withLlmConfig(model), templateRef); + } + + /** + * Creates a new configuration with the given Data Masking configuration. + * + * @param maskingProvider The Data Masking configuration to use. + * @param maskingProviders Additional Data Masking configurations to use. + * @return A new configuration with the given Data Masking configuration. + * @see OrchestrationModuleConfig#withMaskingConfig(MaskingProvider, MaskingProvider...) + */ + @Nonnull + public OrchestrationModuleConfigWithRef withMaskingConfig( + @Nonnull final MaskingProvider maskingProvider, + @Nonnull final MaskingProvider... maskingProviders) { + return new OrchestrationModuleConfigWithRef( + inner.withMaskingConfig(maskingProvider, maskingProviders), templateRef); + } + + /** + * Adds input content filters to the configuration. + * + * @param contentFilter A filter to apply to the input. + * @param contentFilters Zero or more additional content filters to apply to the input. + * @return A new configuration with the specified input filters added. + * @see OrchestrationModuleConfig#withInputFiltering(ContentFilter, ContentFilter...) + */ + @Nonnull + public OrchestrationModuleConfigWithRef withInputFiltering( + @Nonnull final ContentFilter contentFilter, @Nonnull final ContentFilter... contentFilters) { + return new OrchestrationModuleConfigWithRef( + inner.withInputFiltering(contentFilter, contentFilters), templateRef); + } + + /** + * Adds output content filters to the configuration. + * + * @param contentFilter A filter to apply to the output. + * @param contentFilters Zero or more additional content filters to apply to the output. + * @return A new configuration with the specified output filters added. + * @see OrchestrationModuleConfig#withOutputFiltering(ContentFilter, ContentFilter...) + */ + @Nonnull + public OrchestrationModuleConfigWithRef withOutputFiltering( + @Nonnull final ContentFilter contentFilter, @Nonnull final ContentFilter... contentFilters) { + return new OrchestrationModuleConfigWithRef( + inner.withOutputFiltering(contentFilter, contentFilters), templateRef); + } + + /** + * Creates a new configuration with the given grounding configuration. + * + * @param groundingProvider The grounding configuration to use. + * @return A new configuration with the given grounding configuration. + * @see OrchestrationModuleConfig#withGrounding(GroundingProvider) + */ + @Nonnull + public OrchestrationModuleConfigWithRef withGrounding( + @Nonnull final GroundingProvider groundingProvider) { + return new OrchestrationModuleConfigWithRef( + inner.withGrounding(groundingProvider), templateRef); + } + + /** + * Configure input translation using a high-level TranslationConfig. + * + * @param translationConfig The translation configuration. + * @return A new configuration with input translation configured. + * @see OrchestrationModuleConfig#withInputTranslationConfig(TranslationConfig.Input) + */ + @Nonnull + public OrchestrationModuleConfigWithRef withInputTranslationConfig( + @Nonnull final TranslationConfig.Input translationConfig) { + return new OrchestrationModuleConfigWithRef( + inner.withInputTranslationConfig(translationConfig), templateRef); + } + + /** + * Configure output translation using a high-level TranslationConfig. + * + * @param translationConfig The translation configuration. + * @return A new configuration with output translation configured. + * @see OrchestrationModuleConfig#withOutputTranslationConfig(TranslationConfig.Output) + */ + @Nonnull + public OrchestrationModuleConfigWithRef withOutputTranslationConfig( + @Nonnull final TranslationConfig.Output translationConfig) { + return new OrchestrationModuleConfigWithRef( + inner.withOutputTranslationConfig(translationConfig), templateRef); + } + + /** + * Creates a new configuration with the given stream configuration. + * + * @param config The stream configuration to use. + * @return A new configuration with the given stream configuration. + * @see OrchestrationModuleConfig#withStreamConfig(OrchestrationStreamConfig) + */ + @Nonnull + public OrchestrationModuleConfigWithRef withStreamConfig( + @Nonnull final OrchestrationStreamConfig config) { + return new OrchestrationModuleConfigWithRef(inner.withStreamConfig(config), templateRef); + } +} diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplateReference.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplateReference.java index 62fd0e067..82eeaf14a 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplateReference.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplateReference.java @@ -5,10 +5,13 @@ import com.sap.ai.sdk.orchestration.model.TemplateRefByID; import com.sap.ai.sdk.orchestration.model.TemplateRefByScenarioNameVersion; import com.sap.ai.sdk.orchestration.model.TemplateRefTemplateRef; +import java.util.List; +import java.util.Map; import javax.annotation.Nonnull; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.Value; import lombok.With; @@ -28,6 +31,48 @@ public class OrchestrationTemplateReference extends TemplateConfig { /** The scope of the template reference. */ @With @Nonnull ScopeEnum scope; + @Getter(AccessLevel.PACKAGE) + @Nonnull + List messagesHistory; + + @Getter(AccessLevel.PACKAGE) + @Nonnull + Map templateParameters; + + /** + * Build a template reference with scope only. + */ + OrchestrationTemplateReference( + @Nonnull final TemplateRefTemplateRef reference, @Nonnull final ScopeEnum scope) { + this(reference, scope, List.of(), Map.of()); + } + + /** + * Set the chat history. + * + * @param messagesHistory The chat history to set. + * @return A new instance with the specified chat history. + */ + @Nonnull + public OrchestrationTemplateReference withMessageHistory( + @Nonnull final List messagesHistory) { + return new OrchestrationTemplateReference( + reference, scope, messagesHistory, templateParameters); + } + + /** + * Set the template parameters. + * + * @param templateParameters The template parameters to set. + * @return A new instance with the specified template parameters. + */ + @Nonnull + public OrchestrationTemplateReference withTemplateParameters( + @Nonnull final Map templateParameters) { + return new OrchestrationTemplateReference( + reference, scope, messagesHistory, templateParameters); + } + /** * Create a low-level representation of the template. * 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 088cfab5f..eea2aa9c1 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 @@ -695,13 +695,14 @@ public OrchestrationChatResponse templateFromPromptRegistryByIdTenant( @Nonnull final String topic) { final var llmWithImageSupportConfig = new OrchestrationModuleConfig().withLlmConfig(GPT_5_MINI); - val template = TemplateConfig.reference().byId("21cb1358-0bf1-4f43-870b-00f14d0f9f16"); - val configWithTemplate = llmWithImageSupportConfig.withTemplateConfig(template); - val inputParams = Map.of("language", "Italian", "input", topic); - val prompt = new OrchestrationPrompt(inputParams); + val template = + TemplateConfig.reference() + .byId("21cb1358-0bf1-4f43-870b-00f14d0f9f16") + .withTemplateParameters(inputParams); + val configWithTemplate = llmWithImageSupportConfig.withTemplateConfig(template); - return client.chatCompletion(prompt, configWithTemplate); + return client.chatCompletion(configWithTemplate); } /** @@ -720,16 +721,15 @@ public OrchestrationChatResponse templateFromPromptRegistryByIdResourceGroup( final var clientWithResourceGroup = client.withResourceGroup("ai-sdk-java-e2e", "orchestration"); + val inputParams = Map.of("categories", "Finance, Tech, Sports", "inputExample", inputExample); val template = TemplateConfig.reference() .byId("8bf72116-11ab-41bb-8933-8be56f59cb67") - .withScope(RESOURCE_GROUP); + .withScope(RESOURCE_GROUP) + .withTemplateParameters(inputParams); val configWithTemplate = config.withTemplateConfig(template); - val inputParams = Map.of("categories", "Finance, Tech, Sports", "inputExample", inputExample); - val prompt = new OrchestrationPrompt(inputParams); - - return clientWithResourceGroup.chatCompletion(prompt, configWithTemplate); + return clientWithResourceGroup.chatCompletion(configWithTemplate); } /** @@ -744,13 +744,16 @@ public OrchestrationChatResponse templateFromPromptRegistryByIdResourceGroup( @Nonnull public OrchestrationChatResponse templateFromPromptRegistryByScenarioTenant( @Nonnull final String topic) { - val template = TemplateConfig.reference().byScenario("test").name("test").version("0.0.1"); - val configWithTemplate = config.withTemplateConfig(template); - val inputParams = Map.of("language", "Italian", "input", topic); - val prompt = new OrchestrationPrompt(inputParams); + val template = + TemplateConfig.reference() + .byScenario("test") + .name("test") + .version("0.0.1") + .withTemplateParameters(inputParams); + val configWithTemplate = config.withTemplateConfig(template); - return client.chatCompletion(prompt, configWithTemplate); + return client.chatCompletion(configWithTemplate); } /** @@ -769,18 +772,17 @@ public OrchestrationChatResponse templateFromPromptRegistryByScenarioResourceGro final var clientWithResourceGroup = client.withResourceGroup("ai-sdk-java-e2e", "orchestration"); + val inputParams = Map.of("categories", "Finance, Tech, Sports", "inputExample", inputExample); val template = TemplateConfig.reference() .byScenario("categorization") .name("example-prompt-template") .version("0.0.1") - .withScope(RESOURCE_GROUP); + .withScope(RESOURCE_GROUP) + .withTemplateParameters(inputParams); val configWithTemplate = config.withTemplateConfig(template); - val inputParams = Map.of("categories", "Finance, Tech, Sports", "inputExample", inputExample); - val prompt = new OrchestrationPrompt(inputParams); - - return clientWithResourceGroup.chatCompletion(prompt, configWithTemplate); + return clientWithResourceGroup.chatCompletion(configWithTemplate); } /** From a2729cc457983f05a56a1a2e98082305eaf449d3 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Wed, 16 Sep 2026 12:31:32 +0200 Subject: [PATCH 2/9] formatting --- .../ai/sdk/orchestration/OrchestrationTemplateReference.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplateReference.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplateReference.java index 82eeaf14a..922bb9677 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplateReference.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationTemplateReference.java @@ -39,9 +39,7 @@ public class OrchestrationTemplateReference extends TemplateConfig { @Nonnull Map templateParameters; - /** - * Build a template reference with scope only. - */ + /** Build a template reference with scope only. */ OrchestrationTemplateReference( @Nonnull final TemplateRefTemplateRef reference, @Nonnull final ScopeEnum scope) { this(reference, scope, List.of(), Map.of()); From 951193cfe2371650bb41cd8675f9740a5428531b Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Wed, 16 Sep 2026 12:50:07 +0200 Subject: [PATCH 3/9] fix method names --- .../sap/ai/sdk/orchestration/ConfigToRequestTransformer.java | 2 +- .../java/com/sap/ai/sdk/orchestration/OrchestrationClient.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java index 218db8cc2..b2c08a137 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java @@ -31,7 +31,7 @@ @NoArgsConstructor(access = AccessLevel.NONE) final class ConfigToRequestTransformer { @Nonnull - static CompletionRequestConfiguration fromTemplateRefToCompletionPostRequest( + static CompletionRequestConfiguration toCompletionPostRequest( @Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config, @Nonnull final OrchestrationModuleConfig... fallbackConfigs) { diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java index 53036e8ac..4a67748ea 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java @@ -84,8 +84,7 @@ public static CompletionRequestConfiguration toCompletionPostRequest( @Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config, @Nonnull final OrchestrationModuleConfig... fallbackConfigs) { - return ConfigToRequestTransformer.fromTemplateRefToCompletionPostRequest( - prompt, config, fallbackConfigs); + return ConfigToRequestTransformer.toCompletionPostRequest(prompt, config, fallbackConfigs); } /** From 7060833e5842e875d925959558c84e4856507ec2 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Wed, 16 Sep 2026 13:14:03 +0200 Subject: [PATCH 4/9] testing --- .../ConfigToRequestTransformerTest.java | 18 ++ .../OrchestrationModuleConfigWithRefTest.java | 72 ++++++++ .../orchestration/OrchestrationUnitTest.java | 157 ++++++++++-------- 3 files changed, 177 insertions(+), 70 deletions(-) create mode 100644 orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRefTest.java diff --git a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformerTest.java b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformerTest.java index e01415733..50ca2c29b 100644 --- a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformerTest.java +++ b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformerTest.java @@ -205,4 +205,22 @@ void testUserMessageHistory() { assertThat(actual.getMessagesHistory()).containsExactly(userMessage.createChatMessage()); } + + @Test + void testToCompletionPostRequestWithRef() { + var ref = + TemplateConfig.reference() + .byId("test-id") + .withTemplateParameters(Map.of("lang", "DE")) + .withMessageHistory(List.of(new UserMessage("prev"))); + var config = + new OrchestrationModuleConfig() + .withLlmConfig(OrchestrationAiModel.GPT_4O) + .withTemplateConfig(ref); + + var request = ConfigToRequestTransformer.fromTemplateRefToCompletionPostRequest(config); + + assertThat(request.getPlaceholderValues()).containsEntry("lang", "DE"); + assertThat(request.getMessagesHistory()).hasSize(1); + } } diff --git a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRefTest.java b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRefTest.java new file mode 100644 index 000000000..859644f11 --- /dev/null +++ b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRefTest.java @@ -0,0 +1,72 @@ +package com.sap.ai.sdk.orchestration; + +import static com.sap.ai.sdk.orchestration.AzureFilterThreshold.ALLOW_SAFE; +import static org.assertj.core.api.Assertions.assertThat; + +import com.sap.ai.sdk.orchestration.model.DPIEntities; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.Test; + +class OrchestrationModuleConfigWithRefTest { + + @Test + void withTemplateConfigReturnsWrapperWithRef() { + var ref = TemplateConfig.reference().byId("abc"); + OrchestrationModuleConfigWithRef withRef = + new OrchestrationModuleConfig() + .withLlmConfig(OrchestrationAiModel.GPT_4O) + .withTemplateConfig(ref); + + assertThat(withRef.getTemplateRef()).isSameAs(ref); + assertThat(withRef.getInner()).isNotNull(); + } + + @Test + void templateRefCarriesHistoryAndParams() { + var ref = + TemplateConfig.reference() + .byId("abc") + .withMessageHistory(List.of(new UserMessage("hi"))) + .withTemplateParameters(Map.of("k", "v")); + + assertThat(ref.getMessagesHistory()).hasSize(1); + assertThat(ref.getTemplateParameters()).containsEntry("k", "v"); + } + + @Test + void delegateMethodsPreserveTemplateRef() { + var ref = TemplateConfig.reference().byId("abc"); + var withRef = + new OrchestrationModuleConfig() + .withLlmConfig(OrchestrationAiModel.GPT_4O) + .withTemplateConfig(ref); + + assertThat(withRef.withLlmConfig(OrchestrationAiModel.GPT_4O).getTemplateRef()).isSameAs(ref); + assertThat(withRef.withLlmConfig(OrchestrationAiModel.GPT_4O.createConfig()).getTemplateRef()) + .isSameAs(ref); + + var filter = new AzureContentFilter().hate(ALLOW_SAFE); + assertThat(withRef.withInputFiltering(filter).getTemplateRef()).isSameAs(ref); + assertThat(withRef.withOutputFiltering(filter).getTemplateRef()).isSameAs(ref); + + var masking = DpiMasking.anonymization().withEntities(DPIEntities.PERSON); + assertThat(withRef.withMaskingConfig(masking).getTemplateRef()).isSameAs(ref); + + assertThat(withRef.withGrounding(Grounding.create()).getTemplateRef()).isSameAs(ref); + + assertThat( + withRef + .withInputTranslationConfig(TranslationConfig.translateInputTo("en-US")) + .getTemplateRef()) + .isSameAs(ref); + assertThat( + withRef + .withOutputTranslationConfig(TranslationConfig.translateOutputTo("de-DE")) + .getTemplateRef()) + .isSameAs(ref); + + assertThat(withRef.withStreamConfig(new OrchestrationStreamConfig()).getTemplateRef()) + .isSameAs(ref); + } +} 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 713669abe..ba23f3939 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 @@ -1449,65 +1449,58 @@ void testResponseFormatText() throws IOException { @Test void testTemplateFromPromptRegistryByIdTenant() throws IOException { - { - stubFor( - post(anyUrl()) - .willReturn( - aResponse() - .withBodyFile("templateReferenceResponse.json") - .withHeader("Content-Type", "application/json"))); - - var template = TemplateConfig.reference().byId("21cb1358-0bf1-4f43-870b-00f14d0f9f16"); - var configWithTemplate = config.withTemplateConfig(template); - - var inputParams = Map.of("language", "Italian", "input", "Cloud ERP systems"); - var prompt = new OrchestrationPrompt(inputParams); - - final var response = client.chatCompletion(prompt, configWithTemplate); - assertThat(response.getContent()).startsWith("I sistemi ERP (Enterprise Resource Planning)"); - assertThat(response.getOriginalResponse().getIntermediateResults().getTemplating()) - .hasSize(2); - - final String request = fileLoaderStr.apply("templateReferenceByIdRequest.json"); - verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(request))); - } + stubFor( + post(anyUrl()) + .willReturn( + aResponse() + .withBodyFile("templateReferenceResponse.json") + .withHeader("Content-Type", "application/json"))); + + var inputParams = Map.of("language", "Italian", "input", "Cloud ERP systems"); + var template = + TemplateConfig.reference() + .byId("21cb1358-0bf1-4f43-870b-00f14d0f9f16") + .withTemplateParameters(inputParams); + var configWithTemplate = config.withTemplateConfig(template); + + final var response = client.chatCompletion(configWithTemplate); + assertThat(response.getContent()).startsWith("I sistemi ERP (Enterprise Resource Planning)"); + assertThat(response.getOriginalResponse().getIntermediateResults().getTemplating()).hasSize(2); + + final String request = fileLoaderStr.apply("templateReferenceByIdRequest.json"); + verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(request))); } @Test void testTemplateFromPromptRegistryByIdResourceGroup() throws IOException { - { - stubFor( - post(anyUrl()) - .willReturn( - aResponse() - .withBodyFile("templateReferenceResourceGroupResponse.json") - .withHeader("Content-Type", "application/json"))); - - var template = - TemplateConfig.reference() - .byId("8bf72116-11ab-41bb-8933-8be56f59cb67") - .withScope(RESOURCE_GROUP); - var config = - new OrchestrationModuleConfig() - .withLlmConfig(GEMINI_2_5_FLASH.withParam(TEMPERATURE, 0.0)); - var configWithTemplate = config.withTemplateConfig(template); - - var inputParams = - Map.of( - "categories", - "Finance, Tech, Sports", - "inputExample", - "What's the latest news on the stock market?"); - var prompt = new OrchestrationPrompt(inputParams); - - final var response = client.chatCompletion(prompt, configWithTemplate); - assertThat(response.getContent()).startsWith("Finance"); - assertThat(response.getOriginalResponse().getIntermediateResults().getTemplating()) - .hasSize(2); - - final String request = fileLoaderStr.apply("templateReferenceResourceGroupByIdRequest.json"); - verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(request))); - } + stubFor( + post(anyUrl()) + .willReturn( + aResponse() + .withBodyFile("templateReferenceResourceGroupResponse.json") + .withHeader("Content-Type", "application/json"))); + + var inputParams = + Map.of( + "categories", + "Finance, Tech, Sports", + "inputExample", + "What's the latest news on the stock market?"); + var template = + TemplateConfig.reference() + .byId("8bf72116-11ab-41bb-8933-8be56f59cb67") + .withScope(RESOURCE_GROUP) + .withTemplateParameters(inputParams); + var config = + new OrchestrationModuleConfig().withLlmConfig(GEMINI_2_5_FLASH.withParam(TEMPERATURE, 0.0)); + var configWithTemplate = config.withTemplateConfig(template); + + final var response = client.chatCompletion(configWithTemplate); + assertThat(response.getContent()).startsWith("Finance"); + assertThat(response.getOriginalResponse().getIntermediateResults().getTemplating()).hasSize(2); + + final String request = fileLoaderStr.apply("templateReferenceResourceGroupByIdRequest.json"); + verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(request))); } @Test @@ -1519,13 +1512,16 @@ void testTemplateFromPromptRegistryByScenarioTenant() throws IOException { .withBodyFile("templateReferenceResponse.json") .withHeader("Content-Type", "application/json"))); - var template = TemplateConfig.reference().byScenario("test").name("test").version("0.0.1"); - var configWithTemplate = config.withTemplateConfig(template); - var inputParams = Map.of("language", "Italian", "input", "Cloud ERP systems"); - var prompt = new OrchestrationPrompt(inputParams); + var template = + TemplateConfig.reference() + .byScenario("test") + .name("test") + .version("0.0.1") + .withTemplateParameters(inputParams); + var configWithTemplate = config.withTemplateConfig(template); - final var response = client.chatCompletion(prompt, configWithTemplate); + final var response = client.chatCompletion(configWithTemplate); assertThat(response.getContent()).startsWith("I sistemi ERP (Enterprise Resource Planning)"); assertThat(response.getOriginalResponse().getIntermediateResults().getTemplating()).hasSize(2); @@ -1542,25 +1538,24 @@ void testTemplateFromPromptRegistryByScenarioResourceGroup() throws IOException .withBodyFile("templateReferenceResourceGroupResponse.json") .withHeader("Content-Type", "application/json"))); + var inputParams = + Map.of( + "categories", + "Finance, Tech, Sports", + "inputExample", + "What's the latest news on the stock market?"); var template = TemplateConfig.reference() .byScenario("categorization") .name("example-prompt-template") .version("0.0.1") - .withScope(RESOURCE_GROUP); + .withScope(RESOURCE_GROUP) + .withTemplateParameters(inputParams); var config = new OrchestrationModuleConfig().withLlmConfig(GEMINI_2_5_FLASH.withParam(TEMPERATURE, 0.0)); var configWithTemplate = config.withTemplateConfig(template); - var inputParams = - Map.of( - "categories", - "Finance, Tech, Sports", - "inputExample", - "What's the latest news on the stock market?"); - var prompt = new OrchestrationPrompt(inputParams); - - final var response = client.chatCompletion(prompt, configWithTemplate); + final var response = client.chatCompletion(configWithTemplate); assertThat(response.getContent()).startsWith("Finance"); assertThat(response.getOriginalResponse().getIntermediateResults().getTemplating()).hasSize(2); @@ -1854,4 +1849,26 @@ void multiTurnReasoningPreservesReasoningContent() { postRequestedFor(urlPathEqualTo("/v2/completion")) .withRequestBody(equalToJson(expectedTurn2Request, true, true))); } + + @Test + void testChatCompletionWithRefConfigOnly() throws IOException { + stubFor( + post(anyUrl()) + .willReturn( + aResponse() + .withBodyFile("templateReferenceResponse.json") + .withHeader("Content-Type", "application/json"))); + + var ref = + TemplateConfig.reference() + .byId("21cb1358-0bf1-4f43-870b-00f14d0f9f16") + .withTemplateParameters(Map.of("language", "Italian", "input", "Cloud ERP systems")); + var configWithRef = config.withTemplateConfig(ref); + + final var response = client.chatCompletion(configWithRef); + assertThat(response.getContent()).startsWith("I sistemi ERP (Enterprise Resource Planning)"); + + final String expectedRequest = fileLoaderStr.apply("templateReferenceByIdRequest.json"); + verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(expectedRequest))); + } } From 488c574eb45e89d1c94e73f3045ab0b1ae8ba2e7 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Wed, 16 Sep 2026 14:10:58 +0200 Subject: [PATCH 5/9] renaming methods --- .../sap/ai/sdk/orchestration/OrchestrationClient.java | 2 +- .../ai/sdk/orchestration/OrchestrationUnitTest.java | 10 +++++----- .../sap/ai/sdk/app/controllers/OpenAiController.java | 4 ++-- .../sap/ai/sdk/app/services/OrchestrationService.java | 8 ++++---- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java index 4a67748ea..f4dd80ee8 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java @@ -203,7 +203,7 @@ public OrchestrationChatResponse chatCompletionUsingReference( * @since 1.26.0 */ @Nonnull - public OrchestrationChatResponse chatCompletion( + public OrchestrationChatResponse chatCompletionUsingTemplateRef( @Nonnull final OrchestrationModuleConfigWithRef config) { val request = ConfigToRequestTransformer.fromTemplateRefToCompletionPostRequest(config); val response = executeRequest(request); 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 ba23f3939..679041912 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 @@ -1463,7 +1463,7 @@ void testTemplateFromPromptRegistryByIdTenant() throws IOException { .withTemplateParameters(inputParams); var configWithTemplate = config.withTemplateConfig(template); - final var response = client.chatCompletion(configWithTemplate); + final var response = client.chatCompletionUsingTemplateRef(configWithTemplate); assertThat(response.getContent()).startsWith("I sistemi ERP (Enterprise Resource Planning)"); assertThat(response.getOriginalResponse().getIntermediateResults().getTemplating()).hasSize(2); @@ -1495,7 +1495,7 @@ void testTemplateFromPromptRegistryByIdResourceGroup() throws IOException { new OrchestrationModuleConfig().withLlmConfig(GEMINI_2_5_FLASH.withParam(TEMPERATURE, 0.0)); var configWithTemplate = config.withTemplateConfig(template); - final var response = client.chatCompletion(configWithTemplate); + final var response = client.chatCompletionUsingTemplateRef(configWithTemplate); assertThat(response.getContent()).startsWith("Finance"); assertThat(response.getOriginalResponse().getIntermediateResults().getTemplating()).hasSize(2); @@ -1521,7 +1521,7 @@ void testTemplateFromPromptRegistryByScenarioTenant() throws IOException { .withTemplateParameters(inputParams); var configWithTemplate = config.withTemplateConfig(template); - final var response = client.chatCompletion(configWithTemplate); + final var response = client.chatCompletionUsingTemplateRef(configWithTemplate); assertThat(response.getContent()).startsWith("I sistemi ERP (Enterprise Resource Planning)"); assertThat(response.getOriginalResponse().getIntermediateResults().getTemplating()).hasSize(2); @@ -1555,7 +1555,7 @@ void testTemplateFromPromptRegistryByScenarioResourceGroup() throws IOException new OrchestrationModuleConfig().withLlmConfig(GEMINI_2_5_FLASH.withParam(TEMPERATURE, 0.0)); var configWithTemplate = config.withTemplateConfig(template); - final var response = client.chatCompletion(configWithTemplate); + final var response = client.chatCompletionUsingTemplateRef(configWithTemplate); assertThat(response.getContent()).startsWith("Finance"); assertThat(response.getOriginalResponse().getIntermediateResults().getTemplating()).hasSize(2); @@ -1865,7 +1865,7 @@ void testChatCompletionWithRefConfigOnly() throws IOException { .withTemplateParameters(Map.of("language", "Italian", "input", "Cloud ERP systems")); var configWithRef = config.withTemplateConfig(ref); - final var response = client.chatCompletion(configWithRef); + final var response = client.chatCompletionUsingTemplateRef(configWithRef); assertThat(response.getContent()).startsWith("I sistemi ERP (Enterprise Resource Planning)"); final String expectedRequest = fileLoaderStr.apply("templateReferenceByIdRequest.json"); diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java index 88e5e247f..5c3f5584a 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java @@ -42,7 +42,7 @@ public class OpenAiController { private final Resource sampleQuestionPcm = new ClassPathResource("static/question.pcm"); - @GetMapping("/chatCompletion") + @GetMapping("/chatCompletionUsingTemplateRef") @Nonnull Object chatCompletion( @Nullable @RequestParam(value = "format", required = false) final String format) { @@ -216,7 +216,7 @@ Object embedding() { return service.embedding("Hello world"); } - @GetMapping("/chatCompletion/{resourceGroup}") + @GetMapping("/chatCompletionUsingTemplateRef/{resourceGroup}") @Nonnull Object chatCompletionWithResource( @Nullable @RequestParam(value = "format", required = false) final String format, 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 eea2aa9c1..81b487bec 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 @@ -702,7 +702,7 @@ public OrchestrationChatResponse templateFromPromptRegistryByIdTenant( .withTemplateParameters(inputParams); val configWithTemplate = llmWithImageSupportConfig.withTemplateConfig(template); - return client.chatCompletion(configWithTemplate); + return client.chatCompletionUsingTemplateRef(configWithTemplate); } /** @@ -729,7 +729,7 @@ public OrchestrationChatResponse templateFromPromptRegistryByIdResourceGroup( .withTemplateParameters(inputParams); val configWithTemplate = config.withTemplateConfig(template); - return clientWithResourceGroup.chatCompletion(configWithTemplate); + return clientWithResourceGroup.chatCompletionUsingTemplateRef(configWithTemplate); } /** @@ -753,7 +753,7 @@ public OrchestrationChatResponse templateFromPromptRegistryByScenarioTenant( .withTemplateParameters(inputParams); val configWithTemplate = config.withTemplateConfig(template); - return client.chatCompletion(configWithTemplate); + return client.chatCompletionUsingTemplateRef(configWithTemplate); } /** @@ -782,7 +782,7 @@ public OrchestrationChatResponse templateFromPromptRegistryByScenarioResourceGro .withTemplateParameters(inputParams); val configWithTemplate = config.withTemplateConfig(template); - return clientWithResourceGroup.chatCompletion(configWithTemplate); + return clientWithResourceGroup.chatCompletionUsingTemplateRef(configWithTemplate); } /** From 6a02f00e3b596a8c7949deb3299fd469b1cd228e Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Wed, 16 Sep 2026 14:26:35 +0200 Subject: [PATCH 6/9] revert --- .../java/com/sap/ai/sdk/app/controllers/OpenAiController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java index 5c3f5584a..ca7250e09 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java @@ -42,7 +42,7 @@ public class OpenAiController { private final Resource sampleQuestionPcm = new ClassPathResource("static/question.pcm"); - @GetMapping("/chatCompletionUsingTemplateRef") + @GetMapping("/chatCompletion") @Nonnull Object chatCompletion( @Nullable @RequestParam(value = "format", required = false) final String format) { From 25427b6b66c3ff511d61faadef265e3dfc805012 Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 16 Sep 2026 14:38:59 +0200 Subject: [PATCH 7/9] Charles review --- .../ConfigToRequestTransformer.java | 6 +++--- .../sdk/orchestration/OrchestrationClient.java | 2 +- .../OrchestrationModuleConfig.java | 18 ++++++++++++++++-- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java index b2c08a137..f15dbacfa 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/ConfigToRequestTransformer.java @@ -248,18 +248,18 @@ static CompletionPostRequest fromReferenceToCompletionPostRequest( @Nonnull static CompletionRequestConfiguration fromTemplateRefToCompletionPostRequest( - @Nonnull final OrchestrationModuleConfigWithRef configWithRef) { + @Nonnull final OrchestrationModuleConfig configWithRef) { final OrchestrationTemplateReference templateRef = configWithRef.getTemplateRef(); final var messageHistory = templateRef.getMessagesHistory().stream().map(Message::createChatMessage).toList(); final var placeholders = templateRef.getTemplateParameters(); final OrchestrationModuleConfig inner = - configWithRef.getInner().withTemplateConfig(templateRef.toLowLevel()); + configWithRef.withTemplateConfig(templateRef.toLowLevel()); val requestConfig = OrchestrationConfig.create().modules(toModuleConfigs(inner)).stream( - configWithRef.getInner().getGlobalStreamOptions()); + configWithRef.getGlobalStreamOptions()); return CompletionRequestConfiguration.create() .config(requestConfig) diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java index f4dd80ee8..69eb20fa4 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java @@ -204,7 +204,7 @@ public OrchestrationChatResponse chatCompletionUsingReference( */ @Nonnull public OrchestrationChatResponse chatCompletionUsingTemplateRef( - @Nonnull final OrchestrationModuleConfigWithRef config) { + @Nonnull final OrchestrationModuleConfig config) { val request = ConfigToRequestTransformer.fromTemplateRefToCompletionPostRequest(config); val response = executeRequest(request); return new OrchestrationChatResponse(response); diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java index 97f5f3a62..4144beb83 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java @@ -136,6 +136,9 @@ public class OrchestrationModuleConfig { @Nullable SAPDocumentTranslationOutput outputTranslationConfig; + @Nullable + OrchestrationTemplateReference templateRef; + /** Configuration of optional streaming options for output filtering. */ @With(AccessLevel.NONE) // may be exposed to public in the future @Getter(AccessLevel.PACKAGE) @@ -304,6 +307,7 @@ OrchestrationModuleConfig withOutputFilteringStreamOptions( this.groundingConfig, this.inputTranslationConfig, this.outputTranslationConfig, + this.templateRef, outputFilteringStreamOptions, this.globalStreamOptions); } @@ -350,9 +354,19 @@ public OrchestrationModuleConfig withTemplateConfig( */ @Tolerate @Nonnull - public OrchestrationModuleConfigWithRef withTemplateConfig( + public OrchestrationModuleConfig withTemplateConfig( @Nonnull final OrchestrationTemplateReference templateRef) { - return new OrchestrationModuleConfigWithRef(this, templateRef); + return new OrchestrationModuleConfig( + this.llmConfig, + this.templateConfig, + this.maskingConfig, + this.filteringConfig, + this.groundingConfig, + this.inputTranslationConfig, + this.outputTranslationConfig, + templateRef, + outputFilteringStreamOptions, + this.globalStreamOptions); } /** From 86146cb1a108ba3a90587a64b7555e8f28354eaa Mon Sep 17 00:00:00 2001 From: SAP Cloud SDK Bot Date: Wed, 16 Sep 2026 12:39:56 +0000 Subject: [PATCH 8/9] Formatting --- .../sap/ai/sdk/orchestration/OrchestrationModuleConfig.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java index 4144beb83..8de0bf366 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java @@ -136,8 +136,7 @@ public class OrchestrationModuleConfig { @Nullable SAPDocumentTranslationOutput outputTranslationConfig; - @Nullable - OrchestrationTemplateReference templateRef; + @Nullable OrchestrationTemplateReference templateRef; /** Configuration of optional streaming options for output filtering. */ @With(AccessLevel.NONE) // may be exposed to public in the future From 450aad046676d28c0bd5b05c4f7edf970e7cb2d3 Mon Sep 17 00:00:00 2001 From: Nourhan Shata Date: Wed, 16 Sep 2026 15:46:13 +0200 Subject: [PATCH 9/9] approach v.2.0 --- orchestration/pom.xml | 4 +- .../OrchestrationModuleConfig.java | 2 +- .../OrchestrationModuleConfigWithRef.java | 153 ------------------ .../OrchestrationModuleConfigTest.java | 23 +++ .../OrchestrationModuleConfigWithRefTest.java | 72 --------- .../sdk/app/controllers/OpenAiController.java | 2 +- 6 files changed, 27 insertions(+), 229 deletions(-) delete mode 100644 orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRef.java delete mode 100644 orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRefTest.java diff --git a/orchestration/pom.xml b/orchestration/pom.xml index abcc51494..fd62fa900 100644 --- a/orchestration/pom.xml +++ b/orchestration/pom.xml @@ -39,8 +39,8 @@ 82% 94% 93% - 75% - 94% + 77% + 95% 100% diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java index 8de0bf366..a29b773fa 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java @@ -348,7 +348,7 @@ public OrchestrationModuleConfig withTemplateConfig( * only source of prompt input * * @param templateRef The template reference to use. - * @return A new {@link OrchestrationModuleConfigWithRef} wrapping this config. + * @return A new {@link OrchestrationModuleConfig} wrapping this config. * @since 1.26.0 */ @Tolerate diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRef.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRef.java deleted file mode 100644 index b7c951781..000000000 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRef.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.sap.ai.sdk.orchestration; - -import com.sap.ai.sdk.orchestration.model.LLMModelDetails; -import javax.annotation.Nonnull; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * An {@link OrchestrationModuleConfig} that carries an {@link OrchestrationTemplateReference}. The - * template reference is the only source of prompt input — no free-form messages are accepted. - * Obtain instances via {@link - * OrchestrationModuleConfig#withTemplateConfig(OrchestrationTemplateReference)}. - * - * @since 1.26.0 - */ -@AllArgsConstructor(access = AccessLevel.PACKAGE) -public class OrchestrationModuleConfigWithRef { - - @Getter(AccessLevel.PACKAGE) - @Nonnull - private final OrchestrationModuleConfig inner; - - @Getter(AccessLevel.PACKAGE) - @Nonnull - private final OrchestrationTemplateReference templateRef; - - /** - * Creates a new configuration with the given LLM configuration. - * - * @param llm The LLM configuration to use. - * @return A new configuration with the given LLM configuration. - * @see OrchestrationModuleConfig#withLlmConfig(LLMModelDetails) - */ - @SuppressWarnings("PMD.PublicApiExposesModelType") - @Nonnull - public OrchestrationModuleConfigWithRef withLlmConfig(@Nonnull final LLMModelDetails llm) { - return new OrchestrationModuleConfigWithRef(inner.withLlmConfig(llm), templateRef); - } - - /** - * Creates a new configuration with the given LLM configuration. - * - * @param model The LLM configuration to use. - * @return A new configuration with the given LLM configuration. - * @see OrchestrationModuleConfig#withLlmConfig(OrchestrationAiModel) - */ - @Nonnull - public OrchestrationModuleConfigWithRef withLlmConfig(@Nonnull final OrchestrationAiModel model) { - return new OrchestrationModuleConfigWithRef(inner.withLlmConfig(model), templateRef); - } - - /** - * Creates a new configuration with the given Data Masking configuration. - * - * @param maskingProvider The Data Masking configuration to use. - * @param maskingProviders Additional Data Masking configurations to use. - * @return A new configuration with the given Data Masking configuration. - * @see OrchestrationModuleConfig#withMaskingConfig(MaskingProvider, MaskingProvider...) - */ - @Nonnull - public OrchestrationModuleConfigWithRef withMaskingConfig( - @Nonnull final MaskingProvider maskingProvider, - @Nonnull final MaskingProvider... maskingProviders) { - return new OrchestrationModuleConfigWithRef( - inner.withMaskingConfig(maskingProvider, maskingProviders), templateRef); - } - - /** - * Adds input content filters to the configuration. - * - * @param contentFilter A filter to apply to the input. - * @param contentFilters Zero or more additional content filters to apply to the input. - * @return A new configuration with the specified input filters added. - * @see OrchestrationModuleConfig#withInputFiltering(ContentFilter, ContentFilter...) - */ - @Nonnull - public OrchestrationModuleConfigWithRef withInputFiltering( - @Nonnull final ContentFilter contentFilter, @Nonnull final ContentFilter... contentFilters) { - return new OrchestrationModuleConfigWithRef( - inner.withInputFiltering(contentFilter, contentFilters), templateRef); - } - - /** - * Adds output content filters to the configuration. - * - * @param contentFilter A filter to apply to the output. - * @param contentFilters Zero or more additional content filters to apply to the output. - * @return A new configuration with the specified output filters added. - * @see OrchestrationModuleConfig#withOutputFiltering(ContentFilter, ContentFilter...) - */ - @Nonnull - public OrchestrationModuleConfigWithRef withOutputFiltering( - @Nonnull final ContentFilter contentFilter, @Nonnull final ContentFilter... contentFilters) { - return new OrchestrationModuleConfigWithRef( - inner.withOutputFiltering(contentFilter, contentFilters), templateRef); - } - - /** - * Creates a new configuration with the given grounding configuration. - * - * @param groundingProvider The grounding configuration to use. - * @return A new configuration with the given grounding configuration. - * @see OrchestrationModuleConfig#withGrounding(GroundingProvider) - */ - @Nonnull - public OrchestrationModuleConfigWithRef withGrounding( - @Nonnull final GroundingProvider groundingProvider) { - return new OrchestrationModuleConfigWithRef( - inner.withGrounding(groundingProvider), templateRef); - } - - /** - * Configure input translation using a high-level TranslationConfig. - * - * @param translationConfig The translation configuration. - * @return A new configuration with input translation configured. - * @see OrchestrationModuleConfig#withInputTranslationConfig(TranslationConfig.Input) - */ - @Nonnull - public OrchestrationModuleConfigWithRef withInputTranslationConfig( - @Nonnull final TranslationConfig.Input translationConfig) { - return new OrchestrationModuleConfigWithRef( - inner.withInputTranslationConfig(translationConfig), templateRef); - } - - /** - * Configure output translation using a high-level TranslationConfig. - * - * @param translationConfig The translation configuration. - * @return A new configuration with output translation configured. - * @see OrchestrationModuleConfig#withOutputTranslationConfig(TranslationConfig.Output) - */ - @Nonnull - public OrchestrationModuleConfigWithRef withOutputTranslationConfig( - @Nonnull final TranslationConfig.Output translationConfig) { - return new OrchestrationModuleConfigWithRef( - inner.withOutputTranslationConfig(translationConfig), templateRef); - } - - /** - * Creates a new configuration with the given stream configuration. - * - * @param config The stream configuration to use. - * @return A new configuration with the given stream configuration. - * @see OrchestrationModuleConfig#withStreamConfig(OrchestrationStreamConfig) - */ - @Nonnull - public OrchestrationModuleConfigWithRef withStreamConfig( - @Nonnull final OrchestrationStreamConfig config) { - return new OrchestrationModuleConfigWithRef(inner.withStreamConfig(config), templateRef); - } -} 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 9cde42daa..800baa71e 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 @@ -375,4 +375,27 @@ void testResponseFormatOverwrittenByNewTemplateRef() { TemplateRef.create().templateRef(TemplateRefByID.create().id("123"))); assertThat(config.getTemplateConfig()).isInstanceOf(TemplateRef.class); } + + @Test + void withTemplateConfigReturnsWrapperWithRef() { + var ref = TemplateConfig.reference().byId("abc"); + OrchestrationModuleConfig withRef = + new OrchestrationModuleConfig() + .withLlmConfig(OrchestrationAiModel.GPT_4O) + .withTemplateConfig(ref); + + assertThat(withRef.getTemplateRef()).isSameAs(ref); + } + + @Test + void templateRefCarriesHistoryAndParams() { + var ref = + TemplateConfig.reference() + .byId("abc") + .withMessageHistory(List.of(new UserMessage("hi"))) + .withTemplateParameters(Map.of("k", "v")); + + assertThat(ref.getMessagesHistory()).hasSize(1); + assertThat(ref.getTemplateParameters()).containsEntry("k", "v"); + } } diff --git a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRefTest.java b/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRefTest.java deleted file mode 100644 index 859644f11..000000000 --- a/orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfigWithRefTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.sap.ai.sdk.orchestration; - -import static com.sap.ai.sdk.orchestration.AzureFilterThreshold.ALLOW_SAFE; -import static org.assertj.core.api.Assertions.assertThat; - -import com.sap.ai.sdk.orchestration.model.DPIEntities; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.Test; - -class OrchestrationModuleConfigWithRefTest { - - @Test - void withTemplateConfigReturnsWrapperWithRef() { - var ref = TemplateConfig.reference().byId("abc"); - OrchestrationModuleConfigWithRef withRef = - new OrchestrationModuleConfig() - .withLlmConfig(OrchestrationAiModel.GPT_4O) - .withTemplateConfig(ref); - - assertThat(withRef.getTemplateRef()).isSameAs(ref); - assertThat(withRef.getInner()).isNotNull(); - } - - @Test - void templateRefCarriesHistoryAndParams() { - var ref = - TemplateConfig.reference() - .byId("abc") - .withMessageHistory(List.of(new UserMessage("hi"))) - .withTemplateParameters(Map.of("k", "v")); - - assertThat(ref.getMessagesHistory()).hasSize(1); - assertThat(ref.getTemplateParameters()).containsEntry("k", "v"); - } - - @Test - void delegateMethodsPreserveTemplateRef() { - var ref = TemplateConfig.reference().byId("abc"); - var withRef = - new OrchestrationModuleConfig() - .withLlmConfig(OrchestrationAiModel.GPT_4O) - .withTemplateConfig(ref); - - assertThat(withRef.withLlmConfig(OrchestrationAiModel.GPT_4O).getTemplateRef()).isSameAs(ref); - assertThat(withRef.withLlmConfig(OrchestrationAiModel.GPT_4O.createConfig()).getTemplateRef()) - .isSameAs(ref); - - var filter = new AzureContentFilter().hate(ALLOW_SAFE); - assertThat(withRef.withInputFiltering(filter).getTemplateRef()).isSameAs(ref); - assertThat(withRef.withOutputFiltering(filter).getTemplateRef()).isSameAs(ref); - - var masking = DpiMasking.anonymization().withEntities(DPIEntities.PERSON); - assertThat(withRef.withMaskingConfig(masking).getTemplateRef()).isSameAs(ref); - - assertThat(withRef.withGrounding(Grounding.create()).getTemplateRef()).isSameAs(ref); - - assertThat( - withRef - .withInputTranslationConfig(TranslationConfig.translateInputTo("en-US")) - .getTemplateRef()) - .isSameAs(ref); - assertThat( - withRef - .withOutputTranslationConfig(TranslationConfig.translateOutputTo("de-DE")) - .getTemplateRef()) - .isSameAs(ref); - - assertThat(withRef.withStreamConfig(new OrchestrationStreamConfig()).getTemplateRef()) - .isSameAs(ref); - } -} diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java index ca7250e09..88e5e247f 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java @@ -216,7 +216,7 @@ Object embedding() { return service.embedding("Hello world"); } - @GetMapping("/chatCompletionUsingTemplateRef/{resourceGroup}") + @GetMapping("/chatCompletion/{resourceGroup}") @Nonnull Object chatCompletionWithResource( @Nullable @RequestParam(value = "format", required = false) final String format,