diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java index c48f739e3..9a48b73c1 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java @@ -1,49 +1,51 @@ package com.sap.ai.sdk.app.controllers; -import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.GPT_35_TURBO; -import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.GPT_4O; -import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.TEXT_EMBEDDING_ADA_002; -import static com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionTool.ToolType.FUNCTION; - +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.sap.ai.sdk.core.AiCoreService; -import com.sap.ai.sdk.foundationmodels.openai.OpenAiClient; -import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionFunction; +import com.sap.ai.sdk.app.services.OpenAiService; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionOutput; -import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionParameters; -import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionTool; -import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatMessage.OpenAiChatUserMessage; -import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatMessage.OpenAiChatUserMessage.ImageDetailLevel; -import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingOutput; -import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingParameters; import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutors; import java.io.IOException; import java.util.Arrays; -import java.util.List; -import java.util.Map; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter; /** Endpoints for OpenAI operations */ @Slf4j @RestController +@SuppressWarnings("unused") public class OpenAiController { + @Autowired private OpenAiService service; + private final ObjectMapper mapper = + new ObjectMapper().setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); + /** * Chat request to OpenAI * - * @return the assistant message response + * @return a ResponseEntity with the response content */ @GetMapping("/chatCompletion") @Nonnull - OpenAiChatCompletionOutput chatCompletion() { - return OpenAiClient.forModel(GPT_35_TURBO).chatCompletion("Who is the prettiest"); + ResponseEntity chatCompletion( + @RequestHeader(value = "accept", required = false) final String accept) + throws JsonProcessingException { + final var response = service.chatCompletion("Who is the prettiest"); + if ("application/json".equals(accept)) { + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(mapper.writeValueAsString(response)); + } + return ResponseEntity.ok(response.getContent()); } /** @@ -55,14 +57,9 @@ OpenAiChatCompletionOutput chatCompletion() { @GetMapping("/streamChatCompletionDeltas") @Nonnull ResponseEntity streamChatCompletionDeltas() { - final var msg = "Can you give me the first 100 numbers of the Fibonacci sequence?"; - final var request = - new OpenAiChatCompletionParameters().addMessages(new OpenAiChatUserMessage().addText(msg)); - - final var stream = OpenAiClient.forModel(GPT_35_TURBO).streamChatCompletionDeltas(request); - + final var message = "Can you give me the first 100 numbers of the Fibonacci sequence?"; + final var stream = service.streamChatCompletionDeltas(message); final var emitter = new ResponseBodyEmitter(); - final Runnable consumeStream = () -> { final var totalOutput = new OpenAiChatCompletionOutput(); @@ -76,21 +73,12 @@ ResponseEntity streamChatCompletionDeltas() { emitter.complete(); } }; - ThreadContextExecutors.getExecutor().execute(consumeStream); // TEXT_EVENT_STREAM allows the browser to display the content as it is streamed return ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(emitter); } - private static String objectToJson(@Nonnull final Object obj) { - try { - return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(obj); - } catch (final JsonProcessingException ignored) { - return "Could not parse object to JSON"; - } - } - /** * Asynchronous stream of an OpenAI chat request * @@ -100,12 +88,8 @@ private static String objectToJson(@Nonnull final Object obj) { @GetMapping("/streamChatCompletion") @Nonnull ResponseEntity streamChatCompletion() { - final var stream = - OpenAiClient.forModel(GPT_35_TURBO) - .withSystemPrompt("Be a good, honest AI and answer the following question:") - .streamChatCompletion( - "Can you give me the first 100 numbers of the Fibonacci sequence?"); - + final var message = "Can you give me the first 100 numbers of the Fibonacci sequence?"; + final var stream = service.streamChatCompletion(message); final var emitter = new ResponseBodyEmitter(); final Runnable consumeStream = @@ -116,7 +100,6 @@ ResponseEntity streamChatCompletion() { emitter.complete(); } }; - ThreadContextExecutors.getExecutor().execute(consumeStream); // TEXT_EVENT_STREAM allows the browser to display the content as it is streamed @@ -138,80 +121,94 @@ public static void send(@Nonnull final ResponseBodyEmitter emitter, @Nonnull fin } } + /** + * Convert an object to JSON + * + * @param obj The object to convert + * @return The JSON representation of the object + */ + private static String objectToJson(@Nonnull final Object obj) { + try { + return new ObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(obj); + } catch (final JsonProcessingException ignored) { + return "Could not parse object to JSON"; + } + } + /** * Chat request to OpenAI with an image * - * @return the assistant message response + * @return a ResponseEntity with the response content */ @GetMapping("/chatCompletionImage") @Nonnull - OpenAiChatCompletionOutput chatCompletionImage() { - final var request = - new OpenAiChatCompletionParameters() - .addMessages( - new OpenAiChatUserMessage() - .addText("Describe the following image.") - .addImage( - "https://upload.wikimedia.org/wikipedia/commons/thumb/5/59/SAP_2011_logo.svg/440px-SAP_2011_logo.svg.png", - ImageDetailLevel.HIGH)); - - return OpenAiClient.forModel(GPT_4O).chatCompletion(request); + ResponseEntity chatCompletionImage( + @RequestHeader(value = "accept", required = false) final String accept) + throws JsonProcessingException { + final var response = + service.chatCompletionImage( + "https://upload.wikimedia.org/wikipedia/commons/thumb/5/59/SAP_2011_logo.svg/440px-SAP_2011_logo.svg.png"); + if ("application/json".equals(accept)) { + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(mapper.writeValueAsString(response)); + } + return ResponseEntity.ok(response.getContent()); } /** * Chat request to OpenAI with a tool. * - * @return the assistant message response + * @return a ResponseEntity with the response content */ @GetMapping("/chatCompletionTool") @Nonnull - OpenAiChatCompletionOutput chatCompletionTools() { - final var question = - "A pair of rabbits is placed in a field. Each month, every pair produces one new pair, starting from the second month. How many rabbits will there be after 12 months?"; - final var par = Map.of("type", "object", "properties", Map.of("N", Map.of("type", "integer"))); - final var function = - new OpenAiChatCompletionFunction() - .setName("fibonacci") - .setDescription("Calculate the Fibonacci number for given sequence index.") - .setParameters(par); - final var tool = new OpenAiChatCompletionTool().setType(FUNCTION).setFunction(function); - final var request = - new OpenAiChatCompletionParameters() - .addMessages(new OpenAiChatUserMessage().addText(question)) - .setTools(List.of(tool)) - .setToolChoiceFunction("fibonacci"); - - return OpenAiClient.forModel(GPT_35_TURBO).chatCompletion(request); + ResponseEntity chatCompletionTools( + @RequestHeader(value = "accept", required = false) final String accept) + throws JsonProcessingException { + final var response = + service.chatCompletionTools("Calculate the Fibonacci number for given sequence index."); + if ("application/json".equals(accept)) { + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(mapper.writeValueAsString(response)); + } + return ResponseEntity.ok(response.getContent()); } /** * Get the embedding of a text * - * @return the embedding response + * @return a ResponseEntity with the response content */ @GetMapping("/embedding") @Nonnull - OpenAiEmbeddingOutput embedding() { - final var request = new OpenAiEmbeddingParameters().setInput("Hello World"); - - return OpenAiClient.forModel(TEXT_EMBEDDING_ADA_002).embedding(request); + ResponseEntity embedding() throws JsonProcessingException { + final var response = service.embedding("Hello world"); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(mapper.writeValueAsString(response)); } /** * Chat request to OpenAI filtering by resource group * * @param resourceGroup The resource group to use - * @return the assistant message response + * @return a ResponseEntity with the response content */ @GetMapping("/chatCompletion/{resourceGroup}") @Nonnull - public static OpenAiChatCompletionOutput chatCompletionWithResource( - @Nonnull @PathVariable("resourceGroup") final String resourceGroup) { - - final var destination = - new AiCoreService().getInferenceDestination(resourceGroup).forModel(GPT_4O); - - return OpenAiClient.withCustomDestination(destination) - .chatCompletion("Where is the nearest coffee shop?"); + ResponseEntity chatCompletionWithResource( + @RequestHeader(value = "accept", required = false) final String accept, + @Nonnull @PathVariable("resourceGroup") final String resourceGroup) + throws JsonProcessingException { + final var response = + service.chatCompletionWithResource(resourceGroup, "Where is the nearest coffee shop?"); + if ("application/json".equals(accept)) { + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_JSON) + .body(mapper.writeValueAsString(response)); + } + return ResponseEntity.ok(response.getContent()); } } diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java index ef301ad6b..3c3f6ab6e 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/controllers/OrchestrationController.java @@ -1,5 +1,7 @@ package com.sap.ai.sdk.app.controllers; +import static com.sap.ai.sdk.app.controllers.OpenAiController.send; + import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.core.JsonProcessingException; @@ -7,6 +9,7 @@ import com.sap.ai.sdk.app.services.OrchestrationService; import com.sap.ai.sdk.orchestration.AzureFilterThreshold; import com.sap.ai.sdk.orchestration.model.DPIEntities; +import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutors; import javax.annotation.Nonnull; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -40,7 +43,7 @@ ResponseEntity completion( @RequestHeader(value = "accept", required = false) final String accept) throws JsonProcessingException { final var response = service.completion("HelloWorld!"); - if (accept.equals("application/json")) { + if ("application/json".equals(accept)) { return ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) .body(mapper.writeValueAsString(response)); @@ -56,7 +59,25 @@ ResponseEntity completion( @GetMapping("/streamChatCompletion") @Nonnull ResponseEntity streamChatCompletion() { - return service.streamChatCompletion("developing a software project"); + final var stream = service.streamChatCompletion("developing a software project"); + final var emitter = new ResponseBodyEmitter(); + final Runnable consumeStream = + () -> { + try (stream) { + stream.forEach( + deltaMessage -> { + log.info("Service: {}", deltaMessage); + send(emitter, deltaMessage); + }); + } finally { + emitter.complete(); + } + }; + + ThreadContextExecutors.getExecutor().execute(consumeStream); + + // TEXT_EVENT_STREAM allows the browser to display the content as it is streamed + return ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(emitter); } /** @@ -72,7 +93,7 @@ ResponseEntity template( @RequestHeader(value = "accept", required = false) final String accept) throws JsonProcessingException { final var response = service.template("German"); - if (accept.equals("application/json")) { + if ("application/json".equals(accept)) { return ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) .body(mapper.writeValueAsString(response)); @@ -91,7 +112,7 @@ ResponseEntity messagesHistory( @RequestHeader(value = "accept", required = false) final String accept) throws JsonProcessingException { final var response = service.messagesHistory("What is the capital of France?"); - if (accept.equals("application/json")) { + if ("application/json".equals(accept)) { return ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) .body(mapper.writeValueAsString(response)); @@ -118,7 +139,7 @@ ResponseEntity filter( @Nonnull @PathVariable("policy") final AzureFilterThreshold policy) throws JsonProcessingException { final var response = service.filter(policy, "the downtown area"); - if (accept.equals("application/json")) { + if ("application/json".equals(accept)) { return ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) .body(mapper.writeValueAsString(response)); @@ -142,7 +163,7 @@ ResponseEntity maskingAnonymization( @RequestHeader(value = "accept", required = false) final String accept) throws JsonProcessingException { final var response = service.maskingAnonymization(DPIEntities.PERSON); - if (accept.equals("application/json")) { + if ("application/json".equals(accept)) { return ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) .body(mapper.writeValueAsString(response)); @@ -162,7 +183,7 @@ public ResponseEntity completionWithResourceGroup( @PathVariable("resourceGroup") @Nonnull final String resourceGroup) throws JsonProcessingException { final var response = service.completionWithResourceGroup(resourceGroup, "Hello world!"); - if (accept.equals("application/json")) { + if ("application/json".equals(accept)) { return ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) .body(mapper.writeValueAsString(response)); @@ -185,7 +206,7 @@ ResponseEntity maskingPseudonymization( @RequestHeader(value = "accept", required = false) final String accept) throws JsonProcessingException { final var response = service.maskingPseudonymization(DPIEntities.PERSON); - if (accept.equals("application/json")) { + if ("application/json".equals(accept)) { return ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) .body(mapper.writeValueAsString(response)); @@ -206,7 +227,7 @@ ResponseEntity grounding( @RequestHeader(value = "accept", required = false) final String accept) throws JsonProcessingException { final var response = service.grounding("What does Joule do?"); - if (accept.equals("application/json")) { + if ("application/json".equals(accept)) { return ResponseEntity.ok() .contentType(MediaType.APPLICATION_JSON) .body(mapper.writeValueAsString(response)); diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OpenAiService.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OpenAiService.java new file mode 100644 index 000000000..5d5f26209 --- /dev/null +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OpenAiService.java @@ -0,0 +1,143 @@ +package com.sap.ai.sdk.app.services; + +import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.GPT_35_TURBO; +import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.GPT_4O; +import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.TEXT_EMBEDDING_ADA_002; +import static com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionTool.ToolType.FUNCTION; + +import com.sap.ai.sdk.core.AiCoreService; +import com.sap.ai.sdk.foundationmodels.openai.OpenAiClient; +import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionDelta; +import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionFunction; +import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionOutput; +import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionParameters; +import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionTool; +import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatMessage; +import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingOutput; +import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingParameters; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; +import javax.annotation.Nonnull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** Service class for OpenAI service */ +@Service +@Slf4j +public class OpenAiService { + + /** + * Chat request to OpenAI + * + * @param prompt The prompt to send to the assistant + * @return the assistant message response + */ + @Nonnull + public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) { + return OpenAiClient.forModel(GPT_35_TURBO).chatCompletion(prompt); + } + + /** + * Asynchronous stream of an OpenAI chat request + * + * @return the emitter that streams the assistant message response + */ + @Nonnull + public Stream streamChatCompletionDeltas( + @Nonnull final String message) { + final var request = + new OpenAiChatCompletionParameters() + .addMessages(new OpenAiChatMessage.OpenAiChatUserMessage().addText(message)); + + return OpenAiClient.forModel(GPT_35_TURBO).streamChatCompletionDeltas(request); + } + + /** + * Asynchronous stream of an OpenAI chat request + * + * @return the emitter that streams the assistant message response + */ + @Nonnull + public Stream streamChatCompletion(@Nonnull final String message) { + return OpenAiClient.forModel(GPT_35_TURBO) + .withSystemPrompt("Be a good, honest AI and answer the following question:") + .streamChatCompletion(message); + } + + /** + * Chat request to OpenAI with an image + * + * @param linkToImage The link to the image + * @return the assistant message response + */ + @Nonnull + public OpenAiChatCompletionOutput chatCompletionImage(@Nonnull final String linkToImage) { + final var request = + new OpenAiChatCompletionParameters() + .addMessages( + new OpenAiChatMessage.OpenAiChatUserMessage() + .addText("Describe the following image.") + .addImage( + linkToImage, + OpenAiChatMessage.OpenAiChatUserMessage.ImageDetailLevel.HIGH)); + + return OpenAiClient.forModel(GPT_4O).chatCompletion(request); + } + + /** + * Chat request to OpenAI with a tool. + * + * @param prompt The prompt to send to the assistant + * @return the assistant message response + */ + @Nonnull + public OpenAiChatCompletionOutput chatCompletionTools(@Nonnull final String prompt) { + final var question = + "A pair of rabbits is placed in a field. Each month, every pair produces one new pair, starting from the second month. How many rabbits will there be after 12 months?"; + final var par = Map.of("type", "object", "properties", Map.of("N", Map.of("type", "integer"))); + final var function = + new OpenAiChatCompletionFunction() + .setName("fibonacci") + .setDescription(prompt) + .setParameters(par); + final var tool = new OpenAiChatCompletionTool().setType(FUNCTION).setFunction(function); + final var request = + new OpenAiChatCompletionParameters() + .addMessages(new OpenAiChatMessage.OpenAiChatUserMessage().addText(question)) + .setTools(List.of(tool)) + .setToolChoiceFunction("fibonacci"); + + return OpenAiClient.forModel(GPT_35_TURBO).chatCompletion(request); + } + + /** + * Get the embedding of a text + * + * @param input The text to embed + * @return the embedding response + */ + @Nonnull + public OpenAiEmbeddingOutput embedding(@Nonnull final String input) { + final var request = new OpenAiEmbeddingParameters().setInput(input); + + return OpenAiClient.forModel(TEXT_EMBEDDING_ADA_002).embedding(request); + } + + /** + * Chat request to OpenAI filtering by resource group + * + * @param resourceGroup The resource group to use + * @param prompt The prompt to send to the assistant + * @return the assistant message response + */ + @Nonnull + public OpenAiChatCompletionOutput chatCompletionWithResource( + @Nonnull final String resourceGroup, @Nonnull final String prompt) { + + final var destination = + new AiCoreService().getInferenceDestination(resourceGroup).forModel(GPT_4O); + + return OpenAiClient.withCustomDestination(destination).chatCompletion(prompt); + } +} diff --git a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java index 50ff245d9..f96394514 100644 --- a/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java +++ b/sample-code/spring-app/src/main/java/com/sap/ai/sdk/app/services/OrchestrationService.java @@ -1,6 +1,5 @@ package com.sap.ai.sdk.app.services; -import static com.sap.ai.sdk.app.controllers.OpenAiController.send; import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.GEMINI_1_5_FLASH; import static com.sap.ai.sdk.orchestration.OrchestrationAiModel.Parameter.TEMPERATURE; @@ -19,16 +18,13 @@ import com.sap.ai.sdk.orchestration.model.GroundingModuleConfig; import com.sap.ai.sdk.orchestration.model.GroundingModuleConfigConfig; import com.sap.ai.sdk.orchestration.model.Template; -import com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutors; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import javax.annotation.Nonnull; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; -import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter; /** Service class for the Orchestration service */ @Service @@ -239,30 +235,10 @@ public OrchestrationChatResponse grounding(@Nonnull final String groundingInput) * @return the emitter that streams the assistant message response */ @Nonnull - public ResponseEntity streamChatCompletion(@Nonnull final String topic) { + public Stream streamChatCompletion(@Nonnull final String topic) { final var prompt = new OrchestrationPrompt( "Please create a small story about " + topic + " with around 700 words."); - final var stream = client.streamChatCompletion(prompt, config); - - final var emitter = new ResponseBodyEmitter(); - - final Runnable consumeStream = - () -> { - try (stream) { - stream.forEach( - deltaMessage -> { - log.info("Service: {}", deltaMessage); - send(emitter, deltaMessage); - }); - } finally { - emitter.complete(); - } - }; - - ThreadContextExecutors.getExecutor().execute(consumeStream); - - // TEXT_EVENT_STREAM allows the browser to display the content as it is streamed - return ResponseEntity.ok().contentType(MediaType.TEXT_EVENT_STREAM).body(emitter); + return client.streamChatCompletion(prompt, config); } } diff --git a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OpenAiTest.java b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OpenAiTest.java index c1cc5af94..ec07fb037 100644 --- a/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OpenAiTest.java +++ b/sample-code/spring-app/src/test/java/com/sap/ai/sdk/app/controllers/OpenAiTest.java @@ -3,6 +3,7 @@ import static com.sap.ai.sdk.foundationmodels.openai.OpenAiModel.GPT_35_TURBO; import static org.assertj.core.api.Assertions.assertThat; +import com.sap.ai.sdk.app.services.OpenAiService; import com.sap.ai.sdk.foundationmodels.openai.OpenAiClient; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionOutput; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionParameters; @@ -14,16 +15,16 @@ @Slf4j class OpenAiTest { - OpenAiController controller; + OpenAiService service; @BeforeEach void setUp() { - controller = new OpenAiController(); + service = new OpenAiService(); } @Test void chatCompletion() { - final var completion = controller.chatCompletion(); + final var completion = service.chatCompletion("Who is the prettiest"); final var message = completion.getChoices().get(0).getMessage(); assertThat(message.getRole()).isEqualTo("assistant"); @@ -32,7 +33,9 @@ void chatCompletion() { @Test void chatCompletionImage() { - final var completion = controller.chatCompletionImage(); + final var completion = + service.chatCompletionImage( + "https://upload.wikimedia.org/wikipedia/commons/thumb/5/59/SAP_2011_logo.svg/440px-SAP_2011_logo.svg.png"); final var message = completion.getChoices().get(0).getMessage(); assertThat(message.getRole()).isEqualTo("assistant"); @@ -72,7 +75,8 @@ void streamChatCompletion() { @Test void chatCompletionTools() { - final var completion = controller.chatCompletionTools(); + final var completion = + service.chatCompletionTools("Calculate the Fibonacci number for given sequence index."); final var message = completion.getChoices().get(0).getMessage(); assertThat(message.getRole()).isEqualTo("assistant"); @@ -82,7 +86,7 @@ void chatCompletionTools() { @Test void embedding() { - final var embedding = controller.embedding(); + final var embedding = service.embedding("Hello world"); assertThat(embedding.getData().get(0).getEmbedding()).hasSizeGreaterThan(1); assertThat(embedding.getModel()).isEqualTo("ada"); @@ -91,7 +95,8 @@ void embedding() { @Test void chatCompletionWithResource() { - final var completion = OpenAiController.chatCompletionWithResource("ai-sdk-java-e2e"); + final var completion = + service.chatCompletionWithResource("ai-sdk-java-e2e", "Where is the nearest coffee shop?"); final var message = completion.getChoices().get(0).getMessage(); assertThat(message.getRole()).isEqualTo("assistant");