Skip to content

Commit b146cfd

Browse files
committed
Use library to generate schemas from java classes
1 parent 76fe48d commit b146cfd

File tree

5 files changed

+109
-26
lines changed

5 files changed

+109
-26
lines changed

orchestration/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@
132132
<artifactId>junit-jupiter-params</artifactId>
133133
<scope>test</scope>
134134
</dependency>
135+
<dependency>
136+
<groupId>com.github.victools</groupId>
137+
<artifactId>jsonschema-generator</artifactId>
138+
<scope>test</scope>
139+
</dependency>
140+
<dependency>
141+
<groupId>com.github.victools</groupId>
142+
<artifactId>jsonschema-module-jackson</artifactId>
143+
<scope>test</scope>
144+
</dependency>
135145
</dependencies>
136146

137147
<profiles>

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

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,23 @@
3030
import static org.mockito.Mockito.times;
3131
import static org.mockito.Mockito.when;
3232

33+
import com.fasterxml.jackson.annotation.JsonProperty;
3334
import com.fasterxml.jackson.core.JsonParseException;
35+
import com.fasterxml.jackson.core.JsonProcessingException;
36+
import com.fasterxml.jackson.core.type.TypeReference;
37+
import com.fasterxml.jackson.databind.JsonNode;
38+
import com.fasterxml.jackson.databind.ObjectMapper;
39+
import com.fasterxml.jackson.databind.node.ObjectNode;
3440
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
3541
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
3642
import com.github.tomakehurst.wiremock.stubbing.Scenario;
43+
import com.github.victools.jsonschema.generator.Option;
44+
import com.github.victools.jsonschema.generator.OptionPreset;
45+
import com.github.victools.jsonschema.generator.SchemaGenerator;
46+
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
47+
import com.github.victools.jsonschema.generator.SchemaVersion;
48+
import com.github.victools.jsonschema.module.jackson.JacksonModule;
49+
import com.github.victools.jsonschema.module.jackson.JacksonOption;
3750
import com.sap.ai.sdk.orchestration.model.DPIEntities;
3851
import com.sap.ai.sdk.orchestration.model.DataRepositoryType;
3952
import com.sap.ai.sdk.orchestration.model.DocumentGroundingFilter;
@@ -777,18 +790,33 @@ void testResponseObjectJsonSchema() throws IOException {
777790
var llmWithImageSupportConfig = new OrchestrationModuleConfig().withLlmConfig(GPT_4O_MINI);
778791

779792
val template = Message.user("Whats 'apple' in German?");
780-
var schema =
781-
Map.of(
782-
"type",
783-
"object",
784-
"properties",
785-
Map.of(
786-
"language", Map.of("type", "string"),
787-
"translation", Map.of("type", "string")),
788-
"required",
789-
List.of("language", "translation"),
790-
"additionalProperties",
791-
false);
793+
794+
// Example class
795+
class Translation {
796+
@JsonProperty(required = true)
797+
private String translation;
798+
799+
@JsonProperty(required = true)
800+
private String language;
801+
}
802+
803+
// Build JSON schema from class
804+
JacksonModule module =
805+
new JacksonModule(
806+
JacksonOption.RESPECT_JSONPROPERTY_REQUIRED, JacksonOption.RESPECT_JSONPROPERTY_ORDER);
807+
808+
SchemaGenerator generator =
809+
new SchemaGenerator(
810+
new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON)
811+
.without(Option.SCHEMA_VERSION_INDICATOR)
812+
.with(Option.FORBIDDEN_ADDITIONAL_PROPERTIES_BY_DEFAULT)
813+
.with(module)
814+
.build());
815+
JsonNode jsonSchema = generator.generateSchema(Translation.class);
816+
817+
// Convert JSON schema to Map
818+
val mapper = new ObjectMapper();
819+
Map<String, Object> schemaMap = mapper.convertValue(jsonSchema, new TypeReference<>() {});
792820

793821
val templatingConfig =
794822
Template.create()
@@ -799,7 +827,7 @@ void testResponseObjectJsonSchema() throws IOException {
799827
.jsonSchema(
800828
ResponseFormatJsonSchemaJsonSchema.create()
801829
.name("translation_response")
802-
.schema(schema)
830+
.schema(schemaMap)
803831
.strict(true)
804832
.description("Output schema for language translation.")));
805833
val configWithTemplate = llmWithImageSupportConfig.withTemplateConfig(templatingConfig);

