From 2e49a47324c7369d7154709ab0827661034e0536 Mon Sep 17 00:00:00 2001 From: John Oliver <1615532+johnoliver@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:44:52 +0000 Subject: [PATCH] Java: Implement Example61_MultipleLLMs (#4655) ### Motivation and Context ### Description ### Contribution Checklist - [ ] The code builds clean without any errors or warnings - [ ] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [ ] All unit tests pass, and I have added new tests where possible - [ ] I didn't break anyone :smile: --- .../chatcompletion/OpenAIChatCompletion.java | 25 ++- .../OpenAITextGenerationService.java | 22 +- .../src/main/java/InlineFunctionExample.java | 2 +- ...nlineFunctionWithPreBuiltSkillExample.java | 2 +- .../e2e/AbstractKernelTest.java | 4 +- ...xample05_InlineFunctionDefinitionTest.java | 2 +- .../AzureCognitiveSearchMemoryTests.java | 2 +- .../Example05_InlineFunctionDefinition.java | 2 +- .../Example06_TemplateLanguage.java | 2 +- .../Example08_RetryHandler.java | 2 +- .../Example09_FunctionTypes.java | 2 +- ...mple10_DescribeAllPluginsAndFunctions.java | 2 +- .../Example13_ConversationSummaryPlugin.java | 2 +- .../syntaxexamples/Example26_AADAuth.java | 2 +- ...Example27_PromptFunctionsUsingChatGPT.java | 2 +- .../Example41_HttpClientUsage.java | 57 +++++ .../Example42_KernelBuilder.java | 10 +- .../syntaxexamples/Example49_LogitBias.java | 111 ++++++++++ ...eMethodFunctionsWithMultipleArguments.java | 2 +- .../Example58_ConfigureExecutionSettings.java | 4 +- .../Example61_MultipleLLMs.java | 147 +++++++++++++ .../Example62_CustomAIServiceSelector.java | 110 ++++++++++ .../Example64_MultiplePromptTemplates.java | 2 +- .../01-SimpleChat/src/main/java/Main.java | 2 +- .../src/test/java/SimpleChatTest.java | 2 +- .../02-PersonaChat/src/main/java/Main.java | 2 +- .../src/test/java/PersonaChatTest.java | 2 +- .../03-SimpleRag/src/main/java/Main.java | 2 +- .../src/test/java/SimpleRagTest.java | 2 +- .../services/AIServiceCollection.java | 2 +- .../services/AIServiceSelector.java | 34 --- .../services/DefaultAIServiceProvider.java | 20 -- .../services/OrderedAIServiceSelector.java | 19 -- .../microsoft/semantickernel/AIService.java | 2 + .../semantickernel/DefaultKernel.java | 92 +++----- .../com/microsoft/semantickernel/Kernel.java | 12 +- .../semantickernel/ServiceProvider.java | 23 -- .../chatcompletion/ChatCompletionService.java | 6 + .../orchestration/DefaultKernelFunction.java | 6 + .../orchestration/KernelFunction.java | 3 + .../PromptExecutionSettings.java | 36 +++- .../DefaultKernelArguments.java | 54 +++-- .../contextvariables/KernelArguments.java | 2 +- .../plugin/KernelFunctionFactory.java | 3 +- .../KernelFunctionFromPrompt.java | 74 +++++-- .../semanticfunctions/OutputVariable.java | 3 +- .../PromptTemplateConfig.java | 8 +- .../services/AIServiceSelection.java | 2 +- .../services/AIServiceSelector.java | 34 +++ .../services/BaseAIServiceSelector.java | 36 ++++ .../services/NamedServiceProvider.java | 0 .../services/OrderedAIServiceSelector.java | 204 ++++++++++++++++++ .../semantickernel/blocks/CodeBlock.java | 1 + .../textcompletion/TextGenerationService.java | 7 + 54 files changed, 974 insertions(+), 239 deletions(-) create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example41_HttpClientUsage.java create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example49_LogitBias.java create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example61_MultipleLLMs.java create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example62_CustomAIServiceSelector.java delete mode 100644 java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/AIServiceSelector.java delete mode 100644 java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/DefaultAIServiceProvider.java delete mode 100644 java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.java delete mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ServiceProvider.java rename java/semantickernel-api/src/main/{ignore => }/java/com/microsoft/semantickernel/services/AIServiceSelection.java (93%) create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelector.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/BaseAIServiceSelector.java rename java/semantickernel-api/src/main/{ignore => }/java/com/microsoft/semantickernel/services/NamedServiceProvider.java (100%) create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.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 a2333d94d9..7a525213d5 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 @@ -52,8 +52,10 @@ public class OpenAIChatCompletion implements ChatCompletionService { private static final Logger LOGGER = LoggerFactory.getLogger(OpenAIChatCompletion.class); private final OpenAIAsyncClient client; private final Map> attributes; + private final String serviceId; - public OpenAIChatCompletion(OpenAIAsyncClient client, String modelId) { + public OpenAIChatCompletion(OpenAIAsyncClient client, String modelId, String serviceId) { + this.serviceId = serviceId; this.client = client; this.attributes = new HashMap<>(); attributes.put(MODEL_ID_KEY, ContextVariable.of(modelId)); @@ -68,6 +70,11 @@ public class OpenAIChatCompletion implements ChatCompletionService { return attributes; } + @Override + public String getServiceId() { + return serviceId; + } + @Override public Mono> getChatMessageContentsAsync(ChatHistory chatHistory, @Nullable PromptExecutionSettings promptExecutionSettings, @Nullable Kernel kernel) { @@ -322,6 +329,18 @@ public class OpenAIChatCompletion implements ChatCompletionService { return options; } + Map logit = null; + if (promptExecutionSettings.getTokenSelectionBiases() != null) { + logit = promptExecutionSettings + .getTokenSelectionBiases() + .entrySet() + .stream() + .collect(Collectors.toMap( + entry -> entry.getKey().toString(), + entry -> entry.getValue()) + ); + } + options .setTemperature(promptExecutionSettings.getTemperature()) .setTopP(promptExecutionSettings.getTopP()) @@ -335,7 +354,7 @@ public class OpenAIChatCompletion implements ChatCompletionService { || promptExecutionSettings.getStopSequences().isEmpty() ? null : promptExecutionSettings.getStopSequences()) .setUser(promptExecutionSettings.getUser()) - .setLogitBias(new HashMap<>()); + .setLogitBias(logit); return options; } @@ -550,7 +569,7 @@ public class OpenAIChatCompletion implements ChatCompletionService { @Override public OpenAIChatCompletion build() { - return new OpenAIChatCompletion(client, modelId); + return new OpenAIChatCompletion(client, modelId, serviceId); } } } 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 c8a4da7d44..c400a0d995 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 @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import javax.annotation.Nullable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -23,6 +24,7 @@ public class OpenAITextGenerationService implements TextGenerationService { private final OpenAIAsyncClient client; private final Map> attributes; + private final String serviceId; /// /// Creates a new client instance supporting AAD auth @@ -35,7 +37,8 @@ public class OpenAITextGenerationService implements TextGenerationService { /// The to use for logging. If null, no logging will be performed. public OpenAITextGenerationService( OpenAIAsyncClient client, - String modelId) { + String modelId, String serviceId) { + this.serviceId = serviceId; this.client = client; attributes = new HashMap<>(); attributes.put(MODEL_ID_KEY, ContextVariable.of(modelId)); @@ -50,10 +53,22 @@ public class OpenAITextGenerationService implements TextGenerationService { return attributes; } + @Override + public String getServiceId() { + return serviceId; + } + @Override public Mono> getTextContentsAsync(String prompt, @Nullable PromptExecutionSettings executionSettings, @Nullable Kernel kernel) { - return null; + return this + .internalCompleteTextAsync(prompt, executionSettings) + .map(it -> { + return it.stream().map( + TextContent::new + ) + .collect(Collectors.toList()); + }); } @Override @@ -121,7 +136,8 @@ public class OpenAITextGenerationService implements TextGenerationService { public TextGenerationService build() { return new OpenAITextGenerationService( this.client, - this.modelId + this.modelId, + this.serviceId ); } } diff --git a/java/api-test/api-usage-example/src/main/java/InlineFunctionExample.java b/java/api-test/api-usage-example/src/main/java/InlineFunctionExample.java index 2a1bb79dc1..ca40ea404e 100644 --- a/java/api-test/api-usage-example/src/main/java/InlineFunctionExample.java +++ b/java/api-test/api-usage-example/src/main/java/InlineFunctionExample.java @@ -101,7 +101,7 @@ public class InlineFunctionExample { SKBuilders.textCompletion().withOpenAIAsyncClient(client).withModelId(MODEL).build(); String prompt = "{{$input}}\n" + "Summarize the content above."; - Kernel kernel = SKBuilders.kernel().withDefaultAIService(textCompletion).build(); + Kernel kernel = SKBuilders.kernel().withAIService(textCompletion).build(); CompletionKernelFunction summarize = kernel.getSemanticFunctionBuilder() diff --git a/java/api-test/api-usage-example/src/main/java/InlineFunctionWithPreBuiltSkillExample.java b/java/api-test/api-usage-example/src/main/java/InlineFunctionWithPreBuiltSkillExample.java index 03fbf0d3a3..0b843861e5 100644 --- a/java/api-test/api-usage-example/src/main/java/InlineFunctionWithPreBuiltSkillExample.java +++ b/java/api-test/api-usage-example/src/main/java/InlineFunctionWithPreBuiltSkillExample.java @@ -102,7 +102,7 @@ public class InlineFunctionWithPreBuiltSkillExample { SKBuilders.textCompletion().withOpenAIAsyncClient(client).withModelId(MODEL).build(); String prompt = "{{$input}}\nSummarize the content above."; - Kernel kernel = SKBuilders.kernel().withDefaultAIService(textCompletion).build(); + Kernel kernel = SKBuilders.kernel().withAIService(textCompletion).build(); CompletionKernelFunction summarize = SKBuilders.completionFunctions() diff --git a/java/api-test/integration-tests/src/test/ignore/com/microsoft/semantickernel/e2e/AbstractKernelTest.java b/java/api-test/integration-tests/src/test/ignore/com/microsoft/semantickernel/e2e/AbstractKernelTest.java index 5a71bed8fb..9fdc155148 100644 --- a/java/api-test/integration-tests/src/test/ignore/com/microsoft/semantickernel/e2e/AbstractKernelTest.java +++ b/java/api-test/integration-tests/src/test/ignore/com/microsoft/semantickernel/e2e/AbstractKernelTest.java @@ -27,8 +27,8 @@ public class AbstractKernelTest { TextCompletion textCompletion = new OpenAITextCompletion(openAIClient, model); return SKBuilders.kernel() - .withDefaultAIService(textCompletion) - .withDefaultAIService( + .withAIService(textCompletion) + .withAIService( SKBuilders.textEmbeddingGeneration() .withOpenAIClient(openAIClient) .withModelId(model) diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example05_InlineFunctionDefinitionTest.java b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example05_InlineFunctionDefinitionTest.java index 2ccd007928..26917d45a7 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example05_InlineFunctionDefinitionTest.java +++ b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example05_InlineFunctionDefinitionTest.java @@ -36,7 +36,7 @@ public class Example05_InlineFunctionDefinitionTest { .build(); Kernel kernel = new DefaultKernel.Builder() - .withDefaultAIService(TextGenerationService.class, textGenerationService) + .withAIService(TextGenerationService.class, textGenerationService) .build(); System.out.println("======== Inline Function Definition ========"); diff --git a/java/connectors/semantickernel-connectors-memory-azurecognitivesearch/src/test/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchMemoryTests.java b/java/connectors/semantickernel-connectors-memory-azurecognitivesearch/src/test/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchMemoryTests.java index e1d081c828..5c4dce0c93 100644 --- a/java/connectors/semantickernel-connectors-memory-azurecognitivesearch/src/test/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchMemoryTests.java +++ b/java/connectors/semantickernel-connectors-memory-azurecognitivesearch/src/test/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchMemoryTests.java @@ -150,7 +150,7 @@ class AzureCognitiveSearchMemoryTests { .withMemoryStorage( new AzureCognitiveSearchMemoryStore( searchIndexAsyncClient, memoryRecordMapper)) - .withDefaultAIService(embeddingGeneration) + .withAIService(embeddingGeneration) .build(); } diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example05_InlineFunctionDefinition.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example05_InlineFunctionDefinition.java index d8dd049d6a..9aabd14a0e 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example05_InlineFunctionDefinition.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example05_InlineFunctionDefinition.java @@ -49,7 +49,7 @@ public class Example05_InlineFunctionDefinition { .build(); Kernel kernel = Kernel.builder() - .withDefaultAIService(TextGenerationService.class, textGenerationService) + .withAIService(TextGenerationService.class, textGenerationService) .build(); System.out.println("======== Inline Function Definition ========"); diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example06_TemplateLanguage.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example06_TemplateLanguage.java index 79aa025f58..3dd7cad9c3 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example06_TemplateLanguage.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example06_TemplateLanguage.java @@ -62,7 +62,7 @@ public class Example06_TemplateLanguage { .build(); Kernel kernel = Kernel.builder() - .withDefaultAIService(TextGenerationService.class, textGenerationService) + .withAIService(TextGenerationService.class, textGenerationService) .build(); // Load native plugin into the kernel function collection, sharing its functions with prompt templates diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example08_RetryHandler.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example08_RetryHandler.java index 630c215181..8a81abd0e6 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example08_RetryHandler.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example08_RetryHandler.java @@ -35,7 +35,7 @@ public class Example08_RetryHandler { .build(); Kernel kernel = Kernel.builder() - .withDefaultAIService(TextGenerationService.class, textGenerationService) + .withAIService(TextGenerationService.class, textGenerationService) .build(); String question = "How popular is the Polly library?"; diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example09_FunctionTypes.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example09_FunctionTypes.java index 94c74d2ffa..9747a877e4 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example09_FunctionTypes.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example09_FunctionTypes.java @@ -108,7 +108,7 @@ public class Example09_FunctionTypes { ); Kernel kernel = Kernel.builder() - .withDefaultAIService(TextGenerationService.class, textGenerationService) + .withAIService(TextGenerationService.class, textGenerationService) .withPlugin(plugin) .withPlugin(summarize) .withPlugin(examplePlugin) diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example10_DescribeAllPluginsAndFunctions.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example10_DescribeAllPluginsAndFunctions.java index f3d0aa3b21..113cf3af9f 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example10_DescribeAllPluginsAndFunctions.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example10_DescribeAllPluginsAndFunctions.java @@ -91,7 +91,7 @@ public class Example10_DescribeAllPluginsAndFunctions { .build(); Kernel kernel = Kernel.builder() - .withDefaultAIService(TextGenerationService.class, textGenerationService) + .withAIService(TextGenerationService.class, textGenerationService) .build(); kernel.getPlugins().add( diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example13_ConversationSummaryPlugin.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example13_ConversationSummaryPlugin.java index c9edd73ea7..e7fc0307e3 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example13_ConversationSummaryPlugin.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example13_ConversationSummaryPlugin.java @@ -239,7 +239,7 @@ public class Example13_ConversationSummaryPlugin { .build(); return Kernel.builder() - .withDefaultAIService(TextGenerationService.class, textGenerationService) + .withAIService(TextGenerationService.class, textGenerationService) .build(); } } diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example26_AADAuth.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example26_AADAuth.java index 4dcb26eea2..0e2832826a 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example26_AADAuth.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example26_AADAuth.java @@ -31,7 +31,7 @@ public class Example26_AADAuth { .build(); Kernel kernel = Kernel.builder() - .withDefaultAIService(ChatCompletionService.class, chatService) + .withAIService(ChatCompletionService.class, chatService) .build(); var chatHistory = new ChatHistory(); diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example27_PromptFunctionsUsingChatGPT.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example27_PromptFunctionsUsingChatGPT.java index 50f3c7c58f..56f0df406e 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example27_PromptFunctionsUsingChatGPT.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example27_PromptFunctionsUsingChatGPT.java @@ -44,7 +44,7 @@ public class Example27_PromptFunctionsUsingChatGPT { .build(); Kernel kernel = Kernel.builder() - .withDefaultAIService(ChatCompletionService.class, openAIChatCompletion) + .withAIService(ChatCompletionService.class, openAIChatCompletion) .build(); var func = KernelFunctionFactory.createFromPrompt( diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example41_HttpClientUsage.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example41_HttpClientUsage.java new file mode 100644 index 0000000000..493d2c52c6 --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example41_HttpClientUsage.java @@ -0,0 +1,57 @@ +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.http.HttpClient; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.textcompletion.TextGenerationService; + +public class Example41_HttpClientUsage { + + public static void main(String[] args) { + useDefaultHttpClient(); + useCustomHttpClient(); + } + + /// + /// Demonstrates the usage of the default HttpClient provided by the SK SDK. + /// + private static void useDefaultHttpClient() { + OpenAIAsyncClient client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential("a-key")) + .endpoint("an-endpoint") + .buildAsyncClient(); + + var kernel = Kernel.builder() + .withAIService(ChatCompletionService.class, + ChatCompletionService.builder() + .withOpenAIAsyncClient(client) + .withModelId("gpt-35-turbo-2") + .build()) + .build(); + } + + /// + /// Demonstrates the usage of a custom HttpClient. + /// + private static void useCustomHttpClient() { + HttpClient customHttpClient = HttpClient.createDefault(); + + OpenAIAsyncClient client = new OpenAIClientBuilder() + .httpClient(customHttpClient) + .endpoint("https://localhost:5000") + .credential(new AzureKeyCredential("BAD KEY")) + .buildAsyncClient(); + + TextGenerationService textGenerationService = TextGenerationService.builder() + .withOpenAIAsyncClient(client) + .withModelId("text-davinci-003") + .build(); + + Kernel kernel = Kernel.builder() + .withAIService(TextGenerationService.class, textGenerationService) + .build(); + } +} diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example42_KernelBuilder.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example42_KernelBuilder.java index c41c22026e..64f3eb0893 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example42_KernelBuilder.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example42_KernelBuilder.java @@ -6,12 +6,12 @@ import com.azure.core.credential.AzureKeyCredential; import com.azure.core.credential.KeyCredential; import com.azure.core.http.HttpClient; import com.microsoft.semantickernel.DefaultKernel; -import com.microsoft.semantickernel.DefaultKernel.DefaultServiceProvider; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.plugin.KernelPluginCollection; import com.microsoft.semantickernel.plugin.KernelPluginFactory; import com.microsoft.semantickernel.samples.plugins.ConversationSummaryPlugin; +import com.microsoft.semantickernel.services.OrderedAIServiceSelector; import com.microsoft.semantickernel.textcompletion.TextGenerationService; import java.util.HashMap; @@ -28,7 +28,7 @@ public class Example42_KernelBuilder { .buildAsyncClient(); Kernel kernel1 = Kernel.builder() - .withDefaultAIService(ChatCompletionService.class, + .withAIService(ChatCompletionService.class, ChatCompletionService.builder() .withOpenAIAsyncClient(client) .withModelId("gpt-35-turbo-2") @@ -51,7 +51,7 @@ public class Example42_KernelBuilder { .build(); Kernel kernel2 = Kernel.builder() - .withDefaultAIService(TextGenerationService.class, textGenerationService) + .withAIService(TextGenerationService.class, textGenerationService) .build(); ///////////////////////////////////////////////////////// @@ -68,9 +68,7 @@ public class Example42_KernelBuilder { // wrapper, ultimately constructing a Kernel // using the public constructor that's available for anyone to use directly if desired. Kernel kernel = new DefaultKernel( - new DefaultServiceProvider( - new HashMap<>() - ), + new OrderedAIServiceSelector(), new KernelPluginCollection() ); } diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example49_LogitBias.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example49_LogitBias.java new file mode 100644 index 0000000000..07216b1fb4 --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example49_LogitBias.java @@ -0,0 +1,111 @@ +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.Kernel; +import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.chatcompletion.ChatHistory; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Logit_bias is an optional parameter that modifies the likelihood of specified tokens appearing in + * a Completion. When using the Token Selection Biases parameter, the bias is added to the logits + * generated by the model prior to sampling. + */ +public class Example49_LogitBias { + + private static final boolean USE_AZURE_CLIENT = Boolean.parseBoolean( + System.getenv("USE_AZURE_CLIENT")); + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + + // Only required if USE_AZURE_CLIENT is true + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + + public static void main(String[] args) { + + OpenAIAsyncClient client; + + if (USE_AZURE_CLIENT) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + ChatCompletionService openAIChatCompletion = ChatCompletionService.builder() + .withOpenAIAsyncClient(client) + .withModelId("gpt-35-turbo-2") + .build(); + + Kernel kernel = Kernel.builder() + .withAIService(ChatCompletionService.class, openAIChatCompletion) + .build(); + + // To use Logit Bias you will need to know the token ids of the words you want to use. + // Getting the token ids using the GPT Tokenizer: https://platform.openai.com/tokenizer + + // The following text is the tokenized version of the book related tokens + // "novel literature reading author library story chapter paperback hardcover ebook publishing fiction nonfiction manuscript textbook bestseller bookstore reading list bookworm" + var keys = new int[]{39142, 301, 17649, 5403, 3229, 6875, 3446, 12735, 86831, 2653, 3773, + 35097, 23763, 17422, 2536, 58162, 47913, 56185, 1888, 35199, 79761, 5403, 1160, 2363, + 56741}; +/* + // If using GPT-3 (not GPT-3.5 or GPT-4) + var keys = new int[]{3919, 626, 9285, 3555, 1772, 5888, 1621, 6843, 46771, 1327, 9631, + 47179, 12407, 10165, 1729, 24046, 17116, 28979, 1266, 32932, 44346, 3555, 1351, 1492, + 25323}; + */ + // This will make the model try its best to avoid any of the above related words. + //-100 to potentially ban all the tokens from the list. + Map biases = new HashMap<>(); + Arrays.stream(keys) + .asLongStream() + .forEach(key -> biases.put((int) key, -100)); + + var settings = PromptExecutionSettings.builder() + .withTokenSelectionBiases(biases) + .build(); + + System.out.println("Chat content:"); + System.out.println("------------------------"); + + var chatHistory = new ChatHistory("You are a librarian expert"); + + // First user message + chatHistory.addUserMessage("Hi, I'm looking some suggestions"); + messageOutputAsync(chatHistory); + + var replyMessage = openAIChatCompletion.getChatMessageContentsAsync(chatHistory, + settings, kernel).block(); + replyMessage.forEach(message -> chatHistory.addAssistantMessage(message.getContent())); + messageOutputAsync(chatHistory); + + chatHistory.addUserMessage( + "I love history and philosophy, I'd like to learn something new about Greece, any suggestion"); + messageOutputAsync(chatHistory); + + replyMessage = openAIChatCompletion.getChatMessageContentsAsync(chatHistory, + settings, kernel).block(); + replyMessage.forEach(message -> chatHistory.addAssistantMessage(message.getContent())); + messageOutputAsync(chatHistory); + + + } + + private static void messageOutputAsync(ChatHistory chatHistory) { + var message = chatHistory.getLastMessage(); + + System.out.println(message.get().getAuthorRole() + ": " + message.get().getContent()); + System.out.println("------------------------"); + } + +} diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example56_TemplateMethodFunctionsWithMultipleArguments.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example56_TemplateMethodFunctionsWithMultipleArguments.java index d1c92248d3..c12d74b506 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example56_TemplateMethodFunctionsWithMultipleArguments.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example56_TemplateMethodFunctionsWithMultipleArguments.java @@ -47,7 +47,7 @@ public class Example56_TemplateMethodFunctionsWithMultipleArguments { .build(); Kernel kernel = Kernel.builder() - .withDefaultAIService(ChatCompletionService.class, openAIChatCompletion) + .withAIService(ChatCompletionService.class, openAIChatCompletion) .build(); System.out.println("======== TemplateMethodFunctionsWithMultipleArguments ========"); diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example58_ConfigureExecutionSettings.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example58_ConfigureExecutionSettings.java index fdc9d0d9a8..f1f58699dc 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example58_ConfigureExecutionSettings.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example58_ConfigureExecutionSettings.java @@ -44,10 +44,8 @@ public class Example58_ConfigureExecutionSettings { .withOpenAIAsyncClient(client) .build(); - System.out.println("======== Using Chat GPT model for text generation ========"); - var kernel = Kernel.builder() - .withDefaultAIService(ChatCompletionService.class, openAIChatCompletion) + .withAIService(ChatCompletionService.class, openAIChatCompletion) .build(); var prompt = "Hello AI, what can you do for me?"; diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example61_MultipleLLMs.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example61_MultipleLLMs.java new file mode 100644 index 0000000000..f6f8d38ca6 --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example61_MultipleLLMs.java @@ -0,0 +1,147 @@ +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.Kernel; +import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.orchestration.KernelFunction; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt; +import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; +import com.microsoft.semantickernel.textcompletion.TextGenerationService; +import java.util.Arrays; +import java.util.HashMap; + +public class Example61_MultipleLLMs { + + private static final boolean USE_AZURE_CLIENT = Boolean.parseBoolean( + System.getenv("USE_AZURE_CLIENT")); + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + + // Only required if USE_AZURE_CLIENT is true + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + + public static void main(String[] args) { + System.out.println("======== Example61_MultipleLLMs ========"); + + OpenAIAsyncClient client; + + if (USE_AZURE_CLIENT) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + System.out.println("======== Using Chat GPT model for text generation ========"); + + var openAIChatCompletion = ChatCompletionService.builder() + .withOpenAIAsyncClient(client) + .withServiceId("AzureOpenAIChat") + .withModelId("gpt-35-turbo-2") + .build(); + + var textGenerationService = TextGenerationService.builder() + .withOpenAIAsyncClient(client) + .withServiceId("AzureOpenAIText") + .withModelId("text-davinci-003") + .build(); + + var kernel = Kernel.builder() + .withAIService(ChatCompletionService.class, openAIChatCompletion) + .withAIService(TextGenerationService.class, textGenerationService) + .build(); + + runByServiceIdAsync(kernel, "AzureOpenAIText"); + runByServiceIdAsync(kernel, "AzureOpenAIChat"); + + runByModelIdAsync(kernel, "gpt-35-turbo-2"); + + runByFirstModelIdAsync(kernel, "text-davinci-003", "gpt-35-turbo-2"); + } + + + public static void runByServiceIdAsync(Kernel kernel, String serviceId) { + System.out.println("======== Service Id: " + serviceId + " ========"); + + var prompt = "Hello AI, what can you do for me?"; + + KernelArguments arguments = KernelArguments.builder().build(); + + KernelFunction func = KernelFunctionFromPrompt + .builder() + .withTemplate(prompt) + .withDefaultExecutionSettings( + PromptExecutionSettings.builder() + .withServiceId(serviceId) + .build() + ) + .build(); + + var result = kernel.invokeAsync(func, arguments, String.class).block(); + System.out.println(result.getValue()); + } + + + public static void runByModelIdAsync(Kernel kernel, String modelId) { + System.out.println("======== Model Id: " + modelId + " ========"); + + var prompt = "Hello AI, what can you do for me?"; + + var result = kernel.invokeAsync( + KernelFunctionFromPrompt + .builder() + .withTemplate(prompt) + .withDefaultExecutionSettings( + PromptExecutionSettings.builder() + .withModelId(modelId) + .build() + ) + .build(), + KernelArguments.builder().build(), + String.class + ).block(); + + System.out.println(result.getValue()); + } + + + public static void runByFirstModelIdAsync(Kernel kernel, String... modelIds) { + System.out.println("======== Model Ids: " + String.join(",", modelIds) + " ========"); + + var prompt = "Hello AI, what can you do for me?"; + + var modelSettings = new HashMap(); + + Arrays.stream(modelIds).forEach( + modelId -> { + modelSettings.put( + modelId, + PromptExecutionSettings.builder() + .withModelId(modelId) + .build()); + } + ); + + var promptConfig = new PromptTemplateConfig(prompt); + promptConfig.setName("HelloAI"); + promptConfig.setExecutionSettings(modelSettings); + + var function = KernelFunctionFromPrompt.create(promptConfig); + + var result = kernel.invokeAsync( + function, + KernelArguments.builder().build(), + String.class) + .block(); + + System.out.println(result.getValue()); + } + +} diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example62_CustomAIServiceSelector.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example62_CustomAIServiceSelector.java new file mode 100644 index 0000000000..f6c78664d4 --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example62_CustomAIServiceSelector.java @@ -0,0 +1,110 @@ +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.AIService; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.orchestration.KernelFunction; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt; +import com.microsoft.semantickernel.services.AIServiceSelection; +import com.microsoft.semantickernel.services.BaseAIServiceSelector; +import com.microsoft.semantickernel.textcompletion.TextGenerationService; +import java.util.Map; +import javax.annotation.Nullable; + +public class Example62_CustomAIServiceSelector { + + + private static final boolean USE_AZURE_CLIENT = Boolean.parseBoolean( + System.getenv("USE_AZURE_CLIENT")); + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + + // Only required if USE_AZURE_CLIENT is true + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + + public static void main(String[] args) { + System.out.println("======== Example62_CustomAIServiceSelector ========"); + + OpenAIAsyncClient client; + + if (USE_AZURE_CLIENT) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + // Build a kernel with multiple chat completion services + + var openAIChatCompletion = ChatCompletionService.builder() + .withOpenAIAsyncClient(client) + .withServiceId("AzureOpenAIChat") + .withModelId("gpt-35-turbo-2") + .build(); + + var textGenerationService = TextGenerationService.builder() + .withOpenAIAsyncClient(client) + .withServiceId("AzureOpenAIText") + .withModelId("text-davinci-003") + .build(); + + var kernel = Kernel.builder() + .withAIService(ChatCompletionService.class, openAIChatCompletion) + .withAIService(TextGenerationService.class, textGenerationService) + .withServiceSelector(CustomAIServiceSelector::new) + .build(); + + var prompt = "Hello AI, what can you do for me?"; + + KernelArguments arguments = KernelArguments.builder().build(); + + KernelFunction func = KernelFunctionFromPrompt + .builder() + .withTemplate(prompt) + .withDefaultExecutionSettings( + PromptExecutionSettings.builder() + .withTopP(1.0) + .build() + ) + .build(); + + var result = kernel.invokeAsync(func, arguments, String.class).block(); + System.out.println(result.getValue()); + } + + // A dumb AIServiceSelector that just returns the first service and execution settings it finds + static class CustomAIServiceSelector extends BaseAIServiceSelector { + + public CustomAIServiceSelector(Map, AIService> services) { + super(services); + } + + @Nullable + @Override + public AIServiceSelection trySelectAIService( + Class serviceType, + KernelFunction function, + KernelArguments arguments, + Map, AIService> services) { + + // Just get the first one + PromptExecutionSettings executionSettings = function.getExecutionSettings() + .values().stream().findFirst().get(); + AIService service = services.values().stream().findFirst().get(); + + return new AIServiceSelection( + service, + executionSettings + ); + } + } +} diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example64_MultiplePromptTemplates.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example64_MultiplePromptTemplates.java index dfc162c4be..22de9ec092 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example64_MultiplePromptTemplates.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example64_MultiplePromptTemplates.java @@ -50,7 +50,7 @@ public class Example64_MultiplePromptTemplates { .build(); var kernel = new DefaultKernel.Builder() - .withDefaultAIService(ChatCompletionService.class, openAIChatCompletion) + .withAIService(ChatCompletionService.class, openAIChatCompletion) .build(); var templateFactory = new AggregatorPromptTemplateFactory( diff --git a/java/samples/sk-v1/01-SimpleChat/src/main/java/Main.java b/java/samples/sk-v1/01-SimpleChat/src/main/java/Main.java index 2659c12c23..aa59cc7bb7 100644 --- a/java/samples/sk-v1/01-SimpleChat/src/main/java/Main.java +++ b/java/samples/sk-v1/01-SimpleChat/src/main/java/Main.java @@ -42,7 +42,7 @@ public class Main { */ Kernel kernel = Kernel.builder() - .withDefaultAIService(ChatCompletionService.class, gpt35Turbo) + .withAIService(ChatCompletionService.class, gpt35Turbo) .withPromptTemplate(new HandlebarsPromptTemplate()) .build(); diff --git a/java/samples/sk-v1/01-SimpleChat/src/test/java/SimpleChatTest.java b/java/samples/sk-v1/01-SimpleChat/src/test/java/SimpleChatTest.java index 86b947eba8..729f47b09f 100644 --- a/java/samples/sk-v1/01-SimpleChat/src/test/java/SimpleChatTest.java +++ b/java/samples/sk-v1/01-SimpleChat/src/test/java/SimpleChatTest.java @@ -61,7 +61,7 @@ public class SimpleChatTest { private static void execute(ChatCompletionService gpt35Turbo, List messages) throws IOException { Kernel kernel = Kernel.builder() - .withDefaultAIService(ChatCompletionService.class, gpt35Turbo) + .withAIService(ChatCompletionService.class, gpt35Turbo) .withPromptTemplate(new HandlebarsPromptTemplate()) .build(); diff --git a/java/samples/sk-v1/02-PersonaChat/src/main/java/Main.java b/java/samples/sk-v1/02-PersonaChat/src/main/java/Main.java index 8eac424937..60b64f30eb 100644 --- a/java/samples/sk-v1/02-PersonaChat/src/main/java/Main.java +++ b/java/samples/sk-v1/02-PersonaChat/src/main/java/Main.java @@ -40,7 +40,7 @@ public class Main { .build(); Kernel kernel = Kernel.builder() - .withDefaultAIService(ChatCompletionService.class, gpt35Turbo) + .withAIService(ChatCompletionService.class, gpt35Turbo) .withPromptTemplate(new HandlebarsPromptTemplate()) .build(); diff --git a/java/samples/sk-v1/02-PersonaChat/src/test/java/PersonaChatTest.java b/java/samples/sk-v1/02-PersonaChat/src/test/java/PersonaChatTest.java index adb1ed1742..273e094cf6 100644 --- a/java/samples/sk-v1/02-PersonaChat/src/test/java/PersonaChatTest.java +++ b/java/samples/sk-v1/02-PersonaChat/src/test/java/PersonaChatTest.java @@ -55,7 +55,7 @@ public class PersonaChatTest { ChatCompletionService gpt35Turbo = mockService(messages); Kernel kernel = Kernel.builder() - .withDefaultAIService(ChatCompletionService.class, gpt35Turbo) + .withAIService(ChatCompletionService.class, gpt35Turbo) .withPromptTemplate(new HandlebarsPromptTemplate()) .build(); diff --git a/java/samples/sk-v1/03-SimpleRag/src/main/java/Main.java b/java/samples/sk-v1/03-SimpleRag/src/main/java/Main.java index 8e4b1a10c4..98ffd0c09b 100644 --- a/java/samples/sk-v1/03-SimpleRag/src/main/java/Main.java +++ b/java/samples/sk-v1/03-SimpleRag/src/main/java/Main.java @@ -48,7 +48,7 @@ public class Main { ); Kernel kernel = Kernel.builder() - .withDefaultAIService(ChatCompletionService.class, gpt35Turbo) + .withAIService(ChatCompletionService.class, gpt35Turbo) .withPromptTemplate(new HandlebarsPromptTemplate()) .withPlugin(searchPlugin) .build(); diff --git a/java/samples/sk-v1/03-SimpleRag/src/test/java/SimpleRagTest.java b/java/samples/sk-v1/03-SimpleRag/src/test/java/SimpleRagTest.java index b4b34ad78b..7c52fd3429 100644 --- a/java/samples/sk-v1/03-SimpleRag/src/test/java/SimpleRagTest.java +++ b/java/samples/sk-v1/03-SimpleRag/src/test/java/SimpleRagTest.java @@ -68,7 +68,7 @@ public class SimpleRagTest { ); Kernel kernel = Kernel.builder() - .withDefaultAIService(ChatCompletionService.class, gpt35Turbo) + .withAIService(ChatCompletionService.class, gpt35Turbo) .withPromptTemplate(new HandlebarsPromptTemplate()) .withPlugin(searchPlugin) .build(); diff --git a/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/AIServiceCollection.java b/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/AIServiceCollection.java index 9f1527988a..b337002fbe 100644 --- a/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/AIServiceCollection.java +++ b/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/AIServiceCollection.java @@ -140,7 +140,7 @@ public class AIServiceCollection { Map, String> defaultsClone = new HashMap<>(defaultIds); - return new DefaultAIServiceProvider(servicesClone, defaultsClone); + return new OrderedAIServiceSelector(servicesClone, defaultsClone); } /** diff --git a/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/AIServiceSelector.java b/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/AIServiceSelector.java deleted file mode 100644 index 6dd6a8f4c7..0000000000 --- a/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/AIServiceSelector.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -package com.microsoft.semantickernel.services; - -import com.microsoft.semantickernel.AIService; -import com.microsoft.semantickernel.Kernel; -import com.microsoft.semantickernel.KernelArguments; -import com.microsoft.semantickernel.KernelFunction; -import com.microsoft.semantickernel.PromptExecutionSettings; - -/** - * Represents a selector which will return an {@link AIServiceSelection} containing instances - * of {@link AIService} and {@link PromptExecutionSettings} from the specified provider based - * on the model settings. - */ -public interface AIServiceSelector { - - /** - * Resolves an {@link IAIService} and associated and {@link PromptExecutionSettings} from - * the specified {@link com.microsoft.semantickernel.Kernel} based on the associated - * {@link com.microsoft.semantickernel.KernelFunction} and {@link com.microsoft.semantickernel.KernelArguments}. - * @param serviceType The type of service to select. This must be the same type - * with which the service was registered in the {@link AIServiceSelectionf} - * @param kernel - * @param function - * @param arguments - * @return - */ - AIServiceSelection trySelectAIService( - Class serviceType, - Kernel kernel, - KernelFunction function, - KernelArguments arguments - ); -} diff --git a/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/DefaultAIServiceProvider.java b/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/DefaultAIServiceProvider.java deleted file mode 100644 index fb75ee4af7..0000000000 --- a/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/DefaultAIServiceProvider.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -package com.microsoft.semantickernel.services; - -import java.util.Map; -import java.util.function.Supplier; - -import com.microsoft.semantickernel.AIService; - -/** - * Default implementation of {@link AIServiceProvider} - * @deprecated Use {@code com.microsoft.semantickernel.aiservices.DefaultAIServiceProvider} instead - */ -public class DefaultAIServiceProvider extends DefaultNamedServiceProvider - implements AIServiceProvider { - public DefaultAIServiceProvider( - Map, Map>> services, - Map, String> defaultIds) { - super(services, defaultIds); - } -} diff --git a/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.java b/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.java deleted file mode 100644 index 8b0ba6be58..0000000000 --- a/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.microsoft.semantickernel.services; - -import com.microsoft.semantickernel.AIService; -import com.microsoft.semantickernel.Kernel; -import com.microsoft.semantickernel.KernelArguments; -import com.microsoft.semantickernel.KernelFunction; - -public enum OrderedAIServiceSelector implements AIServiceSelector { - - INST; - - @Override - public AIServiceSelection trySelectAIService(Class serviceType, Kernel kernel, - KernelFunction function, KernelArguments arguments) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Not implemented"); - } - -} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/AIService.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/AIService.java index 47c0c9c425..a9df954780 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/AIService.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/AIService.java @@ -45,6 +45,8 @@ public interface AIService { */ default String getApiVersion() { return getAttribute(this, API_VERSION_KEY); } + String getServiceId(); + /// /// Gets the specified attribute. /// diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/DefaultKernel.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/DefaultKernel.java index 2835b7069b..19365cf1b5 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/DefaultKernel.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/DefaultKernel.java @@ -1,16 +1,5 @@ package com.microsoft.semantickernel; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.annotation.Nullable; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.orchestration.KernelFunction; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType; @@ -20,8 +9,16 @@ import com.microsoft.semantickernel.orchestration.contextvariables.KernelArgumen import com.microsoft.semantickernel.plugin.KernelPlugin; import com.microsoft.semantickernel.plugin.KernelPluginCollection; import com.microsoft.semantickernel.semanticfunctions.PromptTemplate; -import com.microsoft.semantickernel.textcompletion.TextGenerationService; - +import com.microsoft.semantickernel.services.AIServiceSelector; +import com.microsoft.semantickernel.services.OrderedAIServiceSelector; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -29,13 +26,13 @@ public class DefaultKernel implements Kernel { private static final Logger LOGGER = LoggerFactory.getLogger(DefaultKernel.class); - private final ServiceProvider serviceProvider; + private final AIServiceSelector serviceSelector; public KernelPluginCollection plugins; public DefaultKernel( - ServiceProvider serviceProvider, + AIServiceSelector serviceSelector, @Nullable KernelPluginCollection plugins) { - this.serviceProvider = serviceProvider; + this.serviceSelector = serviceSelector; if (plugins != null) { this.plugins = plugins; } else { @@ -104,8 +101,8 @@ public class DefaultKernel implements Kernel { } @Override - public ServiceProvider getServiceSelector() { - return serviceProvider; + public AIServiceSelector getServiceSelector() { + return serviceSelector; } @Override @@ -113,51 +110,15 @@ public class DefaultKernel implements Kernel { return plugins; } - public static class DefaultServiceProvider implements ServiceProvider { - - private final Map, AIService> services; - - public DefaultServiceProvider(Map, AIService> services) { - this.services = services; - } - - @Nullable - @Override - public T getService(Class clazz) { - T service = (T) services.get(clazz); - - if (service == null) { - service = (T) services - .entrySet() - .stream() - .filter(it -> clazz.isAssignableFrom(it.getKey())) - .map(it -> it.getValue()) - .findFirst() - .orElseGet(() -> null); - } - - if (service == null && - (clazz.equals(TextGenerationService.class) || - clazz.equals(ChatCompletionService.class))) { - LOGGER.warn( - "Requested a non-existent service type of {}. Consider requesting a TextAIService instead.", - clazz.getName()); - } - - return service; - } - - } public static class Builder implements Kernel.Builder { - private AIService defaultAIService; - private final Map, AIService> services = new HashMap<>(); + private final Map, AIService> services = new HashMap<>(); private final List plugins = new ArrayList<>(); + private Function, AIService>, AIServiceSelector> serviceSelectorProvider; @Override - public Builder withDefaultAIService(Class clazz, T aiService) { - this.defaultAIService = aiService; + public Builder withAIService(Class clazz, T aiService) { services.put(clazz, aiService); return this; } @@ -173,10 +134,25 @@ public class DefaultKernel implements Kernel { return this; } + @Override + public Kernel.Builder withServiceSelector( + Function, AIService>, AIServiceSelector> serviceSelector) { + this.serviceSelectorProvider = serviceSelector; + return this; + } + @Override public Kernel build() { + + AIServiceSelector serviceSelector; + if (serviceSelectorProvider == null) { + serviceSelector = new OrderedAIServiceSelector(services); + } else { + serviceSelector = serviceSelectorProvider.apply(services); + } + return new DefaultKernel( - new DefaultServiceProvider(services), + serviceSelector, new KernelPluginCollection(plugins)); } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Kernel.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Kernel.java index 3c0ead415c..1665819dca 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Kernel.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Kernel.java @@ -10,7 +10,11 @@ import com.microsoft.semantickernel.orchestration.contextvariables.KernelArgumen import com.microsoft.semantickernel.plugin.KernelPlugin; import com.microsoft.semantickernel.plugin.KernelPluginCollection; import com.microsoft.semantickernel.semanticfunctions.PromptTemplate; +import com.microsoft.semantickernel.services.AIServiceSelector; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.function.Function; import javax.annotation.Nullable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -57,7 +61,7 @@ public interface Kernel extends Buildable { List getFunctions(); - ServiceProvider getServiceSelector(); + AIServiceSelector getServiceSelector(); KernelPluginCollection getPlugins(); @@ -65,12 +69,16 @@ public interface Kernel extends Buildable { return new DefaultKernel.Builder(); } + interface Builder extends SemanticKernelBuilder { - Builder withDefaultAIService(Class clazz, T aiService); + Builder withAIService(Class clazz, T aiService); Builder withPromptTemplate(PromptTemplate promptTemplate); Builder withPlugin(KernelPlugin searchPlugin); + + Builder withServiceSelector( + Function, AIService>, AIServiceSelector> serviceSelector); } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ServiceProvider.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ServiceProvider.java deleted file mode 100644 index 4fe39e1a29..0000000000 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ServiceProvider.java +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -package com.microsoft.semantickernel; - -import javax.annotation.Nullable; - -/** - * Provides access to services. - */ -public interface ServiceProvider { - - // TODO: IServiceProvider is a .NET API for DI. Do we need to support this? - // And how does NamedServiceProvider interface fit in? - - /** - * Gets the service of the specified type and name, or null if not found. - * - * @param The specific type of the service - * @param clazz The type of the service. - * @return The service instance, or null if not found. - */ - @Nullable - public U getService(Class clazz); -} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/chatcompletion/ChatCompletionService.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/chatcompletion/ChatCompletionService.java index bec2b7e7c2..4158db008e 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/chatcompletion/ChatCompletionService.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/chatcompletion/ChatCompletionService.java @@ -10,6 +10,7 @@ import com.microsoft.semantickernel.builders.SemanticKernelBuilder; import com.microsoft.semantickernel.builders.ServiceLoadUtil; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import java.util.List; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings.Builder; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -49,6 +50,7 @@ public interface ChatCompletionService extends Buildable, TextAIService { protected OpenAIAsyncClient client; protected String modelId; + protected String serviceId; /** @@ -74,6 +76,10 @@ public interface ChatCompletionService extends Buildable, TextAIService { return this; } + public Builder withServiceId(String serviceId) { + this.serviceId = serviceId; + return this; + } } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/DefaultKernelFunction.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/DefaultKernelFunction.java index 9a48fb47ed..774b8cc801 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/DefaultKernelFunction.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/DefaultKernelFunction.java @@ -60,4 +60,10 @@ public abstract class DefaultKernelFunction implements KernelFunction { public String toManualString(boolean includeOutputs) { return null; } + + @Override + @Nullable + public Map getExecutionSettings() { + return executionSettings; + } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunction.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunction.java index 93b842286c..8e0f8328c7 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunction.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunction.java @@ -101,6 +101,9 @@ public interface KernelFunction extends Buildable { @Nullable KernelArguments arguments, ContextVariableType variableType); + @Nullable + Map getExecutionSettings(); + interface FromPromptBuilder { FromPromptBuilder withName(String name); 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 49ad955ad0..0b7312bdf4 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 @@ -1,20 +1,18 @@ package com.microsoft.semantickernel.orchestration; -import java.util.List; - import com.fasterxml.jackson.annotation.JsonProperty; -import com.microsoft.semantickernel.DefaultKernel; -import com.microsoft.semantickernel.Kernel; -import com.microsoft.semantickernel.Kernel.Builder; +import java.util.List; +import java.util.Map; public class PromptExecutionSettings { + public static final String DEFAULT_SERVICE_ID = "default"; public static final int DEFAULT_MAX_TOKENS = 256; public static final double DEFAULT_TEMPERATURE = 1.0; public static final double DEFAULT_TOP_P = 1.0; 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_BEST_OF = 1; /// /// Service identifier. @@ -38,6 +36,11 @@ public class PromptExecutionSettings { private final String user; private final List stopSequences; + /// + /// Modify the likelihood of specified tokens appearing in the completion. + /// s + public Map tokenSelectionBiases; + public PromptExecutionSettings( @JsonProperty("service_id") String serviceId, @JsonProperty("model_id") String modelId, @@ -48,7 +51,8 @@ public class PromptExecutionSettings { @JsonProperty("max_tokens") int maxTokens, @JsonProperty("best_of") int bestOf, @JsonProperty("user") String user, - @JsonProperty(value = "stop_sequences") List stopSequences) { + @JsonProperty(value = "stop_sequences") List stopSequences, + @JsonProperty(value = "token_selection_biases") Map tokenSelectionBiases) { this.serviceId = serviceId; this.modelId = modelId; this.temperature = temperature; @@ -59,6 +63,7 @@ public class PromptExecutionSettings { this.bestOf = bestOf; this.user = user; this.stopSequences = stopSequences; + this.tokenSelectionBiases = tokenSelectionBiases; } public String getServiceId() { @@ -102,6 +107,10 @@ public class PromptExecutionSettings { return stopSequences; } + public Map getTokenSelectionBiases() { + return tokenSelectionBiases; + } + public static Builder builder() { return new Builder(); @@ -119,6 +128,7 @@ public class PromptExecutionSettings { private int bestOf = Integer.MIN_VALUE; private String user; private List stopSequences; + public Map tokenSelectionBiases; public Builder withServiceId(String serviceId) { this.serviceId = serviceId; @@ -170,18 +180,24 @@ public class PromptExecutionSettings { return this; } + public Builder withTokenSelectionBiases(Map tokenSelectionBiases) { + this.tokenSelectionBiases = tokenSelectionBiases; + return this; + } + public PromptExecutionSettings build() { return new PromptExecutionSettings( serviceId, - modelId, + modelId, Double.isNaN(temperature) ? DEFAULT_TEMPERATURE : temperature, Double.isNaN(topP) ? DEFAULT_TOP_P : topP, Double.isNaN(presencePenalty) ? DEFAULT_PRESENCE_PENALTY : presencePenalty, Double.isNaN(frequencyPenalty) ? DEFAULT_FREQUENCY_PENALTY : frequencyPenalty, maxTokens == Integer.MIN_VALUE ? DEFAULT_MAX_TOKENS : maxTokens, bestOf == Integer.MIN_VALUE ? DEFAULT_BEST_OF : bestOf, - user, - stopSequences + user, + stopSequences, + tokenSelectionBiases ); } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/DefaultKernelArguments.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/DefaultKernelArguments.java index ac110fa7c9..550c069feb 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/DefaultKernelArguments.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/DefaultKernelArguments.java @@ -3,6 +3,7 @@ package com.microsoft.semantickernel.orchestration.contextvariables; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import java.util.Collection; +import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; @@ -15,6 +16,7 @@ import reactor.util.annotation.NonNull; public class DefaultKernelArguments implements KernelArguments, WritableKernelArguments { private final CaseInsensitiveMap> variables; + private final Map executionSettings; /// /// In the simplest scenario, the data is an input string, stored here. @@ -25,17 +27,45 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr /// Constructor for context variables. /// /// Optional value for the main variable of the context. - DefaultKernelArguments(@NonNull ContextVariable content) { + public DefaultKernelArguments(@NonNull ContextVariable content) { this.variables = new CaseInsensitiveMap<>(); this.variables.put(MAIN_KEY, content); + this.executionSettings = new HashMap<>(); } - public DefaultKernelArguments(Map> variables) { - this.variables = new CaseInsensitiveMap<>(variables); + public DefaultKernelArguments( + Map> variables, + Map executionSettings) { + if (variables == null) { + this.variables = new CaseInsensitiveMap<>(); + } else { + this.variables = new CaseInsensitiveMap<>(variables); + } + + if (executionSettings == null) { + this.executionSettings = new HashMap<>(); + } else { + this.executionSettings = new HashMap<>(executionSettings); + } } + public DefaultKernelArguments( + Map> variables, + @Nullable + PromptExecutionSettings executionSettings) { + this.variables = new CaseInsensitiveMap<>(variables); + this.executionSettings = new HashMap<>(); + this.executionSettings.put(PromptExecutionSettings.DEFAULT_SERVICE_ID, executionSettings); + } + + public DefaultKernelArguments() { this.variables = new CaseInsensitiveMap<>(); + this.executionSettings = new HashMap<>(); + } + + public DefaultKernelArguments(KernelArguments arguments) { + this(arguments, new HashMap<>()); } @Override @@ -93,7 +123,7 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr @Override public WritableKernelArguments writableClone() { - return new DefaultKernelArguments(variables); + return new DefaultKernelArguments(variables, executionSettings); } @Override @@ -134,8 +164,8 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr @Nullable @Override - public PromptExecutionSettings getExecutionSettings() { - return null; + public Map getExecutionSettings() { + return executionSettings; } @Override @@ -209,21 +239,17 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr return variables.entrySet(); } - public static class WritableBuilder implements WritableKernelArguments.Builder { - - @Override - public WritableKernelArguments build(Map> map) { - return new DefaultKernelArguments(map); - } - } public static class Builder implements KernelArguments.Builder { private final DefaultKernelArguments variables; + private final Map executionSettings; public Builder() { variables = new DefaultKernelArguments(); this.variables.put(MAIN_KEY, ContextVariable.of("")); + + executionSettings = new HashMap<>(); } @Override @@ -257,7 +283,7 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr @Override public KernelArguments build() { - return new DefaultKernelArguments(variables); + return new DefaultKernelArguments(variables, executionSettings); } } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/KernelArguments.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/KernelArguments.java index 410920c4e3..1294a2e1f9 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/KernelArguments.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/KernelArguments.java @@ -61,7 +61,7 @@ public interface KernelArguments extends Buildable, Map ContextVariable get(String key, Class clazz); @Nullable - PromptExecutionSettings getExecutionSettings(); + Map getExecutionSettings(); static Builder builder() { return new DefaultKernelArguments.Builder(); diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelFunctionFactory.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelFunctionFactory.java index b80314c547..17d19dc706 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelFunctionFactory.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelFunctionFactory.java @@ -123,7 +123,8 @@ public class KernelFunctionFactory { PromptTemplateConfig promptConfig) { return new KernelFunctionFromPrompt( promptTemplate, - promptConfig); + promptConfig, + null); } private static Map createSettingsDictionary( diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromPrompt.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromPrompt.java index 17e9b554bb..a3a2437435 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromPrompt.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromPrompt.java @@ -22,6 +22,7 @@ import com.microsoft.semantickernel.orchestration.StreamingContent; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType; import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; +import com.microsoft.semantickernel.services.AIServiceSelection; import com.microsoft.semantickernel.textcompletion.TextGenerationService; import reactor.core.publisher.Flux; @@ -33,7 +34,11 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { private final PromptTemplate template; - public KernelFunctionFromPrompt(PromptTemplate template, PromptTemplateConfig promptConfig) { + public KernelFunctionFromPrompt( + PromptTemplate template, + PromptTemplateConfig promptConfig, + @Nullable + Map executionSettings) { super( new KernelFunctionMetadata( promptConfig.getName(), @@ -41,11 +46,44 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { promptConfig.getKernelParametersMetadata(), promptConfig.getKernelReturnParameterMetadata() ), - promptConfig.getExecutionSettings() + executionSettings != null ? executionSettings : promptConfig.getExecutionSettings() ); this.template = template; } + public static KernelFunction create( + PromptTemplateConfig promptConfig + ) { + return create( + promptConfig, + null + ); + } + + public static KernelFunction create( + PromptTemplateConfig promptConfig, + @Nullable + PromptTemplateFactory promptTemplateFactory + ) { + if (promptTemplateFactory == null) { + promptTemplateFactory = new KernelPromptTemplateFactory(); + } + + return create(promptTemplateFactory.tryCreate(promptConfig), promptConfig); + } + + + public static KernelFunction create( + PromptTemplate promptTemplate, + PromptTemplateConfig promptConfig) { + return new KernelFunctionFromPrompt( + promptTemplate, + promptConfig, + null + ); + } + + @Override public Flux> invokeStreamingAsync( Kernel kernel, @@ -57,9 +95,15 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { .flatMapMany(prompt -> { LOGGER.info("RENDERED PROMPT: \n{}", prompt); - AIService client = kernel + AIServiceSelection aiServiceSelection = kernel .getServiceSelector() - .getService(TextAIService.class); + .trySelectAIService( + TextAIService.class, + this, + arguments + ); + + AIService client = aiServiceSelection.getService(); if (client == null) { throw new IllegalStateException( @@ -68,12 +112,7 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { Flux> result; - PromptExecutionSettings executionSettings; - if (arguments != null) { - executionSettings = arguments.getExecutionSettings(); - } else { - executionSettings = null; - } + PromptExecutionSettings executionSettings = aiServiceSelection.getSettings(); if (client instanceof ChatCompletionService) { result = ((ChatCompletionService) client) @@ -234,7 +273,10 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { @Override public FromPromptBuilder withExecutionSettings( Map executionSettings) { - this.executionSettings = executionSettings; + if (this.executionSettings == null) { + this.executionSettings = new HashMap<>(); + } + this.executionSettings.putAll(executionSettings); return this; } @@ -244,7 +286,12 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { if (this.executionSettings == null) { this.executionSettings = new HashMap<>(); } + this.executionSettings.put("default", executionSettings); + + if (executionSettings.getServiceId() != null) { + this.executionSettings.put(executionSettings.getServiceId(), executionSettings); + } return this; } @@ -301,14 +348,15 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { temp = new KernelPromptTemplateFactory().tryCreate(config); } - return new KernelFunctionFromPrompt(temp, config); + return new KernelFunctionFromPrompt(temp, config, executionSettings); } public KernelFunction build(PromptTemplateConfig functionModel) { return new KernelFunctionFromPrompt( promptTemplate, - functionModel + functionModel, + executionSettings ); } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/OutputVariable.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/OutputVariable.java index ab54aedbab..2269e7f37c 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/OutputVariable.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/OutputVariable.java @@ -29,7 +29,8 @@ public class OutputVariable { try { return this.getClass().getClassLoader().loadClass(type); } catch (ClassNotFoundException e) { - throw new SKException("Could not find return type " + type); + throw new SKException("Requested output type could not be found: " + type + + ", note this needs to be a fully qualified class name, i.e 'java.lang.String'."); } } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfig.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfig.java index 5e8fdcaf9d..de042e6248 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfig.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplateConfig.java @@ -45,7 +45,7 @@ public class PromptTemplateConfig { SEMANTIC_KERNEL_TEMPLATE_FORMAT, "", Collections.emptyList(), - new OutputVariable("out", "string"), + new OutputVariable("out", String.class.getName()), Collections.emptyMap() ); } @@ -122,12 +122,12 @@ public class PromptTemplateConfig { .collect(Collectors.toList()); } - public KernelReturnParameterMetadata getKernelReturnParameterMetadata() { + public KernelReturnParameterMetadata getKernelReturnParameterMetadata() { if (outputVariable == null) { - return new KernelReturnParameterMetadata("", String.class); + return new KernelReturnParameterMetadata<>("", String.class); } - return new KernelReturnParameterMetadata( + return new KernelReturnParameterMetadata<>( outputVariable.getDescription(), outputVariable.getType() ); diff --git a/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/AIServiceSelection.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelection.java similarity index 93% rename from java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/AIServiceSelection.java rename to java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelection.java index 697be1b669..54c5fd9196 100644 --- a/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/AIServiceSelection.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelection.java @@ -2,7 +2,7 @@ package com.microsoft.semantickernel.services; import com.microsoft.semantickernel.AIService; -import com.microsoft.semantickernel.PromptExecutionSettings; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; /** * The result of an AI service selection. diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelector.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelector.java new file mode 100644 index 0000000000..18e7d56e3b --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelector.java @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.services; + +import com.microsoft.semantickernel.AIService; +import com.microsoft.semantickernel.orchestration.KernelFunction; +import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; +import javax.annotation.Nullable; + +/** + * Represents a selector which will return an {@link AIServiceSelection} containing instances of + * {@link AIService} and {@link com.microsoft.semantickernel.orchestration.PromptExecutionSettings} + * from the specified provider based on the model settings. + */ +public interface AIServiceSelector { + + /** + * Resolves an {@link AIService} and associated and + * {@link com.microsoft.semantickernel.orchestration.PromptExecutionSettings} from the specified + * {@link com.microsoft.semantickernel.Kernel} based on the associated {@link KernelFunction} + * and {@link KernelArguments}. + * + * @param serviceType The type of service to select. This must be the same type with which the + * service was registered in the {@link AIServiceSelection} + * @param function + * @param arguments + * @return + */ + @Nullable + AIServiceSelection trySelectAIService( + Class serviceType, + KernelFunction function, + KernelArguments arguments + ); +} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/BaseAIServiceSelector.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/BaseAIServiceSelector.java new file mode 100644 index 0000000000..4e1472ff84 --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/BaseAIServiceSelector.java @@ -0,0 +1,36 @@ +package com.microsoft.semantickernel.services; + +import com.microsoft.semantickernel.AIService; +import com.microsoft.semantickernel.orchestration.KernelFunction; +import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; +import java.util.Map; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class BaseAIServiceSelector implements AIServiceSelector { + + private static final Logger LOGGER = LoggerFactory.getLogger(BaseAIServiceSelector.class); + + protected final Map, AIService> services; + + protected BaseAIServiceSelector(Map, AIService> services) { + this.services = services; + } + + @Override + @Nullable + public AIServiceSelection trySelectAIService( + Class serviceType, + KernelFunction function, + KernelArguments arguments) { + return trySelectAIService(serviceType, function, arguments, services); + } + + @Nullable + public abstract AIServiceSelection trySelectAIService( + Class serviceType, + KernelFunction function, + KernelArguments arguments, + Map, AIService> services); +} diff --git a/java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/NamedServiceProvider.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/NamedServiceProvider.java similarity index 100% rename from java/semantickernel-api/src/main/ignore/java/com/microsoft/semantickernel/services/NamedServiceProvider.java rename to java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/NamedServiceProvider.java diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.java new file mode 100644 index 0000000000..8731516bfc --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/OrderedAIServiceSelector.java @@ -0,0 +1,204 @@ +package com.microsoft.semantickernel.services; + +import com.microsoft.semantickernel.AIService; +import com.microsoft.semantickernel.Verify; +import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.orchestration.KernelFunction; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; +import com.microsoft.semantickernel.textcompletion.TextGenerationService; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OrderedAIServiceSelector extends BaseAIServiceSelector { + + private static final Logger LOGGER = LoggerFactory.getLogger(OrderedAIServiceSelector.class); + + public OrderedAIServiceSelector() { + super(new HashMap<>()); + } + + public OrderedAIServiceSelector(Map, AIService> services) { + super(services); + } + + @Nullable + public AIServiceSelection trySelectAIService( + Class serviceType, + KernelFunction function, + KernelArguments arguments, + Map, AIService> services) { + + // Allow the execution settings from the kernel arguments to take precedence + Map executionSettings = null; + + executionSettings = settingsFromArguments(arguments, executionSettings); + executionSettings = settingsFromFunctionSettings(function, executionSettings); + + if (executionSettings == null || executionSettings.isEmpty()) { + AIService service = getAnyService(serviceType); + if (service != null) { + return new AIServiceSelection(service, null); + } + } else { + AIServiceSelection selection = executionSettings + .entrySet() + .stream() + .map(keyValue -> { + + PromptExecutionSettings settings = keyValue.getValue(); + String serviceId = keyValue.getKey(); + + if (!Verify.isNullOrEmpty(serviceId)) { + AIService service = getService(serviceId); + if (service != null) { + return new AIServiceSelection(service, settings); + } + } + + return null; + }) + .filter(Objects::nonNull) + .findFirst() + .orElseGet(() -> null); + + if (selection != null) { + return selection; + } + + selection = executionSettings + .entrySet() + .stream() + .map(keyValue -> { + PromptExecutionSettings settings = keyValue.getValue(); + + if (!Verify.isNullOrEmpty(settings.getModelId())) { + AIService service = getServiceByModelId(settings.getModelId()); + if (service != null) { + return new AIServiceSelection(service, settings); + } + } + + return null; + }) + .filter(Objects::nonNull) + .findFirst() + .orElseGet(() -> null); + + if (selection != null) { + return selection; + } + } + + AIService defaultService = getService(PromptExecutionSettings.DEFAULT_SERVICE_ID); + + if (defaultService != null && serviceType.isAssignableFrom(defaultService.getClass())) { + return new AIServiceSelection(defaultService, null); + } + + AIService service = getAnyService(serviceType); + PromptExecutionSettings settings; + + if (executionSettings.containsKey(PromptExecutionSettings.DEFAULT_SERVICE_ID)) { + executionSettings.get(PromptExecutionSettings.DEFAULT_SERVICE_ID); + settings = executionSettings.get(PromptExecutionSettings.DEFAULT_SERVICE_ID); + } else { + settings = executionSettings.values().stream().findFirst().orElseGet(null); + } + + if (service != null) { + return new AIServiceSelection(service, settings); + } + + LOGGER.warn("No service found meeting requirements"); + return null; + } + + @Nullable + private static Map settingsFromFunctionSettings( + KernelFunction function, Map executionSettings) { + if (executionSettings == null || executionSettings.isEmpty()) { + executionSettings = function.getExecutionSettings(); + } + return executionSettings; + } + + @Nullable + private static Map settingsFromArguments( + KernelArguments arguments, Map executionSettings) { + if (arguments != null) { + executionSettings = arguments.getExecutionSettings(); + } + return executionSettings; + } + + private AIService getServiceByModelId(String modelId) { + return services + .values() + .stream() + .filter(s -> modelId.equalsIgnoreCase(s.getModelId())) + .findFirst() + .orElseGet(() -> null); + } + + + @Nullable + public AIService getService(String serviceId) { + return services + .values() + .stream() + .filter(s -> serviceId.equalsIgnoreCase(s.getServiceId())) + .findFirst() + .orElseGet(() -> null); + } + + @Nullable + public T getService(Class clazz) { + T service = (T) services.get(clazz); + + if (service == null) { + service = (T) services + .entrySet() + .stream() + .filter(it -> clazz.isAssignableFrom(it.getKey())) + .map(it -> it.getValue()) + .findFirst() + .orElseGet(() -> null); + } + + if (service == null && + (clazz.equals(TextGenerationService.class) || + clazz.equals(ChatCompletionService.class))) { + LOGGER.warn( + "Requested a non-existent service type of {}. Consider requesting a TextAIService instead.", + clazz.getName()); + } + + return service; + } + + AIService getAnyService(Class serviceType) { + List services = getServices(serviceType); + if (services.isEmpty()) { + return null; + } + return services.get(0); + } + + private List getServices(Class serviceType) { + return + services + .entrySet() + .stream() + .filter(it -> serviceType.isAssignableFrom(it.getKey())) + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + } + +} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/blocks/CodeBlock.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/blocks/CodeBlock.java index 51f4acaab8..e02ebb9416 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/blocks/CodeBlock.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/blocks/CodeBlock.java @@ -11,6 +11,7 @@ import com.microsoft.semantickernel.orchestration.contextvariables.DefaultKernel import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; import com.microsoft.semantickernel.templateengine.semantickernel.TemplateException; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Optional; diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/TextGenerationService.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/TextGenerationService.java index 8b9c020ea6..53874448e8 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/TextGenerationService.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/TextGenerationService.java @@ -60,6 +60,7 @@ public interface TextGenerationService extends Buildable, TextAIService { protected String modelId; protected OpenAIAsyncClient client; + protected String serviceId; public Builder withModelId(String modelId) { this.modelId = modelId; @@ -71,6 +72,12 @@ public interface TextGenerationService extends Buildable, TextAIService { return this; } + public Builder withServiceId(String serviceId) { + this.serviceId = serviceId; + return this; + } + public abstract TextGenerationService build(); + } }