From 3e3445340c5769ef6dffed67ad1b09e8203cdea9 Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Thu, 4 Dec 2025 16:28:21 +0100 Subject: [PATCH 1/7] add OrchestrationConfigClient, test list, add, delete --- core-services/prompt-registry/pom.xml | 4 + .../registry/OrchestrationConfigClient.java | 109 ++++++++++++++++++ .../controllers/PromptRegistryController.java | 75 ++++++++++-- .../app/controllers/PromptRegistryTest.java | 26 +++++ 4 files changed, 203 insertions(+), 11 deletions(-) create mode 100644 core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java diff --git a/core-services/prompt-registry/pom.xml b/core-services/prompt-registry/pom.xml index c6ebefe49..b39461112 100644 --- a/core-services/prompt-registry/pom.xml +++ b/core-services/prompt-registry/pom.xml @@ -97,6 +97,10 @@ com.google.code.findbugs jsr305 + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + org.projectlombok diff --git a/core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java b/core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java new file mode 100644 index 000000000..e819b6a8e --- /dev/null +++ b/core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java @@ -0,0 +1,109 @@ +package com.sap.ai.sdk.prompt.registry; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.google.common.collect.Iterables; +import com.sap.ai.sdk.core.AiCoreService; +import com.sap.ai.sdk.prompt.registry.client.OrchestrationConfigsApi; +import com.sap.ai.sdk.prompt.registry.model.AzureContentSafetyInputFilterConfig; +import com.sap.ai.sdk.prompt.registry.model.AzureContentSafetyOutputFilterConfig; +import com.sap.ai.sdk.prompt.registry.model.InputFilterConfig; +import com.sap.ai.sdk.prompt.registry.model.LlamaGuard38bFilterConfig; +import com.sap.ai.sdk.prompt.registry.model.OutputFilterConfig; +import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; +import com.sap.cloud.sdk.services.openapi.apiclient.ApiClient; +import javax.annotation.Nonnull; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.springframework.http.client.BufferingClientHttpRequestFactory; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.converter.yaml.MappingJackson2YamlHttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import static com.sap.ai.sdk.core.JacksonConfiguration.getDefaultObjectMapper; + +/** + * Client for the Prompt Registry service. + * + * @since 1.6.0 + */ +public class OrchestrationConfigClient extends OrchestrationConfigsApi { + + /** + * Instantiates this a client to invoke operations on the Prompt Registry service. + * + * @since 1.14.0 + */ + public OrchestrationConfigClient() { + this(new AiCoreService()); + } + + /** + * Instantiates this a client to invoke operations on the Prompt Registry service. + * + * @param aiCoreService The configured connectivity instance to AI Core + * @since 1.14.0 + */ + public OrchestrationConfigClient(@Nonnull final AiCoreService aiCoreService) { + super(addMixin(aiCoreService)); + } + + @Nonnull + private static ApiClient addMixin(@Nonnull final AiCoreService service) { + final var destination = service.getBaseDestination(); + final var httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); + httpRequestFactory.setHttpClient(ApacheHttpClient5Accessor.getHttpClient(destination)); + + final var rt = new RestTemplate(); + Iterables.filter(rt.getMessageConverters(), MappingJackson2HttpMessageConverter.class) + .forEach( + converter -> + converter.setObjectMapper( + getDefaultObjectMapper() + .addMixIn(OutputFilterConfig.class, JacksonMixin.OutputFilter.class) + .addMixIn(InputFilterConfig.class, JacksonMixin.InputFilter.class))); + var yamlMapper = new ObjectMapper(new YAMLFactory()); + yamlMapper + .addMixIn(OutputFilterConfig.class, JacksonMixin.OutputFilter.class) + .addMixIn(InputFilterConfig.class, JacksonMixin.InputFilter.class); + Iterables.filter(rt.getMessageConverters(), MappingJackson2YamlHttpMessageConverter.class) + .forEach(converter -> converter.setObjectMapper(yamlMapper)); + + rt.setRequestFactory(new BufferingClientHttpRequestFactory(httpRequestFactory)); + + return new ApiClient(rt).setBasePath(destination.asHttp().getUri().toString()); + } + + @NoArgsConstructor(access = AccessLevel.PRIVATE) + private static class JacksonMixin { + + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type", + visible = true) + @JsonSubTypes({ + @JsonSubTypes.Type(value = LlamaGuard38bFilterConfig.class, name = "llama_guard_3_8b"), + @JsonSubTypes.Type( + value = AzureContentSafetyOutputFilterConfig.class, + name = "azure_content_safety") + }) + interface OutputFilter {} + + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXISTING_PROPERTY, + property = "type", + visible = true) + @JsonSubTypes({ + @JsonSubTypes.Type(value = LlamaGuard38bFilterConfig.class, name = "llama_guard_3_8b"), + @JsonSubTypes.Type( + value = AzureContentSafetyInputFilterConfig.class, + name = "azure_content_safety") + }) + interface InputFilter {} + } +} diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java index 4018b94bd..957854e26 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java @@ -3,7 +3,17 @@ import com.sap.ai.sdk.foundationmodels.openai.OpenAiClient; import com.sap.ai.sdk.foundationmodels.openai.OpenAiModel; import com.sap.ai.sdk.foundationmodels.openai.spring.OpenAiChatModel; +import com.sap.ai.sdk.prompt.registry.OrchestrationConfigClient; import com.sap.ai.sdk.prompt.registry.PromptClient; +import com.sap.ai.sdk.prompt.registry.model.ChatMessage; +import com.sap.ai.sdk.prompt.registry.model.ChatMessageContent; +import com.sap.ai.sdk.prompt.registry.model.LLMModelDetails; +import com.sap.ai.sdk.prompt.registry.model.ModuleConfigs; +import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfig; +import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigDeleteResponse; +import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigListResponse; +import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigPostRequest; +import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigPostResponse; import com.sap.ai.sdk.prompt.registry.model.PromptTemplateDeleteResponse; import com.sap.ai.sdk.prompt.registry.model.PromptTemplateListResponse; import com.sap.ai.sdk.prompt.registry.model.PromptTemplatePostRequest; @@ -11,7 +21,11 @@ import com.sap.ai.sdk.prompt.registry.model.PromptTemplateSpec; import com.sap.ai.sdk.prompt.registry.model.PromptTemplateSubstitutionRequest; import com.sap.ai.sdk.prompt.registry.model.PromptTemplateSubstitutionResponse; +import com.sap.ai.sdk.prompt.registry.model.PromptTemplatingModuleConfig; +import com.sap.ai.sdk.prompt.registry.model.PromptTemplatingModuleConfigPrompt; import com.sap.ai.sdk.prompt.registry.model.SingleChatTemplate; +import com.sap.ai.sdk.prompt.registry.model.SystemChatMessage; +import com.sap.ai.sdk.prompt.registry.model.Template; import com.sap.ai.sdk.prompt.registry.spring.SpringAiConverter; import java.io.File; import java.io.IOException; @@ -37,23 +51,24 @@ @RequestMapping("/prompt-registry") class PromptRegistryController { static final String NAME = "java-e2e-test"; - private static final PromptClient client = new PromptClient(); + private static final PromptClient promptClient = new PromptClient(); + private static final OrchestrationConfigClient orchConfigClient = new OrchestrationConfigClient(); @GetMapping("/listTemplates") PromptTemplateListResponse listTemplates() { - return client.listPromptTemplates(); + return promptClient.listPromptTemplates(); } @GetMapping("/createTemplate") PromptTemplatePostResponse createTemplate() { - return client.createUpdatePromptTemplate(getTemplate("Finance, Tech, Sports")); + return promptClient.createUpdatePromptTemplate(getTemplate("Finance, Tech, Sports")); } @GetMapping("/updateTemplate") PromptTemplatePostResponse updateTemplate() { // create template then update - client.createUpdatePromptTemplate(getTemplate("Finance, Tech, Sports")); - return client.createUpdatePromptTemplate(getTemplate("Finance, Tech, Sports, Politics")); + promptClient.createUpdatePromptTemplate(getTemplate("Finance, Tech, Sports")); + return promptClient.createUpdatePromptTemplate(getTemplate("Finance, Tech, Sports, Politics")); } private PromptTemplatePostRequest getTemplate(final String categories) { @@ -76,25 +91,25 @@ private PromptTemplatePostRequest getTemplate(final String categories) { @GetMapping("/history") PromptTemplateListResponse history() { - return client.listPromptTemplateHistory("categorization", "0.0.1", NAME); + return promptClient.listPromptTemplateHistory("categorization", "0.0.1", NAME); } @GetMapping("/importTemplate") PromptTemplatePostResponse importTemplate() throws IOException { final Resource template = new ClassPathResource("prompt-template.yaml"); - return client.importPromptTemplate("default", null, template.getFile()); + return promptClient.importPromptTemplate("default", null, template.getFile()); } @GetMapping("/exportTemplate") File exportTemplate() throws IOException { final var template = importTemplate(); - return client.exportPromptTemplate(template.getId()); + return promptClient.exportPromptTemplate(template.getId()); } @GetMapping("/useTemplate") PromptTemplateSubstitutionResponse useTemplate() { final var template = createTemplate(); - return client.parsePromptTemplateById( + return promptClient.parsePromptTemplateById( template.getId(), "default", null, @@ -105,11 +120,11 @@ PromptTemplateSubstitutionResponse useTemplate() { @GetMapping("/deleteTemplate") List deleteTemplate() { - final PromptTemplateListResponse templates = client.listPromptTemplates(); + final PromptTemplateListResponse templates = promptClient.listPromptTemplates(); return templates.getResources().stream() .filter(template -> NAME.equals(template.getName())) - .map(template -> client.deletePromptTemplate(template.getId())) + .map(template -> promptClient.deletePromptTemplate(template.getId())) .toList(); } @@ -138,4 +153,42 @@ Generation promptRegistryToSpringAi() { val response = cl.prompt(prompt).call().chatResponse(); return response != null ? response.getResult() : null; } + + OrchestrationConfigListResponse listOrchConfigs() { + return orchConfigClient.listOrchestrationConfigs(); + } + + OrchestrationConfigPostResponse createOrchConfig() { + OrchestrationConfigPostRequest postRequest = + OrchestrationConfigPostRequest.create() + .name(NAME) + .version("0.0.1") + .scenario("sdk-test-scenario") + .spec(buildOrchestrationConfig()); + return orchConfigClient.createUpdateOrchestrationConfig(postRequest); + } + + List deleteOrchConfig() { + final OrchestrationConfigListResponse configs = orchConfigClient.listOrchestrationConfigs(); + + return configs.getResources().stream() + .filter(config -> NAME.equals(config.getName())) + .map(config -> orchConfigClient.deleteOrchestrationConfig(config.getId())) + .toList(); + } + + private OrchestrationConfig buildOrchestrationConfig() { + return OrchestrationConfig.create() + .modules( + ModuleConfigs.create() + .promptTemplating( + PromptTemplatingModuleConfig.create() + .prompt( + Template.create() + .template( + SystemChatMessage.create() + .role(SystemChatMessage.RoleEnum.SYSTEM) + .content(new ChatMessageContent.InnerString("text")))) + .model(LLMModelDetails.create().name("model-name")))); + } } diff --git a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/PromptRegistryTest.java b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/PromptRegistryTest.java index e52422d4b..f331435d3 100644 --- a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/PromptRegistryTest.java +++ b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/PromptRegistryTest.java @@ -2,6 +2,8 @@ import static org.assertj.core.api.Assertions.assertThat; +import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigDeleteResponse; +import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigPostResponse; import com.sap.ai.sdk.prompt.registry.model.PromptTemplate; import com.sap.ai.sdk.prompt.registry.model.PromptTemplateDeleteResponse; import com.sap.ai.sdk.prompt.registry.model.PromptTemplateListResponse; @@ -93,4 +95,28 @@ void promptRegistryToSpringAi() { assertThat(ChatResponse).isNotNull(); assertThat(ChatResponse.getOutput().getText()).contains("Sports"); } + + @Test + void listOrchestrationConfigs() { + var controller = new PromptRegistryController(); + var result = controller.listOrchConfigs(); + assertThat(result.getCount()).isGreaterThan(0); + } + + @Test + void createDeleteOrchestrationConfig() { + var controller = new PromptRegistryController(); + // cleanup + controller.deleteOrchConfig(); + + // create + OrchestrationConfigPostResponse createdConfig = controller.createOrchConfig(); + assertThat(createdConfig.getMessage()).contains("successful"); + assertThat(createdConfig.getName()).contains(PromptRegistryController.NAME); + + // cleanup + List deletedConfig = controller.deleteOrchConfig(); + assertThat(deletedConfig).hasSize(1); + assertThat(deletedConfig.get(0).getMessage()).contains("successful"); + } } From 8abe277d458b9b866028d6494368ed6d63fb035d Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Tue, 16 Dec 2025 09:21:19 +0100 Subject: [PATCH 2/7] intermediate --- .../registry/OrchestrationConfigClient.java | 8 ++-- .../ConfigToRequestTransformer.java | 41 +++++++++++++++++ .../orchestration/OrchestrationClient.java | 14 ++++++ .../OrchestrationConfigReference.java | 46 +++++++++++++++++++ .../controllers/PromptRegistryController.java | 13 ++++-- .../app/services/OrchestrationService.java | 4 ++ .../app/controllers/OrchestrationTest.java | 6 +++ 7 files changed, 123 insertions(+), 9 deletions(-) create mode 100644 orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java diff --git a/core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java b/core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java index e819b6a8e..454cffdd7 100644 --- a/core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java +++ b/core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java @@ -26,14 +26,14 @@ import static com.sap.ai.sdk.core.JacksonConfiguration.getDefaultObjectMapper; /** - * Client for the Prompt Registry service. + * Client for managing Orchestration Configurations in the Prompt Registry service. * - * @since 1.6.0 + * @since 1.14.0 */ public class OrchestrationConfigClient extends OrchestrationConfigsApi { /** - * Instantiates this a client to invoke operations on the Prompt Registry service. + * Instantiates a client to manage Orchestration Configurations on the Prompt Registry service. * * @since 1.14.0 */ @@ -42,7 +42,7 @@ public OrchestrationConfigClient() { } /** - * Instantiates this a client to invoke operations on the Prompt Registry service. + * Instantiates a client to manage Orchestration Configurations on the Prompt Registry service. * * @param aiCoreService The configured connectivity instance to AI Core * @since 1.14.0 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 cbaeac33e..81ba01d02 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 @@ -1,6 +1,12 @@ package com.sap.ai.sdk.orchestration; +import com.sap.ai.sdk.orchestration.model.ChatMessage; +import com.sap.ai.sdk.orchestration.model.CompletionPostRequest; import com.sap.ai.sdk.orchestration.model.CompletionRequestConfiguration; +import com.sap.ai.sdk.orchestration.model.CompletionRequestConfigurationReferenceById; +import com.sap.ai.sdk.orchestration.model.CompletionRequestConfigurationReferenceByIdConfigRef; +import com.sap.ai.sdk.orchestration.model.CompletionRequestConfigurationReferenceByNameScenarioVersion; +import com.sap.ai.sdk.orchestration.model.CompletionRequestConfigurationReferenceByNameScenarioVersionConfigRef; import com.sap.ai.sdk.orchestration.model.ModuleConfigs; import com.sap.ai.sdk.orchestration.model.OrchestrationConfig; import com.sap.ai.sdk.orchestration.model.OrchestrationConfigModules; @@ -12,6 +18,8 @@ import com.sap.ai.sdk.orchestration.model.TranslationModuleConfig; import io.vavr.control.Option; import java.util.ArrayList; +import java.util.List; +import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.AccessLevel; @@ -113,4 +121,37 @@ static InnerModuleConfigs toModuleConfigs(@Nonnull final OrchestrationModuleConf return OrchestrationConfigModules.createInnerModuleConfigs(moduleConfig); } + + @Nonnull + static CompletionPostRequest fromReferenceToCompletionPostRequest( + @Nullable final OrchestrationPrompt prompt, + @Nonnull final OrchestrationConfigReference reference) { + List messageHistory = List.of(); + Map placeholders = Map.of(); + if (prompt != null) { + messageHistory = + prompt.getMessagesHistory().stream().map(Message::createChatMessage).toList(); + placeholders = prompt.getTemplateParameters(); + } + + CompletionPostRequest request; + if (reference.getId() != null) { + request = + CompletionRequestConfigurationReferenceById.create() + .configRef( + CompletionRequestConfigurationReferenceByIdConfigRef.create() + .id(reference.getId())); + ((CompletionRequestConfigurationReferenceById) request).setMessagesHistory(messageHistory); + ((CompletionRequestConfigurationReferenceById) request).setPlaceholderValues(placeholders); + } else { + request = + CompletionRequestConfigurationReferenceByNameScenarioVersion.create() + .configRef( + CompletionRequestConfigurationReferenceByNameScenarioVersionConfigRef.create() + .scenario(reference.getScenario()) + .name(reference.getName()) + .version(reference.getVersion())); + } + return request; + } } 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 6ec9de4cf..35bd36572 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 @@ -11,6 +11,8 @@ import com.sap.ai.sdk.orchestration.model.CompletionPostRequest; import com.sap.ai.sdk.orchestration.model.CompletionPostResponse; import com.sap.ai.sdk.orchestration.model.CompletionRequestConfiguration; +import com.sap.ai.sdk.orchestration.model.CompletionRequestConfigurationReferenceById; +import com.sap.ai.sdk.orchestration.model.CompletionRequestConfigurationReferenceByIdConfigRef; import com.sap.ai.sdk.orchestration.model.EmbeddingsPostRequest; import com.sap.ai.sdk.orchestration.model.EmbeddingsPostResponse; import com.sap.ai.sdk.orchestration.model.GlobalStreamOptions; @@ -166,6 +168,18 @@ public CompletionPostResponse executeRequest(@Nonnull final CompletionPostReques COMPLETION_ENDPOINT, request, CompletionPostResponse.class, customHeaders); } + @Beta + public OrchestrationChatResponse executeRequestFromReference( + final OrchestrationPrompt prompt, final OrchestrationConfigReference reference) { + String testID = "41e09fb5-6d2a-48b5-a9f6-637974732a19"; + var testReference = OrchestrationConfigReference.fromId(testID); + OrchestrationPrompt testPrompt = new OrchestrationPrompt(Map.of("phrase", "Hello World")); + var request = + ConfigToRequestTransformer.fromReferenceToCompletionPostRequest(testPrompt, testReference); + var 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/OrchestrationConfigReference.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java new file mode 100644 index 000000000..b23de3ad4 --- /dev/null +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java @@ -0,0 +1,46 @@ +package com.sap.ai.sdk.orchestration; + +import com.google.common.annotations.Beta; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Value; + +import javax.annotation.Nonnull; + +// JONAS: Making this a sealed interface permitting 2 classes for ID and SNV seemed like an overkill +// JONAS: Can I make this a record class? (Probably not because I do not want an all-args constructor) +// JONAS: Use @VALUE? + +//@Value +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Beta +public class OrchestrationConfigReference { + private String id; + private String name; + private String scenario; + private String version; + + public static OrchestrationConfigReference fromId(@Nonnull final String id) { + return new OrchestrationConfigReference(id, null, null, null); + } + + public static OrchestrationConfigReference fromScenarioNameVersion(@Nonnull final String scenario, @Nonnull final String name, @Nonnull final String version) { + return new OrchestrationConfigReference(null, scenario, name, version); + } + + String getId() { + return id; + } + + String getName() { + return name; + } + + String getScenario() { + return scenario; + } + + String getVersion() { + return version; + } +} diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java index 957854e26..7dfe85e92 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java @@ -26,6 +26,8 @@ import com.sap.ai.sdk.prompt.registry.model.SingleChatTemplate; import com.sap.ai.sdk.prompt.registry.model.SystemChatMessage; import com.sap.ai.sdk.prompt.registry.model.Template; +import com.sap.ai.sdk.prompt.registry.model.UserChatMessage; +import com.sap.ai.sdk.prompt.registry.model.UserChatMessageContent; import com.sap.ai.sdk.prompt.registry.spring.SpringAiConverter; import java.io.File; import java.io.IOException; @@ -161,7 +163,7 @@ OrchestrationConfigListResponse listOrchConfigs() { OrchestrationConfigPostResponse createOrchConfig() { OrchestrationConfigPostRequest postRequest = OrchestrationConfigPostRequest.create() - .name(NAME) + .name("test-config") .version("0.0.1") .scenario("sdk-test-scenario") .spec(buildOrchestrationConfig()); @@ -186,9 +188,10 @@ private OrchestrationConfig buildOrchestrationConfig() { .prompt( Template.create() .template( - SystemChatMessage.create() - .role(SystemChatMessage.RoleEnum.SYSTEM) - .content(new ChatMessageContent.InnerString("text")))) - .model(LLMModelDetails.create().name("model-name")))); + UserChatMessage.create() + .content(new UserChatMessageContent.InnerString("Create {{?number}} paraphrases of {{?phrase}}")) + .role(UserChatMessage.RoleEnum.USER)) + .defaults(Map.of("number", "3"))) + .model(LLMModelDetails.create().name("gpt-4.1-nano")))); } } 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 161a75255..745d46d29 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 @@ -660,4 +660,8 @@ public OrchestrationEmbeddingResponse embed(@Nonnull final List texts) { .withMasking(masking); return client.embed(request); } + + public OrchestrationChatResponse executeConfigFromReference() { + return client.executeRequestFromReference(null, null); + } } 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 e3d60efa3..7a1300314 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 @@ -521,4 +521,10 @@ void wrongModelVersion() { .hasMessageContaining("400") .hasMessageContaining("Model gpt-5 in version wrong-version not found."); } + + @Test + void testExecuteRequestFromReference() { + val result = service.executeConfigFromReference(); + assertThat(result).isNotNull(); + } } From 7039c855e8e2626ca8d1e0d4308f1b175cd73b65 Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Fri, 19 Dec 2025 14:01:51 +0100 Subject: [PATCH 3/7] clean up code --- .../orchestration/OrchestrationClient.java | 5 +--- .../OrchestrationConfigReference.java | 29 ++++--------------- .../controllers/PromptRegistryController.java | 1 + .../app/services/OrchestrationService.java | 6 +++- .../app/controllers/OrchestrationTest.java | 4 ++- 5 files changed, 16 insertions(+), 29 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 35bd36572..086c1546a 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 @@ -171,11 +171,8 @@ public CompletionPostResponse executeRequest(@Nonnull final CompletionPostReques @Beta public OrchestrationChatResponse executeRequestFromReference( final OrchestrationPrompt prompt, final OrchestrationConfigReference reference) { - String testID = "41e09fb5-6d2a-48b5-a9f6-637974732a19"; - var testReference = OrchestrationConfigReference.fromId(testID); - OrchestrationPrompt testPrompt = new OrchestrationPrompt(Map.of("phrase", "Hello World")); var request = - ConfigToRequestTransformer.fromReferenceToCompletionPostRequest(testPrompt, testReference); + ConfigToRequestTransformer.fromReferenceToCompletionPostRequest(prompt, reference); var response = executeRequest(request); return new OrchestrationChatResponse(response); } diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java index b23de3ad4..406a9e10e 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java @@ -8,17 +8,16 @@ import javax.annotation.Nonnull; // JONAS: Making this a sealed interface permitting 2 classes for ID and SNV seemed like an overkill -// JONAS: Can I make this a record class? (Probably not because I do not want an all-args constructor) -// JONAS: Use @VALUE? +// JONAS: Not a record because I do not want a public all-args constructor -//@Value +@Value @AllArgsConstructor(access = AccessLevel.PRIVATE) @Beta public class OrchestrationConfigReference { - private String id; - private String name; - private String scenario; - private String version; + String id; + String name; + String scenario; + String version; public static OrchestrationConfigReference fromId(@Nonnull final String id) { return new OrchestrationConfigReference(id, null, null, null); @@ -27,20 +26,4 @@ public static OrchestrationConfigReference fromId(@Nonnull final String id) { public static OrchestrationConfigReference fromScenarioNameVersion(@Nonnull final String scenario, @Nonnull final String name, @Nonnull final String version) { return new OrchestrationConfigReference(null, scenario, name, version); } - - String getId() { - return id; - } - - String getName() { - return name; - } - - String getScenario() { - return scenario; - } - - String getVersion() { - return version; - } } diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java index 7dfe85e92..0c825f02b 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java @@ -161,6 +161,7 @@ OrchestrationConfigListResponse listOrchConfigs() { } OrchestrationConfigPostResponse createOrchConfig() { + // JONAS: this is still the version used to build the config for the Orchestration test, change back to the version of the first commit to have correct test OrchestrationConfigPostRequest postRequest = OrchestrationConfigPostRequest.create() .name("test-config") 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 745d46d29..197ce4dbf 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 @@ -17,6 +17,7 @@ import com.sap.ai.sdk.orchestration.OrchestrationChatResponse; import com.sap.ai.sdk.orchestration.OrchestrationClient; import com.sap.ai.sdk.orchestration.OrchestrationClientException; +import com.sap.ai.sdk.orchestration.OrchestrationConfigReference; import com.sap.ai.sdk.orchestration.OrchestrationEmbeddingRequest; import com.sap.ai.sdk.orchestration.OrchestrationEmbeddingResponse; import com.sap.ai.sdk.orchestration.OrchestrationModuleConfig; @@ -662,6 +663,9 @@ public OrchestrationEmbeddingResponse embed(@Nonnull final List texts) { } public OrchestrationChatResponse executeConfigFromReference() { - return client.executeRequestFromReference(null, null); + String testID = "80299222-f576-4682-b3d9-0a5aaf9be92d"; + var testReference = OrchestrationConfigReference.fromId(testID); + OrchestrationPrompt testPrompt = new OrchestrationPrompt(Map.of("phrase", "Hello World")); + return client.executeRequestFromReference(testPrompt, testReference); } } 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 7a1300314..80da35524 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 @@ -524,7 +524,9 @@ void wrongModelVersion() { @Test void testExecuteRequestFromReference() { + // JONAS: make sure somehow that a correct config exists val result = service.executeConfigFromReference(); - assertThat(result).isNotNull(); + val choices = (result.getOriginalResponse().getFinalResult()).getChoices(); + assertThat(choices.get(0).getMessage().getContent()).isNotEmpty(); } } From 435aac55cbcbc11ac61942e24677150f9415a3fc Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Fri, 19 Dec 2025 17:47:50 +0100 Subject: [PATCH 4/7] add e2e tests --- core-services/prompt-registry/pom.xml | 8 +-- .../ConfigToRequestTransformer.java | 4 ++ .../orchestration/OrchestrationClient.java | 2 - .../OrchestrationConfigReference.java | 10 ++-- .../controllers/OrchestrationController.java | 10 ++++ .../controllers/PromptRegistryController.java | 13 ++--- .../app/services/OrchestrationService.java | 51 ++++++++++++++++++- .../src/main/resources/static/index.html | 26 +++++++++- 8 files changed, 105 insertions(+), 19 deletions(-) diff --git a/core-services/prompt-registry/pom.xml b/core-services/prompt-registry/pom.xml index b39461112..6b2569f03 100644 --- a/core-services/prompt-registry/pom.xml +++ b/core-services/prompt-registry/pom.xml @@ -97,10 +97,10 @@ com.google.code.findbugs jsr305 - - com.fasterxml.jackson.dataformat - jackson-dataformat-yaml - + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + org.projectlombok 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 81ba01d02..b7d68c90d 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 @@ -151,6 +151,10 @@ static CompletionPostRequest fromReferenceToCompletionPostRequest( .scenario(reference.getScenario()) .name(reference.getName()) .version(reference.getVersion())); + ((CompletionRequestConfigurationReferenceByNameScenarioVersion) request) + .setMessagesHistory(messageHistory); + ((CompletionRequestConfigurationReferenceByNameScenarioVersion) request) + .setPlaceholderValues(placeholders); } return request; } 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 086c1546a..a779d4970 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 @@ -11,8 +11,6 @@ import com.sap.ai.sdk.orchestration.model.CompletionPostRequest; import com.sap.ai.sdk.orchestration.model.CompletionPostResponse; import com.sap.ai.sdk.orchestration.model.CompletionRequestConfiguration; -import com.sap.ai.sdk.orchestration.model.CompletionRequestConfigurationReferenceById; -import com.sap.ai.sdk.orchestration.model.CompletionRequestConfigurationReferenceByIdConfigRef; import com.sap.ai.sdk.orchestration.model.EmbeddingsPostRequest; import com.sap.ai.sdk.orchestration.model.EmbeddingsPostResponse; import com.sap.ai.sdk.orchestration.model.GlobalStreamOptions; diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java index 406a9e10e..41851cfc5 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java @@ -1,29 +1,31 @@ package com.sap.ai.sdk.orchestration; import com.google.common.annotations.Beta; +import javax.annotation.Nonnull; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Value; -import javax.annotation.Nonnull; - // JONAS: Making this a sealed interface permitting 2 classes for ID and SNV seemed like an overkill // JONAS: Not a record because I do not want a public all-args constructor +// JONAS: Is string correct for ID? (Use UUID?) +// JONAS: Split SNV into 3 methods? @Value @AllArgsConstructor(access = AccessLevel.PRIVATE) @Beta public class OrchestrationConfigReference { String id; - String name; String scenario; + String name; String version; public static OrchestrationConfigReference fromId(@Nonnull final String id) { return new OrchestrationConfigReference(id, null, null, null); } - public static OrchestrationConfigReference fromScenarioNameVersion(@Nonnull final String scenario, @Nonnull final String name, @Nonnull final String version) { + public static OrchestrationConfigReference fromScenarioNameVersion( + @Nonnull final String scenario, @Nonnull final String name, @Nonnull final String version) { return new OrchestrationConfigReference(null, scenario, name, version); } } diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java index f9ad72596..b856a6264 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java @@ -361,4 +361,14 @@ Object embedding(@RequestParam(value = "format", required = false) final String } return response.getEmbeddingVectors(); } + + @GetMapping("/configFromRegistry") + @Nonnull + Object configFromRegistry(@RequestParam(value = "format", required = false) final String format) { + final var response = service.executeConfigFromReference(); + if ("json".equals(format)) { + return response; + } + return response.getContent(); + } } diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java index 0c825f02b..6652af18d 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java @@ -156,21 +156,23 @@ Generation promptRegistryToSpringAi() { return response != null ? response.getResult() : null; } + @GetMapping("/listOrchConfigs") OrchestrationConfigListResponse listOrchConfigs() { return orchConfigClient.listOrchestrationConfigs(); } + @GetMapping("/createOrchConfig") OrchestrationConfigPostResponse createOrchConfig() { - // JONAS: this is still the version used to build the config for the Orchestration test, change back to the version of the first commit to have correct test OrchestrationConfigPostRequest postRequest = OrchestrationConfigPostRequest.create() - .name("test-config") + .name(NAME) .version("0.0.1") .scenario("sdk-test-scenario") .spec(buildOrchestrationConfig()); return orchConfigClient.createUpdateOrchestrationConfig(postRequest); } + @GetMapping("/deleteOrchConfig") List deleteOrchConfig() { final OrchestrationConfigListResponse configs = orchConfigClient.listOrchestrationConfigs(); @@ -190,9 +192,8 @@ private OrchestrationConfig buildOrchestrationConfig() { Template.create() .template( UserChatMessage.create() - .content(new UserChatMessageContent.InnerString("Create {{?number}} paraphrases of {{?phrase}}")) - .role(UserChatMessage.RoleEnum.USER)) - .defaults(Map.of("number", "3"))) - .model(LLMModelDetails.create().name("gpt-4.1-nano")))); + .content(new UserChatMessageContent.InnerString("message")) + .role(UserChatMessage.RoleEnum.USER))) + .model(LLMModelDetails.create().name("model-name")))); } } 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 197ce4dbf..40c3b5720 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 @@ -43,6 +43,15 @@ import java.util.Map; import java.util.stream.Stream; import javax.annotation.Nonnull; + +import com.sap.ai.sdk.prompt.registry.OrchestrationConfigClient; +import com.sap.ai.sdk.prompt.registry.model.LLMModelDetails; +import com.sap.ai.sdk.prompt.registry.model.ModuleConfigs; +import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfig; +import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigPostRequest; +import com.sap.ai.sdk.prompt.registry.model.PromptTemplatingModuleConfig; +import com.sap.ai.sdk.prompt.registry.model.UserChatMessage; +import com.sap.ai.sdk.prompt.registry.model.UserChatMessageContent; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import lombok.val; @@ -663,9 +672,47 @@ public OrchestrationEmbeddingResponse embed(@Nonnull final List texts) { } public OrchestrationChatResponse executeConfigFromReference() { - String testID = "80299222-f576-4682-b3d9-0a5aaf9be92d"; - var testReference = OrchestrationConfigReference.fromId(testID); + ensureOrchestrationConfigExists(); + var testReference = + OrchestrationConfigReference.fromScenarioNameVersion( + "sdk-test-scenario", "test-config-for-OrchestrationTest", "0.0.1"); OrchestrationPrompt testPrompt = new OrchestrationPrompt(Map.of("phrase", "Hello World")); return client.executeRequestFromReference(testPrompt, testReference); } + + private void ensureOrchestrationConfigExists() { + OrchestrationConfigClient orchConfigClient = new OrchestrationConfigClient(); + if (!orchConfigExists("test-config-for-OrchestrationTest", orchConfigClient)) { + OrchestrationConfigPostRequest postRequest = + OrchestrationConfigPostRequest.create() + .name("test-config-for-OrchestrationTest") + .version("0.0.1") + .scenario("sdk-test-scenario") + .spec(buildOrchestrationConfig()); + orchConfigClient.createUpdateOrchestrationConfig(postRequest); + } + } + + private boolean orchConfigExists(String configName, OrchestrationConfigClient orchConfigClient) { + return orchConfigClient.listOrchestrationConfigs().getResources().stream() + .anyMatch(resp -> resp.getName().equals(configName)); + } + + private OrchestrationConfig buildOrchestrationConfig() { + return OrchestrationConfig.create() + .modules( + ModuleConfigs.create() + .promptTemplating( + PromptTemplatingModuleConfig.create() + .prompt( + com.sap.ai.sdk.prompt.registry.model.Template.create() + .template( + UserChatMessage.create() + .content( + new UserChatMessageContent.InnerString( + "Create {{?number}} paraphrases of {{?phrase}}")) + .role(UserChatMessage.RoleEnum.USER)) + .defaults(Map.of("number", "3"))) + .model(LLMModelDetails.create().name("gpt-4.1-nano")))); + } } diff --git a/sample-code/spring-app/src/main/resources/static/index.html b/sample-code/spring-app/src/main/resources/static/index.html index 5393c1804..491c7f912 100644 --- a/sample-code/spring-app/src/main/resources/static/index.html +++ b/sample-code/spring-app/src/main/resources/static/index.html @@ -503,7 +503,7 @@

Orchestration


-
Template reference
+
Template and Config Reference
  • @@ -547,6 +547,18 @@

    Orchestration

  • +
  • +
    + +
    + Chat request to an LLM using an Orchestration config stored in prompt registry. +
    +
    +

@@ -1108,6 +1120,18 @@

📚 Prompt Registry

+
  • +
    + +
    + List all Orchestration Configs. +
    +
    +
  • From 7a6d1171f5d712611c7fdcdd7342e133a577abb9 Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Mon, 22 Dec 2025 15:35:23 +0100 Subject: [PATCH 5/7] add unit tests --- .../ConfigToRequestTransformer.java | 6 +++ .../orchestration/OrchestrationClient.java | 4 +- .../orchestration/OrchestrationUnitTest.java | 52 +++++++++++++++++++ .../test/resources/orchConfigByIdRequest.json | 7 +++ .../orchConfigByRequestHistoryParams.json | 14 +++++ .../resources/orchConfigBySNVRequest.json | 9 ++++ .../app/services/OrchestrationService.java | 6 ++- 7 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 orchestration/src/test/resources/orchConfigByIdRequest.json create mode 100644 orchestration/src/test/resources/orchConfigByRequestHistoryParams.json create mode 100644 orchestration/src/test/resources/orchConfigBySNVRequest.json 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 b7d68c90d..0edca8693 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 @@ -24,9 +24,11 @@ import javax.annotation.Nullable; import lombok.AccessLevel; import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; import lombok.val; /** Factory to create all data objects from an orchestration configuration. */ +@Slf4j @NoArgsConstructor(access = AccessLevel.NONE) final class ConfigToRequestTransformer { @Nonnull @@ -129,6 +131,10 @@ static CompletionPostRequest fromReferenceToCompletionPostRequest( List messageHistory = List.of(); Map placeholders = Map.of(); if (prompt != null) { + if (!prompt.getMessages().isEmpty()) { + log.debug( + "Messages in prompts are ignored when using Orchestration configs via reference. Change the Orchestration config instead."); + } messageHistory = prompt.getMessagesHistory().stream().map(Message::createChatMessage).toList(); placeholders = prompt.getTemplateParameters(); 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 a779d4970..4d5d5f00f 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 @@ -25,6 +25,8 @@ import java.util.function.Supplier; import java.util.stream.Stream; import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import lombok.val; @@ -168,7 +170,7 @@ public CompletionPostResponse executeRequest(@Nonnull final CompletionPostReques @Beta public OrchestrationChatResponse executeRequestFromReference( - final OrchestrationPrompt prompt, final OrchestrationConfigReference reference) { + @Nullable final OrchestrationPrompt prompt, @Nonnull final OrchestrationConfigReference reference) { var request = ConfigToRequestTransformer.fromReferenceToCompletionPostRequest(prompt, reference); var 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 6bfd757af..a0d6f0a4c 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 @@ -1347,6 +1347,58 @@ void testTemplateFromInputThrows() { .hasMessageContaining("Failed to deserialize"); } + @Test + void testExecuteFromReferenceById() { + stubFor( + post(anyUrl()) + .willReturn( + aResponse() + .withBodyFile("templatingResponse.json") + .withHeader("Content-Type", "application/json"))); + + var reference = OrchestrationConfigReference.fromId("test-id"); + final var response = client.executeRequestFromReference(null, reference); + + final String expectedRequest = fileLoaderStr.apply("orchConfigByIdRequest.json"); + verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(expectedRequest))); + } + + @Test + void testExecuteFromReferenceBySNV() { + stubFor( + post(anyUrl()) + .willReturn( + aResponse() + .withBodyFile("templatingResponse.json") + .withHeader("Content-Type", "application/json"))); + + var reference = + OrchestrationConfigReference.fromScenarioNameVersion("scenario", "name", "0.0.1"); + final var response = client.executeRequestFromReference(null, reference); + + final String expectedRequest = fileLoaderStr.apply("orchConfigBySNVRequest.json"); + verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(expectedRequest))); + } + + @Test + void testExecuteFromReferenceWithMessageHistoryAndInputParams() { + stubFor( + post(anyUrl()) + .willReturn( + aResponse() + .withBodyFile("templatingResponse.json") + .withHeader("Content-Type", "application/json"))); + + var reference = + OrchestrationConfigReference.fromScenarioNameVersion("scenario", "name", "0.0.1"); + List history = List.of(new SystemMessage("System Message")); + var prompt = new OrchestrationPrompt(Map.of("placeholder", "value")).messageHistory(history); + final var response = client.executeRequestFromReference(prompt, reference); + + final String expectedRequest = fileLoaderStr.apply("orchConfigByRequestHistoryParams.json"); + verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(expectedRequest))); + } + @Test void testGetAllMessages() { stubFor( diff --git a/orchestration/src/test/resources/orchConfigByIdRequest.json b/orchestration/src/test/resources/orchConfigByIdRequest.json new file mode 100644 index 000000000..9c919cb4b --- /dev/null +++ b/orchestration/src/test/resources/orchConfigByIdRequest.json @@ -0,0 +1,7 @@ +{ + "config_ref": { + "id": "test-id" + }, + "placeholder_values": {}, + "messages_history": [] +} \ No newline at end of file diff --git a/orchestration/src/test/resources/orchConfigByRequestHistoryParams.json b/orchestration/src/test/resources/orchConfigByRequestHistoryParams.json new file mode 100644 index 000000000..08e115741 --- /dev/null +++ b/orchestration/src/test/resources/orchConfigByRequestHistoryParams.json @@ -0,0 +1,14 @@ +{ + "config_ref" : { + "scenario" : "scenario", + "name" : "name", + "version" : "0.0.1" + }, + "placeholder_values" : { + "placeholder" : "value" + }, + "messages_history" : [ { + "role" : "system", + "content" : "System Message" + } ] +} \ No newline at end of file diff --git a/orchestration/src/test/resources/orchConfigBySNVRequest.json b/orchestration/src/test/resources/orchConfigBySNVRequest.json new file mode 100644 index 000000000..c8218d6d4 --- /dev/null +++ b/orchestration/src/test/resources/orchConfigBySNVRequest.json @@ -0,0 +1,9 @@ +{ + "config_ref": { + "scenario": "scenario", + "name": "name", + "version": "0.0.1" + }, + "placeholder_values": {}, + "messages_history": [] +} \ No newline at end of file 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 40c3b5720..ee7ac67e6 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 @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.sap.ai.sdk.core.AiCoreService; +import com.sap.ai.sdk.orchestration.AssistantMessage; import com.sap.ai.sdk.orchestration.AzureContentFilter; import com.sap.ai.sdk.orchestration.AzureFilterThreshold; import com.sap.ai.sdk.orchestration.DpiMasking; @@ -23,6 +24,7 @@ import com.sap.ai.sdk.orchestration.OrchestrationModuleConfig; import com.sap.ai.sdk.orchestration.OrchestrationPrompt; import com.sap.ai.sdk.orchestration.ResponseJsonSchema; +import com.sap.ai.sdk.orchestration.SystemMessage; import com.sap.ai.sdk.orchestration.TemplateConfig; import com.sap.ai.sdk.orchestration.model.DPIEntities; import com.sap.ai.sdk.orchestration.model.DataRepositoryType; @@ -37,6 +39,7 @@ import com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationOutputTargetLanguage; import com.sap.ai.sdk.orchestration.model.SearchDocumentKeyValueListPair; import com.sap.ai.sdk.orchestration.model.SearchSelectOptionEnum; +import com.sap.ai.sdk.orchestration.model.SystemChatMessage; import com.sap.ai.sdk.orchestration.model.Template; import java.io.IOException; import java.util.List; @@ -676,7 +679,8 @@ public OrchestrationChatResponse executeConfigFromReference() { var testReference = OrchestrationConfigReference.fromScenarioNameVersion( "sdk-test-scenario", "test-config-for-OrchestrationTest", "0.0.1"); - OrchestrationPrompt testPrompt = new OrchestrationPrompt(Map.of("phrase", "Hello World")); + List history = List.of(new SystemMessage("Start every sentence with an emoji.")); + OrchestrationPrompt testPrompt = new OrchestrationPrompt(Map.of("phrase", "Hello World")).messageHistory(history); return client.executeRequestFromReference(testPrompt, testReference); } From e6a8239ef5306b3a5503d886172af6d70b37b235 Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Mon, 22 Dec 2025 21:56:45 +0100 Subject: [PATCH 6/7] small changes, more tests, and codestyle etc. --- core-services/prompt-registry/pom.xml | 14 ++-- .../registry/OrchestrationConfigClient.java | 6 +- .../OrchestrationConfigClientTest.java | 43 +++++++++++ .../mappings/orchestrationConfigs.json | 35 +++++++++ docs/release_notes.md | 2 + orchestration/pom.xml | 8 +- .../orchestration/OrchestrationClient.java | 17 +++- .../OrchestrationConfigReference.java | 77 +++++++++++++++++-- .../orchestration/OrchestrationUnitTest.java | 4 +- .../controllers/PromptRegistryController.java | 6 +- .../app/services/OrchestrationService.java | 39 ++++++---- 11 files changed, 204 insertions(+), 47 deletions(-) create mode 100644 core-services/prompt-registry/src/test/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClientTest.java create mode 100644 core-services/prompt-registry/src/test/resources/mappings/orchestrationConfigs.json diff --git a/core-services/prompt-registry/pom.xml b/core-services/prompt-registry/pom.xml index 6b2569f03..b5a3c974a 100644 --- a/core-services/prompt-registry/pom.xml +++ b/core-services/prompt-registry/pom.xml @@ -38,11 +38,11 @@ ${project.basedir}/../../ - 73% - 87% - 89% - 75% - 75% + 85% + 91% + 93% + 100% + 81% 100% @@ -101,6 +101,10 @@ com.fasterxml.jackson.dataformat jackson-dataformat-yaml
    + + com.fasterxml.jackson.core + jackson-core + org.projectlombok diff --git a/core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java b/core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java index 454cffdd7..a12a1ddba 100644 --- a/core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java +++ b/core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClient.java @@ -1,5 +1,7 @@ package com.sap.ai.sdk.prompt.registry; +import static com.sap.ai.sdk.core.JacksonConfiguration.getDefaultObjectMapper; + import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.ObjectMapper; @@ -23,8 +25,6 @@ import org.springframework.http.converter.yaml.MappingJackson2YamlHttpMessageConverter; import org.springframework.web.client.RestTemplate; -import static com.sap.ai.sdk.core.JacksonConfiguration.getDefaultObjectMapper; - /** * Client for managing Orchestration Configurations in the Prompt Registry service. * @@ -65,7 +65,7 @@ private static ApiClient addMixin(@Nonnull final AiCoreService service) { getDefaultObjectMapper() .addMixIn(OutputFilterConfig.class, JacksonMixin.OutputFilter.class) .addMixIn(InputFilterConfig.class, JacksonMixin.InputFilter.class))); - var yamlMapper = new ObjectMapper(new YAMLFactory()); + final var yamlMapper = new ObjectMapper(new YAMLFactory()); yamlMapper .addMixIn(OutputFilterConfig.class, JacksonMixin.OutputFilter.class) .addMixIn(InputFilterConfig.class, JacksonMixin.InputFilter.class); diff --git a/core-services/prompt-registry/src/test/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClientTest.java b/core-services/prompt-registry/src/test/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClientTest.java new file mode 100644 index 000000000..2365d0bdc --- /dev/null +++ b/core-services/prompt-registry/src/test/java/com/sap/ai/sdk/prompt/registry/OrchestrationConfigClientTest.java @@ -0,0 +1,43 @@ +package com.sap.ai.sdk.prompt.registry; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import com.sap.ai.sdk.core.AiCoreService; +import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; +import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination; +import java.util.UUID; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class OrchestrationConfigClientTest { + @RegisterExtension + private static final WireMockExtension WM = + WireMockExtension.newInstance().options(wireMockConfig().dynamicPort()).build(); + + private static OrchestrationConfigClient client; + + @BeforeEach + void setup() { + final HttpDestination destination = DefaultHttpDestination.builder(WM.baseUrl()).build(); + final AiCoreService service = new AiCoreService().withBaseDestination(destination); + client = new OrchestrationConfigClient(service); + } + + @Test + void testPipelines() { + final var result = client.listOrchestrationConfigs(); + assertThat(result.getCount()).isEqualTo(2); + assertThat(result.getResources()).hasSize(2); + final var template = result.getResources().get(0); + assertThat(template.getId()).isEqualTo(UUID.fromString("62e8638a-ae87-4bd5-9027-a0bc67db1609")); + assertThat(template.getName()).isEqualTo("test-config-for-OrchestrationTest"); + assertThat(template.getVersion()).isEqualTo("0.0.1"); + assertThat(template.getScenario()).isEqualTo("sdk-test-scenario"); + assertThat(template.getCreationTimestamp()).isEqualTo("2025-12-19T16:24:27.442000"); + assertThat(template.getManagedBy()).isEqualTo("imperative"); + assertThat(template.isIsVersionHead()).isEqualTo(true); + } +} diff --git a/core-services/prompt-registry/src/test/resources/mappings/orchestrationConfigs.json b/core-services/prompt-registry/src/test/resources/mappings/orchestrationConfigs.json new file mode 100644 index 000000000..f376835b7 --- /dev/null +++ b/core-services/prompt-registry/src/test/resources/mappings/orchestrationConfigs.json @@ -0,0 +1,35 @@ +{ + "request": { + "method": "GET", + "url": "/v2/registry/v2/orchestrationConfigs" + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "count": 2, + "resources": [ + { + "id": "62e8638a-ae87-4bd5-9027-a0bc67db1609", + "name": "test-config-for-OrchestrationTest", + "version": "0.0.1", + "scenario": "sdk-test-scenario", + "creation_timestamp": "2025-12-19T16:24:27.442000", + "managed_by": "imperative", + "is_version_head": true + }, + { + "id": "f9f2875a-4c92-471b-a403-51a50e70fe52", + "name": "test-config", + "version": "0.0.1", + "scenario": "sdk-test-scenario", + "creation_timestamp": "2025-12-19T16:33:05.607000", + "managed_by": "imperative", + "is_version_head": true + } + ] + } + } +} diff --git a/docs/release_notes.md b/docs/release_notes.md index 8ded2fad1..d4477714c 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -13,7 +13,9 @@ ### ✨ New Functionality - [Orchestration] Added new models for `OrchestrationAiModel`: `SONAR`,`SONAR_PRO`, `GEMINI_2_5_FLASH_LITE`, `CLAUDE_4_5_HAIKU`. +- [Orchestration] Configs stored in prompt registry can now be used for Orchestration calls via reference. - [Orchestration] Convenience for adding the `metadata_params` option to grounding calls. +- [Prompt Registry] Added support to manage Orchestration configs stored in Prompt Registry. ### 📈 Improvements diff --git a/orchestration/pom.xml b/orchestration/pom.xml index 6cf763016..b87833891 100644 --- a/orchestration/pom.xml +++ b/orchestration/pom.xml @@ -36,11 +36,11 @@ ${project.basedir}/../ - 80% - 94% + 82% + 95% 93% - 74% - 93% + 75% + 94% 100% 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 4d5d5f00f..3f1de9102 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 @@ -26,7 +26,6 @@ import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import lombok.val; @@ -168,12 +167,22 @@ public CompletionPostResponse executeRequest(@Nonnull final CompletionPostReques COMPLETION_ENDPOINT, request, CompletionPostResponse.class, customHeaders); } + /** + * Generate a completion using a referenced Orchestration config and a prompt. + * + * @param prompt The prompt to store placeholder values and the message history + * @param reference A reference to an Orchestration config stored in prompt registry + * @return The completion output + * @since 1.14.0 + */ @Beta + @Nonnull public OrchestrationChatResponse executeRequestFromReference( - @Nullable final OrchestrationPrompt prompt, @Nonnull final OrchestrationConfigReference reference) { - var request = + @Nullable final OrchestrationPrompt prompt, + @Nonnull final OrchestrationConfigReference reference) { + val request = ConfigToRequestTransformer.fromReferenceToCompletionPostRequest(prompt, reference); - var response = executeRequest(request); + val response = executeRequest(request); return new OrchestrationChatResponse(response); } diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java index 41851cfc5..860323422 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationConfigReference.java @@ -4,13 +4,14 @@ import javax.annotation.Nonnull; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.RequiredArgsConstructor; import lombok.Value; -// JONAS: Making this a sealed interface permitting 2 classes for ID and SNV seemed like an overkill -// JONAS: Not a record because I do not want a public all-args constructor -// JONAS: Is string correct for ID? (Use UUID?) -// JONAS: Split SNV into 3 methods? - +/** + * Class representing a reference to an Orchestration config stored in prompt registry. + * + * @since 1.14.0 + */ @Value @AllArgsConstructor(access = AccessLevel.PRIVATE) @Beta @@ -20,12 +21,72 @@ public class OrchestrationConfigReference { String name; String version; + /** + * Build a reference from an ID. + * + * @param id The id of the reference + * @return A reference object with the specified id + * @since 1.14.0 + */ + @Nonnull public static OrchestrationConfigReference fromId(@Nonnull final String id) { return new OrchestrationConfigReference(id, null, null, null); } - public static OrchestrationConfigReference fromScenarioNameVersion( - @Nonnull final String scenario, @Nonnull final String name, @Nonnull final String version) { - return new OrchestrationConfigReference(null, scenario, name, version); + /** + * Build a reference from a scenario, name, and version. + * + * @param scenario The scenario of the reference + * @return A builder object with the specified scenario + * @since 1.14.0 + */ + @Nonnull + public static Builder fromScenario(@Nonnull final String scenario) { + return new Builder(scenario); + } + + /** + * Builder to create an Orchestration config reference from scenario, name, and version. + * + * @since 1.14.0 + */ + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + public static class Builder { + private final String scenario; + + /** + * Build a reference from a scenario, name, and version. + * + * @param name The name of the reference + * @return A builder object with the specified scenario and name + * @since 1.14.0 + */ + @Nonnull + public Builder1 name(@Nonnull final String name) { + return new Builder1(scenario, name); + } + } + + /** + * Builder to create an Orchestration config reference from scenario, name, and version. + * + * @since 1.14.0 + */ + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + public static class Builder1 { + private final String scenario; + private final String name; + + /** + * Build a reference from a scenario, name, and version. + * + * @param version The version of the reference + * @return A reference object with the specified scenario, name, and version + * @since 1.14.0 + */ + @Nonnull + public OrchestrationConfigReference version(@Nonnull final String version) { + return new OrchestrationConfigReference(null, scenario, name, version); + } } } 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 a0d6f0a4c..a21d9f2d7 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 @@ -1373,7 +1373,7 @@ void testExecuteFromReferenceBySNV() { .withHeader("Content-Type", "application/json"))); var reference = - OrchestrationConfigReference.fromScenarioNameVersion("scenario", "name", "0.0.1"); + OrchestrationConfigReference.fromScenario("scenario").name("name").version("0.0.1"); final var response = client.executeRequestFromReference(null, reference); final String expectedRequest = fileLoaderStr.apply("orchConfigBySNVRequest.json"); @@ -1390,7 +1390,7 @@ void testExecuteFromReferenceWithMessageHistoryAndInputParams() { .withHeader("Content-Type", "application/json"))); var reference = - OrchestrationConfigReference.fromScenarioNameVersion("scenario", "name", "0.0.1"); + OrchestrationConfigReference.fromScenario("scenario").name("name").version("0.0.1"); List history = List.of(new SystemMessage("System Message")); var prompt = new OrchestrationPrompt(Map.of("placeholder", "value")).messageHistory(history); final var response = client.executeRequestFromReference(prompt, reference); diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java index 6652af18d..3b61fa33d 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java @@ -5,8 +5,6 @@ import com.sap.ai.sdk.foundationmodels.openai.spring.OpenAiChatModel; import com.sap.ai.sdk.prompt.registry.OrchestrationConfigClient; import com.sap.ai.sdk.prompt.registry.PromptClient; -import com.sap.ai.sdk.prompt.registry.model.ChatMessage; -import com.sap.ai.sdk.prompt.registry.model.ChatMessageContent; import com.sap.ai.sdk.prompt.registry.model.LLMModelDetails; import com.sap.ai.sdk.prompt.registry.model.ModuleConfigs; import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfig; @@ -22,9 +20,7 @@ import com.sap.ai.sdk.prompt.registry.model.PromptTemplateSubstitutionRequest; import com.sap.ai.sdk.prompt.registry.model.PromptTemplateSubstitutionResponse; import com.sap.ai.sdk.prompt.registry.model.PromptTemplatingModuleConfig; -import com.sap.ai.sdk.prompt.registry.model.PromptTemplatingModuleConfigPrompt; import com.sap.ai.sdk.prompt.registry.model.SingleChatTemplate; -import com.sap.ai.sdk.prompt.registry.model.SystemChatMessage; import com.sap.ai.sdk.prompt.registry.model.Template; import com.sap.ai.sdk.prompt.registry.model.UserChatMessage; import com.sap.ai.sdk.prompt.registry.model.UserChatMessageContent; @@ -163,7 +159,7 @@ OrchestrationConfigListResponse listOrchConfigs() { @GetMapping("/createOrchConfig") OrchestrationConfigPostResponse createOrchConfig() { - OrchestrationConfigPostRequest postRequest = + final OrchestrationConfigPostRequest postRequest = OrchestrationConfigPostRequest.create() .name(NAME) .version("0.0.1") 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 ee7ac67e6..ad1a83d8c 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 @@ -7,7 +7,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.sap.ai.sdk.core.AiCoreService; -import com.sap.ai.sdk.orchestration.AssistantMessage; import com.sap.ai.sdk.orchestration.AzureContentFilter; import com.sap.ai.sdk.orchestration.AzureFilterThreshold; import com.sap.ai.sdk.orchestration.DpiMasking; @@ -39,14 +38,7 @@ import com.sap.ai.sdk.orchestration.model.SAPDocumentTranslationOutputTargetLanguage; import com.sap.ai.sdk.orchestration.model.SearchDocumentKeyValueListPair; import com.sap.ai.sdk.orchestration.model.SearchSelectOptionEnum; -import com.sap.ai.sdk.orchestration.model.SystemChatMessage; import com.sap.ai.sdk.orchestration.model.Template; -import java.io.IOException; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; -import javax.annotation.Nonnull; - import com.sap.ai.sdk.prompt.registry.OrchestrationConfigClient; import com.sap.ai.sdk.prompt.registry.model.LLMModelDetails; import com.sap.ai.sdk.prompt.registry.model.ModuleConfigs; @@ -55,6 +47,11 @@ import com.sap.ai.sdk.prompt.registry.model.PromptTemplatingModuleConfig; import com.sap.ai.sdk.prompt.registry.model.UserChatMessage; import com.sap.ai.sdk.prompt.registry.model.UserChatMessageContent; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.Nonnull; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import lombok.val; @@ -674,20 +671,29 @@ public OrchestrationEmbeddingResponse embed(@Nonnull final List texts) { return client.embed(request); } + /** + * Chat request to an LLM through the Orchestration service using a template from the prompt + * registry identified by a reference. + * + * @return the assistant response object + */ + @Nonnull public OrchestrationChatResponse executeConfigFromReference() { ensureOrchestrationConfigExists(); - var testReference = - OrchestrationConfigReference.fromScenarioNameVersion( - "sdk-test-scenario", "test-config-for-OrchestrationTest", "0.0.1"); - List history = List.of(new SystemMessage("Start every sentence with an emoji.")); - OrchestrationPrompt testPrompt = new OrchestrationPrompt(Map.of("phrase", "Hello World")).messageHistory(history); + final var testReference = + OrchestrationConfigReference.fromScenario("sdk-test-scenario") + .name("test-config-for-OrchestrationTest") + .version("0.0.1"); + final List history = List.of(new SystemMessage("Start every sentence with an emoji.")); + final OrchestrationPrompt testPrompt = + new OrchestrationPrompt(Map.of("phrase", "Hello World")).messageHistory(history); return client.executeRequestFromReference(testPrompt, testReference); } private void ensureOrchestrationConfigExists() { - OrchestrationConfigClient orchConfigClient = new OrchestrationConfigClient(); + final OrchestrationConfigClient orchConfigClient = new OrchestrationConfigClient(); if (!orchConfigExists("test-config-for-OrchestrationTest", orchConfigClient)) { - OrchestrationConfigPostRequest postRequest = + final OrchestrationConfigPostRequest postRequest = OrchestrationConfigPostRequest.create() .name("test-config-for-OrchestrationTest") .version("0.0.1") @@ -697,7 +703,8 @@ private void ensureOrchestrationConfigExists() { } } - private boolean orchConfigExists(String configName, OrchestrationConfigClient orchConfigClient) { + private boolean orchConfigExists( + final String configName, final OrchestrationConfigClient orchConfigClient) { return orchConfigClient.listOrchestrationConfigs().getResources().stream() .anyMatch(resp -> resp.getName().equals(configName)); } From 54849bde82c3da7f62e56d93d06d4a952b342c83 Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Mon, 22 Dec 2025 22:16:38 +0100 Subject: [PATCH 7/7] fix after merge --- docs/release_notes.md | 3 +- .../controllers/PromptRegistryController.java | 23 ++++++++------- .../app/services/OrchestrationService.java | 28 ++++++++++--------- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/docs/release_notes.md b/docs/release_notes.md index 42e77f2e7..eedbd36e5 100644 --- a/docs/release_notes.md +++ b/docs/release_notes.md @@ -12,10 +12,9 @@ ### ✨ New Functionality -- [Orchestration] Added new models for `OrchestrationAiModel`: `SAP_ABAP_1`, `SONAR`,`SONAR_PRO`, `GEMINI_2_5_FLASH_LITE`, `CLAUDE_4_5_HAIKU`, `GPT_REALTIME`. +- [Orchestration] Added new models for `OrchestrationAiModel`: `SAP_ABAP_1`, `SONAR`,`SONAR_PRO`, `GEMINI_2_5_FLASH_LITE`, `CLAUDE_4_5_HAIKU`, `GPT_REALTIME`, `COHERE_COMMAND_A_REASONING`, `NOVA_PREMIER`, `COHERE_RERANKER`. - [Orchestration] Configs stored in prompt registry can now be used for Orchestration calls via reference. - [Orchestration] Convenience for adding the `metadata_params` option to grounding calls. -- [Orchestration] Added new models for `OrchestrationAiModel`: `COHERE_COMMAND_A_REASONING`, `NOVA_PREMIER`, `COHERE_RERANKER`. - [Orchestration] Deprecated `DEEPSEEK_R1` model from `OrchestrationAiModel` with no replacement. - [Prompt Registry] Added support to manage Orchestration configs stored in Prompt Registry. diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java index 3b61fa33d..670433ade 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/PromptRegistryController.java @@ -10,6 +10,7 @@ import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfig; import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigDeleteResponse; import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigListResponse; +import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigModules; import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigPostRequest; import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigPostResponse; import com.sap.ai.sdk.prompt.registry.model.PromptTemplateDeleteResponse; @@ -181,15 +182,17 @@ List deleteOrchConfig() { private OrchestrationConfig buildOrchestrationConfig() { return OrchestrationConfig.create() .modules( - ModuleConfigs.create() - .promptTemplating( - PromptTemplatingModuleConfig.create() - .prompt( - Template.create() - .template( - UserChatMessage.create() - .content(new UserChatMessageContent.InnerString("message")) - .role(UserChatMessage.RoleEnum.USER))) - .model(LLMModelDetails.create().name("model-name")))); + OrchestrationConfigModules.createInnerModuleConfigs( + ModuleConfigs.create() + .promptTemplating( + PromptTemplatingModuleConfig.create() + .prompt( + Template.create() + .template( + UserChatMessage.create() + .content( + new UserChatMessageContent.InnerString("message")) + .role(UserChatMessage.RoleEnum.USER))) + .model(LLMModelDetails.create().name("model-name"))))); } } 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 877306875..b6eb26397 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 @@ -39,6 +39,7 @@ import com.sap.ai.sdk.prompt.registry.model.LLMModelDetails; import com.sap.ai.sdk.prompt.registry.model.ModuleConfigs; import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfig; +import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigModules; import com.sap.ai.sdk.prompt.registry.model.OrchestrationConfigPostRequest; import com.sap.ai.sdk.prompt.registry.model.PromptTemplatingModuleConfig; import com.sap.ai.sdk.prompt.registry.model.UserChatMessage; @@ -697,18 +698,19 @@ private boolean orchConfigExists( private OrchestrationConfig buildOrchestrationConfig() { return OrchestrationConfig.create() .modules( - ModuleConfigs.create() - .promptTemplating( - PromptTemplatingModuleConfig.create() - .prompt( - com.sap.ai.sdk.prompt.registry.model.Template.create() - .template( - UserChatMessage.create() - .content( - new UserChatMessageContent.InnerString( - "Create {{?number}} paraphrases of {{?phrase}}")) - .role(UserChatMessage.RoleEnum.USER)) - .defaults(Map.of("number", "3"))) - .model(LLMModelDetails.create().name("gpt-4.1-nano")))); + OrchestrationConfigModules.createInnerModuleConfigs( + ModuleConfigs.create() + .promptTemplating( + PromptTemplatingModuleConfig.create() + .prompt( + com.sap.ai.sdk.prompt.registry.model.Template.create() + .template( + UserChatMessage.create() + .content( + new UserChatMessageContent.InnerString( + "Create {{?number}} paraphrases of {{?phrase}}")) + .role(UserChatMessage.RoleEnum.USER)) + .defaults(Map.of("number", "3"))) + .model(LLMModelDetails.create().name("gpt-4.1-nano"))))); } }