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 6 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
Expand Up @@ -19,11 +19,11 @@ final class ConfigToRequestTransformer {
@Nonnull
static CompletionPostRequest toCompletionPostRequest(
@Nonnull final OrchestrationPrompt prompt, @Nonnull final OrchestrationModuleConfig config) {
val template = toTemplateModuleConfig(prompt, config.getTemplate());
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.withTemplate(template);
val configCopy = config.withTemplateConfig(template);

return CompletionPostRequest.create()
.orchestrationConfig(
Expand Down Expand Up @@ -62,7 +62,7 @@ static ModuleConfigs toModuleConfigs(@Nonnull final OrchestrationModuleConfig co
val moduleConfig =
ModuleConfigs.create()
.llmModuleConfig(llmConfig)
.templatingModuleConfig(config.getTemplate());
.templatingModuleConfig(config.getTemplateConfig());

Option.of(config.getFilteringConfig()).forEach(moduleConfig::filteringModuleConfig);
Option.of(config.getMaskingConfig()).forEach(moduleConfig::maskingModuleConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class OrchestrationModuleConfig {
* A template to be populated with input parameters. Upon request execution, this template will be
* enhanced with any messages and parameter values from {@link OrchestrationPrompt}.
*/
@Nullable TemplatingModuleConfig template;
@Nullable TemplatingModuleConfig templateConfig;

/** A masking configuration to pseudonymous or anonymize sensitive data in the input. */
@Nullable MaskingModuleConfig maskingConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.sap.ai.sdk.orchestration.client.model.MaskingModuleConfig;
import com.sap.ai.sdk.orchestration.client.model.MaskingProviderConfig;
import com.sap.ai.sdk.orchestration.client.model.OutputFilteringConfig;
import com.sap.ai.sdk.orchestration.client.model.TemplatingModuleConfig;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand All @@ -29,14 +30,11 @@
@RestController
@RequestMapping("/orchestration")
class OrchestrationController {
static final LLMModuleConfig LLM_CONFIG =
LLMModuleConfig.create().modelName("gpt-35-turbo").modelParams(Map.of());

private static final OrchestrationClient CLIENT = new OrchestrationClient();

static final String MODEL = "gpt-35-turbo";

private static final LLMModuleConfig LLM_CONFIG =
LLMModuleConfig.create().modelName(MODEL).modelParams(Map.of());
private static final OrchestrationModuleConfig CONFIG =
private final OrchestrationClient client = new OrchestrationClient();
private final OrchestrationModuleConfig config =
new OrchestrationModuleConfig().withLlmConfig(LLM_CONFIG);

/**
Expand All @@ -47,10 +45,9 @@ class OrchestrationController {
@GetMapping("/completion")
@Nonnull
public CompletionPostResponse completion() {

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

return CLIENT.chatCompletion(prompt, CONFIG);
return client.chatCompletion(prompt, config);
}

/**
Expand All @@ -61,12 +58,17 @@ public CompletionPostResponse completion() {
@GetMapping("/template")
@Nonnull
public CompletionPostResponse template() {
final var message = ChatMessage.create().role("user").content("{{?input}}");
final var inputParams =
Map.of("input", "Reply with 'Orchestration Service is working!' in German");
final var template =
ChatMessage.create()
.role("user")
.content("Reply with 'Orchestration Service is working!' in {{?language}}");
final var templatingConfig = TemplatingModuleConfig.create().template(template);
final var configWithTemplate = config.withTemplateConfig(templatingConfig);

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

final var prompt = new OrchestrationPrompt(inputParams, message);
return CLIENT.chatCompletion(prompt, CONFIG);
return client.chatCompletion(prompt, configWithTemplate);
}

/**
Expand All @@ -86,7 +88,7 @@ public CompletionPostResponse messagesHistory() {

final var prompt = new OrchestrationPrompt(message).messageHistory(messagesHistory);

return CLIENT.chatCompletion(prompt, CONFIG);
return client.chatCompletion(prompt, config);
}

/**
Expand All @@ -99,7 +101,6 @@ public CompletionPostResponse messagesHistory() {
@Nonnull
public CompletionPostResponse filter(
@Nonnull @PathVariable("threshold") final AzureThreshold threshold) {

final var prompt =
new OrchestrationPrompt(
"""
Expand All @@ -108,8 +109,9 @@ public CompletionPostResponse filter(
```DISCLAIMER: The area surrounding the apartment is known for prostitutes and gang violence including armed conflicts, gun violence is frequent.
""");
final var filterConfig = createAzureContentFilter(threshold);
final var configWithFilter = config.withFilteringConfig(filterConfig);

return CLIENT.chatCompletion(prompt, CONFIG.withFilteringConfig(filterConfig));
return client.chatCompletion(prompt, configWithFilter);
}

/**
Expand Down Expand Up @@ -162,8 +164,9 @@ public CompletionPostResponse maskingAnonymization() {
final var prompt = new OrchestrationPrompt(systemMessage, userMessage);
final var maskingConfig =
createMaskingConfig(MaskingProviderConfig.MethodEnum.ANONYMIZATION, DPIEntities.PERSON);
final var configWithMasking = config.withMaskingConfig(maskingConfig);

return CLIENT.chatCompletion(prompt, CONFIG.withMaskingConfig(maskingConfig));
return client.chatCompletion(prompt, configWithMasking);
}

/**
Expand Down Expand Up @@ -202,8 +205,9 @@ public CompletionPostResponse maskingPseudonymization() {
MaskingProviderConfig.MethodEnum.PSEUDONYMIZATION,
DPIEntities.PERSON,
DPIEntities.EMAIL);
final var configWithMasking = config.withMaskingConfig(maskingConfig);

return CLIENT.chatCompletion(prompt, CONFIG.withMaskingConfig(maskingConfig));
return client.chatCompletion(prompt, configWithMasking);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ void testTemplate() {
assertThat(llm.getId()).isNotEmpty();
assertThat(llm.getObject()).isEqualTo("chat.completion");
assertThat(llm.getCreated()).isGreaterThan(1);
assertThat(llm.getModel()).isEqualTo(OrchestrationController.MODEL);
assertThat(llm.getModel()).isEqualTo(OrchestrationController.LLM_CONFIG.getModelName());
var choices = llm.getChoices();
assertThat(choices.get(0).getIndex()).isZero();
assertThat(choices.get(0).getMessage().getContent()).isNotEmpty();
Expand All @@ -53,7 +53,8 @@ void testTemplate() {
assertThat(usage.getTotalTokens()).isGreaterThan(1);
assertThat(result.getOrchestrationResult().getObject()).isEqualTo("chat.completion");
assertThat(result.getOrchestrationResult().getCreated()).isGreaterThan(1);
assertThat(result.getOrchestrationResult().getModel()).isEqualTo(OrchestrationController.MODEL);
assertThat(result.getOrchestrationResult().getModel())
.isEqualTo(OrchestrationController.LLM_CONFIG.getModelName());
choices = result.getOrchestrationResult().getChoices();
assertThat(choices.get(0).getIndex()).isZero();
assertThat(choices.get(0).getMessage().getContent()).isNotEmpty();
Expand Down