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

feat: Orchestration Prompt #140

Merged
merged 18 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 69 additions & 100 deletions docs/guides/ORCHESTRATION_CHAT_COMPLETION.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,90 +68,81 @@ In addition to the prerequisites above, we assume you have already set up the fo
```

</details>

### Chat completion with Templates

Use a chat completion template to generate a response in German:
### Create a Client

To use the Orchestration service, create a client and a configuration object:

```java
var llmConfig = LLMModuleConfig.create().modelName("gpt-35-turbo").modelParams(Map.of());
var client = new OrchestrationClient();

var inputParams =
Map.of("input", "Reply with 'Orchestration Service is working!' in German");
var template = ChatMessage.create().role("user").content("{{?input}}");
var templatingConfig = TemplatingModuleConfig.create().template(template);
var config = new OrchestrationModuleConfig()
.withLlmConfig(LLMModuleConfig.create().modelName("gpt-35-turbo").modelParams(Map.of()));
```

var config =
CompletionPostRequest.create()
.orchestrationConfig(
OrchestrationConfig.create()
.moduleConfigurations(
ModuleConfigs.create()
.llmModuleConfig(llmConfig)
.templatingModuleConfig(templatingConfig)))
.inputParams(inputParams);
Please also refer to [our sample code](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java) for this and all following code examples.

### Chat Completion

CompletionPostResponse result =
new OrchestrationClient().chatCompletion(config);
Use the Orchestration service to generate a response to a user message:

```java
var prompt = new OrchestrationPrompt("Hello world! Why is this phrase so famous?");

var result = client.chatCompletion(prompt, config);

String messageResult =
result.getOrchestrationResult().getChoices().get(0).getMessage().getContent();
result.getOrchestrationResult().getChoices().get(0).getMessage().getContent();
```

See [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java)
In this example, the Orchestration service generates a response to the user message "Hello world! Why is this phrase so famous?".
The LLM response is available as the first choice under the `result.getOrchestrationResult()` object.

### Message history
### Chat completion with Templates

Include a message history to maintain context in the conversation:
Use a prepared template and execute requests with by passing only the input parameters:

```java
var llmConfig = LLMModuleConfig.create().modelName("gpt-35-turbo").modelParams(Map.of());
var template = ChatMessage.create().role("user").content("Reply with 'Orchestration Service is working!' in {{?language}}");
MatKuhr marked this conversation as resolved.
Show resolved Hide resolved
var templatingConfig = TemplatingModuleConfig.create().template(template);
var configWithTemplate = config.withTemplateConfig(templatingConfig);

var inputParams = Map.of("language", "German");
var prompt = new OrchestrationPrompt(inputParams);

var result = client.chatCompletion(prompt, configWithTemplate);
```

In this case the template is defined with the placeholder `{{?language}}` which is replaced by the value `German` in the input parameters.

List<ChatMessage> messagesHistory =
List.of(
ChatMessage.create().role("user").content("What is the capital of France?"),
ChatMessage.create().role("assistant").content("The capital of France is Paris."));
### Message history

Include a message history to maintain context in the conversation:

```java
var messagesHistory =
List.of(
ChatMessage.create().role("user").content("What is the capital of France?"),
ChatMessage.create().role("assistant").content("The capital of France is Paris."));
var message =
ChatMessage.create().role("user").content("What is the typical food there?");
var templatingConfig = TemplatingModuleConfig.create().template(message);

var config =
CompletionPostRequest.create()
.orchestrationConfig(
OrchestrationConfig.create()
.moduleConfigurations(
ModuleConfigs.create()
.llmModuleConfig(llmConfig)
.templatingModuleConfig(templatingConfig)))
.messagesHistory(messagesHistory);

CompletionPostResponse result =
new OrchestrationClient().chatCompletion(config);
ChatMessage.create().role("user").content("What is the typical food there?");

String messageResult =
result.getOrchestrationResult().getChoices().get(0).getMessage().getContent();
```
var prompt = new OrchestrationPrompt(message).messageHistory(messagesHistory);

See [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java)
var result = new OrchestrationClient().chatCompletion(prompt, config);
```

### Chat completion filter

Apply content filtering to the chat completion:

