Skip to content

Commit 172bee8

Browse files
fix: Add actual messages to AI Core endpoints in sample app (#270)
* WiP (fixed Scenario and Config endpoints) * Fixed Deployment endpoints * Finishing touches * Remove potential cross site vulnerability * Formatting * Minimal stuff to re-trigger CI -.- * Make mapper static * One more static mapper * Use streams to build messages * Reduce complexity by relying on Spring Boot serialization * Remove unneeded dependency * Improve readability * Minor changes --------- Co-authored-by: Jonas Israel <[email protected]> Co-authored-by: SAP Cloud SDK Bot <[email protected]>
1 parent f53662c commit 172bee8

File tree

5 files changed

+194
-100
lines changed

5 files changed

+194
-100
lines changed

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/ConfigurationController.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package com.sap.ai.sdk.app.controllers;
22

33
import com.sap.ai.sdk.core.client.ConfigurationApi;
4-
import com.sap.ai.sdk.core.model.AiConfigurationList;
4+
import com.sap.ai.sdk.core.model.AiConfiguration;
5+
import java.util.stream.Collectors;
6+
import javax.annotation.Nullable;
7+
import org.springframework.http.ResponseEntity;
58
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.RequestHeader;
610
import org.springframework.web.bind.annotation.RestController;
711

812
/** Endpoint for Configuration operations */
@@ -15,10 +19,20 @@ class ConfigurationController {
1519
/**
1620
* Get the list of configurations.
1721
*
18-
* @return the list of configurations
22+
* @param accept the accept header
23+
* @return a response entity with a string representation of the list of configurations
1924
*/
2025
@GetMapping("/configurations")
21-
AiConfigurationList getConfigurations() {
22-
return CLIENT.query("default");
26+
ResponseEntity<Object> getConfigurations(
27+
@Nullable @RequestHeader(value = "accept", required = false) final String accept) {
28+
final var configList = CLIENT.query("default");
29+
if ("application/json".equals(accept)) {
30+
return ResponseEntity.ok().body(configList);
31+
}
32+
final var items =
33+
configList.getResources().stream()
34+
.map(AiConfiguration::getName)
35+
.collect(Collectors.joining(", "));
36+
return ResponseEntity.ok("The following configurations are available: %s.".formatted(items));
2337
}
2438
}

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/DeploymentController.java

Lines changed: 102 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.sap.ai.sdk.app.controllers;
22

3+
import static com.sap.ai.sdk.core.model.AiDeploymentTargetStatus.STOPPED;
4+
35
import com.sap.ai.sdk.core.client.ConfigurationApi;
46
import com.sap.ai.sdk.core.client.DeploymentApi;
57
import com.sap.ai.sdk.core.model.AiConfigurationBaseData;
@@ -10,38 +12,38 @@
1012
import com.sap.ai.sdk.core.model.AiDeploymentDeletionResponse;
1113
import com.sap.ai.sdk.core.model.AiDeploymentList;
1214
import com.sap.ai.sdk.core.model.AiDeploymentModificationRequest;
13-
import com.sap.ai.sdk.core.model.AiDeploymentModificationResponse;
14-
import com.sap.ai.sdk.core.model.AiDeploymentTargetStatus;
1515
import com.sap.ai.sdk.core.model.AiParameterArgumentBinding;
1616
import com.sap.ai.sdk.foundationmodels.openai.OpenAiModel;
1717
import java.util.List;
18+
import java.util.stream.Collectors;
1819
import javax.annotation.Nonnull;
1920
import javax.annotation.Nullable;
2021
import lombok.extern.slf4j.Slf4j;
22+
import org.springframework.http.ResponseEntity;
2123
import org.springframework.web.bind.annotation.GetMapping;
2224
import org.springframework.web.bind.annotation.PathVariable;
25+
import org.springframework.web.bind.annotation.RequestHeader;
2326
import org.springframework.web.bind.annotation.RequestMapping;
2427
import org.springframework.web.bind.annotation.RestController;
2528

2629
/** Endpoints for AI Core AiDeployment operations */
2730
@Slf4j
2831
@RestController
32+
@SuppressWarnings("unused")
2933
@RequestMapping("/deployments")
3034
class DeploymentController {
3135

3236
private static final DeploymentApi CLIENT = new DeploymentApi();
3337
private static final String RESOURCE_GROUP = "default";
3438

3539
/**
36-
* Create and delete a deployment with the Java specific configuration ID
40+
* Create and delete a deployment with the Java specific configuration ID.
3741
*
3842
* @param configId The configuration id.
3943
* @return the deployment deletion response
4044
*/
41-
@GetMapping("/by-config/{id}/createDelete")
4245
@Nullable
43-
AiDeploymentDeletionResponse createAndDeleteDeploymentByConfigId(
44-
@Nonnull @PathVariable("id") final String configId) {
46+
AiDeploymentDeletionResponse createAndDeleteDeploymentByConfigId(final String configId) {
4547
final var deployment =
4648
CLIENT.create(
4749
RESOURCE_GROUP, AiDeploymentCreationRequest.create().configurationId(configId));
@@ -52,62 +54,109 @@ AiDeploymentDeletionResponse createAndDeleteDeploymentByConfigId(
5254
}
5355

5456
/**
55-
* Stop all deployments with the Java specific configuration ID
57+
* Create and delete a deployment with the Java specific configuration ID.
58+
*
59+
* @param configId The configuration id.
60+
* @param accept The accept header.
61+
* @return a response entity with a string representation of the deployment creation response
62+
*/
63+
@GetMapping("/by-config/{id}/createDelete")
64+
ResponseEntity<Object> createAndDeleteDeploymentByConfigId(
65+
@Nonnull @PathVariable("id") final String configId,
66+
@Nullable @RequestHeader(value = "accept", required = false) final String accept) {
67+
final var response = createAndDeleteDeploymentByConfigId(configId);
68+
if ("application/json".equals(accept)) {
69+
return ResponseEntity.ok().body(response);
70+
}
71+
return ResponseEntity.ok("Deployment created and will be deleted.");
72+
}
73+
74+
/**
75+
* Stop all deployments with the Java specific configuration ID.
5676
*
5777
* <p>Only RUNNING deployments can be STOPPED
5878
*
5979
* @param configId The configuration id.
60-
* @return the deployment modification response
80+
* @param accept The accept header.
81+
* @return a response entity with a string representation of the deployment modification response
6182
*/
6283
@GetMapping("/by-config/{id}/stop")
6384
@Nonnull
64-
@SuppressWarnings("unused") // debug method that doesn't need to be tested
65-
List<AiDeploymentModificationResponse> stopByConfigId(
66-
@Nonnull @PathVariable("id") final String configId) {
85+
ResponseEntity<Object> stopByConfigId(
86+
@Nonnull @PathVariable("id") final String configId,
87+
@Nullable @RequestHeader(value = "accept", required = false) final String accept) {
6788
final List<AiDeployment> myDeployments = getAllByConfigId(configId);
6889
log.info("Found {} deployments to STOP", myDeployments.size());
6990

7091
// STOP my deployments
71-
return myDeployments.stream()
72-
.map(
73-
deployment ->
74-
CLIENT.modify(
75-
RESOURCE_GROUP,
76-
deployment.getId(),
77-
AiDeploymentModificationRequest.create()
78-
.targetStatus(AiDeploymentTargetStatus.STOPPED)))
79-
.toList();
92+
final var modificationRequest = AiDeploymentModificationRequest.create().targetStatus(STOPPED);
93+
final var stoppedDeployments =
94+
myDeployments.stream()
95+
.map(AiDeployment::getId)
96+
.map(id -> CLIENT.modify(RESOURCE_GROUP, id, modificationRequest))
97+
.toList();
98+
99+
if ("application/json".equals(accept)) {
100+
return ResponseEntity.ok().body(stoppedDeployments);
101+
}
102+
return ResponseEntity.ok("Deployments under the given config ID stopped.");
80103
}
81104

82105
/**
83-
* Delete all deployments with the Java specific configuration ID
106+
* Delete all deployments with the Java specific configuration ID.
84107
*
85108
* <p>Only UNKNOWN and STOPPED deployments can be DELETED
86109
*
87110
* @param configId The configuration id.
88-
* @return the deployment deletion response
111+
* @param accept The accept header.
112+
* @return a response entity with a string representation of the deployment deletion response
89113
*/
90114
@GetMapping("/by-config/{id}/delete")
91115
@Nonnull
92-
@SuppressWarnings("unused") // debug method that doesn't need to be tested
93-
List<AiDeploymentDeletionResponse> deleteByConfigId(
94-
@Nonnull @PathVariable("id") final String configId) {
116+
ResponseEntity<Object> deleteByConfigId(
117+
@Nonnull @PathVariable("id") final String configId,
118+
@Nullable @RequestHeader(value = "accept", required = false) final String accept) {
95119
final List<AiDeployment> myDeployments = getAllByConfigId(configId);
96120
log.info("Found {} deployments to DELETE", myDeployments.size());
97121

98122
// DELETE my deployments
99-
return myDeployments.stream()
100-
.map(deployment -> CLIENT.delete(RESOURCE_GROUP, deployment.getId()))
101-
.toList();
123+
final var responseList =
124+
myDeployments.stream()
125+
.map(deployment -> CLIENT.delete(RESOURCE_GROUP, deployment.getId()))
126+
.toList();
127+
if ("application/json".equals(accept)) {
128+
return ResponseEntity.ok().body(responseList);
129+
}
130+
return ResponseEntity.ok("Deployments under the given config ID deleted.");
102131
}
103132

104133
/**
105-
* Get all deployments with the Java specific configuration ID
134+
* Get all deployments with the Java specific configuration ID.
106135
*
107136
* @param configId The configuration id.
108-
* @return the Java specific deployments
137+
* @param accept The accept header.
138+
* @return a response entity with a string representation of the Java specific deployments
109139
*/
110140
@GetMapping("/by-config/{id}/getAll")
141+
ResponseEntity<Object> getAllByConfigId(
142+
@Nonnull @PathVariable("id") final String configId,
143+
@Nullable @RequestHeader(value = "accept", required = false) final String accept) {
144+
final var deployments = getAllByConfigId(configId);
145+
if ("application/json".equals(accept)) {
146+
return ResponseEntity.ok().body(deployments);
147+
}
148+
final var items =
149+
deployments.stream().map(AiDeployment::getId).collect(Collectors.joining(", "));
150+
return ResponseEntity.ok(
151+
"The following Java-specific deployments are available: %s.".formatted(items));
152+
}
153+
154+
/**
155+
* Get all deployments with the Java specific configuration ID.
156+
*
157+
* @param configId The configuration id.
158+
* @return the Java specific deployments
159+
*/
111160
@Nonnull
112161
List<AiDeployment> getAllByConfigId(@Nonnull @PathVariable("id") final String configId) {
113162
final AiDeploymentList deploymentList = CLIENT.query(RESOURCE_GROUP);
@@ -120,9 +169,31 @@ List<AiDeployment> getAllByConfigId(@Nonnull @PathVariable("id") final String co
120169
/**
121170
* Get all deployments, including non-Java specific deployments
122171
*
123-
* @return the Java specific deployments
172+
* @param accept The accept header.
173+
* @return a response entity with a string representation of the Java specific deployments
124174
*/
125175
@GetMapping("/getAll")
176+
@Nonnull
177+
ResponseEntity<Object> getAll(
178+
@Nullable @RequestHeader(value = "accept", required = false) final String accept) {
179+
final var deployments = getAll();
180+
if ("application/json".equals(accept)) {
181+
return ResponseEntity.ok().body(deployments);
182+
}
183+
final var items =
184+
deployments != null
185+
? deployments.getResources().stream()
186+
.map(AiDeployment::getId)
187+
.collect(Collectors.joining(", "))
188+
: "";
189+
return ResponseEntity.ok("The following deployments are available: %s.".formatted(items));
190+
}
191+
192+
/**
193+
* Get all deployments
194+
*
195+
* @return all deployments
196+
*/
126197
@Nullable
127198
AiDeploymentList getAll() {
128199
return CLIENT.query(RESOURCE_GROUP);

sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.io.IOException;
1111
import java.util.Arrays;
1212
import javax.annotation.Nonnull;
13+
import javax.annotation.Nullable;
1314
import lombok.extern.slf4j.Slf4j;
1415
import org.springframework.beans.factory.annotation.Autowired;
1516
import org.springframework.http.MediaType;
@@ -26,7 +27,7 @@
2627
@SuppressWarnings("unused")
2728
public class OpenAiController {
2829
@Autowired private OpenAiService service;
29-
private final ObjectMapper mapper =
30+
private static final ObjectMapper MAPPER =
3031
new ObjectMapper().setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
3132

3233
/**
@@ -37,13 +38,11 @@ public class OpenAiController {
3738
@GetMapping("/chatCompletion")
3839
@Nonnull
3940
ResponseEntity<String> chatCompletion(
40-
@RequestHeader(value = "accept", required = false) final String accept)
41+
@Nullable @RequestHeader(value = "accept", required = false) final String accept)
4142
throws JsonProcessingException {
4243
final var response = service.chatCompletion("Who is the prettiest");
4344
if ("application/json".equals(accept)) {
44-
return ResponseEntity.ok()
45-
.contentType(MediaType.APPLICATION_JSON)
46-
.body(mapper.writeValueAsString(response));
45+
return ResponseEntity.ok().body(MAPPER.writeValueAsString(response));
4746
}
4847
return ResponseEntity.ok(response.getContent());
4948
}
@@ -143,15 +142,13 @@ private static String objectToJson(@Nonnull final Object obj) {
143142
@GetMapping("/chatCompletionImage")
144143
@Nonnull
145144
ResponseEntity<String> chatCompletionImage(
146-
@RequestHeader(value = "accept", required = false) final String accept)
145+
@Nullable @RequestHeader(value = "accept", required = false) final String accept)
147146
throws JsonProcessingException {
148147
final var response =
149148
service.chatCompletionImage(
150149
"https://upload.wikimedia.org/wikipedia/commons/thumb/5/59/SAP_2011_logo.svg/440px-SAP_2011_logo.svg.png");
151150
if ("application/json".equals(accept)) {
152-
return ResponseEntity.ok()
153-
.contentType(MediaType.APPLICATION_JSON)
154-
.body(mapper.writeValueAsString(response));
151+
return ResponseEntity.ok().body(MAPPER.writeValueAsString(response));
155152
}
156153
return ResponseEntity.ok(response.getContent());
157154
}
@@ -164,14 +161,12 @@ ResponseEntity<String> chatCompletionImage(
164161
@GetMapping("/chatCompletionTool")
165162
@Nonnull
166163
ResponseEntity<String> chatCompletionTools(
167-
@RequestHeader(value = "accept", required = false) final String accept)
164+
@Nullable @RequestHeader(value = "accept", required = false) final String accept)
168165
throws JsonProcessingException {
169166
final var response =
170167
service.chatCompletionTools("Calculate the Fibonacci number for given sequence index.");
171168
if ("application/json".equals(accept)) {
172-
return ResponseEntity.ok()
173-
.contentType(MediaType.APPLICATION_JSON)
174-
.body(mapper.writeValueAsString(response));
169+
return ResponseEntity.ok().body(MAPPER.writeValueAsString(response));
175170
}
176171
return ResponseEntity.ok(response.getContent());
177172
}
@@ -185,9 +180,7 @@ ResponseEntity<String> chatCompletionTools(
185180
@Nonnull
186181
ResponseEntity<String> embedding() throws JsonProcessingException {
187182
final var response = service.embedding("Hello world");
188-
return ResponseEntity.ok()
189-
.contentType(MediaType.APPLICATION_JSON)
190-
.body(mapper.writeValueAsString(response));
183+
return ResponseEntity.ok().body(MAPPER.writeValueAsString(response));
191184
}
192185

193186
/**
@@ -199,15 +192,13 @@ ResponseEntity<String> embedding() throws JsonProcessingException {
199192
@GetMapping("/chatCompletion/{resourceGroup}")
200193
@Nonnull
201194
ResponseEntity<String> chatCompletionWithResource(
202-
@RequestHeader(value = "accept", required = false) final String accept,
195+
@Nullable @RequestHeader(value = "accept", required = false) final String accept,
203196
@Nonnull @PathVariable("resourceGroup") final String resourceGroup)
204197
throws JsonProcessingException {
205198
final var response =
206199
service.chatCompletionWithResource(resourceGroup, "Where is the nearest coffee shop?");
207200
if ("application/json".equals(accept)) {
208-
return ResponseEntity.ok()
209-
.contentType(MediaType.APPLICATION_JSON)
210-
.body(mapper.writeValueAsString(response));
201+
return ResponseEntity.ok().body(MAPPER.writeValueAsString(response));
211202
}
212203
return ResponseEntity.ok(response.getContent());
213204
}

0 commit comments

Comments
 (0)