Skip to content

Commit

Permalink
feat: [Orchestration] Configuration as JSON String (#177)
Browse files Browse the repository at this point in the history
* feat: Orchestration Configuration as JSON String

* Formatting

* Minor improvements

* Add example to load from file

---------

Co-authored-by: SAP Cloud SDK Bot <cloudsdk@sap.com>
  • Loading branch information
MatKuhr and bot-sdk-js authored Nov 25, 2024
1 parent 5463c38 commit 918e12c
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 8 deletions.
18 changes: 18 additions & 0 deletions docs/guides/ORCHESTRATION_CHAT_COMPLETION.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,21 @@ OrchestrationAiModel customGPT4O =
"presence_penalty", 0))
.withVersion("2024-05-13");
```

### Using a Configuration from AI Launchpad

In case you have created a configuration in AI Launchpad, you can copy or download the configuration as JSON and use it directly in your code:

```java
var configJson = """
... paste your configuration JSON in here ...
""";
// or load your config from a file, e.g.
// configJson = Files.readString(Paths.get("path/to/my/orchestration-config.json"));

var prompt = new OrchestrationPrompt(Map.of("your-input-parameter", "your-param-value"));

new OrchestrationClient().executeRequestFromJsonModuleConfig(prompt, configJson);
```

While this is not recommended for long term use, it can be useful for creating demos and PoCs.
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.google.common.annotations.Beta;
import com.sap.ai.sdk.core.AiCoreDeployment;
import com.sap.ai.sdk.core.AiCoreService;
import com.sap.ai.sdk.orchestration.client.model.CompletionPostRequest;
Expand All @@ -30,7 +33,6 @@
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;

/** Client to execute requests to the orchestration service. */
Expand Down Expand Up @@ -132,26 +134,75 @@ public OrchestrationChatResponse chatCompletion(
@Nonnull
public CompletionPostResponse executeRequest(@Nonnull final CompletionPostRequest request)
throws OrchestrationClientException {
final BasicClassicHttpRequest postRequest = new HttpPost("/completion");
final String jsonRequest;
try {
val json = JACKSON.writeValueAsString(request);
log.debug("Serialized request into JSON payload: {}", json);
postRequest.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
jsonRequest = JACKSON.writeValueAsString(request);
log.debug("Serialized request into JSON payload: {}", jsonRequest);
} catch (final JsonProcessingException e) {
throw new OrchestrationClientException("Failed to serialize request parameters", e);
}

return executeRequest(postRequest);
return executeRequest(jsonRequest);
}

/**
* Perform a request to the orchestration service using a module configuration provided as JSON
* string. This can be useful when building a configuration in the AI Launchpad UI and exporting
* it as JSON. Furthermore, this allows for using features that are not yet supported natively by
* the API.
*
* <p><b>NOTE:</b> This method does not support streaming.
*
* @param prompt The input parameters and optionally message history to use for prompt execution.
* @param moduleConfig The module configuration in JSON format.
* @return The completion response.
* @throws OrchestrationClientException If the request fails.
*/
@Beta
@Nonnull
public OrchestrationChatResponse executeRequestFromJsonModuleConfig(
@Nonnull final OrchestrationPrompt prompt, @Nonnull final String moduleConfig)
throws OrchestrationClientException {
if (!prompt.getMessages().isEmpty()) {
throw new IllegalArgumentException(
"Prompt must not contain any messages when using a JSON module configuration, as the template is already defined in the JSON.");
}

final var request =
new CompletionPostRequest()
.messagesHistory(prompt.getMessagesHistory())
.inputParams(prompt.getTemplateParameters());

final ObjectNode requestJson = JACKSON.valueToTree(request);
final JsonNode moduleConfigJson;
try {
moduleConfigJson = JACKSON.readTree(moduleConfig);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException(
"The provided module configuration is not valid JSON: " + moduleConfig, e);
}
requestJson.set("orchestration_config", moduleConfigJson);

final String body;
try {
body = JACKSON.writeValueAsString(requestJson);
} catch (JsonProcessingException e) {
throw new OrchestrationClientException("Failed to serialize request to JSON", e);
}
return new OrchestrationChatResponse(executeRequest(body));
}

@Nonnull
CompletionPostResponse executeRequest(@Nonnull final BasicClassicHttpRequest request) {
CompletionPostResponse executeRequest(@Nonnull final String request) {
val postRequest = new HttpPost("/completion");
postRequest.setEntity(new StringEntity(request, ContentType.APPLICATION_JSON));

try {
val destination = deployment.get().destination();
log.debug("Using destination {} to connect to orchestration service", destination);
val client = ApacheHttpClient5Accessor.getHttpClient(destination);
return client.execute(
request, new OrchestrationResponseHandler<>(CompletionPostResponse.class));
postRequest, new OrchestrationResponseHandler<>(CompletionPostResponse.class));
} catch (NoSuchElementException
| DestinationAccessException
| DestinationNotFoundException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,4 +380,55 @@ void testErrorHandling() {

softly.assertAll();
}

@Test
void testExecuteRequestFromJson() {
stubFor(post(anyUrl()).willReturn(okJson("{}")));

prompt = new OrchestrationPrompt(Map.of());
final var configJson =
"""
{
"module_configurations": {
"llm_module_config": {
"model_name": "mistralai--mistral-large-instruct",
"model_params": {}
}
}
}
""";

final var expectedJson =
"""
{
"messages_history": [],
"input_params": {},
"orchestration_config": {
"module_configurations": {
"llm_module_config": {
"model_name": "mistralai--mistral-large-instruct",
"model_params": {}
}
}
}
}
""";

var result = client.executeRequestFromJsonModuleConfig(prompt, configJson);
assertThat(result).isNotNull();

verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(expectedJson)));
}

@Test
void testExecuteRequestFromJsonThrows() {
assertThatThrownBy(() -> client.executeRequestFromJsonModuleConfig(prompt, "{}"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("messages");

prompt = new OrchestrationPrompt(Map.of());
assertThatThrownBy(() -> client.executeRequestFromJsonModuleConfig(prompt, "{ foo"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("not valid JSON");
}
}

0 comments on commit 918e12c

Please sign in to comment.