```java
var llmConfig = LLMModuleConfig.create().modelName("gpt-35-turbo").modelParams(Map.of());

var inputParams =
Map.of(
"disclaimer",
"```DISCLAIMER: The area surrounding the apartment is known for prostitutes and gang violence including armed conflicts, gun violence is frequent.");
var template =
ChatMessage.create()
.role("user")
.content(
"Create a rental posting for subletting my apartment in the downtown area. Keep it short. Make sure to add the following disclaimer to the end. Do not change it! {{?disclaimer}}");
var templatingConfig = TemplatingModuleConfig.create().template(template);
var prompt = new OrchestrationPrompt(
"""
Create a rental posting for subletting my apartment in the downtown area. Keep it short. Make sure to add the following disclaimer to the end. Do not change it!

```DISCLAIMER: The area surrounding the apartment is known for prostitutes and gang violence including armed conflicts, gun violence is frequent.
""");

var filterStrict =
FilterConfig.create()
Expand All @@ -176,40 +167,21 @@ var filterLoose =
var filteringConfig =
FilteringModuleConfig.create()
// changing the input to filterLoose will allow the message to pass
.input(FilteringConfig.create().filters(filterStrict))
.output(FilteringConfig.create().filters(filterStrict));

var config =
CompletionPostRequest.create()
.orchestrationConfig(
OrchestrationConfig.create()
.moduleConfigurations(
ModuleConfigs.create()
.llmModuleConfig(llmConfig)
.templatingModuleConfig(templatingConfig)
.filteringModuleConfig(filteringConfig)))
.inputParams(inputParams);
.input(InputFilteringConfig.create().filters(filterStrict))
.output(OutputFilteringConfig.create().filters(filterStrict));

// this fails with Bad Request because the strict filter prohibits the input message
CompletionPostResponse result =
new OrchestrationClient().chatCompletion(config);
var configWithFilter = config.withFilteringConfig(filteringConfig);

String messageResult =
result.getOrchestrationResult().getChoices().get(0).getMessage().getContent();
// this fails with Bad Request because the strict filter prohibits the input message
var result =
new OrchestrationClient().chatCompletion(prompt, configWithFilter);
```

See [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java)

### Data masking

Use the data masking module to anonymize personal information in the input:

```java
var inputParams = Map.of("privateInfo", "Patrick Morgan +49 (970) 333-3833");
var template =
ChatMessage.create().role("user").content("What is the nationality of {{?privateInfo}}");
var templatingConfig = TemplatingModuleConfig.create().template(template);

var maskingProvider =
MaskingProviderConfig.create()
.type(MaskingProviderConfig.TypeEnum.SAP_DATA_PRIVACY_INTEGRATION)
Expand All @@ -218,29 +190,26 @@ var maskingProvider =
DPIEntityConfig.create().type(DPIEntities.PHONE),
DPIEntityConfig.create().type(DPIEntities.PERSON));
var maskingConfig = MaskingModuleConfig.create().maskingProviders(maskingProvider);
var configWithMasking = config.withMaskingConfig(maskingConfig);

CompletionPostRequest config =
CompletionPostRequest.create()
.orchestrationConfig(
OrchestrationConfig.create()
.moduleConfigurations(
ModuleConfigs.create()
.llmModuleConfig(LLM_CONFIG)
.templatingModuleConfig(templatingConfig)
.maskingModuleConfig(maskingConfig)))
.inputParams(inputParams);
var systemMessage = ChatMessage.create()
.role("system")
.content("Please evaluate the following user feedback and judge if the sentiment is positive or negative.");
var userMessage = ChatMessage.create()
.role("user")
.content("""
I think the SDK is good, but could use some further enhancements.
My architect Alice and manager Bob pointed out that we need the grounding capabilities, which aren't supported yet.
""");

CompletionPostResponse result =
new OrchestrationClient().chatCompletion(config);
var prompt = new OrchestrationPrompt(systemMessage, userMessage);

