diff --git a/docs/guides/ORCHESTRATION_CHAT_COMPLETION.md b/docs/guides/ORCHESTRATION_CHAT_COMPLETION.md index 4ac242b4..d1a23f18 100644 --- a/docs/guides/ORCHESTRATION_CHAT_COMPLETION.md +++ b/docs/guides/ORCHESTRATION_CHAT_COMPLETION.md @@ -186,6 +186,37 @@ var result = In this example, the input will be masked before the call to the LLM and will remain masked in the output. +### Grounding + +Use the grounding module to provide additional context to the AI model. + +```java + var message = + Message.user( + "{{?groundingInput}} Use the following information as additional context: {{?groundingOutput}}"); + var prompt = + new OrchestrationPrompt(Map.of("groundingInput", "What does Joule do?"), message); + + var filterInner = + DocumentGroundingFilter.create().id("someID").dataRepositoryType(DataRepositoryType.VECTOR); + var groundingConfigConfig = + GroundingModuleConfigConfig.create() + .inputParams(List.of("groundingInput")) + .outputParam("groundingOutput") + .addFiltersItem(filterInner); + + var groundingConfig = + GroundingModuleConfig.create() + .type(GroundingModuleConfig.TypeEnum.DOCUMENT_GROUNDING_SERVICE) + .config(groundingConfigConfig); + var configWithGrounding = config.withGroundingConfig(groundingConfig); + + var result = + new OrchestrationClient().chatCompletion(prompt, configWithGrounding); +``` + +In this example, the AI model is provided with additional context in the form of grounding information. Note, that it is necessary to provide the grounding input via one or more input variables. + ### Set model parameters Change your LLM configuration to add model parameters: 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 ab0b2619..f5483d79 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 @@ -70,6 +70,7 @@ static ModuleConfigs toModuleConfigs(@Nonnull final OrchestrationModuleConfig co Option.of(config.getFilteringConfig()).forEach(moduleConfig::filteringModuleConfig); Option.of(config.getMaskingConfig()).forEach(moduleConfig::maskingModuleConfig); + Option.of(config.getGroundingConfig()).forEach(moduleConfig::groundingModuleConfig); return moduleConfig; } diff --git a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java index 0bed1ddc..2d603678 100644 --- a/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java +++ b/orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationModuleConfig.java @@ -1,6 +1,7 @@ package com.sap.ai.sdk.orchestration; import com.sap.ai.sdk.orchestration.model.FilteringModuleConfig; +import com.sap.ai.sdk.orchestration.model.GroundingModuleConfig; import com.sap.ai.sdk.orchestration.model.InputFilteringConfig; import com.sap.ai.sdk.orchestration.model.LLMModuleConfig; import com.sap.ai.sdk.orchestration.model.MaskingModuleConfig; @@ -83,6 +84,15 @@ public class OrchestrationModuleConfig { */ @Nullable FilteringModuleConfig filteringConfig; + /** + * A grounding configuration to provide additional context to the AI model. + * + * @link SAP + * AI Core: Orchestration - + * @since 1.1.0 + */ + @Nullable GroundingModuleConfig groundingConfig; + /** * Creates a new configuration with the given LLM configuration. * 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 bc1eb910..8cb554f0 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 @@ -85,6 +85,37 @@ void testCompletion() { assertThat(result.getContent()).isNotEmpty(); } + @Test + void testGrounding() { + stubFor( + post(anyUrl()) + .willReturn( + aResponse() + .withBodyFile("groundingResponse.json") + .withHeader("Content-Type", "application/json"))); + final var response = client.chatCompletion(prompt, config); + final var result = response.getOriginalResponse(); + var llmChoice = + ((LLMModuleResultSynchronous) result.getOrchestrationResult()).getChoices().get(0); + + final var groundingData = + (Map) result.getModuleResults().getGrounding().getData(); + assertThat(groundingData.get("grounding_query")).isEqualTo("grounding call"); + assertThat(groundingData.get("grounding_result").toString()) + .startsWith("Joule is the AI copilot that truly understands your business."); + assertThat(result.getModuleResults().getGrounding().getMessage()).isEqualTo("grounding result"); + assertThat(result.getModuleResults().getTemplating().get(0).getContent()) + .startsWith( + "What does Joule do? Use the following information as additional context: Joule is the AI copilot that truly understands your business."); + assertThat(llmChoice.getMessage().getContent()) + .startsWith( + "Joule is an AI copilot that revolutionizes how users interact with their SAP business systems."); + assertThat(llmChoice.getFinishReason()).isEqualTo("stop"); + assertThat(llmChoice.getMessage().getContent()) + .startsWith( + "Joule is an AI copilot that revolutionizes how users interact with their SAP business systems."); + } + @Test void testTemplating() throws IOException { stubFor( diff --git a/orchestration/src/test/resources/__files/groundingResponse.json b/orchestration/src/test/resources/__files/groundingResponse.json new file mode 100644 index 00000000..0c4c4e8b --- /dev/null +++ b/orchestration/src/test/resources/__files/groundingResponse.json @@ -0,0 +1,62 @@ +{ + "request_id": "0d9d7ce3-9ded-481f-80c6-977e78e2e905", + "module_results": { + "grounding": { + "message": "grounding result", + "data": { + "grounding_query": "grounding call", + "grounding_result": "Joule is the AI copilot that truly understands your business. Joule revolutionizes how you interact with your SAP business systems, making every touchpoint count and every task simpler.```It enables the companion of the Intelligent Enterprise, guiding you through content discovery within SAP Ecosystem, and giving a transparent role-based access to the relevant processes from everywhere. This is the one assistant experience, a unified and delightful user experience across SAP’s \u01ee solution portfolio." + } + }, + "templating": [ + { + "role": "user", + "content": "What does Joule do? Use the following information as additional context: Joule is the AI copilot that truly understands your business. Joule revolutionizes how you interact with your SAP business systems, making every touchpoint count and every task simpler.```It enables the companion of the Intelligent Enterprise, guiding you through content discovery within SAP Ecosystem, and giving a transparent role-based access to the relevant processes from everywhere. This is the one assistant experience, a unified and delightful user experience across SAP’s \u01ee solution portfolio." + } + ], + "llm": { + "id": "chatcmpl-AbRlNdsyQJfvBINnw3MOTP77WSE4X", + "object": "chat.completion", + "created": 1733488221, + "model": "gpt-35-turbo", + "system_fingerprint": "fp_808245b034", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Joule is an AI copilot that revolutionizes how users interact with their SAP business systems. It enables the companion of the Intelligent Enterprise, guiding users through content discovery within the SAP Ecosystem and providing transparent role-based access to relevant processes from anywhere. Joule aims to provide a unified and delightful user experience across SAP's solution portfolio." + }, + "finish_reason": "stop" + } + ], + "usage": { + "completion_tokens": 68, + "prompt_tokens": 113, + "total_tokens": 181 + } + } + }, + "orchestration_result": { + "id": "chatcmpl-AbRlNdsyQJfvBINnw3MOTP77WSE4X", + "object": "chat.completion", + "created": 1733488221, + "model": "gpt-35-turbo", + "system_fingerprint": "fp_808245b034", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Joule is an AI copilot that revolutionizes how users interact with their SAP business systems. It enables the companion of the Intelligent Enterprise, guiding users through content discovery within the SAP Ecosystem and providing transparent role-based access to relevant processes from anywhere. Joule aims to provide a unified and delightful user experience across SAP's solution portfolio." + }, + "finish_reason": "stop" + } + ], + "usage": { + "completion_tokens": 68, + "prompt_tokens": 113, + "total_tokens": 181 + } + } +} \ 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 1a499e0f..f6f1b549 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 @@ -13,6 +13,10 @@ import com.sap.ai.sdk.orchestration.OrchestrationModuleConfig; import com.sap.ai.sdk.orchestration.OrchestrationPrompt; import com.sap.ai.sdk.orchestration.model.DPIEntities; +import com.sap.ai.sdk.orchestration.model.DataRepositoryType; +import com.sap.ai.sdk.orchestration.model.DocumentGroundingFilter; +import com.sap.ai.sdk.orchestration.model.GroundingModuleConfig; +import com.sap.ai.sdk.orchestration.model.GroundingModuleConfigConfig; import com.sap.ai.sdk.orchestration.model.Template; import java.util.List; import java.util.Map; @@ -207,4 +211,35 @@ OrchestrationChatResponse maskingPseudonymization() { return client.chatCompletion(prompt, configWithMasking); } + + /** + * Using grounding to provide additional context to the AI model. + * + * @link SAP + * AI Core: Orchestration - Grounding + */ + @GetMapping("/grounding") + @Nonnull + OrchestrationChatResponse grounding() { + final var message = + Message.user( + "{{?groundingInput}} Use the following information as additional context: {{?groundingOutput}}"); + final var prompt = + new OrchestrationPrompt(Map.of("groundingInput", "What does Joule do?"), message); + + final var filterInner = + DocumentGroundingFilter.create().id("someID").dataRepositoryType(DataRepositoryType.VECTOR); + final var groundingConfigConfig = + GroundingModuleConfigConfig.create() + .inputParams(List.of("groundingInput")) + .outputParam("groundingOutput") + .addFiltersItem(filterInner); + final var groundingConfig = + GroundingModuleConfig.create() + .type(GroundingModuleConfig.TypeEnum.DOCUMENT_GROUNDING_SERVICE) + .config(groundingConfigConfig); + final var configWithGrounding = config.withGroundingConfig(groundingConfig); + + return client.chatCompletion(prompt, configWithGrounding); + } } 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 27a5cfff..497c382e 100644 --- a/sample-code/spring-app/src/main/resources/static/index.html +++ b/sample-code/spring-app/src/main/resources/static/index.html @@ -73,6 +73,7 @@

Endpoints

  • /orchestration/filter/NUMBER_0 Strict filter (fails)
  • /orchestration/maskingAnonymization
  • /orchestration/maskingPseudonymization
  • +
  • /orchestration/grounding
  • Foundation Models

    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 d32c6930..35944f7f 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 @@ -154,8 +154,16 @@ void testMaskingPseudonymization() { @Test @DisabledIfSystemProperty(named = "aicore.landscape", matches = "production") void testGrounding() { - // Placeholder for grounding test assertThat(System.getProperty("aicore.landscape")).isNotEqualTo("production"); + var response = controller.grounding(); + var result = response.getOriginalResponse(); + var llmChoice = + ((LLMModuleResultSynchronous) result.getOrchestrationResult()).getChoices().get(0); + assertThat(response).isNotNull(); + assertThat(llmChoice.getFinishReason()).isEqualTo("stop"); + assertThat(result.getModuleResults().getGrounding()).isNotNull(); + assertThat(result.getModuleResults().getGrounding().getData()).isNotNull(); + assertThat(result.getModuleResults().getGrounding().getMessage()).isEqualTo("grounding result"); } @Test