pom.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
<reactor-core.version>3.6.12</reactor-core.version>
6969
<dotenv-java.version>3.1.0</dotenv-java.version>
7070
<mockito.version>5.15.2</mockito.version>
71+
<jsonschema-generator.version>4.37.0</jsonschema-generator.version>
7172
<!-- conflicts resolution -->
7273
<micrometer.version>1.14.2</micrometer.version>
7374
<json.version>20250107</json.version>
@@ -129,6 +130,16 @@
129130
<artifactId>jackson-module-parameter-names</artifactId>
130131
<version>2.18.2</version>
131132
</dependency>
133+
<dependency>
134+
<groupId>com.github.victools</groupId>
135+
<artifactId>jsonschema-generator</artifactId>
136+
<version>${jsonschema-generator.version}</version>
137+
</dependency>
138+
<dependency>
139+
<groupId>com.github.victools</groupId>
140+
<artifactId>jsonschema-module-jackson</artifactId>
141+
<version>${jsonschema-generator.version}</version>
142+
</dependency>
132143
<!-- conflicts resolution scope "compile" -->
133144
<dependency>
134145
<groupId>io.micrometer</groupId>

sample-code/spring-app/pom.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@
122122
<groupId>com.fasterxml.jackson.core</groupId>
123123
<artifactId>jackson-annotations</artifactId>
124124
</dependency>
125+
<dependency>
126+
<groupId>com.github.victools</groupId>
127+
<artifactId>jsonschema-generator</artifactId>
128+
</dependency>
129+
<dependency>
130+
<groupId>com.github.victools</groupId>
131+
<artifactId>jsonschema-module-jackson</artifactId>
132+
</dependency>
125133
<!-- scope "runtime" -->
126134
<dependency>
127135
<groupId>ch.qos.logback</groupId>

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

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GPT_4O_MINI;
55
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.Parameter.TEMPERATURE;
66

7+
import com.fasterxml.jackson.annotation.JsonProperty;
8+
import com.fasterxml.jackson.core.type.TypeReference;
9+
import com.fasterxml.jackson.databind.JsonNode;
10+
import com.fasterxml.jackson.databind.ObjectMapper;
11+
import com.github.victools.jsonschema.generator.Option;
12+
import com.github.victools.jsonschema.generator.OptionPreset;
13+
import com.github.victools.jsonschema.generator.SchemaGenerator;
14+
import com.github.victools.jsonschema.generator.SchemaGeneratorConfigBuilder;
15+
import com.github.victools.jsonschema.generator.SchemaVersion;
16+
import com.github.victools.jsonschema.module.jackson.JacksonModule;
17+
import com.github.victools.jsonschema.module.jackson.JacksonOption;
718
import com.sap.ai.sdk.core.AiCoreService;
819
import com.sap.ai.sdk.orchestration.AzureContentFilter;
920
import com.sap.ai.sdk.orchestration.AzureFilterThreshold;
@@ -354,18 +365,33 @@ public OrchestrationChatResponse responseFormatJsonSchema(@Nonnull final String
354365
new OrchestrationModuleConfig().withLlmConfig(GPT_4O_MINI);
355366

356367
val template = Message.user("Whats '%s' in German?".formatted(word));
357-
val schema =
358-
Map.of(
359-
"type",
360-
"object",
361-
"properties",
362-
Map.of(
363-
"language", Map.of("type", "string"),
364-
"translation", Map.of("type", "string")),
365-
"required",
366-
List.of("language", "translation"),
367-
"additionalProperties",
368-
false);
368+
369+
// Example class
370+
class Translation {
371+
@JsonProperty(required = true)
372+
private String translation;
373+
374+
@JsonProperty(required = true)
375+
private String language;
376+
}
377+
378+
// Build JSON schema from class
379+
JacksonModule module =
380+
new JacksonModule(
381+
JacksonOption.RESPECT_JSONPROPERTY_REQUIRED, JacksonOption.RESPECT_JSONPROPERTY_ORDER);
382+
383+
SchemaGenerator generator =
384+
new SchemaGenerator(
385+
new SchemaGeneratorConfigBuilder(SchemaVersion.DRAFT_2020_12, OptionPreset.PLAIN_JSON)
386+
.without(Option.SCHEMA_VERSION_INDICATOR)
387+
.with(Option.FORBIDDEN_ADDITIONAL_PROPERTIES_BY_DEFAULT)
388+
.with(module)
389+
.build());
390+
JsonNode jsonSchema = generator.generateSchema(Translation.class);
391+
392+
// Convert JSON schema to Map
393+
val mapper = new ObjectMapper();
394+
Map<String, Object> schemaMap = mapper.convertValue(jsonSchema, new TypeReference<>() {});
369395

370396
val templatingConfig =
371397
Template.create()
@@ -376,7 +402,7 @@ public OrchestrationChatResponse responseFormatJsonSchema(@Nonnull final String
376402
.jsonSchema(
377403
ResponseFormatJsonSchemaJsonSchema.create()
378404
.name("translation_response")
379-
.schema(schema)
405+
.schema(schemaMap)
380406
.strict(true)
381407
.description("Output schema for language translation.")));
382408
val configWithTemplate = llmWithImageSupportConfig.withTemplateConfig(templatingConfig);

0 commit comments

Comments
 (0)