From 468ba8bb6a40bcf5be1087720656904e3fdcb8a6 Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 4 Sep 2024 13:51:26 +0200 Subject: [PATCH 01/12] OpenAI API proposal --- README.md | 9 +++++++++ .../sdk/app/controllers/OpenAiController.java | 6 +----- .../foundationmodels/openai/OpenAiClient.java | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 09dba116..bea37542 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,15 @@ See [an example pom in our Spring Boot application](e2e-test-app/pom.xml) ### Simple chat completion +```java +final OpenAiChatCompletionOutput result = + OpenAiClient.forModel(GPT_35_TURBO).chatCompletion("Hello World! Why is this phrase so famous?"); + +final String resultMessage = result.getChoices().get(0).getMessage().getContent(); +``` + +### Chat completion message history + ```java final var systemMessage = new OpenAiChatSystemMessage().setContent("You are a helpful assistant"); diff --git a/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java b/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java index 08a02b5a..a5871ef4 100644 --- a/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java +++ b/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java @@ -31,11 +31,7 @@ class OpenAiController { @GetMapping("/chatCompletion") @Nonnull public static OpenAiChatCompletionOutput chatCompletion() { - final var request = - new OpenAiChatCompletionParameters() - .setMessages(List.of(new OpenAiChatUserMessage().addText("Who is the prettiest"))); - - return OpenAiClient.forModel(GPT_35_TURBO).chatCompletion(request); + return OpenAiClient.forModel(GPT_35_TURBO).chatCompletion("Who is the prettiest"); } /** diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java index af85738a..d6985791 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java @@ -9,12 +9,14 @@ import com.sap.ai.sdk.core.Core; 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.OpenAiChatMessage.OpenAiChatUserMessage; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingOutput; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingParameters; import com.sap.cloud.sdk.cloudplatform.connectivity.ApacheHttpClient5Accessor; import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; import java.io.IOException; +import java.util.List; import javax.annotation.Nonnull; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; @@ -105,6 +107,22 @@ public OpenAiChatCompletionOutput chatCompletion( return execute("/chat/completions", parameters, OpenAiChatCompletionOutput.class); } + /** + * Generate a completion for the given prompt. + * + * @param prompt a text message. + * @return the completion output + * @throws OpenAiClientException if the request fails + */ + @Nonnull + public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) + throws OpenAiClientException { + final var parameters = + new OpenAiChatCompletionParameters() + .setMessages(List.of(new OpenAiChatUserMessage().addText(prompt))); + return execute("/chat/completions", parameters, OpenAiChatCompletionOutput.class); + } + /** * Get a vector representation of a given input that can be easily consumed by machine learning * models and algorithms. From f9f10f4e9c3ecea2ab4aa610e11a6dbca68081eb Mon Sep 17 00:00:00 2001 From: I538344 Date: Wed, 4 Sep 2024 15:56:27 +0200 Subject: [PATCH 02/12] Added convenience getContent --- README.md | 6 +++--- .../openai/model/OpenAiChatCompletionOutput.java | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bea37542..66cb7049 100644 --- a/README.md +++ b/README.md @@ -185,10 +185,10 @@ See [an example pom in our Spring Boot application](e2e-test-app/pom.xml) final OpenAiChatCompletionOutput result = OpenAiClient.forModel(GPT_35_TURBO).chatCompletion("Hello World! Why is this phrase so famous?"); -final String resultMessage = result.getChoices().get(0).getMessage().getContent(); +final String resultMessage = result.getContent(); ``` -### Chat completion message history +### Message history ```java final var systemMessage = @@ -201,7 +201,7 @@ final var request = final OpenAiChatCompletionOutput result = OpenAiClient.forModel(GPT_35_TURBO).chatCompletion(request); -final String resultMessage = result.getChoices().get(0).getMessage().getContent(); +final String resultMessage = result.getContent(); ``` See [an example in our Spring Boot application](e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java) diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java index 0254aee2..d97dd31e 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.List; +import java.util.Objects; import javax.annotation.Nonnull; import lombok.EqualsAndHashCode; import lombok.Getter; @@ -25,4 +26,16 @@ public class OpenAiChatCompletionOutput extends OpenAiCompletionOutput { @JsonProperty("system_fingerprint") @Getter(onMethod_ = @Nonnull) private String systemFingerprint; + + /** + * Get the message content from the output. + * + *

