Skip to content

Commit b801fc8

Browse files
committed
Services don't use Spring classes
1 parent 8d93253 commit b801fc8

File tree

4 files changed

+105
-105
lines changed

4 files changed

+105
-105
lines changed

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

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import com.fasterxml.jackson.databind.ObjectMapper;
77
import com.sap.ai.sdk.app.services.OpenAiService;
88
import javax.annotation.Nonnull;
9+
10+
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionOutput;
11+
import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutors;
912
import lombok.extern.slf4j.Slf4j;
1013
import org.springframework.beans.factory.annotation.Autowired;
1114
import org.springframework.http.MediaType;
@@ -16,6 +19,9 @@
1619
import org.springframework.web.bind.annotation.RestController;
1720
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
1821

22+
import java.io.IOException;
23+
import java.util.Arrays;
24+
1925
/** Endpoints for OpenAI operations */
2026
@Slf4j
2127
@RestController
@@ -54,7 +60,26 @@ ResponseEntity<String> chatCompletion(
5460
@Nonnull
5561
ResponseEntity<ResponseBodyEmitter> streamChatCompletionDeltas() {
5662
final var message = "Can you give me the first 100 numbers of the Fibonacci sequence?";
57-
return service.streamChatCompletionDeltas(message);
63+
final var stream = service.streamChatCompletionDeltas(message);
64+
final var emitter = new ResponseBodyEmitter();
65+
final Runnable consumeStream =
66+
() -> {
67+
final var totalOutput = new OpenAiChatCompletionOutput();
68+
// try-with-resources ensures the stream is closed
69+
try (stream) {
70+
stream
71+
.peek(totalOutput::addDelta)
72+
.forEach(delta -> send(emitter, delta.getDeltaContent()));
73+
} finally {
74+
send(emitter, "\n\n-----Total Output-----\n\n" + objectToJson(totalOutput));
75+
emitter.complete();
76+
}
77+
};
78+
79+
ThreadContextExecutors.getExecutor().execute(consumeStream);
80+
81+
// TEXT_EVENT_STREAM allows the browser to display the content as it is streamed
82+
return ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(emitter);
5883
}
5984

6085
/**
@@ -67,7 +92,51 @@ ResponseEntity<ResponseBodyEmitter> streamChatCompletionDeltas() {
6792
@Nonnull
6893
ResponseEntity<ResponseBodyEmitter> streamChatCompletion() {
6994
final var message = "Can you give me the first 100 numbers of the Fibonacci sequence?";
70-
return service.streamChatCompletion(message);
95+
final var stream = service.streamChatCompletion(message);
96+
final var emitter = new ResponseBodyEmitter();
97+
98+
final Runnable consumeStream =
99+
() -> {
100+
try (stream) {
101+
stream.forEach(deltaMessage -> send(emitter, deltaMessage));
102+
} finally {
103+
emitter.complete();
104+
}
105+
};
106+
107+
ThreadContextExecutors.getExecutor().execute(consumeStream);
108+
109+
// TEXT_EVENT_STREAM allows the browser to display the content as it is streamed
110+
return ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(emitter);
111+
}
112+
113+
/**
114+
* Send a chunk to the emitter
115+
*
116+
* @param emitter The emitter to send the chunk to
117+
* @param chunk The chunk to send
118+
*/
119+
public static void send(@Nonnull final ResponseBodyEmitter emitter, @Nonnull final String chunk) {
120+
try {
121+
emitter.send(chunk);
122+
} catch (final IOException e) {
123+
log.error(Arrays.toString(e.getStackTrace()));
124+
emitter.completeWithError(e);
125+
}
126+
}
127+
128+
/**
129+
* Convert an object to JSON
130+
*
131+
* @param obj The object to convert
132+
* @return The JSON representation of the object
133+
*/
134+
private static String objectToJson(@Nonnull final Object obj) {
135+
try {
136+
return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(obj);
137+
} catch (final JsonProcessingException ignored) {
138+
return "Could not parse object to JSON";
139+
}
71140
}
72141

73142
/**

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import com.sap.ai.sdk.orchestration.AzureFilterThreshold;
99
import com.sap.ai.sdk.orchestration.model.DPIEntities;
1010
import javax.annotation.Nonnull;
11+
12+
import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutors;
1113
import lombok.extern.slf4j.Slf4j;
1214
import org.springframework.beans.factory.annotation.Autowired;
1315
import org.springframework.http.MediaType;
@@ -19,6 +21,8 @@
1921
import org.springframework.web.bind.annotation.RestController;
2022
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
2123

24+
import static com.sap.ai.sdk.app.controllers.OpenAiController.send;
25+
2226
/** Endpoints for the Orchestration service */
2327
@RestController
2428
@Slf4j
@@ -56,7 +60,25 @@ ResponseEntity<String> completion(
5660
@GetMapping("/streamChatCompletion")
5761
@Nonnull
5862
ResponseEntity<ResponseBodyEmitter> streamChatCompletion() {
59-
return service.streamChatCompletion(100);
63+
final var stream = service.streamChatCompletion(100);
64+
final var emitter = new ResponseBodyEmitter();
65+
final Runnable consumeStream =
66+
() -> {
67+
try (stream) {
68+
stream.forEach(
69+
deltaMessage -> {
70+
log.info("Service: {}", deltaMessage);
71+
send(emitter, deltaMessage);
72+
});
73+
} finally {
74+
emitter.complete();
75+
}
76+
};
77+
78+
ThreadContextExecutors.getExecutor().execute(consumeStream);
79+
80+
// TEXT_EVENT_STREAM allows the browser to display the content as it is streamed
81+
return ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(emitter);
6082
}
6183

6284
/**

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

Lines changed: 8 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,22 @@
55
import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.TEXT_EMBEDDING_ADA_002;
66
import static com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionTool.ToolType.FUNCTION;
77

8-
import com.fasterxml.jackson.core.JsonProcessingException;
9-
import com.fasterxml.jackson.databind.ObjectMapper;
108
import com.sap.ai.sdk.core.AiCoreService;
119
import com.sap.ai.sdk.foundationmodels.openai.OpenAiClient;
10+
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionDelta;
1211
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionFunction;
1312
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionOutput;
1413
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionParameters;
1514
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionTool;
1615
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatMessage;
1716
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingOutput;
1817
import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingParameters;
19-
import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutors;
20-
import java.io.IOException;
21-
import java.util.Arrays;
2218
import java.util.List;
2319
import java.util.Map;
20+
import java.util.stream.Stream;
2421
import javax.annotation.Nonnull;
2522
import lombok.extern.slf4j.Slf4j;
26-
import org.springframework.http.MediaType;
27-
import org.springframework.http.ResponseEntity;
2823
import org.springframework.stereotype.Service;
29-
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
3024

3125
/** Service class for OpenAI service */
3226
@Service
@@ -50,34 +44,13 @@ public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) {
5044
* @return the emitter that streams the assistant message response
5145
*/
5246
@Nonnull
53-
public ResponseEntity<ResponseBodyEmitter> streamChatCompletionDeltas(
47+
public Stream<OpenAiChatCompletionDelta> streamChatCompletionDeltas(
5448
@Nonnull final String message) {
5549
final var request =
5650
new OpenAiChatCompletionParameters()
5751
.addMessages(new OpenAiChatMessage.OpenAiChatUserMessage().addText(message));
5852

59-
final var stream = OpenAiClient.forModel(GPT_35_TURBO).streamChatCompletionDeltas(request);
60-
61-
final var emitter = new ResponseBodyEmitter();
62-
63-
final Runnable consumeStream =
64-
() -> {
65-
final var totalOutput = new OpenAiChatCompletionOutput();
66-
// try-with-resources ensures the stream is closed
67-
try (stream) {
68-
stream
69-
.peek(totalOutput::addDelta)
70-
.forEach(delta -> send(emitter, delta.getDeltaContent()));
71-
} finally {
72-
send(emitter, "\n\n-----Total Output-----\n\n" + objectToJson(totalOutput));
73-
emitter.complete();
74-
}
75-
};
76-
77-
ThreadContextExecutors.getExecutor().execute(consumeStream);
78-
79-
// TEXT_EVENT_STREAM allows the browser to display the content as it is streamed
80-
return ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(emitter);
53+
return OpenAiClient.forModel(GPT_35_TURBO).streamChatCompletionDeltas(request);
8154
}
8255

8356
/**
@@ -86,50 +59,10 @@ public ResponseEntity<ResponseBodyEmitter> streamChatCompletionDeltas(
8659
* @return the emitter that streams the assistant message response
8760
*/
8861
@Nonnull
89-
public ResponseEntity<ResponseBodyEmitter> streamChatCompletion(@Nonnull final String message) {
90-
final var stream =
91-
OpenAiClient.forModel(GPT_35_TURBO)
92-
.withSystemPrompt("Be a good, honest AI and answer the following question:")
93-
.streamChatCompletion(message);
94-
95-
final var emitter = new ResponseBodyEmitter();
96-
97-
final Runnable consumeStream =
98-
() -> {
99-
try (stream) {
100-
stream.forEach(deltaMessage -> send(emitter, deltaMessage));
101-
} finally {
102-
emitter.complete();
103-
}
104-
};
105-
106-
ThreadContextExecutors.getExecutor().execute(consumeStream);
107-
108-
// TEXT_EVENT_STREAM allows the browser to display the content as it is streamed
109-
return ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(emitter);
110-
}
111-
112-
private static String objectToJson(@Nonnull final Object obj) {
113-
try {
114-
return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(obj);
115-
} catch (final JsonProcessingException ignored) {
116-
return "Could not parse object to JSON";
117-
}
118-
}
119-
120-
/**
121-
* Send a chunk to the emitter
122-
*
123-
* @param emitter The emitter to send the chunk to
124-
* @param chunk The chunk to send
125-
*/
126-
public static void send(@Nonnull final ResponseBodyEmitter emitter, @Nonnull final String chunk) {
127-
try {
128-
emitter.send(chunk);
129-
} catch (final IOException e) {
130-
log.error(Arrays.toString(e.getStackTrace()));
131-
emitter.completeWithError(e);
132-
}
62+
public Stream<String> streamChatCompletion(@Nonnull final String message) {
63+
return OpenAiClient.forModel(GPT_35_TURBO)
64+
.withSystemPrompt("Be a good, honest AI and answer the following question:")
65+
.streamChatCompletion(message);
13366
}
13467

13568
/**

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

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.sap.ai.sdk.app.services;
22

3-
import static com.sap.ai.sdk.app.services.OpenAiService.send;
43
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GEMINI_1_5_FLASH;
54
import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.Parameter.TEMPERATURE;
65

@@ -19,16 +18,13 @@
1918
import com.sap.ai.sdk.orchestration.model.GroundingModuleConfig;
2019
import com.sap.ai.sdk.orchestration.model.GroundingModuleConfigConfig;
2120
import com.sap.ai.sdk.orchestration.model.Template;
22-
import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutors;
2321
import java.util.List;
2422
import java.util.Map;
23+
import java.util.stream.Stream;
2524
import javax.annotation.Nonnull;
2625
import lombok.Getter;
2726
import lombok.extern.slf4j.Slf4j;
28-
import org.springframework.http.MediaType;
29-
import org.springframework.http.ResponseEntity;
3027
import org.springframework.stereotype.Service;
31-
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
3228

3329
/** Service class for the Orchestration service */
3430
@Service
@@ -239,32 +235,12 @@ public OrchestrationChatResponse grounding(@Nonnull final String groundingInput)
239235
* @return the emitter that streams the assistant message response
240236
*/
241237
@Nonnull
242-
public ResponseEntity<ResponseBodyEmitter> streamChatCompletion(final int numberOfFibonacci) {
238+
public Stream<String> streamChatCompletion(final int numberOfFibonacci) {
243239
final var prompt =
244240
new OrchestrationPrompt(
245241
"Can you give me the first "
246242
+ numberOfFibonacci
247243
+ " numbers of the Fibonacci sequence?");
248-
final var stream = client.streamChatCompletion(prompt, config);
249-
250-
final var emitter = new ResponseBodyEmitter();
251-
252-
final Runnable consumeStream =
253-
() -> {
254-
try (stream) {
255-
stream.forEach(
256-
deltaMessage -> {
257-
log.info("Service: {}", deltaMessage);
258-
send(emitter, deltaMessage);
259-
});
260-
} finally {
261-
emitter.complete();
262-
}
263-
};
264-
265-
ThreadContextExecutors.getExecutor().execute(consumeStream);
266-
267-
// TEXT_EVENT_STREAM allows the browser to display the content as it is streamed
268-
return ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(emitter);
244+
return client.streamChatCompletion(prompt, config);
269245
}
270246
}

0 commit comments

Comments
 (0)