From 300efbe54ec1aef6ab9b2d8a2ca6f3e346544d19 Mon Sep 17 00:00:00 2001 From: Milder Hernandez Date: Wed, 24 Jan 2024 10:55:24 -0800 Subject: [PATCH] Java: Add Example44_MultiChatCompletion (#4725) Add Example44_MultiChatCompletion --- .../chatcompletion/OpenAIChatCompletion.java | 7 +- .../OpenAITextGenerationService.java | 5 ++ .../Example44_MultiChatCompletion.java | 76 +++++++++++++++++++ .../semantickernel/TextAIService.java | 2 +- .../PromptExecutionSettings.java | 15 ++++ 5 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example44_MultiChatCompletion.java diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java b/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java index c14755c7ff..13484fa6f8 100644 --- a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java +++ b/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/OpenAIChatCompletion.java @@ -19,12 +19,12 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.microsoft.semantickernel.Kernel; -import com.microsoft.semantickernel.aiservices.openai.textcompletion.OpenAITextGenerationService; import com.microsoft.semantickernel.chatcompletion.AuthorRole; import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.chatcompletion.ChatHistory; import com.microsoft.semantickernel.chatcompletion.ChatMessageContent; import com.microsoft.semantickernel.chatcompletion.StreamingChatMessageContent; +import com.microsoft.semantickernel.exceptions.AIException; import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; import com.microsoft.semantickernel.orchestration.KernelFunction; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; @@ -266,6 +266,10 @@ public class OpenAIChatCompletion implements ChatCompletionService { if (promptExecutionSettings == null) { return options; } + if (promptExecutionSettings.getResultsPerPrompt() < 1 + || promptExecutionSettings.getResultsPerPrompt() > MAX_RESULTS_PER_PROMPT) { + throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, String.format("Results per prompt must be in range between 1 and %d, inclusive.", MAX_RESULTS_PER_PROMPT)); + } Map logit = null; if (promptExecutionSettings.getTokenSelectionBiases() != null) { @@ -286,6 +290,7 @@ public class OpenAIChatCompletion implements ChatCompletionService { .setFrequencyPenalty(promptExecutionSettings.getFrequencyPenalty()) .setPresencePenalty(promptExecutionSettings.getPresencePenalty()) .setMaxTokens(promptExecutionSettings.getMaxTokens()) + .setN(promptExecutionSettings.getResultsPerPrompt()) // Azure OpenAI WithData API does not allow to send empty array of stop sequences // Gives back "Validation error at #/stop/str: Input should be a valid string\nValidation error at #/stop/list[str]: List should have at least 1 item after validation, not 0" .setStop(promptExecutionSettings.getStopSequences() == null diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java b/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java index afb2379681..13f6d1d6e2 100644 --- a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java +++ b/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java @@ -126,6 +126,10 @@ public class OpenAITextGenerationService implements TextGenerationService { if (requestSettings.getMaxTokens() < 1) { throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, "Max tokens must be >0"); } + if (requestSettings.getResultsPerPrompt() < 1 + || requestSettings.getResultsPerPrompt() > MAX_RESULTS_PER_PROMPT) { + throw new AIException(AIException.ErrorCodes.INVALID_REQUEST, String.format("Results per prompt must be in range between 1 and %d, inclusive.", MAX_RESULTS_PER_PROMPT)); + } CompletionsOptions options = new CompletionsOptions(Collections.singletonList(text)) @@ -135,6 +139,7 @@ public class OpenAITextGenerationService implements TextGenerationService { .setFrequencyPenalty(requestSettings.getFrequencyPenalty()) .setPresencePenalty(requestSettings.getPresencePenalty()) .setModel(getModelId()) + .setN(requestSettings.getResultsPerPrompt()) .setUser(requestSettings.getUser()) .setBestOf(requestSettings.getBestOf()) .setLogitBias(new HashMap<>()); diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example44_MultiChatCompletion.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example44_MultiChatCompletion.java new file mode 100644 index 0000000000..bf63c73161 --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example44_MultiChatCompletion.java @@ -0,0 +1,76 @@ +package com.microsoft.semantickernel.samples.syntaxexamples; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.credential.KeyCredential; +import com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion; +import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; + +public class Example44_MultiChatCompletion { + + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); + + // Only required if AZURE_CLIENT_KEY is set + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + private static final String MODEL_ID = System.getenv() + .getOrDefault("MODEL_ID", "gpt-35-turbo"); + public static void main(String[] args) { + System.out.println("======== Open AI - Multiple Chat Completion ========"); + + OpenAIAsyncClient client; + + if (AZURE_CLIENT_KEY != null) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(AZURE_CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + ChatCompletionService chatGPT = OpenAIChatCompletion.builder() + .withModelId(MODEL_ID) + .withOpenAIAsyncClient(client) + .build(); + + System.out.println("Chat content:"); + System.out.println("------------------------"); + + ChatHistory chatHistory = new ChatHistory("You are a librarian, expert about books"); + var executionSettings = PromptExecutionSettings.builder() + .withMaxTokens(1024) + .withTemperature(1) + .withResultsPerPrompt(2) + .build(); + + chatHistory.addUserMessage("Hi, I'm looking for 3 different book suggestions about sci-fi"); + messageOutput(chatHistory); + + GPTReply(chatGPT, chatHistory, executionSettings); + } + + + private static void messageOutput(ChatHistory chatHistory) { + var message = chatHistory.getLastMessage().get(); + System.out.println(message.getAuthorRole() + ": " + message.getContent()); + System.out.println("------------------------"); + } + + private static void GPTReply(ChatCompletionService chatGPT, ChatHistory chatHistory, + PromptExecutionSettings settings) { + var reply = chatGPT.getChatMessageContentsAsync(chatHistory, settings, null) + .block(); + + reply.forEach(chatMessageContent -> { + chatHistory.addAssistantMessage(chatMessageContent.getContent()); + messageOutput(chatHistory); + }); + } +} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/TextAIService.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/TextAIService.java index e710ab7555..95a85d6b8d 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/TextAIService.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/TextAIService.java @@ -5,5 +5,5 @@ package com.microsoft.semantickernel; * Marker interface for Text AI services, typically Chat or Text generation for OpenAI */ public interface TextAIService extends AIService { - + int MAX_RESULTS_PER_PROMPT = 128; } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettings.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettings.java index 0b7312bdf4..7100b36759 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettings.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/PromptExecutionSettings.java @@ -13,6 +13,7 @@ public class PromptExecutionSettings { public static final double DEFAULT_PRESENCE_PENALTY = 0.0; public static final double DEFAULT_FREQUENCY_PENALTY = 0.0; public static final int DEFAULT_BEST_OF = 1; + public static final int DEFAULT_RESULTS_PER_PROMPT = 1; /// /// Service identifier. @@ -33,6 +34,7 @@ public class PromptExecutionSettings { private final double frequencyPenalty; private final int maxTokens; private final int bestOf; + private final int resultsPerPrompt; private final String user; private final List stopSequences; @@ -49,6 +51,7 @@ public class PromptExecutionSettings { @JsonProperty("presence_penalty") double presencePenalty, @JsonProperty("frequency_penalty") double frequencyPenalty, @JsonProperty("max_tokens") int maxTokens, + @JsonProperty("results_per_prompt") int resultsPerPrompt, @JsonProperty("best_of") int bestOf, @JsonProperty("user") String user, @JsonProperty(value = "stop_sequences") List stopSequences, @@ -60,6 +63,7 @@ public class PromptExecutionSettings { this.presencePenalty = presencePenalty; this.frequencyPenalty = frequencyPenalty; this.maxTokens = maxTokens; + this.resultsPerPrompt = resultsPerPrompt; this.bestOf = bestOf; this.user = user; this.stopSequences = stopSequences; @@ -94,6 +98,10 @@ public class PromptExecutionSettings { return maxTokens; } + public int getResultsPerPrompt() { + return resultsPerPrompt; + } + public int getBestOf() { // TODO: not present in com.azure:azure-ai-openai return bestOf; @@ -125,6 +133,7 @@ public class PromptExecutionSettings { private double presencePenalty = Double.NaN; private double frequencyPenalty = Double.NaN; private int maxTokens = Integer.MIN_VALUE; + private int resultsPerPrompt = Integer.MIN_VALUE; private int bestOf = Integer.MIN_VALUE; private String user; private List stopSequences; @@ -165,6 +174,11 @@ public class PromptExecutionSettings { return this; } + public Builder withResultsPerPrompt(int resultsPerPrompt) { + this.resultsPerPrompt = resultsPerPrompt; + return this; + } + public Builder withBestOf(int bestOf) { this.bestOf = bestOf; return this; @@ -194,6 +208,7 @@ public class PromptExecutionSettings { Double.isNaN(presencePenalty) ? DEFAULT_PRESENCE_PENALTY : presencePenalty, Double.isNaN(frequencyPenalty) ? DEFAULT_FREQUENCY_PENALTY : frequencyPenalty, maxTokens == Integer.MIN_VALUE ? DEFAULT_MAX_TOKENS : maxTokens, + resultsPerPrompt == Integer.MIN_VALUE ? DEFAULT_RESULTS_PER_PROMPT : resultsPerPrompt, bestOf == Integer.MIN_VALUE ? DEFAULT_BEST_OF : bestOf, user, stopSequences,