Note: If there are multiple choices only the first one is returned + * + * @return the message content or empty string. + */ + @Nonnull + public String getContent() { + return Objects.requireNonNullElse(getChoices().get(0).getMessage().getContent(), ""); + } } From 92ca5e1cec8e0e1a5ba0add69b461776edefbb61 Mon Sep 17 00:00:00 2001 From: I538344 Date: Thu, 5 Sep 2024 10:50:46 +0200 Subject: [PATCH 03/12] Applied Alex's suggestions --- .../foundationmodels/openai/OpenAiClient.java | 20 +++++++++---------- .../model/OpenAiChatCompletionOutput.java | 3 +++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java index d6985791..8090c947 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java @@ -97,29 +97,29 @@ public static OpenAiClient withCustomDestination(@Nonnull final Destination dest /** * Generate a completion for the given prompt. * - * @param parameters the prompt, including messages and other parameters. + * @param prompt a text message. * @return the completion output * @throws OpenAiClientException if the request fails */ @Nonnull - public OpenAiChatCompletionOutput chatCompletion( - @Nonnull final OpenAiChatCompletionParameters parameters) throws OpenAiClientException { - return execute("/chat/completions", parameters, OpenAiChatCompletionOutput.class); + public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) + throws OpenAiClientException { + final var parameters = + new OpenAiChatCompletionParameters() + .setMessages(List.of(new OpenAiChatUserMessage().addText(prompt))); + return chatCompletion(parameters); } /** * Generate a completion for the given prompt. * - * @param prompt a text message. + * @param parameters the prompt, including messages and other parameters. * @return the completion output * @throws OpenAiClientException if the request fails */ @Nonnull - public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) - throws OpenAiClientException { - final var parameters = - new OpenAiChatCompletionParameters() - .setMessages(List.of(new OpenAiChatUserMessage().addText(prompt))); + public OpenAiChatCompletionOutput chatCompletion( + @Nonnull final OpenAiChatCompletionParameters parameters) throws OpenAiClientException { return execute("/chat/completions", parameters, OpenAiChatCompletionOutput.class); } diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java index d97dd31e..00b2091f 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java @@ -36,6 +36,9 @@ public class OpenAiChatCompletionOutput extends OpenAiCompletionOutput { */ @Nonnull public String getContent() { + if (getChoices().isEmpty()) { + return ""; + } return Objects.requireNonNullElse(getChoices().get(0).getMessage().getContent(), ""); } } From e52de4636c71c8f66ca2ed1f28168c82ce3e2e28 Mon Sep 17 00:00:00 2001 From: I538344 Date: Thu, 5 Sep 2024 13:10:31 +0200 Subject: [PATCH 04/12] Added tests --- .../openai/OpenAiClientTest.java | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java index aebc3718..12391b3b 100644 --- a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java +++ b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java @@ -18,7 +18,6 @@ import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionDelta; 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.OpenAiChatMessage; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatMessage.OpenAiChatUserMessage; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiContentFilterPromptResults; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingParameters; @@ -30,9 +29,11 @@ import java.io.InputStream; import java.util.List; import java.util.Objects; +import java.util.concurrent.Callable; import java.util.function.Function; import java.util.stream.Stream; import javax.annotation.Nonnull; +import lombok.SneakyThrows; import org.apache.hc.client5.http.classic.HttpClient; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.io.entity.InputStreamEntity; @@ -84,7 +85,7 @@ void apiVersion() { verify(exactly(2), postRequestedFor(anyUrl()).withoutQueryParam("api-version")); } - private static Runnable[] chatCompletionCalls() { + private static Runnable[] errorHandlingCalls() { return new Runnable[] { () -> client.chatCompletion(new OpenAiChatCompletionParameters()), () -> @@ -96,7 +97,7 @@ private static Runnable[] chatCompletionCalls() { } @ParameterizedTest - @MethodSource("chatCompletionCalls") + @MethodSource("errorHandlingCalls") void chatCompletionErrorHandling(@Nonnull final Runnable request) { final var errorJson = """ @@ -169,23 +170,28 @@ void chatCompletionErrorHandling(@Nonnull final Runnable request) { softly.assertAll(); } - @Test - void chatCompletion() throws IOException { + private static Callable[] chatCompletionCalls() { + return new Callable[] { + () -> { + final var userMessage = + new OpenAiChatUserMessage().addText("Hello World! Why is this phrase so famous?"); + final var request = new OpenAiChatCompletionParameters().setMessages(List.of(userMessage)); + return client.chatCompletion(request); + }, + () -> client.chatCompletion("Hello World! Why is this phrase so famous?") + }; + } + + @SneakyThrows + @ParameterizedTest + @MethodSource("chatCompletionCalls") + void chatCompletion(@Nonnull final Callable request) { try (var inputStream = TEST_FILE_LOADER.apply("chatCompletionResponse.json")) { final String response = new String(inputStream.readAllBytes()); stubFor(post("/chat/completions").willReturn(okJson(response))); - final var systemMessage = - new OpenAiChatMessage.OpenAiChatSystemMessage() - .setContent( - "You are a helpful, friendly and sometimes slightly snarky AI assistant!"); - final var userMessage = - new OpenAiChatUserMessage().addText("Hello World! Why is this phrase so famous?"); - final var request = - new OpenAiChatCompletionParameters().setMessages(List.of(systemMessage, userMessage)); - - final var result = client.chatCompletion(request); + final OpenAiChatCompletionOutput result = request.call(); assertThat(result).isNotNull(); assertThat(result.getCreated()).isEqualTo(1719300073); @@ -252,7 +258,7 @@ void chatCompletion() throws IOException { .withRequestBody( equalToJson( """ - {"messages":[{"role":"system","content":"You are a helpful, friendly and sometimes slightly snarky AI assistant!"},{"role":"user","content":[{"type":"text","text":"Hello World! Why is this phrase so famous?"}]}]}"""))); + {"messages":[{"role":"user","content":[{"type":"text","text":"Hello World! Why is this phrase so famous?"}]}]}"""))); } } From f9d9612ad8e65d321828c2b8ae966b1cac0d6dfd Mon Sep 17 00:00:00 2001 From: Charles Dubois <103174266+CharlesDuboisSAP@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:00:02 +0200 Subject: [PATCH 05/12] Update foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java Co-authored-by: Matthias Kuhr <52661546+MatKuhr@users.noreply.github.com> --- .../com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java index 0bd68658..cbfc2872 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java @@ -98,7 +98,7 @@ public static OpenAiClient withCustomDestination(@Nonnull final Destination dest } /** - * Generate a completion for the given prompt. + * Generate a completion for the given user prompt. * * @param prompt a text message. * @return the completion output From 6cd8fe6037b99d65714644cad2a3ed58d266cd3e Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 10 Sep 2024 10:01:08 +0200 Subject: [PATCH 06/12] Throw in getContent --- .../openai/model/OpenAiChatCompletionOutput.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java index 9cc2a96e..7d172aa0 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionOutput.java @@ -1,6 +1,7 @@ package com.sap.ai.sdk.foundationmodels.openai.model; import com.fasterxml.jackson.annotation.JsonProperty; +import com.sap.ai.sdk.foundationmodels.openai.OpenAiClientException; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -35,12 +36,16 @@ public class OpenAiChatCompletionOutput extends OpenAiCompletionOutput *

