From 12b5c675ce3f18e096191de9143aa8675d273f6b Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Wed, 28 Jan 2026 16:40:18 +0100 Subject: [PATCH 1/9] basic functionality --- .../ConfigToRequestTransformer.java | 47 ++++++++++++++++++- .../orchestration/OrchestrationClient.java | 16 ++++--- .../app/services/OrchestrationService.java | 20 ++++++++ .../app/controllers/OrchestrationTest.java | 23 +++++++++ 4 files changed, 99 insertions(+), 7 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 b5725c6ee..e6c89a514 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 @@ -17,6 +17,9 @@ import com.sap.ai.sdk.orchestration.model.TranslationModuleConfig; import io.vavr.control.Option; import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.IntStream; import javax.annotation.Nonnull; import javax.annotation.Nullable; import lombok.AccessLevel; @@ -52,6 +55,32 @@ static CompletionRequestConfiguration toCompletionPostRequest( .messagesHistory(messageHistory); } + static CompletionRequestConfiguration toCompletionPostRequestWithFallbacks( + @Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig[] configs) { + + val templates = Arrays.stream(configs).map(config -> toTemplateModuleConfig(prompt, config.getTemplateConfig())).toList(); + + // note that the config is immutable and implicitly copied here + // copying is required here, to not alter the original config object, which might be reused for + // subsequent requests + val configsCopy = IntStream.range(0, configs.length).mapToObj(i -> configs[i].withTemplateConfig(templates.get(i))).toList(); + + val messageHistory = + prompt.getMessagesHistory().stream().map(Message::createChatMessage).toList(); + + // JONAS: continue here + val moduleConfigs = toListOfModuleConfigs(configsCopy); + + // JONAS: I am just using the stream options of the first config here. So I assume they don't change between the configs. + val requestConfig = + OrchestrationConfig.create().modules(moduleConfigs).stream(configs[0].getGlobalStreamOptions()); + + return CompletionRequestConfiguration.create() + .config(requestConfig) + .placeholderValues(prompt.getTemplateParameters()) + .messagesHistory(messageHistory); + } + @Nonnull static PromptTemplatingModuleConfigPrompt toTemplateModuleConfig( @Nonnull final OrchestrationPrompt prompt, @@ -117,10 +146,26 @@ static InnerModuleConfigs toModuleConfigs(@Nonnull final OrchestrationModuleConf inputTranslation.forEach(moduleConfig.getTranslation()::input); outputTranslation.forEach(moduleConfig.getTranslation()::output); } - return OrchestrationConfigModules.createInnerModuleConfigs(moduleConfig); } + @Nonnull + static OrchestrationConfigModules.ListOfModuleConfigss toListOfModuleConfigs(@Nonnull final List configs) { + val llmConfigs = configs.stream().map(conf -> { + return Option.of(conf.getLlmConfig()) + .getOrElseThrow(() -> new IllegalStateException("LLM config is required.")); + }).toList(); + + //JONAS: Leaving out a lot of pre-processing here compared to toModuleConfigs + + val moduleConfigs = IntStream.range(0, configs.size()).mapToObj(i -> ModuleConfigs.create() + .promptTemplating( + PromptTemplatingModuleConfig.create() + .prompt(configs.get(i).getTemplateConfig()) + .model(llmConfigs.get(i)))).toList(); + return OrchestrationConfigModules.createListOfModuleConfigss(moduleConfigs); + } + @Nonnull static CompletionPostRequest fromReferenceToCompletionPostRequest( @Nonnull final OrchestrationConfigReference reference) { 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 66fe58bf1..e380029b2 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 @@ -73,29 +73,33 @@ public OrchestrationClient(@Nonnull final HttpDestination destination) { * allows for further customization before sending the request. * * @param prompt The {@link OrchestrationPrompt} to generate a completion for. - * @param config The {@link OrchestrationConfig } configuration to use for the completion. + * @param configs The {@link OrchestrationConfig } configuration to use for the completion. * @return The low-level request data object to send to orchestration. */ @Nonnull public static CompletionRequestConfiguration toCompletionPostRequest( - @Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config) { - return ConfigToRequestTransformer.toCompletionPostRequest(prompt, config); + @Nonnull final OrchestrationPrompt prompt, + @Nonnull final OrchestrationModuleConfig... configs) { + return configs.length == 1 + ? ConfigToRequestTransformer.toCompletionPostRequest(prompt, configs[0]) + : ConfigToRequestTransformer.toCompletionPostRequestWithFallbacks(prompt, configs); } /** * Generate a completion for the given prompt. * * @param prompt The {@link OrchestrationPrompt} to send to orchestration. - * @param config The {@link ModuleConfigs} configuration to use for the completion. + * @param configs The {@link ModuleConfigs} configuration to use for the completion. * @return the completion output * @throws OrchestrationClientException if the request fails. */ @Nonnull public OrchestrationChatResponse chatCompletion( - @Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config) + @Nonnull final OrchestrationPrompt prompt, + @Nonnull final OrchestrationModuleConfig... configs) throws OrchestrationClientException { - val request = toCompletionPostRequest(prompt, config); + val request = toCompletionPostRequest(prompt, configs); val response = executeRequest(request); return new OrchestrationChatResponse(response); } 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 54e081a8b..f2c6e4ef1 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 @@ -16,6 +16,7 @@ import com.sap.ai.sdk.orchestration.ImageItem; import com.sap.ai.sdk.orchestration.LlamaGuardFilter; import com.sap.ai.sdk.orchestration.Message; +import com.sap.ai.sdk.orchestration.OrchestrationAiModel; import com.sap.ai.sdk.orchestration.OrchestrationChatResponse; import com.sap.ai.sdk.orchestration.OrchestrationClient; import com.sap.ai.sdk.orchestration.OrchestrationClientException; @@ -78,6 +79,25 @@ public OrchestrationChatResponse completion(@Nonnull final String famousPhrase) return client.chatCompletion(prompt, config); } + @Nonnull + public OrchestrationChatResponse completionWithFallback(@Nonnull final String famousPhrase) { + val prompt = new OrchestrationPrompt(famousPhrase + " Why is this phrase so famous?"); + val workingConfig = new OrchestrationModuleConfig().withLlmConfig(GPT_4O_MINI.withParam(TEMPERATURE, 0.0)); + val brokenConfig = new OrchestrationModuleConfig().withLlmConfig(new OrchestrationAiModel("broken_name", Map.of(), "latest")); + OrchestrationModuleConfig[] configs = new OrchestrationModuleConfig[] { brokenConfig, config, workingConfig }; + return client.chatCompletion(prompt, configs); + // JONAS: or return client.chatCompletion(prompt, brokenConfig, config, workingConfig); + } + + @Nonnull + public OrchestrationChatResponse completionWithFallbackAllFail(@Nonnull final String famousPhrase) { + val prompt = new OrchestrationPrompt(famousPhrase + " Why is this phrase so famous?"); + val brokenConfig = new OrchestrationModuleConfig().withLlmConfig(new OrchestrationAiModel("broken_name", Map.of(), "latest")); + val alsoBrokenConfig = new OrchestrationModuleConfig().withLlmConfig(new OrchestrationAiModel("broken_name_2", Map.of(), "latest")); + OrchestrationModuleConfig[] configs = new OrchestrationModuleConfig[] { brokenConfig, alsoBrokenConfig }; + return client.chatCompletion(prompt, configs); + } + /** * Chat request to OpenAI through the Orchestration service with an image. * 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 e2ed9b6ab..9f2f3c478 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 @@ -61,6 +61,29 @@ void testCompletion() { assertThat(result.getContent()).isNotEmpty(); } + @Test + void testCompletionWithFallback() { + val result = service.completionWithFallback("HelloWorld!"); + + assertThat(result).isNotNull(); + assertThat(result.getContent()).isNotEmpty(); + assertThat(result.getOriginalResponse().getIntermediateFailures().size()).isEqualTo(1); + assertThat(result.getOriginalResponse().getIntermediateFailures().get(0).getMessage()).contains("Model broken_name not supported."); + assertThat(result.getOriginalResponse().getFinalResult().getChoices().get(0).getFinishReason()).contains("stop"); + } + + @Test + void testCompletionWithFallbackAllFail() { + assertThatThrownBy(() -> service.completionWithFallbackAllFail("HelloWorld!")) + .isInstanceOf(OrchestrationClientException.class) + .hasMessageContaining("Model broken_name_2 not supported."); + } + + @Test + void testStreamCompletionWithFallback() { + assertThat(true); + } + @Test void testStreamChatCompletion() { val prompt = new OrchestrationPrompt("Who is the prettiest?"); From d95eeb8c200c0a82abd79cf5227703dd098c26f9 Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Thu, 29 Jan 2026 15:34:38 +0100 Subject: [PATCH 2/9] improve code quality --- .../ConfigToRequestTransformer.java | 80 +++++++------------ .../orchestration/OrchestrationClient.java | 2 +- 2 files changed, 32 insertions(+), 50 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 e6c89a514..b5e91ae95 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,49 +31,36 @@ @Slf4j @NoArgsConstructor(access = AccessLevel.NONE) final class ConfigToRequestTransformer { - @Nonnull static CompletionRequestConfiguration toCompletionPostRequest( - @Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config) { - val template = toTemplateModuleConfig(prompt, config.getTemplateConfig()); - - // note that the config is immutable and implicitly copied here - // copying is required here, to not alter the original config object, which might be reused for - // subsequent requests - val configCopy = config.withTemplateConfig(template); - - val messageHistory = - prompt.getMessagesHistory().stream().map(Message::createChatMessage).toList(); - - val moduleConfigs = toModuleConfigs(configCopy); - - val reqConfig = - OrchestrationConfig.create().modules(moduleConfigs).stream(config.getGlobalStreamOptions()); - - return CompletionRequestConfiguration.create() - .config(reqConfig) - .placeholderValues(prompt.getTemplateParameters()) - .messagesHistory(messageHistory); - } - - static CompletionRequestConfiguration toCompletionPostRequestWithFallbacks( - @Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig[] configs) { + @Nonnull final OrchestrationPrompt prompt, + @Nonnull final OrchestrationModuleConfig... configs) { - val templates = Arrays.stream(configs).map(config -> toTemplateModuleConfig(prompt, config.getTemplateConfig())).toList(); + val templates = + Arrays.stream(configs) + .map(config -> toTemplateModuleConfig(prompt, config.getTemplateConfig())) + .toList(); // note that the config is immutable and implicitly copied here // copying is required here, to not alter the original config object, which might be reused for // subsequent requests - val configsCopy = IntStream.range(0, configs.length).mapToObj(i -> configs[i].withTemplateConfig(templates.get(i))).toList(); + val configsCopy = + IntStream.range(0, configs.length) + .mapToObj(i -> configs[i].withTemplateConfig(templates.get(i))) + .toList(); val messageHistory = prompt.getMessagesHistory().stream().map(Message::createChatMessage).toList(); - // JONAS: continue here - val moduleConfigs = toListOfModuleConfigs(configsCopy); + final OrchestrationConfigModules moduleConfigs = + configsCopy.size() == 1 + ? toModuleConfigs(configsCopy.get(0)) + : toListOfModuleConfigs(configsCopy); - // JONAS: I am just using the stream options of the first config here. So I assume they don't change between the configs. + // JONAS: I am just using the stream options of the first config here. So I assume they don't + // change between the configs. val requestConfig = - OrchestrationConfig.create().modules(moduleConfigs).stream(configs[0].getGlobalStreamOptions()); + OrchestrationConfig.create().modules(moduleConfigs).stream( + configs[0].getGlobalStreamOptions()); return CompletionRequestConfiguration.create() .config(requestConfig) @@ -122,6 +109,18 @@ static PromptTemplatingModuleConfigPrompt toTemplateModuleConfig( @Nonnull static InnerModuleConfigs toModuleConfigs(@Nonnull final OrchestrationModuleConfig config) { + return OrchestrationConfigModules.createInnerModuleConfigs(setupModuleConfigs(config)); + } + + @Nonnull + static OrchestrationConfigModules.ListOfModuleConfigss toListOfModuleConfigs( + @Nonnull final List configs) { + return OrchestrationConfigModules.createListOfModuleConfigss( + configs.stream().map(ConfigToRequestTransformer::setupModuleConfigs).toList()); + } + + @Nonnull + private static ModuleConfigs setupModuleConfigs(@Nonnull final OrchestrationModuleConfig config) { val llmConfig = Option.of(config.getLlmConfig()) .getOrElseThrow(() -> new IllegalStateException("LLM config is required.")); @@ -146,24 +145,7 @@ static InnerModuleConfigs toModuleConfigs(@Nonnull final OrchestrationModuleConf inputTranslation.forEach(moduleConfig.getTranslation()::input); outputTranslation.forEach(moduleConfig.getTranslation()::output); } - return OrchestrationConfigModules.createInnerModuleConfigs(moduleConfig); - } - - @Nonnull - static OrchestrationConfigModules.ListOfModuleConfigss toListOfModuleConfigs(@Nonnull final List configs) { - val llmConfigs = configs.stream().map(conf -> { - return Option.of(conf.getLlmConfig()) - .getOrElseThrow(() -> new IllegalStateException("LLM config is required.")); - }).toList(); - - //JONAS: Leaving out a lot of pre-processing here compared to toModuleConfigs - - val moduleConfigs = IntStream.range(0, configs.size()).mapToObj(i -> ModuleConfigs.create() - .promptTemplating( - PromptTemplatingModuleConfig.create() - .prompt(configs.get(i).getTemplateConfig()) - .model(llmConfigs.get(i)))).toList(); - return OrchestrationConfigModules.createListOfModuleConfigss(moduleConfigs); + return moduleConfig; } @Nonnull 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 e380029b2..51974dafc 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 @@ -82,7 +82,7 @@ public static CompletionRequestConfiguration toCompletionPostRequest( @Nonnull final OrchestrationModuleConfig... configs) { return configs.length == 1 ? ConfigToRequestTransformer.toCompletionPostRequest(prompt, configs[0]) - : ConfigToRequestTransformer.toCompletionPostRequestWithFallbacks(prompt, configs); + : ConfigToRequestTransformer.toCompletionPostRequest(prompt, configs); } /** From 6d088c43cb3ef1dce04dd75e32e3c92a651f46f3 Mon Sep 17 00:00:00 2001 From: Jonas Israel Date: Thu, 29 Jan 2026 16:43:07 +0100 Subject: [PATCH 3/9] add tests and app endpoint --- .../orchestration/OrchestrationUnitTest.java | 36 +++++++ .../resources/__files/fallbackResponse.json | 81 ++++++++++++++++ .../src/test/resources/fallbackRequest.json | 97 +++++++++++++++++++ .../controllers/OrchestrationController.java | 10 ++ .../app/services/OrchestrationService.java | 4 +- .../src/main/resources/static/index.html | 12 +++ .../app/controllers/OrchestrationTest.java | 1 + 7 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 orchestration/src/test/resources/__files/fallbackResponse.json create mode 100644 orchestration/src/test/resources/fallbackRequest.json 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 2d6ba8811..80e28f788 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 @@ -1504,4 +1504,40 @@ void testGetAllMessages() { assertThat(messageListTools.get(1)).isInstanceOf(AssistantMessage.class); assertThat(messageListTools.get(2)).isInstanceOf(ToolMessage.class); } + + @Test + void testFallbackModules() throws IOException { + stubFor( + post(urlPathEqualTo("/v2/completion")) + .willReturn( + aResponse() + .withBodyFile("fallbackResponse.json") + .withHeader("Content-Type", "application/json"))); + + final var prompt = new OrchestrationPrompt("HelloWorld! Why is this phrase so famous?"); + final var llamaFilter = new LlamaGuardFilter().config(LlamaGuard38b.create().selfHarm(true)); + val groundingConfig = Grounding.create().filters(DocumentGroundingFilter.create().dataRepositoryType(DataRepositoryType.HELP_SAP_COM)); + + final var workingConfig = + new OrchestrationModuleConfig() + .withLlmConfig(GPT_4O_MINI.withParam(TEMPERATURE, 0.0)) + .withInputFiltering(llamaFilter) + .withGrounding(groundingConfig); + final var brokenConfig = workingConfig.withLlmConfig(new OrchestrationAiModel("broken_name", Map.of(), "latest")); + + OrchestrationModuleConfig[] configs = new OrchestrationModuleConfig[] { brokenConfig, workingConfig }; + + final var response = client.chatCompletion(prompt, configs); + + var intermediateFailure = response.getOriginalResponse().getIntermediateFailures().get(0); + assertThat(intermediateFailure.getCode()).isEqualTo(400); + assertThat(intermediateFailure.getLocation()).isEqualTo("Request Body"); + assertThat(intermediateFailure.getRequestId()).isEqualTo("a562703b-7fe5-97ba-b417-e8140a25fb7c"); + assertThat(intermediateFailure.getMessage()).isEqualTo("400 - Request Body: Model broken_name not supported."); + assertThat(intermediateFailure.getHeaders().get("Content-Type")).isEqualTo("application/json"); + + final String request = fileLoaderStr.apply("fallbackRequest.json"); + verify( + postRequestedFor(urlPathEqualTo("/v2/completion")).withRequestBody(equalToJson(request))); + } } diff --git a/orchestration/src/test/resources/__files/fallbackResponse.json b/orchestration/src/test/resources/__files/fallbackResponse.json new file mode 100644 index 000000000..64852a79f --- /dev/null +++ b/orchestration/src/test/resources/__files/fallbackResponse.json @@ -0,0 +1,81 @@ +{ + "request_id": "a562703b-7fe5-97ba-b417-e8140a25fb7c", + "intermediate_results": { + "templating": [ + { + "content": "HelloWorld! Why is this phrase so famous?", + "role": "user" + } + ], + "llm": { + "id": "", + "object": "chat.completion", + "created": 1769700208, + "model": "gemini-2.5-flash", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "\"Hello, World!\" is arguably the most famous phrase in the world of programming, and its fame stems from a combination of historical significance, pedagogical utility, and cultural impact.\n\nHere's why it's so famous:\n\n1. **Historical Origin and Standardization:**\n * While simple output programs existed before, the phrase \"Hello, World!\" was popularized and standardized by **Brian Kernighan and Dennis Ritchie** in their seminal 1978 book, **\"The C Programming Language.\"**\n * In the book, the very first example program printed \"hello, world\" (lowercase, no exclamation mark initially). This book became the definitive guide for C, and C became one of the most influential programming languages ever. As C spread, so did its iconic first program.\n * Before K&R, a similar example (\"hello\") appeared in a 1974 Bell Labs internal memo by Kernighan, demonstrating the BCPL language.\n\n2. **Pedagogical Value (Teaching Tool):**\n * **First Step for Beginners:** It's the absolute simplest program that actually *does* something visible. For someone learning to code, successfully running \"Hello, World!\" is an immediate, tangible victory.\n * **Low Barrier to Entry:** It requires minimal syntax and no complex concepts (like variables, loops, functions, or data structures). It focuses solely on the core idea of output.\n * **Immediate Gratification:** Seeing \"Hello, World!\" appear on the screen provides instant feedback and motivation, confirming that the development environment is set up correctly and the basic compilation/execution process works.\n * **Introduction to Core Concepts:** It subtly introduces fundamental ideas like:\n * The concept of a program.\n * The idea of output (printing to the console).\n * The basic structure of a program in a given language.\n\n3. **Practical Utility (Testing and Verification):**\n * **Sanity Check:** When setting up a new programming language, a new development environment (IDE), a new compiler, or a new operating system, \"Hello, World!\" is the go-to program to verify that everything is working correctly. If \"Hello, World!\" doesn't run, you know the problem is fundamental to your setup, not your complex code.\n * **Minimal Viable Product (MVP) for Setup:** It's the quickest way to confirm that the toolchain (editor, compiler/interpreter, linker, runtime) is functional.\n\n4. **Cultural Significance and Rite of Passage:**\n * **Universal Rite of Passage:** Every programmer, regardless of the language they learn, almost certainly starts with \"Hello, World!\" It's a shared experience that connects developers across generations and technologies.\n * **Symbol of Beginning:** It represents the very first step into the vast and complex world of programming. It's a literal \"hello\" to the machine and the coding journey.\n * **Ubiquity:** Its presence in countless tutorials, textbooks, and online guides for virtually every programming language has cemented its status as an iconic and universally recognized phrase.\n\nIn essence, \"Hello, World!\" is famous because it's simple, effective, historically significant, and serves as a universal welcome mat for anyone stepping into the world of computer programming.", + "tool_calls": [] + }, + "finish_reason": "stop" + } + ], + "usage": { + "completion_tokens": 1728, + "prompt_tokens": 9, + "total_tokens": 1737, + "prompt_tokens_details": { + "cached_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 1024 + } + }, + "citations": [] + }, + "output_unmasking": [] + }, + "final_result": { + "id": "", + "object": "chat.completion", + "created": 1769700208, + "model": "gemini-2.5-flash", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "\"Hello, World!\" is arguably the most famous phrase in the world of programming, and its fame stems from a combination of historical significance, pedagogical utility, and cultural impact.\n\nHere's why it's so famous:\n\n1. **Historical Origin and Standardization:**\n * While simple output programs existed before, the phrase \"Hello, World!\" was popularized and standardized by **Brian Kernighan and Dennis Ritchie** in their seminal 1978 book, **\"The C Programming Language.\"**\n * In the book, the very first example program printed \"hello, world\" (lowercase, no exclamation mark initially). This book became the definitive guide for C, and C became one of the most influential programming languages ever. As C spread, so did its iconic first program.\n * Before K&R, a similar example (\"hello\") appeared in a 1974 Bell Labs internal memo by Kernighan, demonstrating the BCPL language.\n\n2. **Pedagogical Value (Teaching Tool):**\n * **First Step for Beginners:** It's the absolute simplest program that actually *does* something visible. For someone learning to code, successfully running \"Hello, World!\" is an immediate, tangible victory.\n * **Low Barrier to Entry:** It requires minimal syntax and no complex concepts (like variables, loops, functions, or data structures). It focuses solely on the core idea of output.\n * **Immediate Gratification:** Seeing \"Hello, World!\" appear on the screen provides instant feedback and motivation, confirming that the development environment is set up correctly and the basic compilation/execution process works.\n * **Introduction to Core Concepts:** It subtly introduces fundamental ideas like:\n * The concept of a program.\n * The idea of output (printing to the console).\n * The basic structure of a program in a given language.\n\n3. **Practical Utility (Testing and Verification):**\n * **Sanity Check:** When setting up a new programming language, a new development environment (IDE), a new compiler, or a new operating system, \"Hello, World!\" is the go-to program to verify that everything is working correctly. If \"Hello, World!\" doesn't run, you know the problem is fundamental to your setup, not your complex code.\n * **Minimal Viable Product (MVP) for Setup:** It's the quickest way to confirm that the toolchain (editor, compiler/interpreter, linker, runtime) is functional.\n\n4. **Cultural Significance and Rite of Passage:**\n * **Universal Rite of Passage:** Every programmer, regardless of the language they learn, almost certainly starts with \"Hello, World!\" It's a shared experience that connects developers across generations and technologies.\n * **Symbol of Beginning:** It represents the very first step into the vast and complex world of programming. It's a literal \"hello\" to the machine and the coding journey.\n * **Ubiquity:** Its presence in countless tutorials, textbooks, and online guides for virtually every programming language has cemented its status as an iconic and universally recognized phrase.\n\nIn essence, \"Hello, World!\" is famous because it's simple, effective, historically significant, and serves as a universal welcome mat for anyone stepping into the world of computer programming.", + "tool_calls": [] + }, + "finish_reason": "stop" + } + ], + "usage": { + "completion_tokens": 1728, + "prompt_tokens": 9, + "total_tokens": 1737, + "prompt_tokens_details": { + "cached_tokens": 0 + }, + "completion_tokens_details": { + "reasoning_tokens": 1024 + } + }, + "citations": [] + }, + "intermediate_failures": [ + { + "request_id": "a562703b-7fe5-97ba-b417-e8140a25fb7c", + "code": 400, + "message": "400 - Request Body: Model broken_name not supported.", + "location": "Request Body", + "headers": { + "Content-Type": "application/json" + } + } + ] +} \ No newline at end of file diff --git a/orchestration/src/test/resources/fallbackRequest.json b/orchestration/src/test/resources/fallbackRequest.json new file mode 100644 index 000000000..3c378e4a8 --- /dev/null +++ b/orchestration/src/test/resources/fallbackRequest.json @@ -0,0 +1,97 @@ +{ + "config" : { + "modules" : [ { + "prompt_templating" : { + "prompt" : { + "template" : [ { + "content" : "HelloWorld! Why is this phrase so famous?", + "role" : "user" + } ], + "defaults" : { }, + "tools" : [ ] + }, + "model" : { + "name" : "broken_name", + "version" : "latest", + "params" : { }, + "timeout" : 600, + "max_retries" : 2 + } + }, + "filtering" : { + "input" : { + "filters" : [ { + "type" : "llama_guard_3_8b", + "config" : { + "self_harm" : true + } + } ] + } + }, + "grounding" : { + "type" : "document_grounding_service", + "config" : { + "filters" : [ { + "data_repositories" : [ "*" ], + "data_repository_type" : "help.sap.com", + "data_repository_metadata" : [ ], + "document_metadata" : [ ], + "chunk_metadata" : [ ] + } ], + "placeholders" : { + "input" : [ "userMessage" ], + "output" : "groundingContext" + } + } + } + }, { + "prompt_templating" : { + "prompt" : { + "template" : [ { + "content" : "HelloWorld! Why is this phrase so famous?", + "role" : "user" + } ], + "defaults" : { }, + "tools" : [ ] + }, + "model" : { + "name" : "gpt-4o-mini", + "version" : "latest", + "params" : { + "temperature" : 0.0 + }, + "timeout" : 600, + "max_retries" : 2 + } + }, + "filtering" : { + "input" : { + "filters" : [ { + "type" : "llama_guard_3_8b", + "config" : { + "self_harm" : true + } + } ] + } + }, + "grounding" : { + "type" : "document_grounding_service", + "config" : { + "filters" : [ { + "data_repositories" : [ "*" ], + "data_repository_type" : "help.sap.com", + "data_repository_metadata" : [ ], + "document_metadata" : [ ], + "chunk_metadata" : [ ] + } ], + "placeholders" : { + "input" : [ "userMessage" ], + "output" : "groundingContext" + } + } + } + } ] + }, + "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/controllers/OrchestrationController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java index 805e5fc19..89bb3de57 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 @@ -74,6 +74,16 @@ ResponseEntity streamChatCompletion() { return ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(emitter); } + @GetMapping("/completionWithFallback") + Object completionWithFallback( + @Nullable @RequestParam(value = "format", required = false) final String format) { + final var response = service.completionWithFallback("HelloWorld!"); + if ("json".equals(format)) { + return response; + } + return response.getContent(); + } + @GetMapping("/template") Object template(@Nullable @RequestParam(value = "format", required = false) final String format) { final var response = service.template("German"); 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 f2c6e4ef1..a6efc1301 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 @@ -93,8 +93,8 @@ public OrchestrationChatResponse completionWithFallback(@Nonnull final String fa public OrchestrationChatResponse completionWithFallbackAllFail(@Nonnull final String famousPhrase) { val prompt = new OrchestrationPrompt(famousPhrase + " Why is this phrase so famous?"); val brokenConfig = new OrchestrationModuleConfig().withLlmConfig(new OrchestrationAiModel("broken_name", Map.of(), "latest")); - val alsoBrokenConfig = new OrchestrationModuleConfig().withLlmConfig(new OrchestrationAiModel("broken_name_2", Map.of(), "latest")); - OrchestrationModuleConfig[] configs = new OrchestrationModuleConfig[] { brokenConfig, alsoBrokenConfig }; + val secondBrokenConfig = new OrchestrationModuleConfig().withLlmConfig(new OrchestrationAiModel("broken_name_2", Map.of(), "latest")); + OrchestrationModuleConfig[] configs = new OrchestrationModuleConfig[] { brokenConfig, secondBrokenConfig }; return client.chatCompletion(prompt, configs); } 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 73f8afdc6..132cabdd4 100644 --- a/sample-code/spring-app/src/main/resources/static/index.html +++ b/sample-code/spring-app/src/main/resources/static/index.html @@ -266,6 +266,18 @@

Orchestration

+
  • +
    + +
    + Chat request with a list of LLM modules to call. If the first fails (which will happen here), the next module is called. +
    +
    +