String messageResult =
result.getOrchestrationResult().getChoices().get(0).getMessage().getContent();
var result =
new OrchestrationClient().chatCompletion(prompt, configWithMasking);
```

In this example, the input will be masked before the call to the LLM. Note that data cannot be unmasked in the LLM output.

See [an example in our Spring Boot application](../../sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java)

### Set model parameters

Change your LLM module configuration to add model parameters:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.sap.ai.sdk.orchestration;

import com.sap.ai.sdk.orchestration.client.model.CompletionPostRequest;
import com.sap.ai.sdk.orchestration.client.model.ModuleConfigs;
import com.sap.ai.sdk.orchestration.client.model.OrchestrationConfig;
import com.sap.ai.sdk.orchestration.client.model.TemplatingModuleConfig;
import io.vavr.control.Option;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.val;

/** Factory to create all data objects from an orchestration configuration. */
@NoArgsConstructor(access = AccessLevel.NONE)
final class ConfigToRequestTransformer {
@Nonnull
static CompletionPostRequest 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);

return CompletionPostRequest.create()
.orchestrationConfig(
OrchestrationConfig.create().moduleConfigurations(toModuleConfigs(configCopy)))
.inputParams(prompt.getTemplateParameters())
.messagesHistory(prompt.getMessagesHistory());
}

@Nonnull
static TemplatingModuleConfig toTemplateModuleConfig(
@Nonnull final OrchestrationPrompt prompt, @Nullable final TemplatingModuleConfig template) {
/*
* Currently, we have to merge the prompt into the template configuration.
* This works around the limitation that the template config is required.
* This comes at the risk that the prompt unintentionally contains the templating pattern "{{? .. }}".
* In this case, the request will fail, since the templating module will try to resolve the parameter.
* To be fixed with https://github.tools.sap/AI/llm-orchestration/issues/662
*/
val messages = Option.of(template).map(TemplatingModuleConfig::getTemplate).getOrElse(List::of);
val messagesWithPrompt = new ArrayList<>(messages);
messagesWithPrompt.addAll(prompt.getMessages());
if (messagesWithPrompt.isEmpty()) {
throw new IllegalStateException(
"A prompt is required. Pass at least one message or configure a template with messages or a template reference.");
}
return TemplatingModuleConfig.create().template(messagesWithPrompt);
}

@Nonnull
static ModuleConfigs toModuleConfigs(@Nonnull final OrchestrationModuleConfig config) {
val llmConfig =
Option.of(config.getLlmConfig())
.getOrElseThrow(() -> new IllegalStateException("LLM config is required."));

//noinspection DataFlowIssue the template is always non-null here
val moduleConfig =
ModuleConfigs.create()
.llmModuleConfig(llmConfig)
.templatingModuleConfig(config.getTemplateConfig());

Option.of(config.getFilteringConfig()).forEach(moduleConfig::filteringModuleConfig);
Option.of(config.getMaskingConfig()).forEach(moduleConfig::maskingModuleConfig);

return moduleConfig;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import com.sap.ai.sdk.core.AiCoreService;
import com.sap.ai.sdk.orchestration.client.model.CompletionPostRequest;
import com.sap.ai.sdk.orchestration.client.model.CompletionPostResponse;
import com.sap.ai.sdk.orchestration.client.model.ModuleConfigs;
import com.sap.ai.sdk.orchestration.client.model.OrchestrationConfig;
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException;
import com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationNotFoundException;
Expand Down Expand Up @@ -67,13 +69,17 @@ public OrchestrationClient(@Nonnull final AiCoreDeployment deployment) {
/**
* Generate a completion for the given prompt.
*
* @param request The request to send to orchestration.
* @param prompt The {@link OrchestrationPrompt} to send to orchestration.
* @param config The {@link ModuleConfigs} configuration to use for the completion.
* @return the completion output
* @throws OrchestrationClientException if the request fails
* @throws OrchestrationClientException if the request fails.
*/
@Nonnull
public CompletionPostResponse chatCompletion(@Nonnull final CompletionPostRequest request)
public CompletionPostResponse chatCompletion(
@Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config)
throws OrchestrationClientException {

val request = toCompletionPostRequest(prompt, config);
return executeRequest(request);
}

Expand All @@ -93,8 +99,8 @@ public CompletionPostResponse chatCompletion(@Nonnull final CompletionPostReques
*
* <p>Alternatively, you can call this method directly with a fully custom request object.
*
* @param request The request DTO to send to orchestration.
* @return The response DTO from orchestration.
* @param request The request data object to send to orchestration.
* @return The response data object from orchestration.
* @throws OrchestrationClientException If the request fails.
*/
@Nonnull
Expand All @@ -112,6 +118,20 @@ public CompletionPostResponse executeRequest(@Nonnull final CompletionPostReques
return executeRequest(postRequest);
}

/**
* Convert the given prompt and config into a low-level request data object. The data object
* 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.
* @return The low-level request data object to send to orchestration.
*/
@Nonnull
public static CompletionPostRequest toCompletionPostRequest(
@Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config) {
return ConfigToRequestTransformer.toCompletionPostRequest(prompt, config);
}

@SuppressWarnings("UnstableApiUsage")
@Nonnull
CompletionPostResponse executeRequest(@Nonnull final BasicClassicHttpRequest request) {
Expand Down
Loading