Note: If there are multiple choices only the first one is returned * * @return the message content or empty string. + * @throws OpenAiClientException if the content filter filtered the output. */ @Nonnull - public String getContent() { + public String getContent() throws OpenAiClientException { if (getChoices().isEmpty()) { return ""; } + if ("content_filter".equals(getChoices().get(0).getFinishReason())) { + throw new OpenAiClientException("Content filter filtered the output."); + } return Objects.requireNonNullElse(getChoices().get(0).getMessage().getContent(), ""); } From 12051c366e681a78100dc1e5870ea2c54309e972 Mon Sep 17 00:00:00 2001 From: I538344 Date: Mon, 16 Sep 2024 14:14:16 +0200 Subject: [PATCH 07/12] Added withSystemPrompt(String) and addMessages(...) --- .../sdk/app/controllers/OpenAiController.java | 26 ++++++------- .../ai/sdk/app/controllers/OpenAiTest.java | 3 +- .../foundationmodels/openai/OpenAiClient.java | 27 +++++++++++-- .../model/OpenAiChatCompletionParameters.java | 11 +++++- .../openai/OpenAiClientTest.java | 39 ++++++++++++------- .../OpenAiChatCompletionParametersTest.java | 2 +- 6 files changed, 72 insertions(+), 36 deletions(-) diff --git a/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java b/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java index f0b69bae..7ebc9c03 100644 --- a/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java +++ b/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java @@ -55,8 +55,7 @@ public static OpenAiChatCompletionOutput chatCompletion() { public static ResponseEntity streamChatCompletionDeltas() { final var msg = "Can you give me the first 100 numbers of the Fibonacci sequence?"; final var request = - new OpenAiChatCompletionParameters() - .setMessages(List.of(new OpenAiChatUserMessage().addText(msg))); + new OpenAiChatCompletionParameters().addMessages(new OpenAiChatUserMessage().addText(msg)); final var stream = OpenAiClient.forModel(GPT_35_TURBO).streamChatCompletionDeltas(request); @@ -101,11 +100,9 @@ private static String objectToJson(@Nonnull final Object obj) { public static ResponseEntity streamChatCompletion() { final var request = new OpenAiChatCompletionParameters() - .setMessages( - List.of( - new OpenAiChatUserMessage() - .addText( - "Can you give me the first 100 numbers of the Fibonacci sequence?"))); + .addMessages( + new OpenAiChatUserMessage() + .addText("Can you give me the first 100 numbers of the Fibonacci sequence?")); final var stream = OpenAiClient.forModel(GPT_35_TURBO).streamChatCompletion(request); @@ -146,13 +143,12 @@ private static void send( public static OpenAiChatCompletionOutput chatCompletionImage() { final var request = new OpenAiChatCompletionParameters() - .setMessages( - List.of( - 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))); + .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); } @@ -176,7 +172,7 @@ public static OpenAiChatCompletionOutput chatCompletionTools() { final var tool = new OpenAiChatCompletionTool().setType(FUNCTION).setFunction(function); final var request = new OpenAiChatCompletionParameters() - .setMessages(List.of(new OpenAiChatUserMessage().addText(question))) + .addMessages(new OpenAiChatUserMessage().addText(question)) .setTools(List.of(tool)) .setToolChoiceFunction("fibonacci"); diff --git a/e2e-test-app/src/test/java/com/sap/ai/sdk/app/controllers/OpenAiTest.java b/e2e-test-app/src/test/java/com/sap/ai/sdk/app/controllers/OpenAiTest.java index 2973239a..e4b1a6a7 100644 --- a/e2e-test-app/src/test/java/com/sap/ai/sdk/app/controllers/OpenAiTest.java +++ b/e2e-test-app/src/test/java/com/sap/ai/sdk/app/controllers/OpenAiTest.java @@ -7,7 +7,6 @@ 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.OpenAiChatMessage.OpenAiChatUserMessage; -import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @@ -36,7 +35,7 @@ void chatCompletionImage() { void streamChatCompletion() { final var request = new OpenAiChatCompletionParameters() - .setMessages(List.of(new OpenAiChatUserMessage().addText("Who is the prettiest?"))); + .addMessages(new OpenAiChatUserMessage().addText("Who is the prettiest?")); final var totalOutput = new OpenAiChatCompletionOutput(); final var filledDeltaCount = new AtomicInteger(0); diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java index cbfc2872..5130b80e 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java @@ -10,6 +10,7 @@ import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionDelta; 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.OpenAiChatMessage.OpenAiChatSystemMessage; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatMessage.OpenAiChatUserMessage; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingOutput; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingParameters; @@ -18,7 +19,6 @@ import com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination; import com.sap.cloud.sdk.cloudplatform.connectivity.Destination; import java.io.IOException; -import java.util.List; import java.util.stream.Stream; import javax.annotation.Nonnull; import lombok.AccessLevel; @@ -36,6 +36,7 @@ public final class OpenAiClient { private static final String DEFAULT_API_VERSION = "2024-02-01"; static final ObjectMapper JACKSON; + private OpenAiChatCompletionParameters parameters = null; static { JACKSON = @@ -97,6 +98,19 @@ public static OpenAiClient withCustomDestination(@Nonnull final Destination dest return new OpenAiClient(destination); } + /** + * Add a system prompt before user prompts. + * + * @param systemPrompt the system prompt + * @return the client + */ + public OpenAiClient withSystemPrompt(@Nonnull final String systemPrompt) { + parameters = + new OpenAiChatCompletionParameters() + .addMessages(new OpenAiChatSystemMessage().setContent(systemPrompt)); + return this; + } + /** * Generate a completion for the given user prompt. * @@ -107,9 +121,10 @@ public static OpenAiClient withCustomDestination(@Nonnull final Destination dest @Nonnull public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) throws OpenAiClientException { - final var parameters = - new OpenAiChatCompletionParameters() - .setMessages(List.of(new OpenAiChatUserMessage().addText(prompt))); + if (parameters == null) { + parameters = new OpenAiChatCompletionParameters(); + } + parameters.addMessages(new OpenAiChatUserMessage().addText(prompt)); return chatCompletion(parameters); } @@ -123,6 +138,10 @@ public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) @Nonnull public OpenAiChatCompletionOutput chatCompletion( @Nonnull final OpenAiChatCompletionParameters parameters) throws OpenAiClientException { + if (this.parameters != null) { + log.warn( + "Previously set messages will be ignored, set it as an argument of this method instead."); + } return execute("/chat/completions", parameters, OpenAiChatCompletionOutput.class); } diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParameters.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParameters.java index 19a3c425..74c942a3 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParameters.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParameters.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonValue; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javax.annotation.Nonnull; @@ -25,7 +26,6 @@ public class OpenAiChatCompletionParameters extends OpenAiCompletionParameters { /** A list of messages comprising the conversation so far. */ @JsonProperty("messages") - @Setter(onParam_ = @Nonnull) private List messages; /** @@ -197,4 +197,13 @@ private record Function(@JsonProperty("name") @Nonnull String name) {} public OpenAiChatCompletionParameters setStop(@Nullable final String... values) { return (OpenAiChatCompletionParameters) super.setStop(values); } + + @Nonnull + public OpenAiChatCompletionParameters addMessages(@Nonnull final OpenAiChatMessage... messages) { + if (this.messages == null) { + this.messages = new ArrayList<>(); + } + this.messages.addAll(Arrays.asList(messages)); + return this; + } } diff --git a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java index 12391b3b..0a73086f 100644 --- a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java +++ b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java @@ -18,6 +18,7 @@ import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatCompletionDelta; 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.OpenAiChatMessage.OpenAiChatSystemMessage; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiChatMessage.OpenAiChatUserMessage; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiContentFilterPromptResults; import com.sap.ai.sdk.foundationmodels.openai.model.OpenAiEmbeddingParameters; @@ -173,12 +174,17 @@ void chatCompletionErrorHandling(@Nonnull final Runnable request) { private static Callable[] chatCompletionCalls() { return new Callable[] { () -> { + final var systemMessage = new OpenAiChatSystemMessage().setContent("You are a helpful AI"); final var userMessage = new OpenAiChatUserMessage().addText("Hello World! Why is this phrase so famous?"); - final var request = new OpenAiChatCompletionParameters().setMessages(List.of(userMessage)); + final var request = + new OpenAiChatCompletionParameters().addMessages(systemMessage, userMessage); return client.chatCompletion(request); }, - () -> client.chatCompletion("Hello World! Why is this phrase so famous?") + () -> + client + .withSystemPrompt("You are a helpful AI") + .chatCompletion("Hello World! Why is this phrase so famous?") }; } @@ -258,7 +264,18 @@ void chatCompletion(@Nonnull final Callable request) .withRequestBody( equalToJson( """ - {"messages":[{"role":"user","content":[{"type":"text","text":"Hello World! Why is this phrase so famous?"}]}]}"""))); + { + "messages" : [ { + "role" : "system", + "content" : "You are a helpful AI" + }, { + "role" : "user", + "content" : [ { + "type" : "text", + "text" : "Hello World! Why is this phrase so famous?" + } ] + } ] + }"""))); } } @@ -319,11 +336,9 @@ void streamChatCompletionDeltasErrorHandling() throws IOException { final var request = new OpenAiChatCompletionParameters() - .setMessages( - List.of( - new OpenAiChatUserMessage() - .addText( - "Can you give me the first 100 numbers of the Fibonacci sequence?"))); + .addMessages( + new OpenAiChatUserMessage() + .addText("Can you give me the first 100 numbers of the Fibonacci sequence?")); try (Stream stream = client.streamChatCompletionDeltas(request)) { assertThatThrownBy(() -> stream.forEach(System.out::println)) @@ -354,11 +369,9 @@ void streamChatCompletionDeltas() throws IOException { final var request = new OpenAiChatCompletionParameters() - .setMessages( - List.of( - new OpenAiChatUserMessage() - .addText( - "Can you give me the first 100 numbers of the Fibonacci sequence?"))); + .addMessages( + new OpenAiChatUserMessage() + .addText("Can you give me the first 100 numbers of the Fibonacci sequence?")); try (Stream stream = client.streamChatCompletionDeltas(request)) { diff --git a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParametersTest.java b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParametersTest.java index 7c610f82..416c348b 100644 --- a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParametersTest.java +++ b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParametersTest.java @@ -131,7 +131,7 @@ public void testCompletionParametersTools() { public void testCompletionParameters() { var params = new OpenAiChatCompletionParameters() - .setMessages(List.of(new OpenAiChatMessage.OpenAiChatUserMessage().addText("foo"))) + .addMessages(new OpenAiChatMessage.OpenAiChatUserMessage().addText("foo")) .setResponseFormat(JSON_OBJECT); // serialization check From f6315b8b71e6a8c203a4e6cc9b769b9bf572107f Mon Sep 17 00:00:00 2001 From: I538344 Date: Mon, 16 Sep 2024 14:18:15 +0200 Subject: [PATCH 08/12] Updated README --- README.md | 10 ++++++---- .../openai/model/OpenAiChatCompletionParameters.java | 6 ++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index ac456fc2..371430c7 100644 --- a/README.md +++ b/README.md @@ -183,7 +183,9 @@ See [an example pom in our Spring Boot application](e2e-test-app/pom.xml) ```java final OpenAiChatCompletionOutput result = - OpenAiClient.forModel(GPT_35_TURBO).chatCompletion("Hello World! Why is this phrase so famous?"); + OpenAiClient.forModel(GPT_35_TURBO) + .withSystemPrompt("You are a helpful AI") + .chatCompletion("Hello World! Why is this phrase so famous?"); final String resultMessage = result.getContent(); ``` @@ -196,7 +198,7 @@ final var systemMessage = final var userMessage = new OpenAiChatUserMessage().addText("Hello World! Why is this phrase so famous?"); final var request = - new OpenAiChatCompletionParameters().setMessages(List.of(systemMessage, userMessage)); + new OpenAiChatCompletionParameters().addMessages(systemMessage, userMessage); final OpenAiChatCompletionOutput result = OpenAiClient.forModel(GPT_35_TURBO).chatCompletion(request); @@ -224,7 +226,7 @@ String msg = "Can you give me the first 100 numbers of the Fibonacci sequence?"; OpenAiChatCompletionParameters request = new OpenAiChatCompletionParameters() - .setMessages(List.of(new OpenAiChatUserMessage().addText(msg))); + .addMessages(new OpenAiChatUserMessage().addText(msg)); OpenAiClient client = OpenAiClient.forModel(GPT_35_TURBO); @@ -248,7 +250,7 @@ String msg = "Can you give me the first 100 numbers of the Fibonacci sequence?"; OpenAiChatCompletionParameters request = new OpenAiChatCompletionParameters() - .setMessages(List.of(new OpenAiChatUserMessage().addText(msg))); + .addMessages(new OpenAiChatUserMessage().addText(msg)); OpenAiChatCompletionOutput totalOutput = new OpenAiChatCompletionOutput(); OpenAiClient client = OpenAiClient.forModel(GPT_35_TURBO); diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParameters.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParameters.java index 74c942a3..f11a1697 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParameters.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/model/OpenAiChatCompletionParameters.java @@ -198,6 +198,12 @@ public OpenAiChatCompletionParameters setStop(@Nullable final String... values) return (OpenAiChatCompletionParameters) super.setStop(values); } + /** + * Add messages to the conversation. + * + * @param messages The messages to add. + * @return this instance for chaining. + */ @Nonnull public OpenAiChatCompletionParameters addMessages(@Nonnull final OpenAiChatMessage... messages) { if (this.messages == null) { From 00451b2d5313a8e4cd43ac3b4dbadcf98e1c8848 Mon Sep 17 00:00:00 2001 From: I538344 Date: Mon, 16 Sep 2024 15:11:44 +0200 Subject: [PATCH 09/12] PMD --- .../com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java | 1 + 1 file changed, 1 insertion(+) diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java index 5130b80e..10a17b49 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java @@ -104,6 +104,7 @@ public static OpenAiClient withCustomDestination(@Nonnull final Destination dest * @param systemPrompt the system prompt * @return the client */ + @Nonnull public OpenAiClient withSystemPrompt(@Nonnull final String systemPrompt) { parameters = new OpenAiChatCompletionParameters() From 4c201464f40f036619bf1e62346e08afaab5cd0c Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 17 Sep 2024 15:55:05 +0200 Subject: [PATCH 10/12] Applied Matthias' suggestions --- README.md | 6 +---- .../sdk/app/controllers/OpenAiController.java | 12 ++++------ .../foundationmodels/openai/OpenAiClient.java | 24 +++++++++++-------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 371430c7..4fcd7227 100644 --- a/README.md +++ b/README.md @@ -224,14 +224,10 @@ This is a blocking example for streaming and printing directly to the console: ```java String msg = "Can you give me the first 100 numbers of the Fibonacci sequence?"; -OpenAiChatCompletionParameters request = - new OpenAiChatCompletionParameters() - .addMessages(new OpenAiChatUserMessage().addText(msg)); - OpenAiClient client = OpenAiClient.forModel(GPT_35_TURBO); // try-with-resources on stream ensures the connection will be closed -try( Stream stream = client.streamChatCompletion(request)) { +try( Stream stream = client.streamChatCompletion(msg)) { stream.forEach(deltaString -> { System.out.print(deltaString); System.out.flush(); diff --git a/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java b/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java index 7ebc9c03..5511ac8b 100644 --- a/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java +++ b/e2e-test-app/src/main/java/com/sap/ai/sdk/app/controllers/OpenAiController.java @@ -98,13 +98,11 @@ private static String objectToJson(@Nonnull final Object obj) { @GetMapping("/streamChatCompletion") @Nonnull public static ResponseEntity streamChatCompletion() { - final var request = - new OpenAiChatCompletionParameters() - .addMessages( - new OpenAiChatUserMessage() - .addText("Can you give me the first 100 numbers of the Fibonacci sequence?")); - - final var stream = OpenAiClient.forModel(GPT_35_TURBO).streamChatCompletion(request); + 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 emitter = new ResponseBodyEmitter(); diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java index 10a17b49..9fe93f60 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java @@ -36,7 +36,7 @@ public final class OpenAiClient { private static final String DEFAULT_API_VERSION = "2024-02-01"; static final ObjectMapper JACKSON; - private OpenAiChatCompletionParameters parameters = null; + private String systemPrompt = null; static { JACKSON = @@ -106,9 +106,7 @@ public static OpenAiClient withCustomDestination(@Nonnull final Destination dest */ @Nonnull public OpenAiClient withSystemPrompt(@Nonnull final String systemPrompt) { - parameters = - new OpenAiChatCompletionParameters() - .addMessages(new OpenAiChatSystemMessage().setContent(systemPrompt)); + this.systemPrompt = systemPrompt; return this; } @@ -122,8 +120,9 @@ public OpenAiClient withSystemPrompt(@Nonnull final String systemPrompt) { @Nonnull public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) throws OpenAiClientException { - if (parameters == null) { - parameters = new OpenAiChatCompletionParameters(); + OpenAiChatCompletionParameters parameters = new OpenAiChatCompletionParameters(); + if (systemPrompt != null) { + parameters.addMessages(new OpenAiChatSystemMessage().setContent(systemPrompt)); } parameters.addMessages(new OpenAiChatUserMessage().addText(prompt)); return chatCompletion(parameters); @@ -139,7 +138,7 @@ public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) @Nonnull public OpenAiChatCompletionOutput chatCompletion( @Nonnull final OpenAiChatCompletionParameters parameters) throws OpenAiClientException { - if (this.parameters != null) { + if (systemPrompt != null) { log.warn( "Previously set messages will be ignored, set it as an argument of this method instead."); } @@ -149,13 +148,18 @@ public OpenAiChatCompletionOutput chatCompletion( /** * Generate a completion for the given prompt. * - * @param parameters the prompt, including messages and other parameters. + * @param prompt a text message. * @return A stream of message deltas * @throws OpenAiClientException if the request fails or if the finish reason is content_filter */ @Nonnull - public Stream streamChatCompletion( - @Nonnull final OpenAiChatCompletionParameters parameters) throws OpenAiClientException { + public Stream streamChatCompletion(@Nonnull final String prompt) + throws OpenAiClientException { + OpenAiChatCompletionParameters parameters = new OpenAiChatCompletionParameters(); + if (systemPrompt != null) { + parameters.addMessages(new OpenAiChatSystemMessage().setContent(systemPrompt)); + } + parameters.addMessages(new OpenAiChatUserMessage().addText(prompt)); return streamChatCompletionDeltas(parameters) .peek(OpenAiClient::throwOnContentFilter) .map(OpenAiChatCompletionDelta::getDeltaContent); From ce97f0b532c646364ae964dd86a2d3c61575eacf Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 17 Sep 2024 15:58:08 +0200 Subject: [PATCH 11/12] PMD --- .../com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java index 9fe93f60..7d7e6ca3 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java @@ -120,7 +120,7 @@ public OpenAiClient withSystemPrompt(@Nonnull final String systemPrompt) { @Nonnull public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) throws OpenAiClientException { - OpenAiChatCompletionParameters parameters = new OpenAiChatCompletionParameters(); + final OpenAiChatCompletionParameters parameters = new OpenAiChatCompletionParameters(); if (systemPrompt != null) { parameters.addMessages(new OpenAiChatSystemMessage().setContent(systemPrompt)); } @@ -155,7 +155,7 @@ public OpenAiChatCompletionOutput chatCompletion( @Nonnull public Stream streamChatCompletion(@Nonnull final String prompt) throws OpenAiClientException { - OpenAiChatCompletionParameters parameters = new OpenAiChatCompletionParameters(); + final OpenAiChatCompletionParameters parameters = new OpenAiChatCompletionParameters(); if (systemPrompt != null) { parameters.addMessages(new OpenAiChatSystemMessage().setContent(systemPrompt)); } From c03a3b6493c601cfb65b4c787ce0e2a0074385c0 Mon Sep 17 00:00:00 2001 From: I538344 Date: Tue, 17 Sep 2024 16:09:00 +0200 Subject: [PATCH 12/12] Added history test --- .../foundationmodels/openai/OpenAiClient.java | 13 +++-- .../openai/OpenAiClientTest.java | 53 +++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java index 7d7e6ca3..819d01f2 100644 --- a/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java +++ b/foundation-models/openai/src/main/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClient.java @@ -138,10 +138,7 @@ public OpenAiChatCompletionOutput chatCompletion(@Nonnull final String prompt) @Nonnull public OpenAiChatCompletionOutput chatCompletion( @Nonnull final OpenAiChatCompletionParameters parameters) throws OpenAiClientException { - if (systemPrompt != null) { - log.warn( - "Previously set messages will be ignored, set it as an argument of this method instead."); - } + warnIfUnsupportedUsage(); return execute("/chat/completions", parameters, OpenAiChatCompletionOutput.class); } @@ -182,10 +179,18 @@ private static void throwOnContentFilter(@Nonnull final OpenAiChatCompletionDelt @Nonnull public Stream streamChatCompletionDeltas( @Nonnull final OpenAiChatCompletionParameters parameters) throws OpenAiClientException { + warnIfUnsupportedUsage(); parameters.enableStreaming(); return executeStream("/chat/completions", parameters, OpenAiChatCompletionDelta.class); } + private void warnIfUnsupportedUsage() { + if (systemPrompt != null) { + log.warn( + "Previously set messages will be ignored, set it as an argument of this method instead."); + } + } + /** * Get a vector representation of a given input that can be easily consumed by machine learning * models and algorithms. diff --git a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java index 0a73086f..3fdce3ca 100644 --- a/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java +++ b/foundation-models/openai/src/test/java/com/sap/ai/sdk/foundationmodels/openai/OpenAiClientTest.java @@ -42,6 +42,7 @@ import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -279,6 +280,58 @@ void chatCompletion(@Nonnull final Callable request) } } + @Test + @DisplayName("Chat history is not implemented yet") + void history() throws IOException { + try (var inputStream = TEST_FILE_LOADER.apply("chatCompletionResponse.json")) { + + final String response = new String(inputStream.readAllBytes()); + stubFor(post("/chat/completions").willReturn(okJson(response))); + + client.withSystemPrompt("system prompt").chatCompletion("chat completion 1"); + + verify( + exactly(1), + postRequestedFor(urlPathEqualTo("/chat/completions")) + .withRequestBody( + equalToJson( + """ + { + "messages" : [ { + "role" : "system", + "content" : "system prompt" + }, { + "role" : "user", + "content" : [ { + "type" : "text", + "text" : "chat completion 1" + } ] + } ] + }"""))); + + client.withSystemPrompt("system prompt").chatCompletion("chat completion 2"); + + verify( + exactly(1), + postRequestedFor(urlPathEqualTo("/chat/completions")) + .withRequestBody( + equalToJson( + """ + { + "messages" : [ { + "role" : "system", + "content" : "system prompt" + }, { + "role" : "user", + "content" : [ { + "type" : "text", + "text" : "chat completion 2" + } ] + } ] + }"""))); + } + } + @Test void embedding() throws IOException { try (var inputStream = TEST_FILE_LOADER.apply("embeddingResponse.json")) {