Skip to content

Commit 2535170

Browse files
authored
fix: [PromptRegistry] deserialization of response_format in retrieved templates (#676)
* Initial version. No unit test * Add unit test for deserialization * Update release notes
1 parent 01ad943 commit 2535170

File tree

6 files changed

+186
-9
lines changed

6 files changed

+186
-9
lines changed

core-services/prompt-registry/src/main/java/com/sap/ai/sdk/prompt/registry/PromptClient.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@
22

33
import static com.sap.ai.sdk.core.JacksonConfiguration.getDefaultObjectMapper;
44

5+
import com.fasterxml.jackson.annotation.JsonSubTypes;
56
import com.fasterxml.jackson.annotation.JsonTypeInfo;
67
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
78
import com.google.common.collect.Iterables;
89
import com.sap.ai.sdk.core.AiCoreService;
910
import com.sap.ai.sdk.prompt.registry.client.PromptTemplatesApi;
1011
import com.sap.ai.sdk.prompt.registry.model.PromptTemplate;
1112
import com.sap.ai.sdk.prompt.registry.model.PromptTemplateSpecResponseFormat;
13+
import com.sap.ai.sdk.prompt.registry.model.ResponseFormatJsonObject;
14+
import com.sap.ai.sdk.prompt.registry.model.ResponseFormatJsonSchema;
1215
import com.sap.ai.sdk.prompt.registry.model.ResponseFormatText;
1316
import com.sap.ai.sdk.prompt.registry.model.SingleChatTemplate;
1417
import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor;
@@ -75,8 +78,16 @@ private static class JacksonMixin {
7578
@JsonDeserialize(as = SingleChatTemplate.class)
7679
interface TemplateMixIn {}
7780

78-
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
79-
@JsonDeserialize(as = ResponseFormatText.class)
81+
@JsonTypeInfo(
82+
use = JsonTypeInfo.Id.NAME,
83+
include = JsonTypeInfo.As.EXISTING_PROPERTY,
84+
property = "type",
85+
visible = true)
86+
@JsonSubTypes({
87+
@JsonSubTypes.Type(value = ResponseFormatJsonSchema.class, name = "json_schema"),
88+
@JsonSubTypes.Type(value = ResponseFormatJsonObject.class, name = "json_object"),
89+
@JsonSubTypes.Type(value = ResponseFormatText.class, name = "text")
90+
})
8091
interface ResponseFormat {}
8192
}
8293
}

core-services/prompt-registry/src/test/java/com/sap/ai/sdk/prompt/registry/PromptRegistryClientTest.java

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,36 @@
66
import com.github.tomakehurst.wiremock.junit5.WireMockExtension;
77
import com.sap.ai.sdk.core.AiCoreService;
88
import com.sap.ai.sdk.prompt.registry.model.PromptTemplateGetResponse;
9+
import com.sap.ai.sdk.prompt.registry.model.ResponseFormatJsonObject;
10+
import com.sap.ai.sdk.prompt.registry.model.ResponseFormatJsonSchema;
11+
import com.sap.ai.sdk.prompt.registry.model.ResponseFormatText;
912
import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination;
1013
import com.sap.cloud.sdk.cloudplatform.connectivity.HttpDestination;
1114
import java.util.UUID;
15+
import org.junit.jupiter.api.BeforeEach;
1216
import org.junit.jupiter.api.Test;
1317
import org.junit.jupiter.api.extension.RegisterExtension;
1418

