Skip to content

Commit 53e8745

Browse files
Merge branch 'main' into spec-update/orchestration/main
# Conflicts: # orchestration/src/test/java/com/sap/ai/sdk/orchestration/OrchestrationConvenienceUnitTest.java
2 parents 3f5eb76 + 97cf149 commit 53e8745

File tree

20 files changed

+491
-14
lines changed

20 files changed

+491
-14
lines changed

core-services/document-grounding/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>com.sap.ai.sdk</groupId>
66
<artifactId>sdk-parent</artifactId>
7-
<version>1.7.0-SNAPSHOT</version>
7+
<version>1.8.0-SNAPSHOT</version>
88
<relativePath>../../pom.xml</relativePath>
99
</parent>
1010
<artifactId>document-grounding</artifactId>

core-services/prompt-registry/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>com.sap.ai.sdk</groupId>
66
<artifactId>sdk-parent</artifactId>
7-
<version>1.7.0-SNAPSHOT</version>
7+
<version>1.8.0-SNAPSHOT</version>
88
<relativePath>../../pom.xml</relativePath>
99
</parent>
1010
<artifactId>prompt-registry</artifactId>

core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>com.sap.ai.sdk</groupId>
66
<artifactId>sdk-parent</artifactId>
7-
<version>1.7.0-SNAPSHOT</version>
7+
<version>1.8.0-SNAPSHOT</version>
88
</parent>
99
<artifactId>core</artifactId>
1010
<name>AI Core client</name>

foundation-models/openai/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>com.sap.ai.sdk</groupId>
66
<artifactId>sdk-parent</artifactId>
7-
<version>1.7.0-SNAPSHOT</version>
7+
<version>1.8.0-SNAPSHOT</version>
88
<relativePath>../../pom.xml</relativePath>
99
</parent>
1010
<groupId>com.sap.ai.sdk.foundationmodels</groupId>

orchestration/pom.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>com.sap.ai.sdk</groupId>
66
<artifactId>sdk-parent</artifactId>
7-
<version>1.7.0-SNAPSHOT</version>
7+
<version>1.8.0-SNAPSHOT</version>
88
</parent>
99
<artifactId>orchestration</artifactId>
1010
<name>Orchestration client</name>
@@ -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>77%</coverage.branch>
37+
<coverage.branch>78%</coverage.branch>
3838
<coverage.method>93%</coverage.method>
3939
<coverage.class>100%</coverage.class>
4040
</properties>
@@ -108,6 +108,10 @@
108108
<groupId>com.github.victools</groupId>
109109
<artifactId>jsonschema-module-jackson</artifactId>
110110
</dependency>
111+
<dependency>
112+
<groupId>com.fasterxml.jackson.dataformat</groupId>
113+
<artifactId>jackson-dataformat-yaml</artifactId>
114+
</dependency>
111115
<!-- scope "provided" -->
112116
<dependency>
113117
<groupId>org.projectlombok</groupId>

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.sap.ai.sdk.orchestration;
22

3+
import com.fasterxml.jackson.annotation.JsonSubTypes;
34
import com.fasterxml.jackson.annotation.JsonTypeInfo;
45
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
56
import com.sap.ai.sdk.orchestration.model.LLMChoice;
@@ -21,4 +22,22 @@ interface ModuleResultsOutputUnmaskingInnerMixIn {}
2122

