Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test: Using Orchestration Grounding module #228

Merged
merged 15 commits into from
Dec 10, 2024
31 changes: 31 additions & 0 deletions docs/guides/ORCHESTRATION_CHAT_COMPLETION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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");
groundingConfigConfig.setFilters(List.of(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 information in the user message.
Jonas-Isr marked this conversation as resolved.
Show resolved Hide resolved

### Set model parameters

Change your LLM configuration to add model parameters:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -83,6 +84,14 @@ public class OrchestrationModuleConfig {
*/
@Nullable FilteringModuleConfig filteringConfig;

/**
* A grounding configuration to provide additional context to the AI model.
*
* @link <a href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/grounding">SAP
* AI Core: Orchestration - </a>
*/
Jonas-Isr marked this conversation as resolved.
Show resolved Hide resolved
@Nullable GroundingModuleConfig groundingConfig;

/**
* Creates a new configuration with the given LLM configuration.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,35 @@ 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);

assertThat(result.getModuleResults().getGrounding().getData().toString())
Copy link
Contributor

@newtork newtork Dec 10, 2024

Choose a reason for hiding this comment

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

(Minor/Comment)

I think AssertJ also has ).hasToString( as replacement for .toString()).isEqualTo(.

Do we actually need the string check when we compare the values anyway? AFAIK we didn't do this in other places.

Copy link
Member Author

@Jonas-Isr Jonas-Isr Dec 10, 2024

Choose a reason for hiding this comment

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

I cannot compare the values directly here, as getModuleResults().getGrounding().getData() returns an Object so that I cannot access "grounding_query" or "grounding_result" via the generated code. This is why I compare it as a String.

Also, I use .toString()).startsWith(... now to not need to provide the complete rather long expected String.

If you feel there is a better way to do that, please let me know :)

Copy link
Member

Choose a reason for hiding this comment

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

Object is somewhat misleading, actually this will always be a Map<String, Object>. (Ideally, our generator would be improved in that regard).

So you could cast to that and use .get("grounding_query")

Copy link
Member Author

Choose a reason for hiding this comment

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

Casting this adds a warning, though. Is that a problem?

Copy link
Member

Choose a reason for hiding this comment

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

yes, it's just a test assertion, so it's okay if this ever fails (which would mean we've changed our generator but didn't update tests)

.isEqualTo(
"{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 Ǯ solution portfolio.}");
Jonas-Isr marked this conversation as resolved.
Show resolved Hide resolved
assertThat(result.getModuleResults().getGrounding().getMessage()).isEqualTo("grounding result");
assertThat(result.getModuleResults().getTemplating().get(0).getContent())
.isEqualTo(
"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.");
assertThat(llmChoice.getMessage().getContent())
.isEqualTo(
"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.");
assertThat(llmChoice.getFinishReason()).isEqualTo("stop");
assertThat(llmChoice.getMessage().getContent())
.isEqualTo(
"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.");
}

@Test
void testTemplating() throws IOException {
stubFor(
Expand Down
62 changes: 62 additions & 0 deletions orchestration/src/test/resources/__files/groundingResponse.json
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -207,4 +211,35 @@ OrchestrationChatResponse maskingPseudonymization() {

return client.chatCompletion(prompt, configWithMasking);
}

/**
* Using grounding to provide additional context to the AI model.
*
* @link <a href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/grounding">SAP
* AI Core: Orchestration - Grounding</a>
*/
@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");
groundingConfigConfig.setFilters(List.of(filterInner));
Jonas-Isr marked this conversation as resolved.
Show resolved Hide resolved
final var groundingConfig =
GroundingModuleConfig.create()
.type(GroundingModuleConfig.TypeEnum.DOCUMENT_GROUNDING_SERVICE)
.config(groundingConfigConfig);
final var configWithGrounding = config.withGroundingConfig(groundingConfig);

return client.chatCompletion(prompt, configWithGrounding);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ <h2>Endpoints</h2>
<li><a href="/orchestration/filter/NUMBER_0">/orchestration/filter/NUMBER_0</a> Strict filter (fails)</li>
<li><a href="/orchestration/maskingAnonymization">/orchestration/maskingAnonymization</a></li>
<li><a href="/orchestration/maskingPseudonymization">/orchestration/maskingPseudonymization</a></li>
<li><a href="/orchestration/grounding">/orchestration/grounding</a></li>
</ul>
</li>
<li><h3>Foundation Models</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading