Skip to content

Commit e9f0ea8

Browse files
authored
feat: [Orchestration] Add Test and Sample Code for Response Format with Spring AI (#422)
* Add Test and Sample Code for Response Format with Spring AI * PMD
1 parent dad524c commit e9f0ea8

File tree

3 files changed

+49
-9
lines changed

3 files changed

+49
-9
lines changed

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -391,13 +391,9 @@ public OrchestrationChatResponse groundingHelpSapCom(@Nonnull final String userM
391391
@Nonnull
392392
public OrchestrationChatResponse responseFormatJsonSchema(@Nonnull final String word) {
393393
// Example class
394-
class Translation {
395-
@JsonProperty(required = true)
396-
private String translation;
397-
398-
@JsonProperty(required = true)
399-
private String language;
400-
}
394+
record Translation(
395+
@JsonProperty(required = true) String translation,
396+
@JsonProperty(required = true) String language) {}
401397

402398
val schema =
403399
ResponseJsonSchema.fromType(Translation.class)

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,22 @@
33
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GEMINI_1_5_FLASH;
44
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GPT_4O_MINI;
55

6+
import com.fasterxml.jackson.annotation.JsonProperty;
67
import com.sap.ai.sdk.orchestration.AzureContentFilter;
78
import com.sap.ai.sdk.orchestration.AzureFilterThreshold;
89
import com.sap.ai.sdk.orchestration.DpiMasking;
910
import com.sap.ai.sdk.orchestration.OrchestrationClientException;
1011
import com.sap.ai.sdk.orchestration.OrchestrationModuleConfig;
12+
import com.sap.ai.sdk.orchestration.ResponseJsonSchema;
13+
import com.sap.ai.sdk.orchestration.TemplateConfig;
1114
import com.sap.ai.sdk.orchestration.model.DPIEntities;
1215
import com.sap.ai.sdk.orchestration.spring.OrchestrationChatModel;
1316
import com.sap.ai.sdk.orchestration.spring.OrchestrationChatOptions;
1417
import java.util.List;
1518
import java.util.Map;
1619
import java.util.Objects;
1720
import javax.annotation.Nonnull;
21+
import javax.annotation.Nullable;
1822
import lombok.val;
1923
import org.springframework.ai.chat.client.ChatClient;
2024
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
@@ -159,7 +163,7 @@ public ChatResponse outputFiltering(@Nonnull final AzureFilterThreshold policy)
159163
*/
160164
@Nonnull
161165
public ChatResponse toolCalling(final boolean internalToolExecutionEnabled) {
162-
final OrchestrationChatOptions options = new OrchestrationChatOptions(config);
166+
val options = new OrchestrationChatOptions(config);
163167
options.setToolCallbacks(List.of(ToolCallbacks.from(new WeatherMethod())));
164168
options.setInternalToolExecutionEnabled(internalToolExecutionEnabled);
165169

@@ -184,4 +188,36 @@ public ChatResponse chatMemory() {
184188
return Objects.requireNonNull(
185189
cl.prompt(prompt2).call().chatResponse(), "Chat response is null");
186190
}
191+
192+
/**
193+
* A simple record to demonstrate the response format feature of the orchestration service.
194+
*
195+
* @param translation the translated text
196+
* @param language the language of the translation
197+
*/
198+
public record Translation(
199+
@JsonProperty(required = true) String translation,
200+
@JsonProperty(required = true) String language) {}
201+
202+
/**
203+
* Perform a chat completion where the LLM response adheres to a JSON schema. Use a Java record or
204+
* class to define the expected format and automatically parse the response into it.
205+
*
206+
* <p>In this case, we expect a {@code Translation} of the input text into Dutch.
207+
*
208+
* @return The translated text.
209+
*/
210+
@Nullable
211+
public Translation responseFormat() {
212+
val cl = ChatClient.builder(new OrchestrationChatModel()).build();
213+
214+
val schema = ResponseJsonSchema.fromType(Translation.class);
215+
val template = TemplateConfig.create().withJsonSchemaResponse(schema);
216+
217+
val options = new OrchestrationChatOptions(config.withTemplateConfig(template));
218+
val prompt =
219+
new Prompt("How do I say 'AI is going to revolutionize the world' in dutch?", options);
220+
221+
return cl.prompt(prompt).call().entity(Translation.class);
222+
}
187223
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import org.springframework.ai.chat.model.ChatResponse;
1616

1717
@Slf4j
18-
public class SpringAiOrchestrationTest {
18+
class SpringAiOrchestrationTest {
1919

2020
SpringAiOrchestrationService service = new SpringAiOrchestrationService();
2121

@@ -160,4 +160,12 @@ void testChatMemory() {
160160
.containsAnyOf(
161161
"French", "onion", "pastries", "cheese", "baguette", "coq au vin", "foie gras");
162162
}
163+
164+
@Test
165+
void testResponseFormat() {
166+
var translation = service.responseFormat();
167+
assertThat(translation).isNotNull();
168+
assertThat(translation.translation()).isNotEmpty();
169+
assertThat(translation.language()).containsIgnoringCase("dutch");
170+
}
163171
}

0 commit comments

Comments
 (0)