2223
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
2324
interface NoneTypeInfoMixin {}
25+
26+
@JsonTypeInfo(
27+
use = JsonTypeInfo.Id.NAME,
28+
include = JsonTypeInfo.As.PROPERTY,
29+
property = "type",
30+
visible = true)
31+
@JsonSubTypes({
32+
@JsonSubTypes.Type(
33+
value = com.sap.ai.sdk.orchestration.model.ResponseFormatJsonSchema.class,
34+
name = "json_schema"),
35+
@JsonSubTypes.Type(
36+
value = com.sap.ai.sdk.orchestration.model.ResponseFormatJsonObject.class,
37+
name = "json_object"),
38+
@JsonSubTypes.Type(
39+
value = com.sap.ai.sdk.orchestration.model.ResponseFormatText.class,
40+
name = "text")
41+
})
42+
interface ResponseFormatSubTypesMixin {}
2443
}

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

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ public class OrchestrationAiModel {
8686
public static final OrchestrationAiModel CLAUDE_3_5_SONNET =
8787
new OrchestrationAiModel("anthropic--claude-3.5-sonnet");
8888

89+
/** Anthropic Claude 3.7 Sonnet model */
90+
public static final OrchestrationAiModel CLAUDE_3_7_SONNET =
91+
new OrchestrationAiModel("anthropic--claude-3.7-sonnet ");
92+
8993
/** Amazon Titan Text Lite model */
9094
public static final OrchestrationAiModel TITAN_TEXT_LITE =
9195
new OrchestrationAiModel("amazon--titan-text-lite");
@@ -158,7 +162,14 @@ public class OrchestrationAiModel {
158162
/** Azure OpenAI o3-mini model */
159163
public static final OrchestrationAiModel OPENAI_O3_MINI = new OrchestrationAiModel("o3-mini");
160164

161-
/** Google Cloud Platform Gemini 1.0 Pro model */
165+
/**
166+
* Google Cloud Platform Gemini 1.0 Pro model
167+
*
168+
* @deprecated This model is deprecated on AI Core with a planned retirement on 2025-04-09. The
169+
* suggested replacement model is {@link OrchestrationAiModel#GEMINI_2_0_FLASH} or {@link
170+
* OrchestrationAiModel#GEMINI_2_0_FLASH_LITE}.
171+
*/
172+
@Deprecated
162173
public static final OrchestrationAiModel GEMINI_1_0_PRO =
163174
new OrchestrationAiModel("gemini-1.0-pro");
164175

@@ -170,6 +181,14 @@ public class OrchestrationAiModel {
170181
public static final OrchestrationAiModel GEMINI_1_5_FLASH =
171182
new OrchestrationAiModel("gemini-1.5-flash");
172183

184+
/** Google Cloud Platform Gemini 2.0 Flash model */
185+
public static final OrchestrationAiModel GEMINI_2_0_FLASH =
186+
new OrchestrationAiModel("gemini-2.0-flash");
187+
188+
/** Google Cloud Platform Gemini 2.0 Flash-Lite model */
189+
public static final OrchestrationAiModel GEMINI_2_0_FLASH_LITE =
190+
new OrchestrationAiModel("gemini-2.0-flash-lite");
191+
173192
OrchestrationAiModel(@Nonnull final String name) {
174193
this(name, Map.of(), "latest");
175194
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.sap.ai.sdk.orchestration.model.ChatMessage;
99
import com.sap.ai.sdk.orchestration.model.LLMModuleResult;
1010
import com.sap.ai.sdk.orchestration.model.ModuleResultsOutputUnmaskingInner;
11+
import com.sap.ai.sdk.orchestration.model.TemplateResponseFormat;
1112
import javax.annotation.Nonnull;
1213
import lombok.AccessLevel;
1314
import lombok.NoArgsConstructor;
@@ -47,7 +48,12 @@ public static ObjectMapper getOrchestrationObjectMapper() {
4748
.addDeserializer(
4849
ChatMessage.class,
4950
PolymorphicFallbackDeserializer.fromJsonSubTypes(ChatMessage.class))
50-
.setMixInAnnotation(ChatMessage.class, JacksonMixins.NoneTypeInfoMixin.class);
51+
.addDeserializer(
52+
TemplateResponseFormat.class,
53+
PolymorphicFallbackDeserializer.fromJsonSubTypes(TemplateResponseFormat.class))
54+
.setMixInAnnotation(ChatMessage.class, JacksonMixins.NoneTypeInfoMixin.class)
55+
.setMixInAnnotation(
56+
TemplateResponseFormat.class, JacksonMixins.ResponseFormatSubTypesMixin.class);
5157
jackson.registerModule(module);
5258
return jackson;
5359
}

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

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
package com.sap.ai.sdk.orchestration;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.core.JsonProcessingException;
5+
import com.fasterxml.jackson.databind.JsonNode;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
38
import com.google.common.annotations.Beta;
49
import com.sap.ai.sdk.orchestration.model.ChatCompletionTool;
510
import com.sap.ai.sdk.orchestration.model.ChatMessage;
@@ -9,6 +14,7 @@
914
import com.sap.ai.sdk.orchestration.model.Template;
1015
import com.sap.ai.sdk.orchestration.model.TemplateResponseFormat;
1116
import com.sap.ai.sdk.orchestration.model.TemplatingModuleConfig;
17+
import java.io.IOException;
1218
import java.util.ArrayList;
1319
import java.util.HashMap;
1420
import java.util.List;
@@ -35,14 +41,22 @@
3541
@NoArgsConstructor(force = true, access = AccessLevel.PACKAGE)
3642
@Beta
3743
public class OrchestrationTemplate extends TemplateConfig {
38-
@Nullable List<ChatMessage> template;
39-
@Nullable Map<String, String> defaults;
44+
@JsonProperty("template")
45+
@Nullable
46+
List<ChatMessage> template;
47+
48+
@JsonProperty("defaults")
49+
@Nullable
50+
Map<String, String> defaults;
4051

52+
@JsonProperty("response_format")
4153
@With(AccessLevel.PRIVATE)
4254
@Nullable
4355
TemplateResponseFormat responseFormat;
4456

45-
@Nullable List<ChatCompletionTool> tools;
57+
@JsonProperty("tools")
58+
@Nullable
59+
List<ChatCompletionTool> tools;
4660

4761
/**
4862
* Create a low-level representation of the template.
@@ -93,4 +107,45 @@ public OrchestrationTemplate withJsonResponse() {
93107
ResponseFormatJsonObject.create().type(ResponseFormatJsonObject.TypeEnum.JSON_OBJECT);
94108
return this.withResponseFormat(responseFormatJsonObject);
95109
}
110+
111+
/**
112+
* Create a {@link Template} object from a JSON provided as String.
113+
*
114+
* @throws IOException if the JSON cannot be deserialized
115+
* @param inputString the provided JSON
116+
* @return A Template object representing the provided JSON
117+
* @since 1.7.0
118+
*/
119+
@Nullable
120+
private OrchestrationTemplate fromJson(@Nonnull final String inputString) throws IOException {
121+
final ObjectMapper objectMapper =
122+
OrchestrationJacksonConfiguration.getOrchestrationObjectMapper();
123+
final JsonNode rootNode = objectMapper.readTree(inputString);
124+
return objectMapper.treeToValue(rootNode.get("spec"), OrchestrationTemplate.class);
125+
}
126+
127+
/**
128+
* Create a {@link Template} object from a YAML provided as String.
129+
*
130+
* @throws IOException if the YAML cannot be parsed or deserialized
131+
* @param inputYaml the provided YAML
132+
* @return A Template object representing the provided YAML
133+
* @since 1.7.0
134+
*/
135+
@Nullable
136+
public OrchestrationTemplate fromYaml(@Nonnull final String inputYaml) throws IOException {
137+
final Object obj;
138+
try {
139+
final ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
140+
obj = yamlReader.readValue(inputYaml, Object.class);
141+
} catch (JsonProcessingException ex) {
142+
throw new IOException("Failed to parse the YAML input: " + ex.getMessage(), ex);
143+
}
144+
try {
145+
final ObjectMapper jsonWriter = new ObjectMapper();
146+
return fromJson(jsonWriter.writeValueAsString(obj));
147+
} catch (JsonProcessingException ex) {
148+
throw new IOException("Failed to deserialize the input: " + ex.getMessage(), ex);
149+
}
150+
}
96151
}

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

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
import com.sap.ai.sdk.orchestration.model.TemplateRefByScenarioNameVersion;
1616
import com.sap.ai.sdk.orchestration.model.UserChatMessage;
1717
import com.sap.ai.sdk.orchestration.model.UserChatMessageContent;
18+
import java.io.IOException;
19+
import java.nio.file.Files;
20+
import java.nio.file.Path;
1821
import java.util.LinkedHashMap;
1922
import java.util.List;
2023
import java.util.Map;
@@ -181,4 +184,96 @@ void testTemplateReferenceConstruction() {
181184
assertThat(templateReferenceScenarioNameVersion.toLowLevel())
182185
.isEqualTo(templateReferenceScenarioNameVersionLowLevel);
183186
}
187+
188+
@Test
189+
void testTemplateFromLocalFileWithJsonSchemaAndTools() throws IOException {
190+
String promptTemplateYaml =
191+
Files.readString(Path.of("src/test/resources/promptTemplateExample.yaml"));
192+
var templateWithJsonSchemaTools = TemplateConfig.create().fromYaml(promptTemplateYaml);
193+
var schema =
194+
Map.of(
195+
"type",
196+
"object",
197+
"properties",
198+
Map.of(
199+
"language", Map.of("type", "string"),
200+
"translation", Map.of("type", "string")),
201+
"required",
202+
List.of("language", "translation"),
203+
"additionalProperties",
204+
false);
205+
var expectedTemplateWithJsonSchemaTools =
206+
OrchestrationTemplate.create()
207+
.withTemplate(
208+
List.of(
209+
SingleChatMessage.create()
210+
.role("system")
211+
.content("You are a language translator."),
212+
SingleChatMessage.create()
213+
.role("user")
214+
.content("Whats {{ ?word }} in {{ ?language }}?")))
215+
.withDefaults(Map.of("word", "apple"))
216+
.withJsonSchemaResponse(
217+
ResponseJsonSchema.fromMap(schema, "translation-schema")
218+
.withDescription("Translate the given word into the provided language.")
219+
.withStrict(true))
220+
.withTools(
221+
List.of(
222+
ChatCompletionTool.create()
223+
.type(ChatCompletionTool.TypeEnum.FUNCTION)
224+
.function(
225+
FunctionObject.create()
226+
.name("translate")
227+
.parameters(
228+
Map.of(
229+
"type",
230+
"object",
231+
"additionalProperties",
232+
false,
233+
"required",
234+
List.of("language", "wordToTranslate"),
235+
"properties",
236+
Map.of(
237+
"language", Map.of("type", "string"),
238+
"wordToTranslate", Map.of("type", "string"))))
239+
.description("Translate a word.")
240+
.strict(true))));
241+
assertThat(templateWithJsonSchemaTools).isEqualTo(expectedTemplateWithJsonSchemaTools);
242+
}
243+
244+
@Test
245+
void testTemplateFromLocalFileWithJsonObject() throws IOException {
246+
String promptTemplateWithJsonObject =
247+
"""
248+
name: translator
249+
version: 0.0.1
250+
scenario: translation scenario
251+
spec:
252+
template:
253+
- role: "system"
254+
content: |-
255+
You are a language translator.
256+
- role: "user"
257+
content: |-
258+
Whats {{ ?word }} in {{ ?language }}?
259+
defaults:
260+
word: "apple"
261+
response_format:
262+
type: json_object
263+
""";
264+
var templateWithJsonObject = TemplateConfig.create().fromYaml(promptTemplateWithJsonObject);
265+
var expectedTemplateWithJsonObject =
266+
OrchestrationTemplate.create()
267+
.withTemplate(
268+
List.of(
269+
SingleChatMessage.create()
270+
.role("system")
271+
.content("You are a language translator."),
272+
SingleChatMessage.create()
273+
.role("user")
274+
.content("Whats {{ ?word }} in {{ ?language }}?")))
275+
.withDefaults(Map.of("word", "apple"))
276+
.withJsonResponse();
277+
assertThat(templateWithJsonObject).isEqualTo(expectedTemplateWithJsonObject);
278+
}
184279
}

0 commit comments

Comments
 (0)