From bc9d4cba11d4cf86ee5e19020b29d18c8c88593c Mon Sep 17 00:00:00 2001 From: John Oliver <1615532+johnoliver@users.noreply.github.com> Date: Tue, 27 Feb 2024 20:34:12 +0000 Subject: [PATCH] Java: Implement a number of fixes from API review (#5201) - Make constructors on classes that have builders protected - Remove the addPlugin function from the kernel to make plugin set immutable, in favour of adding a copy method for creating a clone of a kernel that can have plugins added, i.e: ``` kernel = kernel.copy() .withPlugin(timePlugin) .build() ``` - Add sync invocation methods in addition to async - Add warning if `FunctionInvocation` is used in an unusual manner that might indicate a bug --- .../chatcompletion/OpenAIChatCompletion.java | 2 +- .../OpenAITextGenerationService.java | 2 +- .../Example06_TemplateLanguage.java | 9 +- .../Example07_BingAndGooglePlugins.java | 2 +- ...mple10_DescribeAllPluginsAndFunctions.java | 13 +- .../Example13_ConversationSummaryPlugin.java | 1 - .../Example30_ChatWithPrompts.java | 6 +- .../Example42_KernelBuilder.java | 10 -- ...eMethodFunctionsWithMultipleArguments.java | 6 +- .../Example59_OpenAIFunctionCalling.java | 3 +- .../Example60_AdvancedMethodFunctions.java | 4 +- .../Example69_MutableKernelPlugin.java | 6 +- .../com/microsoft/semantickernel/Kernel.java | 113 ++++++++++++++---- .../hooks/FunctionInvokedEvent.java | 3 +- .../hooks/FunctionInvokingEvent.java | 3 +- .../hooks/PromptRenderedEvent.java | 3 +- .../hooks/PromptRenderingEvent.java | 3 +- .../tokenizer/blocks/CodeBlock.java | 6 +- .../orchestration/FunctionInvocation.java | 36 +++++- .../orchestration/InvocationContext.java | 6 +- .../semanticfunctions/KernelFunction.java | 45 +++++++ .../KernelFunctionArguments.java | 11 +- .../KernelFunctionFromMethod.java | 24 +++- .../KernelFunctionFromPrompt.java | 2 +- .../PromptTemplateConfig.java | 4 +- .../handlebars/HandlebarsPromptTemplate.java | 2 +- 26 files changed, 237 insertions(+), 88 deletions(-) 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 2f06787e9d..a7ff2b5dab 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 @@ -57,7 +57,7 @@ public class OpenAIChatCompletion implements ChatCompletionService { private final String serviceId; private final String modelId; - public OpenAIChatCompletion( + protected OpenAIChatCompletion( OpenAIAsyncClient client, String modelId, @Nullable String 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 2f09faf0a7..7490761c95 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 @@ -40,7 +40,7 @@ public class OpenAITextGenerationService implements TextGenerationService { /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom for HTTP requests. /// The to use for logging. If null, no logging will be performed. - public OpenAITextGenerationService( + protected OpenAITextGenerationService( OpenAIAsyncClient client, String modelId, @Nullable String serviceId) { 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 4d5aa71aae..0761779086 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 @@ -58,7 +58,9 @@ public class Example06_TemplateLanguage { KernelPlugin time = KernelPluginFactory.createFromObject( new Time(), "time"); - kernel.addPlugin(time); + kernel = kernel.toBuilder() + .withPlugin(time) + .build(); // Prompt Function invoking time.Date and time.Time method functions String functionDefinition = """ @@ -74,7 +76,10 @@ public class Example06_TemplateLanguage { System.out.println("--- Rendered Prompt"); var promptTemplate = new KernelPromptTemplateFactory() - .tryCreate(new PromptTemplateConfig(functionDefinition)); + .tryCreate(PromptTemplateConfig + .builder() + .withTemplate(functionDefinition) + .build()); var renderedPrompt = promptTemplate.renderAsync(kernel, null, null).block(); System.out.println(renderedPrompt); diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example07_BingAndGooglePlugins.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example07_BingAndGooglePlugins.java index abe402b2b0..367ae99c28 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example07_BingAndGooglePlugins.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example07_BingAndGooglePlugins.java @@ -178,7 +178,7 @@ public class Example07_BingAndGooglePlugins { // If the answer contains commands, execute them using the prompt renderer. if (result.contains("bing.search")) { PromptTemplate promptTemplate = new KernelPromptTemplateFactory() - .tryCreate(new PromptTemplateConfig(result)); + .tryCreate(PromptTemplateConfig.builder().withTemplate(result).build()); System.out.println("---- Fetching information from Bing..."); var information = promptTemplate.renderAsync(kernel, null, null).block(); 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 62741698d2..2781b2401b 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 @@ -9,6 +9,7 @@ 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.Kernel.Builder; import com.microsoft.semantickernel.aiservices.openai.textcompletion.OpenAITextGenerationService; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import com.microsoft.semantickernel.plugin.KernelPluginFactory; @@ -57,20 +58,19 @@ public class Example10_DescribeAllPluginsAndFunctions { .withModelId(MODEL_ID) .build(); - Kernel kernel = Kernel.builder() - .withAIService(TextGenerationService.class, textGenerationService) - .build(); + Builder kernelBuilder = Kernel.builder() + .withAIService(TextGenerationService.class, textGenerationService); - kernel.addPlugin( + kernelBuilder.withPlugin( KernelPluginFactory.createFromObject( new StaticTextPlugin(), "StaticTextPlugin")); // Import another native plugin - kernel.addPlugin( + kernelBuilder.withPlugin( KernelPluginFactory.createFromObject( new TextPlugin(), "AnotherTextPlugin")); - kernel.addPlugin( + kernelBuilder.withPlugin( KernelPluginFactory .importPluginFromDirectory( Path.of(PLUGIN_DIR, "java/samples/sample-code/src/main/resources/Plugins"), @@ -102,6 +102,7 @@ public class Example10_DescribeAllPluginsAndFunctions { System.out.println("**********************************************"); System.out.println(); + Kernel kernel = kernelBuilder.build(); kernel.getPlugins() .forEach(plugin -> { System.out.println("Plugin: " + plugin.getName()); 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 78291333b1..0e4f6dc209 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 @@ -166,7 +166,6 @@ public class Example13_ConversationSummaryPlugin { new Builder() .withInput(chatTranscript) .build()); - System.out.println("Generated Action Items:"); System.out.println(summary.block().getResult()); } diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example30_ChatWithPrompts.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example30_ChatWithPrompts.java index 5830afce7a..e16c015310 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example30_ChatWithPrompts.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example30_ChatWithPrompts.java @@ -77,7 +77,7 @@ public class Example30_ChatWithPrompts { // We could also use a variable, this is just to show that the prompt can invoke functions. KernelPlugin timePlugin = KernelPluginFactory.createFromObject( new TimePlugin(), "time"); - kernel.addPlugin(timePlugin); + kernel = kernel.toBuilder().withPlugin(timePlugin).build(); // Adding required arguments referenced by the prompt templates. @@ -92,7 +92,7 @@ public class Example30_ChatWithPrompts { // Render the system prompt. This string is used to configure the chat. // This contains the context, ie a piece of a wikipedia page selected by the user. String systemMessage = PromptTemplateFactory - .build(new PromptTemplateConfig(systemPromptTemplate)) + .build(PromptTemplateConfig.builder().withTemplate(systemPromptTemplate).build()) .renderAsync(kernel, arguments, null) .block(); @@ -101,7 +101,7 @@ public class Example30_ChatWithPrompts { // Render the user prompt. This string is the query sent by the user // This contains the user request, ie "extract locations as a bullet point list" String userMessage = PromptTemplateFactory - .build(new PromptTemplateConfig(userPromptTemplate)) + .build(PromptTemplateConfig.builder().withTemplate(userPromptTemplate).build()) .renderAsync(kernel, arguments, null) .block(); 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 ddbe5deb5b..cc35a564d2 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 @@ -9,10 +9,8 @@ import com.azure.core.http.HttpClient; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.plugin.KernelPluginFactory; import com.microsoft.semantickernel.samples.plugins.ConversationSummaryPlugin; -import com.microsoft.semantickernel.services.OrderedAIServiceSelector; import com.microsoft.semantickernel.services.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.services.textcompletion.TextGenerationService; -import java.util.ArrayList; public class Example42_KernelBuilder { @@ -62,13 +60,5 @@ public class Example42_KernelBuilder { "ConversationSummaryPlugin")) .build(); - ///////////////////////////////////////////////////////// - // KernelBuilder provides a convenient API for creating Kernel instances. However, it is just a - // wrapper, ultimately constructing a Kernel - // using the public constructor that's available for anyone to use directly if desired. - Kernel kernel = new Kernel( - new OrderedAIServiceSelector(), - new ArrayList<>(), - null); } } 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 bd3658c8b2..f1854ebf60 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 @@ -59,7 +59,9 @@ public class Example56_TemplateMethodFunctionsWithMultipleArguments { // Load native plugin into the kernel function collection, sharing its functions with prompt templates // Functions loaded here are available as "text.*" - kernel.addPlugin(KernelPluginFactory.createFromObject(new TextPlugin(), "text")); + kernel = kernel.toBuilder() + .withPlugin(KernelPluginFactory.createFromObject(new TextPlugin(), "text")) + .build(); // Prompt Function invoking text.Concat method function with named arguments input and input2 where input is a string and input2 is set to a variable from context called word2. String functionDefinition = "Write a haiku about the following: {{text.Concat input='Harry' input2=$word2}}"; @@ -68,7 +70,7 @@ public class Example56_TemplateMethodFunctionsWithMultipleArguments { System.out.println("--- Rendered Prompt"); var promptTemplateFactory = new KernelPromptTemplateFactory(); var promptTemplate = promptTemplateFactory.tryCreate( - new PromptTemplateConfig(functionDefinition)); + PromptTemplateConfig.builder().withTemplate(functionDefinition).build()); var renderedPrompt = promptTemplate.renderAsync(kernel, arguments, null).block(); System.out.println(renderedPrompt); diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example59_OpenAIFunctionCalling.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example59_OpenAIFunctionCalling.java index 76bf55927c..1d90dbf61d 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example59_OpenAIFunctionCalling.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example59_OpenAIFunctionCalling.java @@ -54,10 +54,9 @@ public class Example59_OpenAIFunctionCalling { var kernel = Kernel.builder() .withAIService(ChatCompletionService.class, chat) + .withPlugin(plugin) .build(); - kernel.addPlugin(plugin); - var function = KernelFunctionFromPrompt.builder() .withTemplate( "What is the probable current color of the sky in Thrapston?") diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example60_AdvancedMethodFunctions.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example60_AdvancedMethodFunctions.java index 4e7eaac079..c7324bedb7 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example60_AdvancedMethodFunctions.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example60_AdvancedMethodFunctions.java @@ -22,7 +22,9 @@ public class Example60_AdvancedMethodFunctions { new FunctionsChainingPlugin(), FunctionsChainingPlugin.PluginName); - kernel.addPlugin(functions); + kernel = kernel.toBuilder() + .withPlugin(functions) + .build(); ContextVariableTypeConverter type = new ContextVariableTypeConverter<>( MyCustomType.class, diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example69_MutableKernelPlugin.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example69_MutableKernelPlugin.java index 4200a1688e..23bd377877 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example69_MutableKernelPlugin.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example69_MutableKernelPlugin.java @@ -23,9 +23,9 @@ public class Example69_MutableKernelPlugin { .withFunctionName("dateFunction") .build()); - Kernel kernel = Kernel.builder().build(); - - kernel.addPlugin(plugin); + Kernel kernel = Kernel.builder() + .withPlugin(plugin) + .build(); var result = kernel.invokeAsync(kernel.getFunction("Plugin", "dateFunction")) .block(); 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 f2a7477cef..4c4889974a 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 @@ -6,6 +6,7 @@ import com.microsoft.semantickernel.builders.SemanticKernelBuilder; import com.microsoft.semantickernel.contextvariables.ContextVariableType; import com.microsoft.semantickernel.hooks.KernelHooks; import com.microsoft.semantickernel.orchestration.FunctionInvocation; +import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.InvocationContext; import com.microsoft.semantickernel.plugin.KernelPlugin; import com.microsoft.semantickernel.semanticfunctions.KernelFunction; @@ -36,21 +37,38 @@ public class Kernel implements Buildable { private final KernelPluginCollection plugins; private final KernelHooks globalKernelHooks; + // Only present so we can create a builder in copy method + private final AIServiceCollection services; + private final Function serviceSelectorProvider; + /** * Initializes a new instance of {@code Kernel}. * - * @param serviceSelector The {@code AIServiceSelector} used to query for services available - * through the kernel. - * @param plugins The collection of plugins available through the kernel. If - * {@code null}, an empty collection will be used. - * @param globalKernelHooks The global hooks to be used throughout the kernel. If {@code null}, - * an empty collection will be used. + * @param services The collection of services available through the kernel. + * @param serviceSelectorProvider The service selector provider for the kernel. If {@code null}, + * an ordered service selector will be used. + * @param plugins The collection of plugins available through the kernel. If + * {@code null}, an empty collection will be used. + * @param globalKernelHooks The global hooks to be used throughout the kernel. If + * {@code null}, an empty collection will be used. */ public Kernel( - AIServiceSelector serviceSelector, + AIServiceCollection services, + Function serviceSelectorProvider, @Nullable List plugins, @Nullable KernelHooks globalKernelHooks) { + + this.services = services; + this.serviceSelectorProvider = serviceSelectorProvider; + + AIServiceSelector serviceSelector; + if (serviceSelectorProvider == null) { + serviceSelector = new OrderedAIServiceSelector(services); + } else { + serviceSelector = serviceSelectorProvider.apply(services); + } this.serviceSelector = serviceSelector; + if (plugins != null) { this.plugins = new KernelPluginCollection(plugins); } else { @@ -65,10 +83,34 @@ public class Kernel implements Buildable { * * @return The fluent builder for creating a new instance of {@code Kernel}. */ - public static Kernel.Builder builder() { + public static Builder builder() { return new Kernel.Builder(); } + /** + * Creates a Builder that can create a copy of the {@code Kernel}. Use this method if you wish + * to modify the state of the kernel such as adding new plugins or services. + * + * @param kernel The kernel to copy. + * @return A Builder that can create a copy of the instance of {@code Kernel}. + */ + public static Builder from(Kernel kernel) { + return new Builder( + kernel.services, + kernel.serviceSelectorProvider, + kernel.plugins); + } + + /** + * Creates a Builder that can create a copy of the current instance of {@code Kernel}. Use this + * method if you wish to modify the state of the kernel such as adding new plugins or services. + * + * @return A Builder that can create a copy of the current instance of {@code Kernel}. + */ + public Builder toBuilder() { + return new Builder(services, serviceSelectorProvider, plugins); + } + /** * Invokes a {@code KernelFunction} function by name. * @@ -88,6 +130,24 @@ public class Kernel implements Buildable { return invokeAsync(function); } + /** + * Invokes a {@code KernelFunction} function by name. + * + * @param The return type of the function. + * @param pluginName The name of the plugin containing the function. + * @param functionName The name of the function to invoke. + * @return The result of the function invocation. + * @throws IllegalArgumentException if the plugin or function is not found. + * @see KernelFunction#invokeAsync(Kernel) + * @see KernelPluginCollection#getFunction(String, String) + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + public FunctionResult invoke( + String pluginName, + String functionName) { + return this.invokeAsync(pluginName, functionName).block(); + } + /** * Invokes a {@code KernelFunction}. * @@ -101,15 +161,15 @@ public class Kernel implements Buildable { } /** - * This method is used to add plugins to the kernel after it has been created. If a plugin with - * the same name already exists, it will be replaced. + * Invokes a {@code KernelFunction}. * - * @param plugin The plugin to add. - * @return {@code this} kernel with the plugin added. + * @param The return type of the function. + * @param function The function to invoke. + * @return The result of the function invocation. + * @see KernelFunction#invokeAsync(Kernel) */ - public Kernel addPlugin(KernelPlugin plugin) { - plugins.add(plugin); - return this; + public FunctionResult invoke(KernelFunction function) { + return invokeAsync(function).block(); } /** @@ -216,6 +276,18 @@ public class Kernel implements Buildable { @Nullable private Function serviceSelectorProvider; + public Builder() { + } + + private Builder( + AIServiceCollection services, + @Nullable Function serviceSelectorProvider, + KernelPluginCollection plugins) { + this.services.putAll(services); + this.serviceSelectorProvider = serviceSelectorProvider; + this.plugins.addAll(plugins.getPlugins()); + } + /** * Adds a service to the kernel. * @@ -259,16 +331,9 @@ public class Kernel implements Buildable { */ @Override public Kernel build() { - - AIServiceSelector serviceSelector; - if (serviceSelectorProvider == null) { - serviceSelector = new OrderedAIServiceSelector(services); - } else { - serviceSelector = serviceSelectorProvider.apply(services); - } - return new Kernel( - serviceSelector, + services, + serviceSelectorProvider, plugins, null); } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokedEvent.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokedEvent.java index 495a374253..8f2d7eed81 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokedEvent.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokedEvent.java @@ -31,8 +31,7 @@ public class FunctionInvokedEvent implements KernelHookEvent { @Nullable KernelFunctionArguments arguments, FunctionResult result) { this.function = function; - this.arguments = arguments != null ? new KernelFunctionArguments(arguments) - : new KernelFunctionArguments(); + this.arguments = KernelFunctionArguments.builder().withVariables(arguments).build(); this.result = result; } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokingEvent.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokingEvent.java index 31f2b63092..ab765cabe4 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokingEvent.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokingEvent.java @@ -27,8 +27,7 @@ public class FunctionInvokingEvent implements KernelHookEvent { public FunctionInvokingEvent(KernelFunction function, @Nullable KernelFunctionArguments arguments) { this.function = function; - this.arguments = arguments != null ? new KernelFunctionArguments(arguments) - : new KernelFunctionArguments(); + this.arguments = KernelFunctionArguments.builder().withVariables(arguments).build(); } /** diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderedEvent.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderedEvent.java index 2221ac78d2..f3f349725d 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderedEvent.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderedEvent.java @@ -27,8 +27,7 @@ public class PromptRenderedEvent implements KernelHookEvent { @Nullable KernelFunctionArguments arguments, String prompt) { this.function = function; - this.arguments = arguments != null ? new KernelFunctionArguments(arguments) - : new KernelFunctionArguments(); + this.arguments = KernelFunctionArguments.builder().withVariables(arguments).build(); this.prompt = prompt; } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderingEvent.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderingEvent.java index 8bec8fed9d..7bba17a38e 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderingEvent.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderingEvent.java @@ -23,8 +23,7 @@ public class PromptRenderingEvent implements KernelHookEvent { public PromptRenderingEvent(KernelFunction function, @Nullable KernelFunctionArguments arguments) { this.function = function; - this.arguments = arguments != null ? new KernelFunctionArguments(arguments) - : new KernelFunctionArguments(); + this.arguments = KernelFunctionArguments.builder().withVariables(arguments).build(); } /** diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeBlock.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeBlock.java index ac81c2edfa..a3cf44ee51 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeBlock.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/implementation/templateengine/tokenizer/blocks/CodeBlock.java @@ -95,7 +95,7 @@ public final class CodeBlock extends Block implements CodeRendering { } if (context == null) { - context = new InvocationContext(); + context = InvocationContext.builder().build(); } // this.Log.LogTrace("Rendering code: `{0}`", this.Content); @@ -138,9 +138,7 @@ public final class CodeBlock extends Block implements CodeRendering { if (this.tokens.size() > 1) { //Cloning the original arguments to avoid side effects - arguments added to the original arguments collection as a result of rendering template variables. arguments = this.enrichFunctionArguments(kernel, fBlock, - arguments == null - ? new KernelFunctionArguments() - : new KernelFunctionArguments(arguments), + KernelFunctionArguments.builder().withVariables(arguments).build(), context); } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionInvocation.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionInvocation.java index 350e775a25..39c5cd5336 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionInvocation.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionInvocation.java @@ -46,6 +46,8 @@ public class FunctionInvocation extends Mono> { @Nullable protected UnmodifiableToolCallBehavior toolCallBehavior; + private boolean isSubscribed = false; + /** * Create a new function invocation. * @@ -101,7 +103,7 @@ public class FunctionInvocation extends Mono> { function .invokeAsync( kernel, - new KernelFunctionArguments(arguments), + KernelFunctionArguments.builder().withVariables(arguments).build(), null, new InvocationContext(context)) .handle(convertToType(variableType)) @@ -162,11 +164,8 @@ public class FunctionInvocation extends Mono> { */ public FunctionInvocation withArguments( @Nullable KernelFunctionArguments arguments) { - if (arguments == null) { - this.arguments = null; - } else { - this.arguments = new KernelFunctionArguments(arguments); - } + logSubscribeWarning(); + this.arguments = KernelFunctionArguments.builder().withVariables(arguments).build(); return this; } @@ -178,6 +177,7 @@ public class FunctionInvocation extends Mono> { * @return A new {@code FunctionInvocation} for fluent chaining. */ public FunctionInvocation withResultType(ContextVariableType resultType) { + logSubscribeWarning(); return new FunctionInvocation<>( kernel, function, @@ -198,6 +198,7 @@ public class FunctionInvocation extends Mono> { if (hook == null) { return this; } + logSubscribeWarning(); KernelHooks clone = new KernelHooks(this.hooks); clone.addHook(hook); this.hooks = unmodifiableClone(clone); @@ -215,6 +216,7 @@ public class FunctionInvocation extends Mono> { if (hooks == null) { return this; } + logSubscribeWarning(); this.hooks = unmodifiableClone(new KernelHooks(this.hooks).addHooks(hooks)); return this; } @@ -228,6 +230,7 @@ public class FunctionInvocation extends Mono> { */ public FunctionInvocation withPromptExecutionSettings( @Nullable PromptExecutionSettings promptExecutionSettings) { + logSubscribeWarning(); this.promptExecutionSettings = promptExecutionSettings; return this; } @@ -239,6 +242,7 @@ public class FunctionInvocation extends Mono> { * @return this {@code FunctionInvocation} for fluent chaining. */ public FunctionInvocation withToolCallBehavior(@Nullable ToolCallBehavior toolCallBehavior) { + logSubscribeWarning(); this.toolCallBehavior = unmodifiableClone(toolCallBehavior); return this; } @@ -250,6 +254,7 @@ public class FunctionInvocation extends Mono> { * @return this {@code FunctionInvocation} for fluent chaining. */ public FunctionInvocation withTypeConverter(ContextVariableTypeConverter typeConverter) { + logSubscribeWarning(); contextVariableTypes.putConverter(typeConverter); return this; } @@ -261,6 +266,7 @@ public class FunctionInvocation extends Mono> { * @return this {@code FunctionInvocation} for fluent chaining. */ public FunctionInvocation withTypes(ContextVariableTypes contextVariableTypes) { + logSubscribeWarning(); this.contextVariableTypes.putConverters(contextVariableTypes); return this; } @@ -277,6 +283,7 @@ public class FunctionInvocation extends Mono> { if (invocationContext == null) { return this; } + logSubscribeWarning(); withTypes(invocationContext.getContextVariableTypes()); withToolCallBehavior(invocationContext.getToolCallBehavior()); withPromptExecutionSettings(invocationContext.getPromptExecutionSettings()); @@ -284,6 +291,14 @@ public class FunctionInvocation extends Mono> { return this; } + private void logSubscribeWarning() { + if (isSubscribed) { + LOGGER.warn( + "Attempting to modify function {}.{} after it has already been subscribed to. This is not necessarily an error but may be an unusual pattern and indicate a potential bug.", + function.getPluginName(), function.getName()); + } + } + /** * This method hanldes the reactive stream when the KernelFunciton is invoked. * @@ -291,6 +306,15 @@ public class FunctionInvocation extends Mono> { */ @Override public void subscribe(CoreSubscriber> coreSubscriber) { + + if (isSubscribed) { + LOGGER.warn( + "Function {}.{} has already been subscribed to. This is not necessarily an error but may be an unusual pattern.", + function.getPluginName(), function.getName()); + } + + isSubscribed = true; + performSubscribe( coreSubscriber, kernel, diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationContext.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationContext.java index 4e94be080e..806953b515 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationContext.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InvocationContext.java @@ -34,7 +34,7 @@ public class InvocationContext implements Buildable { * @param toolCallBehavior The behavior for tool calls. * @param contextVariableTypes The types of context variables. */ - public InvocationContext( + protected InvocationContext( @Nullable KernelHooks hooks, @Nullable PromptExecutionSettings promptExecutionSettings, @Nullable ToolCallBehavior toolCallBehavior, @@ -52,7 +52,7 @@ public class InvocationContext implements Buildable { /** * Create a new instance of InvocationContext. */ - public InvocationContext() { + protected InvocationContext() { this.hooks = null; this.promptExecutionSettings = null; this.toolCallBehavior = null; @@ -64,7 +64,7 @@ public class InvocationContext implements Buildable { * * @param context The context to copy. */ - public InvocationContext(@Nullable InvocationContext context) { + protected InvocationContext(@Nullable InvocationContext context) { if (context == null) { this.hooks = null; this.promptExecutionSettings = null; diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunction.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunction.java index f3fcf4b29b..284c0d978f 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunction.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunction.java @@ -191,6 +191,40 @@ public abstract class KernelFunction implements Buildable { @Nullable ContextVariableType variableType, @Nullable InvocationContext invocationContext); + /** + * Invokes this KernelFunction. + *

+ * If the {@code variableType} parameter is provided, the {@link ContextVariableType} is used to + * convert the result of the function to the appropriate {@link FunctionResult}. The + * {@code variableType} is not required for converting well-known types such as {@link String} + * and {@link Integer} which have pre-defined {@code ContextVariableType}s. + *

+ * The {@link InvocationContext} allows for customization of the behavior of function, including + * the ability to pass in {@link KernelHooks} {@link PromptExecutionSettings}, and + * {@link ToolCallBehavior}. + *

+ * The difference between calling the {@code KernelFunction.invokeAsync} method directly and + * calling the {@code Kernel.invokeAsync} method is that the latter adds the global KernelHooks + * (if any) to the {@link InvocationContext}. Calling {@code KernelFunction.invokeAsync} + * directly does not add the global hooks. + * + * @param kernel The Kernel containing services, plugins, and other state for use + * throughout the operation. + * @param arguments The arguments to pass to the function's invocation + * @param variableType The type of the {@link ContextVariable} returned in the + * {@link FunctionResult} + * @param invocationContext The arguments to pass to the function's invocation + * @return The result of the function's execution. + * @see FunctionResult#getResultVariable() + */ + public FunctionResult invoke( + Kernel kernel, + @Nullable KernelFunctionArguments arguments, + @Nullable ContextVariableType variableType, + @Nullable InvocationContext invocationContext) { + return invokeAsync(kernel, arguments, variableType, invocationContext).block(); + } + /** * Invokes this KernelFunction. * @@ -202,6 +236,17 @@ public abstract class KernelFunction implements Buildable { return new FunctionInvocation<>(kernel, this); } + /** + * Invokes this KernelFunction. + * + * @param kernel The Kernel containing services, plugins, and other state for use throughout the + * operation. + * @return The result of the function's execution. + */ + public FunctionResult invoke(Kernel kernel) { + return invokeAsync(kernel).block(); + } + /** * Builder for creating a {@link KernelFunction} from a prompt. * diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionArguments.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionArguments.java index 99d26764df..2cb9baf1eb 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionArguments.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionArguments.java @@ -32,7 +32,7 @@ public class KernelFunctionArguments implements Buildable, Map> variables) { if (variables == null) { this.variables = new CaseInsensitiveMap<>(); @@ -46,7 +46,7 @@ public class KernelFunctionArguments implements Buildable, Map content) { + protected KernelFunctionArguments(@NonNull ContextVariable content) { this.variables = new CaseInsensitiveMap<>(); this.variables.put(MAIN_KEY, content); } @@ -54,7 +54,7 @@ public class KernelFunctionArguments implements Buildable, Map(); } @@ -257,7 +257,10 @@ public class KernelFunctionArguments implements Buildable, Map> map) { + public Builder withVariables(@Nullable Map> map) { + if (map == null) { + return this; + } variables.putAll(map); return this; } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethod.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethod.java index d7b356d3ab..906fa33ba1 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethod.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelFunctionFromMethod.java @@ -563,7 +563,7 @@ public class KernelFunctionFromMethod extends KernelFunction implements Bu @Nullable KernelFunctionArguments arguments, @Nullable ContextVariableType variableType, @Nullable InvocationContext invocationContext) { - return function.invoke(kernel, this, arguments, variableType, invocationContext); + return function.invokeAsync(kernel, this, arguments, variableType, invocationContext); } /** @@ -581,12 +581,32 @@ public class KernelFunctionFromMethod extends KernelFunction implements Bu * @param invocationContext the invocation context * @return a {@link Mono} that emits the result of the function invocation */ - Mono> invoke( + Mono> invokeAsync( Kernel kernel, KernelFunction function, @Nullable KernelFunctionArguments arguments, @Nullable ContextVariableType variableType, @Nullable InvocationContext invocationContext); + + /** + * Invokes the function. + * + * @param kernel the kernel to invoke the function on + * @param function the function to invoke + * @param arguments the arguments to the function + * @param variableType the variable type of the function + * @param invocationContext the invocation context + * @return a {@link Mono} that emits the result of the function invocation + */ + default FunctionResult invoke( + Kernel kernel, + KernelFunction function, + @Nullable KernelFunctionArguments arguments, + @Nullable ContextVariableType variableType, + @Nullable InvocationContext invocationContext) { + return invokeAsync(kernel, function, arguments, variableType, + invocationContext).block(); + } } /** 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 aeec1ca6eb..fe2c5cb649 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 @@ -48,7 +48,7 @@ public class KernelFunctionFromPrompt extends KernelFunction { * @param promptConfig the configuration for the prompt * @param executionSettings the execution settings to use when invoking the function */ - public KernelFunctionFromPrompt( + protected KernelFunctionFromPrompt( PromptTemplate template, PromptTemplateConfig promptConfig, @Nullable Map executionSettings) { 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 fcde2da7aa..b74f1984f8 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 @@ -56,7 +56,7 @@ public class PromptTemplateConfig implements Buildable { * * @param template Template string */ - public PromptTemplateConfig(String template) { + protected PromptTemplateConfig(String template) { this( CURRENT_SCHEMA, DEFAULT_CONFIG_NAME, @@ -121,7 +121,7 @@ public class PromptTemplateConfig implements Buildable { * @param outputVariable Output variable * @param executionSettings Execution settings */ - public PromptTemplateConfig( + protected PromptTemplateConfig( @Nullable String name, @Nullable String template, @Nullable String templateFormat, diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplate.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplate.java index 48951c5ae7..4ac3558ebd 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplate.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/handlebars/HandlebarsPromptTemplate.java @@ -68,7 +68,7 @@ public class HandlebarsPromptTemplate implements PromptTemplate { template); if (arguments == null) { - arguments = new KernelFunctionArguments(); + arguments = KernelFunctionArguments.builder().build(); } return handler.render(arguments); }