Skip to content

Commit 83f2a4e

Browse files
Merge branch 'main' into chat-memory
2 parents c03203d + 8fefe94 commit 83f2a4e

File tree

15 files changed

+344
-8
lines changed

15 files changed

+344
-8
lines changed

docs/guides/ORCHESTRATION_CHAT_COMPLETION.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,22 @@ var result = client.chatCompletion(prompt, configWithTemplate);
119119

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

122+
Alternatively, you can use already prepared templates from the [Prompt Registry](https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/prompt-registry) of SAP AI Core instead of passing a template in the request yourself.
123+
124+
```java
125+
var template = TemplateConfig.reference().byId("21cb1358-0bf1-4f43-870b-00f14d0f9f16");
126+
var configWithTemplate = config.withTemplateConfig(template);
127+
128+
var inputParams = Map.of("language", "Italian", "input", "cloud ERP systems");
129+
var prompt = new OrchestrationPrompt(inputParams);
130+
131+
var result = client.chatCompletion(prompt, configWithTemplate);
132+
```
133+
134+
A prompt template can be referenced either by ID as above, or by using a combination of name, scenario, and version. For details on storing a template in the Prompt Registry, refer to [this guide](https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/create-prompt-template-imperative).
135+
136+
You can find [some examples](https://github.com/SAP/ai-sdk-java/tree/main/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java) in our Spring Boot application demonstrating using templates from Prompt Registry.
137+
122138
## Message history
123139

124140
Include a message history to maintain context in the conversation:

docs/release-notes/release_notes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
### ✨ New Functionality
1414

1515
- [Orchestration] [Add Spring AI Chat Memory support](https://github.com/SAP/ai-sdk-java/tree/main/docs/guides/SPRING_AI_INTEGRATION.md#chat-memory)
16+
- [Orchestration] [Prompt templates can be consumed from registry.](https://github.com/SAP/ai-sdk-java/tree/main/docs/guides/ORCHESTRATION_CHAT_COMPLETION.md#Chat-completion-with-Templates)
1617

1718
### 📈 Improvements
1819

foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiError.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import lombok.AccessLevel;
99
import lombok.AllArgsConstructor;
1010
import lombok.Value;
11-
import lombok.experimental.Delegate;
1211

1312
/**
1413
* Represents an error response from the OpenAI API.
@@ -20,7 +19,6 @@
2019
@AllArgsConstructor(onConstructor = @__({@JsonCreator}), access = AccessLevel.PROTECTED)
2120
public class OpenAiError implements ClientError {
2221
/** The original error response from the OpenAI API. */
23-
@Delegate(types = {ClientError.class})
2422
ErrorResponse originalResponse;
2523

2624
/**

orchestration/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
<coverage.complexity>83%</coverage.complexity>
3535
<coverage.line>94%</coverage.line>
3636
<coverage.instruction>94%</coverage.instruction>
37-
<coverage.branch>76%</coverage.branch>
37+
<coverage.branch>75%</coverage.branch>
3838
<coverage.method>93%</coverage.method>
3939
<coverage.class>100%</coverage.class>
4040
</properties>

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.sap.ai.sdk.orchestration.model.ModuleConfigs;
66
import com.sap.ai.sdk.orchestration.model.OrchestrationConfig;
77
import com.sap.ai.sdk.orchestration.model.Template;
8+
import com.sap.ai.sdk.orchestration.model.TemplateRef;
89
import com.sap.ai.sdk.orchestration.model.TemplatingModuleConfig;
910
import io.vavr.control.Option;
1011
import java.util.ArrayList;
@@ -47,6 +48,9 @@ static TemplatingModuleConfig toTemplateModuleConfig(
4748
* In this case, the request will fail, since the templating module will try to resolve the parameter.
4849
* To be fixed with https://github.tools.sap/AI/llm-orchestration/issues/662
4950
*/
51+
if (config instanceof TemplateRef) {
52+
return config;
53+
}
5054
val template = config instanceof Template t ? t : Template.create().template();
5155
val messages = template.getTemplate();
5256
val responseFormat = template.getResponseFormat();

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
import com.google.common.annotations.Beta;
55
import com.sap.ai.sdk.core.common.ClientError;
66
import com.sap.ai.sdk.orchestration.model.ErrorResponse;
7+
import javax.annotation.Nonnull;
78
import lombok.AccessLevel;
89
import lombok.AllArgsConstructor;
910
import lombok.Value;
10-
import lombok.experimental.Delegate;
1111

1212
/**
1313
* Orchestration error response.
@@ -18,6 +18,17 @@
1818
@Value
1919
@Beta
2020
public class OrchestrationError implements ClientError {
21-
@Delegate(types = {ClientError.class})
2221
ErrorResponse originalResponse;
22+
23+
/**
24+
* Gets the error message from the contained original response.
25+
*
26+
* @return the error message
27+
*/
28+
@Nonnull
29+
public String getMessage() {
30+
return originalResponse.getCode() == 500
31+
? originalResponse.getMessage() + " located in " + originalResponse.getLocation()
32+
: originalResponse.getMessage();
33+
}
2334
}

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

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,21 @@ void testCompletion() {
126126
assertThat(result.getContent()).isNotEmpty();
127127
}
128128

129+
@Test
130+
void testCompletionError() {
131+
stubFor(
132+
post(urlPathEqualTo("/completion"))
133+
.willReturn(
134+
aResponse()
135+
.withStatus(500)
136+
.withBodyFile("error500Response.json")
137+
.withHeader("Content-Type", "application/json")));
138+
139+
assertThatThrownBy(() -> client.chatCompletion(prompt, config))
140+
.hasMessage(
141+
"Request failed with status 500 Server Error and error message: 'Internal Server Error located in Masking Module - Masking'");
142+
}
143+
129144
@Test
130145
void testGrounding() throws IOException {
131146
stubFor(
@@ -185,9 +200,10 @@ void testGrounding() throws IOException {
185200
"First chunk```Second chunk```Last found chunk");
186201
assertThat(groundingModule.getData()).isEqualTo(groundingData);
187202

188-
final String requestBody = new String(fileLoader.apply("groundingRequest.json").readAllBytes());
189-
verify(
190-
postRequestedFor(urlPathEqualTo("/completion")).withRequestBody(equalToJson(requestBody)));
203+
try (var requestInputStream = fileLoader.apply("groundingRequest.json")) {
204+
final String request = new String(requestInputStream.readAllBytes());
205+
verify(postRequestedFor(urlPathEqualTo("/completion")).withRequestBody(equalToJson(request)));
206+
}
191207
}
192208

193209
@Test
@@ -864,4 +880,56 @@ void testResponseFormatText() throws IOException {
864880
verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(request)));
865881
}
866882
}
883+
884+
@Test
885+
void testTemplateFromPromptRegistryById() throws IOException {
886+
{
887+
stubFor(
888+
post(anyUrl())
889+
.willReturn(
890+
aResponse()
891+
.withBodyFile("templateReferenceResponse.json")
892+
.withHeader("Content-Type", "application/json")));
893+
894+
var template = TemplateConfig.reference().byId("21cb1358-0bf1-4f43-870b-00f14d0f9f16");
895+
var configWithTemplate = config.withTemplateConfig(template);
896+
897+
var inputParams = Map.of("language", "Italian", "input", "Cloud ERP systems");
898+
var prompt = new OrchestrationPrompt(inputParams);
899+
900+
final var response = client.chatCompletion(prompt, configWithTemplate);
901+
assertThat(response.getContent()).startsWith("I sistemi ERP (Enterprise Resource Planning)");
902+
assertThat(response.getOriginalResponse().getModuleResults().getTemplating()).hasSize(2);
903+
904+
try (var requestInputStream = fileLoader.apply("templateReferenceByIdRequest.json")) {
905+
final String request = new String(requestInputStream.readAllBytes());
906+
verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(request)));
907+
}
908+
}
909+
}
910+
911+
@Test
912+
void testTemplateFromPromptRegistryByScenario() throws IOException {
913+
stubFor(
914+
post(anyUrl())
915+
.willReturn(
916+
aResponse()
917+
.withBodyFile("templateReferenceResponse.json")
918+
.withHeader("Content-Type", "application/json")));
919+
920+
var template = TemplateConfig.reference().byScenario("test").name("test").version("0.0.1");
921+
var configWithTemplate = config.withTemplateConfig(template);
922+
923+
var inputParams = Map.of("language", "Italian", "input", "Cloud ERP systems");
924+
var prompt = new OrchestrationPrompt(inputParams);
925+
926+
final var response = client.chatCompletion(prompt, configWithTemplate);
927+
assertThat(response.getContent()).startsWith("I sistemi ERP (Enterprise Resource Planning)");
928+
assertThat(response.getOriginalResponse().getModuleResults().getTemplating()).hasSize(2);
929+
930+
try (var requestInputStream = fileLoader.apply("templateReferenceByScenarioRequest.json")) {
931+
final String request = new String(requestInputStream.readAllBytes());
932+
verify(postRequestedFor(anyUrl()).withRequestBody(equalToJson(request)));
933+
}
934+
}
867935
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"request_id": "d647775b-725b-428a-90b7-348cbf30f7f4",
3+
"code": 500,
4+
"message": "Internal Server Error",
5+
"location": "Masking Module - Masking",
6+
"module_results": {
7+
"templating": [
8+
{
9+
"role": "user",
10+
"content": "yo"
11+
}
12+
]
13+
}
14+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"request_id": "c02a6e4c-5552-4a72-a801-b6512d4f2f2a",
3+
"module_results": {
4+
"templating": [
5+
{
6+
"role": "system",
7+
"content": "Please answer in Italian"
8+
},
9+
{
10+
"role": "user",
11+
"content": "Give me a short introduction of Cloud ERP systems."
12+
}
13+
],
14+
"llm": {
15+
"id": "chatcmpl-B6zYkaS7gjo5Ye6U87iK0B2dp4hde",
16+
"object": "chat.completion",
17+
"created": 1741006302,
18+
"model": "gpt-4o-2024-08-06",
19+
"system_fingerprint": "fp_b705f0c291",
20+
"choices": [
21+
{
22+
"index": 0,
23+
"message": {
24+
"role": "assistant",
25+
"content": "I sistemi ERP (Enterprise Resource Planning) basati su cloud sono soluzioni software che consentono alle aziende di gestire e integrare le loro operazioni aziendali principali attraverso una piattaforma centralizzata accessibile via internet. A differenza dei"
26+
},
27+
"finish_reason": "length"
28+
}
29+
],
30+
"usage": {
31+
"completion_tokens": 50,
32+
"prompt_tokens": 25,
33+
"total_tokens": 75
34+
}
35+
}
36+
},
37+
"orchestration_result": {
38+
"id": "chatcmpl-B6zYkaS7gjo5Ye6U87iK0B2dp4hde",
39+
"object": "chat.completion",
40+
"created": 1741006302,
41+
"model": "gpt-4o-2024-08-06",
42+
"system_fingerprint": "fp_b705f0c291",
43+
"choices": [
44+
{
45+
"index": 0,
46+
"message": {
47+
"role": "assistant",
48+
"content": "I sistemi ERP (Enterprise Resource Planning) basati su cloud sono soluzioni software che consentono alle aziende di gestire e integrare le loro operazioni aziendali principali attraverso una piattaforma centralizzata accessibile via internet. A differenza dei"
49+
},
50+
"finish_reason": "length"
51+
}
52+
],
53+
"usage": {
54+
"completion_tokens": 50,
55+
"prompt_tokens": 25,
56+
"total_tokens": 75
57+
}
58+
}
59+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"orchestration_config" : {
3+
"module_configurations" : {
4+
"llm_module_config" : {
5+
"model_name" : "gpt-4o",
6+
"model_params" : {
7+
"max_tokens" : 50,
8+
"temperature" : 0.1,
9+
"frequency_penalty" : 0,
10+
"presence_penalty" : 0,
11+
"top_p" : 1,
12+
"n" : 1
13+
},
14+
"model_version" : "latest"
15+
},
16+
"templating_module_config" : {
17+
"template_ref" : {
18+
"id" : "21cb1358-0bf1-4f43-870b-00f14d0f9f16"
19+
}
20+
}
21+
},
22+
"stream" : false
23+
},
24+
"input_params" : {
25+
"input" : "Cloud ERP systems",
26+
"language" : "Italian"
27+
},
28+
"messages_history" : [ ]
29+
}

0 commit comments

Comments
 (0)