Skip to content

Commit d7c5039

Browse files
committed
Add additional tests, reset TestCoverage
1 parent 1611a08 commit d7c5039

File tree

8 files changed

+216
-25
lines changed

8 files changed

+216
-25
lines changed

orchestration/pom.xml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@
3131
</developers>
3232
<properties>
3333
<project.rootdir>${project.basedir}/../</project.rootdir>
34-
<coverage.complexity>10%</coverage.complexity>
35-
<coverage.line>10%</coverage.line>
36-
<coverage.instruction>10%</coverage.instruction>
37-
<coverage.branch>10%</coverage.branch>
38-
<coverage.method>10%</coverage.method>
39-
<coverage.class>10%</coverage.class>
34+
<coverage.complexity>82%</coverage.complexity>
35+
<coverage.line>93%</coverage.line>
36+
<coverage.instruction>94%</coverage.instruction>
37+
<coverage.branch>75%</coverage.branch>
38+
<coverage.method>95%</coverage.method>
39+
<coverage.class>100%</coverage.class>
4040
</properties>
4141

4242
<dependencies>

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,10 @@ public OrchestrationModuleConfig withGrounding(
226226
*
227227
* @link <a href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/templating">SAP
228228
* AI Core: Orchestration - Templating</a>
229-
* @param templateConfig
229+
* @param templateConfig The template configuration to use.
230230
* @return A new configuration with the given template configuration.
231231
*/
232+
@Tolerate
232233
@Nonnull
233234
public OrchestrationModuleConfig withTemplateConfig(
234235
@Nullable final TemplatingModuleConfig templateConfig) {
@@ -245,6 +246,7 @@ public OrchestrationModuleConfig withTemplateConfig(
245246
if (this.templateConfig instanceof Template oldTemplate) {
246247
responseFormat = oldTemplate.getResponseFormat();
247248
}
249+
// Template.getResponseFormat() might return null, so the following check is necessary.
248250
if (newTemplate != null && newTemplate.getResponseFormat() == null) {
249251
newTemplate.setResponseFormat(responseFormat);
250252
}
@@ -263,12 +265,12 @@ public OrchestrationModuleConfig withTemplateConfig(
263265
* @link <a
264266
* href="https://help.sap.com/docs/sap-ai-core/sap-ai-core-service-guide/structured-output">SAP
265267
* AI Core: Orchestration - Structured Output</a>
266-
* @param schema
268+
* @param schema The response schema to use.
267269
* @return A new configuration with the given response schema.
268270
* @since 1.4.0
269271
*/
270272
@Nonnull
271-
public OrchestrationModuleConfig withResponseJsonSchema(
273+
public OrchestrationModuleConfig withJsonSchemaResponse(
272274
@Nonnull final ResponseJsonSchema schema) {
273275
val responseFormatJsonSchema =
274276
ResponseFormatJsonSchema.create()
@@ -279,7 +281,6 @@ public OrchestrationModuleConfig withResponseJsonSchema(
279281
.schema(schema.getSchemaMap())
280282
.strict(schema.getIsStrict())
281283
.description(schema.getDescription()));
282-
283284
if (this.templateConfig instanceof Template template) {
284285
template.setResponseFormat(responseFormatJsonSchema);
285286
return this.withTemplateConfig(template);
@@ -300,17 +301,14 @@ public OrchestrationModuleConfig withResponseJsonSchema(
300301
*/
301302
@Nonnull
302303
public OrchestrationModuleConfig withJsonResponse() {
304+
val responseFormatJsonObject =
305+
ResponseFormatJsonObject.create().type(ResponseFormatJsonObject.TypeEnum.JSON_OBJECT);
303306
if (this.templateConfig instanceof Template template) {
304-
template.setResponseFormat(
305-
ResponseFormatJsonObject.create().type(ResponseFormatJsonObject.TypeEnum.JSON_OBJECT));
307+
template.setResponseFormat(responseFormatJsonObject);
306308
return this.withTemplateConfig(template);
307309
}
308310
val templatingConfig =
309-
Template.create()
310-
.template(List.of())
311-
.responseFormat(
312-
ResponseFormatJsonObject.create()
313-
.type(ResponseFormatJsonObject.TypeEnum.JSON_OBJECT));
311+
Template.create().template(List.of()).responseFormat(responseFormatJsonObject);
314312
return this.withTemplateConfig(templatingConfig);
315313
}
316314
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public static ResponseJsonSchema of(
6464
/**
6565
* Create a new instance of {@link ResponseJsonSchema} from a given class.
6666
*
67-
* @param classType
67+
* @param classType The class to generate the schema from
6868
* @return The new instance of {@link ResponseJsonSchema}
6969
* @since 1.4.0
7070
*/

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

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,52 @@
22

33
import static org.assertj.core.api.Assertions.assertThat;
44

5+
import com.fasterxml.jackson.annotation.JsonProperty;
6+
import com.sap.ai.sdk.orchestration.model.ResponseFormatJsonSchema;
7+
import com.sap.ai.sdk.orchestration.model.ResponseFormatJsonSchemaJsonSchema;
8+
import com.sap.ai.sdk.orchestration.model.Template;
9+
import java.util.LinkedHashMap;
510
import java.util.List;
11+
import java.util.Map;
12+
import lombok.val;
613
import org.junit.jupiter.api.Test;
714

815
public class OrchestrationConvenienceUnitTest {
916

17+
static class TestClassForSchemaGeneration {
18+
@JsonProperty(required = true)
19+
private String stringField;
20+
21+
@JsonProperty(required = true)
22+
private int intField;
23+
24+
@JsonProperty(required = true)
25+
private InsideTestClass complexField;
26+
27+
static class InsideTestClass {
28+
@JsonProperty(required = true)
29+
private String anotherStringField;
30+
}
31+
}
32+
33+
private Map<String, Object> generateSchemaMap() {
34+
var schemaMap = new LinkedHashMap<String, Object>();
35+
var propertiesMap = new LinkedHashMap<String, Object>();
36+
var complexFieldMap = new LinkedHashMap<String, Object>();
37+
complexFieldMap.put("type", "object");
38+
complexFieldMap.put("properties", Map.of("anotherStringField", Map.of("type", "string")));
39+
complexFieldMap.put("required", List.of("anotherStringField"));
40+
complexFieldMap.put("additionalProperties", false);
41+
propertiesMap.put("complexField", complexFieldMap);
42+
propertiesMap.put("intField", Map.of("type", "integer"));
43+
propertiesMap.put("stringField", Map.of("type", "string"));
44+
schemaMap.put("type", "object");
45+
schemaMap.put("properties", propertiesMap);
46+
schemaMap.put("required", List.of("complexField", "intField", "stringField"));
47+
schemaMap.put("additionalProperties", false);
48+
return schemaMap;
49+
}
50+
1051
@Test
1152
void testMessageConstructionText() {
1253
var userMessageViaStaticFactory = Message.user("Text 1");
@@ -30,4 +71,40 @@ void testMessageConstructionImage() {
3071
Message.user("Text 1").withImage("url", ImageItem.DetailLevel.AUTO);
3172
assertThat(userMessageWithImage).isEqualTo(userMessageWithImageAndDetail);
3273
}
74+
75+
@Test
76+
void testResponseFormatSchemaConstruction() {
77+
val schemaFromClass = ResponseJsonSchema.from(TestClassForSchemaGeneration.class);
78+
79+
val schemaMap = generateSchemaMap();
80+
val schemaFromMap = ResponseJsonSchema.of(schemaMap, "TestClassForSchemaGeneration-Schema");
81+
82+
assertThat(schemaFromClass).isEqualTo(schemaFromMap);
83+
}
84+
85+
@Test
86+
void testConfigWithResponseSchema() {
87+
val schemaFromClass = ResponseJsonSchema.from(TestClassForSchemaGeneration.class);
88+
val schemaMap = generateSchemaMap();
89+
90+
var configWithResponseSchemaFromClass =
91+
new OrchestrationModuleConfig()
92+
.withJsonSchemaResponse(
93+
schemaFromClass.withDescription("Description").withStrict(true));
94+
var configWithResponseSchemaLowLevel =
95+
new OrchestrationModuleConfig()
96+
.withTemplateConfig(
97+
Template.create()
98+
.template(List.of())
99+
.responseFormat(
100+
ResponseFormatJsonSchema.create()
101+
.type(ResponseFormatJsonSchema.TypeEnum.JSON_SCHEMA)
102+
.jsonSchema(
103+
ResponseFormatJsonSchemaJsonSchema.create()
104+
.name("TestClassForSchemaGeneration-Schema")
105+
.schema(schemaMap)
106+
.strict(true)
107+
.description("Description"))));
108+
assertThat(configWithResponseSchemaFromClass).isEqualTo(configWithResponseSchemaLowLevel);
109+
}
33110
}

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

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,41 @@
88
import static org.assertj.core.api.Assertions.assertThat;
99
import static org.assertj.core.api.Assertions.assertThatThrownBy;
1010

11+
import com.fasterxml.jackson.annotation.JsonProperty;
1112
import com.sap.ai.sdk.orchestration.model.DPIConfig;
1213
import com.sap.ai.sdk.orchestration.model.DPIEntities;
1314
import com.sap.ai.sdk.orchestration.model.DocumentGroundingFilter;
1415
import com.sap.ai.sdk.orchestration.model.GroundingModuleConfigConfig;
1516
import com.sap.ai.sdk.orchestration.model.GroundingModuleConfigConfigFiltersInner;
17+
import com.sap.ai.sdk.orchestration.model.ResponseFormatJsonObject;
18+
import com.sap.ai.sdk.orchestration.model.ResponseFormatJsonSchema;
19+
import com.sap.ai.sdk.orchestration.model.SingleChatMessage;
20+
import com.sap.ai.sdk.orchestration.model.Template;
21+
import com.sap.ai.sdk.orchestration.model.TemplateRef;
22+
import com.sap.ai.sdk.orchestration.model.TemplateRefByID;
1623
import java.util.List;
1724
import java.util.Map;
1825
import org.junit.jupiter.api.Test;
1926

2027
class OrchestrationModuleConfigTest {
2128

29+
static class TestClassForSchemaGeneration {
30+
@JsonProperty(required = true)
31+
private String stringField;
32+
33+
@JsonProperty(required = true)
34+
private int intField;
35+
36+
@JsonProperty(required = true)
37+
private OrchestrationConvenienceUnitTest.TestClassForSchemaGeneration.InsideTestClass
38+
complexField;
39+
40+
static class InsideTestClass {
41+
@JsonProperty(required = true)
42+
private String anotherStringField;
43+
}
44+
}
45+
2246
@Test
2347
void testStackingInputAndOutputFilter() {
2448
final var config = new OrchestrationModuleConfig().withLlmConfig(GPT_4O);
@@ -173,4 +197,100 @@ void testGroundingPrompt() {
173197
.isEqualTo(
174198
"{{?userMessage}} Use the following information as additional context: {{?groundingContext}}");
175199
}
200+
201+
@Test
202+
void testResponseFormatNotOverwrittenWithNewTemplateConfig() {
203+
var schema = ResponseJsonSchema.from(TestClassForSchemaGeneration.class);
204+
var config = new OrchestrationModuleConfig().withJsonSchemaResponse(schema);
205+
assertThat(((Template) config.getTemplateConfig())).isNotNull();
206+
assertThat(
207+
((ResponseFormatJsonSchema) ((Template) config.getTemplateConfig()).getResponseFormat())
208+
.getJsonSchema()
209+
.getSchema())
210+
.isEqualTo(schema.getSchemaMap());
211+
212+
config =
213+
config.withTemplateConfig(
214+
Template.create()
215+
.template(SingleChatMessage.create().role("user").content("Hello, World!")));
216+
assertThat(((Template) config.getTemplateConfig())).isNotNull();
217+
assertThat(
218+
((ResponseFormatJsonSchema) ((Template) config.getTemplateConfig()).getResponseFormat())
219+
.getJsonSchema()
220+
.getSchema())
221+
.isEqualTo(schema.getSchemaMap());
222+
223+
config = config.withTemplateConfig(null);
224+
assertThat(((Template) config.getTemplateConfig())).isNotNull();
225+
assertThat(
226+
((ResponseFormatJsonSchema) ((Template) config.getTemplateConfig()).getResponseFormat())
227+
.getJsonSchema()
228+
.getSchema())
229+
.isEqualTo(schema.getSchemaMap());
230+
}
231+
232+
@Test
233+
void testResponseFormatOverwrittenByNewTemplateConfigWithResponseFormat() {
234+
var schema = ResponseJsonSchema.from(TestClassForSchemaGeneration.class);
235+
var config = new OrchestrationModuleConfig().withJsonSchemaResponse(schema);
236+
assertThat(((Template) config.getTemplateConfig())).isNotNull();
237+
assertThat(
238+
((ResponseFormatJsonSchema) ((Template) config.getTemplateConfig()).getResponseFormat())
239+
.getJsonSchema()
240+
.getSchema())
241+
.isEqualTo(schema.getSchemaMap());
242+
243+
config =
244+
config.withTemplateConfig(
245+
Template.create()
246+
.template(SingleChatMessage.create().role("user").content("Hello, World!"))
247+
.responseFormat(
248+
ResponseFormatJsonObject.create()
249+
.type(ResponseFormatJsonObject.TypeEnum.JSON_OBJECT)));
250+
assertThat(((Template) config.getTemplateConfig())).isNotNull();
251+
assertThat(((Template) config.getTemplateConfig()).getResponseFormat())
252+
.isInstanceOf(ResponseFormatJsonObject.class);
253+
}
254+
255+
@Test
256+
void testResponseFormatOverwrittenByNewTemplateRef() {
257+
var schema = ResponseJsonSchema.from(TestClassForSchemaGeneration.class);
258+
var config = new OrchestrationModuleConfig().withJsonSchemaResponse(schema);
259+
assertThat(((Template) config.getTemplateConfig())).isNotNull();
260+
assertThat(
261+
((ResponseFormatJsonSchema) ((Template) config.getTemplateConfig()).getResponseFormat())
262+
.getJsonSchema()
263+
.getSchema())
264+
.isEqualTo(schema.getSchemaMap());
265+
266+
config =
267+
config.withTemplateConfig(
268+
TemplateRef.create().templateRef(TemplateRefByID.create().id("123")));
269+
assertThat(config.getTemplateConfig()).isInstanceOf(TemplateRef.class);
270+
}
271+
272+
@Test
273+
void testResponseFormatOverwrittenByOtherResponseFormat() {
274+
var schema = ResponseJsonSchema.from(TestClassForSchemaGeneration.class);
275+
var config = new OrchestrationModuleConfig().withJsonSchemaResponse(schema);
276+
assertThat(((Template) config.getTemplateConfig())).isNotNull();
277+
assertThat(
278+
((ResponseFormatJsonSchema) ((Template) config.getTemplateConfig()).getResponseFormat())
279+
.getJsonSchema()
280+
.getSchema())
281+
.isEqualTo(schema.getSchemaMap());
282+
283+
config = config.withJsonResponse();
284+
assertThat(((Template) config.getTemplateConfig())).isNotNull();
285+
assertThat(((Template) config.getTemplateConfig()).getResponseFormat())
286+
.isInstanceOf(ResponseFormatJsonObject.class);
287+
288+
config = config.withJsonSchemaResponse(schema);
289+
assertThat(((Template) config.getTemplateConfig())).isNotNull();
290+
assertThat(
291+
((ResponseFormatJsonSchema) ((Template) config.getTemplateConfig()).getResponseFormat())
292+
.getJsonSchema()
293+
.getSchema())
294+
.isEqualTo(schema.getSchemaMap());
295+
}
176296
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ class Translation {
786786
ResponseJsonSchema.from(Translation.class)
787787
.withDescription("Output schema for language translation.")
788788
.withStrict(true);
789-
val configWithResponseSchema = gpt4oCustomInstance.withResponseJsonSchema(schema);
789+
val configWithResponseSchema = gpt4oCustomInstance.withJsonSchemaResponse(schema);
790790

791791
val prompt =
792792
new OrchestrationPrompt(

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,8 +352,6 @@ public OrchestrationChatResponse grounding(@Nonnull final String userMessage) {
352352
public OrchestrationChatResponse responseFormatJsonSchema(@Nonnull final String word) {
353353
val gpt4oCustomInstance = new OrchestrationModuleConfig().withLlmConfig(GPT_4O_MINI);
354354

355-
val template = Message.user("Whats '%s' in German?".formatted(word));
356-
357355
// Example class
358356
class Translation {
359357
@JsonProperty(required = true)
@@ -367,11 +365,11 @@ class Translation {
367365
ResponseJsonSchema.from(Translation.class)
368366
.withDescription("Output schema for language translation.")
369367
.withStrict(true);
370-
val configWithResponseSchema = gpt4oCustomInstance.withResponseJsonSchema(schema);
368+
val configWithResponseSchema = gpt4oCustomInstance.withJsonSchemaResponse(schema);
371369

372370
val prompt =
373371
new OrchestrationPrompt(
374-
Message.user("Whats 'apple' in German?"),
372+
Message.user("Whats '%s' in German?".formatted(word)),
375373
Message.system("You are a language translator."));
376374

377375
return client.chatCompletion(prompt, configWithResponseSchema);

sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OrchestrationTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,6 @@ void testResponseFormatJsonSchema() {
301301
final var result = service.responseFormatJsonSchema("apple").getOriginalResponse();
302302
final var choices = ((LLMModuleResultSynchronous) result.getOrchestrationResult()).getChoices();
303303
assertThat(choices.get(0).getMessage().getContent()).isNotEmpty();
304-
assertThat(choices.get(0).getMessage().getContent()).contains("\"language\":\"German\"");
305-
assertThat(choices.get(0).getMessage().getContent()).contains("\"translation\":\"Apfel\"");
306304
}
307305

308306
@Test

0 commit comments

Comments
 (0)