15-
public class PromptRegistryClientTest {
19+
class PromptRegistryClientTest {
1620
@RegisterExtension
1721
private static final WireMockExtension WM =
1822
WireMockExtension.newInstance().options(wireMockConfig().dynamicPort()).build();
1923

20-
private final HttpDestination DESTINATION = DefaultHttpDestination.builder(WM.baseUrl()).build();
21-
private final AiCoreService SERVICE = new AiCoreService().withBaseDestination(DESTINATION);
24+
private static PromptClient client;
25+
26+
@BeforeEach
27+
void setup() {
28+
final HttpDestination destination = DefaultHttpDestination.builder(WM.baseUrl()).build();
29+
final AiCoreService service = new AiCoreService().withBaseDestination(destination);
30+
client = new PromptClient(service);
31+
}
2232

2333
@Test
2434
void testPipelines() {
25-
var client = new PromptClient(SERVICE);
26-
var result = client.listPromptTemplates();
35+
final var result = client.listPromptTemplates();
2736
assertThat(result.getCount()).isEqualTo(2);
2837
assertThat(result.getResources()).hasSize(2);
29-
PromptTemplateGetResponse template = result.getResources().get(0);
38+
final var template = result.getResources().get(0);
3039
assertThat(template.getId()).isEqualTo(UUID.fromString("312a9b9c-a532-4c1c-8852-bf75de887d74"));
3140
assertThat(template.getName()).isEqualTo("prompt_template_name");
3241
assertThat(template.getVersion()).isEqualTo("1.0.0");
@@ -36,4 +45,54 @@ void testPipelines() {
3645
assertThat(template.isIsVersionHead()).isEqualTo(true);
3746
assertThat(template.getSpec()).isNull();
3847
}
48+
49+
@Test
50+
void testGetTemplateWithResponseFormatText() {
51+
final var uuid = UUID.fromString("22117a64-9f2c-481b-9402-8acb66eeb707");
52+
final PromptTemplateGetResponse response = client.getPromptTemplateByUuid(uuid);
53+
54+
assertThat(response.getName()).isEqualTo("test");
55+
assertThat(response.getVersion()).isEqualTo("0.0.1");
56+
assertThat(response.getScenario()).isEqualTo("test-retrival");
57+
assertThat(response.getSpec()).isNotNull();
58+
assertThat(response.getSpec().getResponseFormat()).isInstanceOf(ResponseFormatText.class);
59+
60+
final var format = (ResponseFormatText) response.getSpec().getResponseFormat();
61+
assertThat(format.getType()).isEqualTo(ResponseFormatText.TypeEnum.TEXT);
62+
}
63+
64+
@Test
65+
void testGetTemplateWithResponseFormatJsonObject() {
66+
final var uuid = UUID.fromString("21cb1358-0bf1-4f43-870b-00f14d0f9f16");
67+
final var response = client.getPromptTemplateByUuid(uuid);
68+
69+
assertThat(response.getName()).isEqualTo("test");
70+
assertThat(response.getVersion()).isEqualTo("0.0.1");
71+
assertThat(response.getScenario()).isEqualTo("test-retrival");
72+
assertThat(response.getSpec()).isNotNull();
73+
assertThat(response.getSpec().getResponseFormat()).isInstanceOf(ResponseFormatJsonObject.class);
74+
75+
final var format = (ResponseFormatJsonObject) response.getSpec().getResponseFormat();
76+
assertThat(format.getType()).isEqualTo(ResponseFormatJsonObject.TypeEnum.JSON_OBJECT);
77+
}
78+
79+
@Test
80+
void testGetTemplateWithResponseFormatJsonSchema() {
81+
final var uuid = UUID.fromString("0f79fec4-ae07-4c35-96e3-df7f4a3f1df5");
82+
final var response = client.getPromptTemplateByUuid(uuid);
83+
84+
assertThat(response.getName()).isEqualTo("test");
85+
assertThat(response.getVersion()).isEqualTo("0.0.1");
86+
assertThat(response.getScenario()).isEqualTo("test-retrival");
87+
assertThat(response.getSpec()).isNotNull();
88+
assertThat(response.getSpec().getResponseFormat()).isInstanceOf(ResponseFormatJsonSchema.class);
89+
90+
final var format = (ResponseFormatJsonSchema) response.getSpec().getResponseFormat();
91+
assertThat(format.getType()).isEqualTo(ResponseFormatJsonSchema.TypeEnum.JSON_SCHEMA);
92+
assertThat(format.getJsonSchema()).isNotNull();
93+
assertThat(format.getJsonSchema().getName()).isEqualTo("TestSchema");
94+
assertThat(format.getJsonSchema().getDescription()).isEqualTo("Test schema description");
95+
assertThat(format.getJsonSchema().isStrict()).isFalse();
96+
assertThat(format.getJsonSchema().getSchema()).isNotNull();
97+
}
3998
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"request": {
3+
"method": "GET",
4+
"url": "/v2/lm/promptTemplates/0f79fec4-ae07-4c35-96e3-df7f4a3f1df5"
5+
},
6+
"response": {
7+
"status": 200,
8+
"headers": {
9+
"Content-Type": "application/json"
10+
},
11+
"jsonBody": {
12+
"id": "0f79fec4-ae07-4c35-96e3-df7f4a3f1df5",
13+
"name": "test",
14+
"version": "0.0.1",
15+
"scenario": "test-retrival",
16+
"creationTimestamp": "2025-12-02T16:13:32.411000",
17+
"managedBy": "imperative",
18+
"isVersionHead": true,
19+
"spec": {
20+
"template": [
21+
{
22+
"role": "system",
23+
"content": "Test content"
24+
}
25+
],
26+
"response_format": {
27+
"type": "json_schema",
28+
"json_schema": {
29+
"description": "Test schema description",
30+
"name": "TestSchema",
31+
"schema": {
32+
"key": "value"
33+
},
34+
"strict": false
35+
}
36+
},
37+
"tools": []
38+
}
39+
}
40+
}
41+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"request": {
3+
"method": "GET",
4+
"url": "/v2/lm/promptTemplates/21cb1358-0bf1-4f43-870b-00f14d0f9f16"
5+
},
6+
"response": {
7+
"status": 200,
8+
"headers": {
9+
"Content-Type": "application/json"
10+
},
11+
"jsonBody": {
12+
"id": "21cb1358-0bf1-4f43-870b-00f14d0f9f16",
13+
"name": "test",
14+
"version": "0.0.1",
15+
"scenario": "test-retrival",
16+
"creationTimestamp": "2025-12-01T13:56:46.455000",
17+
"managedBy": "imperative",
18+
"isVersionHead": true,
19+
"spec": {
20+
"template": [
21+
{
22+
"role": "system",
23+
"content": "Test content"
24+
}
25+
],
26+
"response_format": {
27+
"type": "json_object"
28+
},
29+
"tools": []
30+
}
31+
}
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"request": {
3+
"method": "GET",
4+
"url": "/v2/lm/promptTemplates/22117a64-9f2c-481b-9402-8acb66eeb707"
5+
},
6+
"response": {
7+
"status": 200,
8+
"headers": {
9+
"Content-Type": "application/json"
10+
},
11+
"jsonBody": {
12+
"id": "22117a64-9f2c-481b-9402-8acb66eeb707",
13+
"name": "test",
14+
"version": "0.0.1",
15+
"scenario": "test-retrival",
16+
"creationTimestamp": "2025-12-02T16:06:05.400000",
17+
"managedBy": "imperative",
18+
"isVersionHead": true,
19+
"spec": {
20+
"template": [
21+
{
22+
"role": "system",
23+
"content": "Test content"
24+
}
25+
],
26+
"response_format": {
27+
"type": "text"
28+
},
29+
"tools": []
30+
}
31+
}
32+
}
33+
}

docs/release_notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@
2121

2222
### 🐛 Fixed Issues
2323

24-
-
24+
- [PromptRegistry] Fix deserialization of `response_format` in retrieved prompt templates.

0 commit comments

Comments
 (0)