Skip to content

Commit 918e12c

Browse files
MatKuhrbot-sdk-js
andauthored
feat: [Orchestration] Configuration as JSON String (#177)
* feat: Orchestration Configuration as JSON String * Formatting * Minor improvements * Add example to load from file --------- Co-authored-by: SAP Cloud SDK Bot <[email protected]>
1 parent 5463c38 commit 918e12c

File tree

3 files changed

+128
-8
lines changed

3 files changed

+128
-8
lines changed

docs/guides/ORCHESTRATION_CHAT_COMPLETION.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,21 @@ OrchestrationAiModel customGPT4O =
208208
"presence_penalty", 0))
209209
.withVersion("2024-05-13");
210210
```
211+
212+
### Using a Configuration from AI Launchpad
213+
214+
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:
215+
216+
```java
217+
var configJson = """
218+
... paste your configuration JSON in here ...
219+
""";
220+
// or load your config from a file, e.g.
221+
// configJson = Files.readString(Paths.get("path/to/my/orchestration-config.json"));
222+
223+
var prompt = new OrchestrationPrompt(Map.of("your-input-parameter", "your-param-value"));
224+
225+
new OrchestrationClient().executeRequestFromJsonModuleConfig(prompt, configJson);
226+
```
227+
228+
While this is not recommended for long term use, it can be useful for creating demos and PoCs.

orchestration/src/main/java/com/sap/ai/sdk/orchestration/OrchestrationClient.java

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
import com.fasterxml.jackson.annotation.JsonInclude;
55
import com.fasterxml.jackson.annotation.PropertyAccessor;
66
import com.fasterxml.jackson.core.JsonProcessingException;
7+
import com.fasterxml.jackson.databind.JsonNode;
78
import com.fasterxml.jackson.databind.ObjectMapper;
9+
import com.fasterxml.jackson.databind.node.ObjectNode;
810
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
11+
import com.google.common.annotations.Beta;
912
import com.sap.ai.sdk.core.AiCoreDeployment;
1013
import com.sap.ai.sdk.core.AiCoreService;
1114
import com.sap.ai.sdk.orchestration.client.model.CompletionPostRequest;
@@ -30,7 +33,6 @@
3033
import org.apache.hc.client5.http.classic.methods.HttpPost;
3134
import org.apache.hc.core5.http.ContentType;
3235
import org.apache.hc.core5.http.io.entity.StringEntity;
33-
import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
3436
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
3537

3638
/** Client to execute requests to the orchestration service. */
@@ -132,26 +134,75 @@ public OrchestrationChatResponse chatCompletion(
132134
@Nonnull
133135
public CompletionPostResponse executeRequest(@Nonnull final CompletionPostRequest request)
134136
throws OrchestrationClientException {
135-
final BasicClassicHttpRequest postRequest = new HttpPost("/completion");
137+
final String jsonRequest;
136138
try {
137-
val json = JACKSON.writeValueAsString(request);
138-
log.debug("Serialized request into JSON payload: {}", json);
139-
postRequest.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
139+
jsonRequest = JACKSON.writeValueAsString(request);
140+
log.debug("Serialized request into JSON payload: {}", jsonRequest);
140141
} catch (final JsonProcessingException e) {
141142
throw new OrchestrationClientException("Failed to serialize request parameters", e);
142143
}
143144

144-
return executeRequest(postRequest);
145+
return executeRequest(jsonRequest);
146+
}
147+
148+
/**
149+
* Perform a request to the orchestration service using a module configuration provided as JSON
150+
* string. This can be useful when building a configuration in the AI Launchpad UI and exporting
151+
* it as JSON. Furthermore, this allows for using features that are not yet supported natively by
152+
* the API.
153+
*
154+
* <p><b>NOTE:</b> This method does not support streaming.
155+
*
156+
* @param prompt The input parameters and optionally message history to use for prompt execution.
157+
* @param moduleConfig The module configuration in JSON format.
158+
* @return The completion response.
159+
* @throws OrchestrationClientException If the request fails.
160+
*/
161+
@Beta
162+
@Nonnull
163+
public OrchestrationChatResponse executeRequestFromJsonModuleConfig(
164+
@Nonnull final OrchestrationPrompt prompt, @Nonnull final String moduleConfig)
165+
throws OrchestrationClientException {
166+
if (!prompt.getMessages().isEmpty()) {
167+
throw new IllegalArgumentException(
168+
"Prompt must not contain any messages when using a JSON module configuration, as the template is already defined in the JSON.");
169+
}
170+
171+
final var request =
172+
new CompletionPostRequest()
173+
.messagesHistory(prompt.getMessagesHistory())
174+
.inputParams(prompt.getTemplateParameters());
175+
176+
final ObjectNode requestJson = JACKSON.valueToTree(request);
177+
final JsonNode moduleConfigJson;
178+
try {
179+
moduleConfigJson = JACKSON.readTree(moduleConfig);
180+
} catch (JsonProcessingException e) {
181+
throw new IllegalArgumentException(
182+
"The provided module configuration is not valid JSON: " + moduleConfig, e);
183+
}
184+
requestJson.set("orchestration_config", moduleConfigJson);
185+
186+
final String body;
187+
try {
188+
body = JACKSON.writeValueAsString(requestJson);
189+
} catch (JsonProcessingException e) {
190+
throw new OrchestrationClientException("Failed to serialize request to JSON", e);
191+
}
192+
return new OrchestrationChatResponse(executeRequest(body));
145193
}
146194

147195
@Nonnull
148-
CompletionPostResponse executeRequest(@Nonnull final BasicClassicHttpRequest request) {
196+
CompletionPostResponse executeRequest(@Nonnull final String request) {
197+
val postRequest = new HttpPost("/completion");
198+
postRequest.setEntity(new StringEntity(request, ContentType.APPLICATION_JSON));
199+
149200
try {
150201
val destination = deployment.get().destination();
151202
log.debug("Using destination {} to connect to orchestration service", destination);
152203
val client = ApacheHttpClient5Accessor.getHttpClient(destination);
153204
return client.execute(
154-
request, new OrchestrationResponseHandler<>(CompletionPostResponse.class));
205+
postRequest, new OrchestrationResponseHandler<>(CompletionPostResponse.class));
155206
} catch (NoSuchElementException
156207
| DestinationAccessException
157208
| DestinationNotFoundException

orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationUnitTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,4 +380,55 @@ void testErrorHandling() {
380380

381381
softly.assertAll();
382382
}
383+
384+
@Test
385+
void testExecuteRequestFromJson() {
386+
stubFor(post(anyUrl()).willReturn(okJson("{}")));
387+
388+
prompt = new OrchestrationPrompt(Map.of());
389+
final var configJson =
390+
"""
391+
{
392+
"module_configurations": {
393+
"llm_module_config": {
394+
"model_name": "mistralai--mistral-large-instruct",
395+
"model_params": {}
396+
}
397+
}
398+
}
399+
""";
400+
401+
final var expectedJson =
402+
"""
403+
{
404+
"messages_history": [],
405+
"input_params": {},
406+
"orchestration_config": {
407+
"module_configurations": {
408+
"llm_module_config": {
409+
"model_name": "mistralai--mistral-large-instruct",
410+
"model_params": {}
411+
}
412+
}
413+
}
414+
}
415+
""";
416+
417+
var result = client.executeRequestFromJsonModuleConfig(prompt, configJson);
418+
assertThat(result).isNotNull();
419+
420+
verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(expectedJson)));
421+
}
422+
423+
@Test
424+
void testExecuteRequestFromJsonThrows() {
425+
assertThatThrownBy(() -> client.executeRequestFromJsonModuleConfig(prompt, "{}"))
426+
.isInstanceOf(IllegalArgumentException.class)
427+
.hasMessageContaining("messages");
428+
429+
prompt = new OrchestrationPrompt(Map.of());
430+
assertThatThrownBy(() -> client.executeRequestFromJsonModuleConfig(prompt, "{ foo"))
431+
.isInstanceOf(IllegalArgumentException.class)
432+
.hasMessageContaining("not valid JSON");
433+
}
383434
}

0 commit comments

Comments
 (0)