From 447eed55c410d8eeba227a3099178ee3585274e6 Mon Sep 17 00:00:00 2001 From: John Oliver <1615532+johnoliver@users.noreply.github.com> Date: Mon, 29 Jan 2024 14:48:01 +0000 Subject: [PATCH] Java: Proposal for adding hooks (#4730) ### 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: --- java/aiservices/openai/pom.xml | 10 +- .../chatcompletion/OpenAIChatCompletion.java | 103 +++-- .../chatcompletion/XMLPromptParser.java | 5 +- .../OpenAITextGenerationService.java | 2 +- .../connectors/web/bing/BingConnector.java | 6 +- .../semantickernel/util/LocaleParser.java | 11 +- java/pom.xml | 14 +- java/samples/pom.xml | 78 ++-- java/samples/sample-code/pom.xml | 23 - .../syntaxexamples/Example17_ChatGPT.java | 2 + .../Example30_ChatWithPrompts.java | 3 +- .../Example42_KernelBuilder.java | 3 +- .../syntaxexamples/Example57_KernelHooks.java | 417 ++++++++++++++++++ .../Example62_CustomAIServiceSelector.java | 4 +- .../01-SimpleChat/src/main/java/Main.java | 1 - .../src/test/java/SimpleChatTest.java | 4 +- .../02-PersonaChat/src/main/java/Main.java | 1 - .../src/test/java/PersonaChatTest.java | 1 - .../03-SimpleRag/src/main/java/Main.java | 1 - .../src/test/java/SimpleRagTest.java | 1 - java/semantickernel-api/pom.xml | 4 + .../microsoft/semantickernel/AIService.java | 2 + .../com/microsoft/semantickernel/Kernel.java | 87 +++- .../ServiceNotFoundException.java | 10 + .../chatcompletion/ChatCompletionService.java | 15 + .../chatcompletion/ChatMessageContent.java | 10 +- .../hooks/FunctionInvokedEvent.java | 31 ++ .../hooks/FunctionInvokingEvent.java | 23 + .../semantickernel/hooks/KernelHook.java | 79 ++++ .../semantickernel/hooks/KernelHookEvent.java | 5 + .../semantickernel/hooks/KernelHooks.java | 93 ++++ .../hooks/PreChatCompletionEvent.java | 16 + .../hooks/PromptRenderedEvent.java | 30 ++ .../hooks/PromptRenderingEvent.java | 23 + .../orchestration/DefaultKernelFunction.java | 7 +- .../orchestration/FunctionResultMetadata.java | 2 +- .../orchestration/KernelFunction.java | 9 +- .../orchestration/KernelFunctionMetadata.java | 6 +- .../orchestration/KernelFunctionYaml.java | 6 +- .../orchestration/MethodDetails.java | 8 +- .../PromptExecutionSettings.java | 127 +++--- .../orchestration/TextRequestContent.java | 34 -- .../orchestration/ToolCallBehavior.java | 16 +- .../contextvariables/CaseInsensitiveMap.java | 2 + .../ContextVariableTypeConverter.java | 5 +- .../DefaultKernelArguments.java | 8 +- .../contextvariables/KernelArguments.java | 2 +- .../plugin/KernelParameterMetadata.java | 8 +- .../semantickernel/plugin/KernelPlugin.java | 13 +- .../plugin/KernelPluginCollection.java | 18 +- .../plugin/KernelPluginFactory.java | 10 +- .../semantickernel/plugin/Plugin.java | 2 +- .../AggregatorPromptTemplateFactory.java | 3 +- .../KernelFunctionFromMethod.java | 95 ++-- .../KernelFunctionFromPrompt.java | 74 +++- .../KernelPromptTemplateFactory.java | 7 +- .../PromptTemplateConfig.java | 125 ++---- .../services/AIServiceSelection.java | 19 +- .../services/AIServiceSelector.java | 4 +- .../services/BaseAIServiceSelector.java | 12 +- .../services/OrderedAIServiceSelector.java | 7 +- .../handlebars/HandlebarsPromptTemplate.java | 206 +++++---- .../semantickernel/DefaultPromptTemplate.java | 12 +- .../semantickernel/blocks/CodeBlock.java | 5 +- java/spotbugs-exclude.xml | 115 ----- 65 files changed, 1401 insertions(+), 684 deletions(-) create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example57_KernelHooks.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ServiceNotFoundException.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokedEvent.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokingEvent.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHook.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHookEvent.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHooks.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreChatCompletionEvent.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderedEvent.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderingEvent.java delete mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/TextRequestContent.java diff --git a/java/aiservices/openai/pom.xml b/java/aiservices/openai/pom.xml index d968dfeffe..d59b83e063 100644 --- a/java/aiservices/openai/pom.xml +++ b/java/aiservices/openai/pom.xml @@ -1,6 +1,7 @@ - + 4.0.0 @@ -33,6 +34,13 @@ jackson-core compile + + + + javax.xml.stream + stax-api + 1.0-2 + 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 ed09110a54..7db347c3fd 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 @@ -26,6 +26,9 @@ import com.microsoft.semantickernel.chatcompletion.ChatHistory; import com.microsoft.semantickernel.chatcompletion.ChatMessageContent; import com.microsoft.semantickernel.chatcompletion.StreamingChatMessageContent; import com.microsoft.semantickernel.exceptions.AIException; +import com.microsoft.semantickernel.hooks.KernelHooks; +import com.microsoft.semantickernel.hooks.PreChatCompletionEvent; +import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; import com.microsoft.semantickernel.orchestration.KernelFunction; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; @@ -33,7 +36,6 @@ import com.microsoft.semantickernel.orchestration.ToolCallBehavior; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes; -import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; import java.nio.charset.Charset; import java.util.ArrayList; @@ -69,7 +71,7 @@ public class OpenAIChatCompletion implements ChatCompletionService { @Override public Map> getAttributes() { - return attributes; + return Collections.unmodifiableMap(attributes); } @Override @@ -78,56 +80,101 @@ public class OpenAIChatCompletion implements ChatCompletionService { } @Override - public Mono> getChatMessageContentsAsync(ChatHistory chatHistory, - @Nullable PromptExecutionSettings promptExecutionSettings, @Nullable Kernel kernel) { + public Mono> getChatMessageContentsAsync( + ChatHistory chatHistory, + PromptExecutionSettings promptExecutionSettings, + Kernel kernel, + KernelHooks kernelHooks) { List chatRequestMessages = getChatRequestMessages(chatHistory); List functions = Collections.emptyList(); - return internalChatMessageContentsAsync(chatRequestMessages, functions, - promptExecutionSettings); + if (kernelHooks == null) { + kernelHooks = new KernelHooks(); + } + + return internalChatMessageContentsAsync( + chatRequestMessages, + functions, + promptExecutionSettings, + kernelHooks); + } + + @Override + public Mono> getChatMessageContentsAsync( + ChatHistory chatHistory, + @Nullable PromptExecutionSettings promptExecutionSettings, + @Nullable Kernel kernel) { + return getChatMessageContentsAsync(chatHistory, promptExecutionSettings, kernel, null); + } + + + @Override + public Mono> getChatMessageContentsAsync( + String prompt, + PromptExecutionSettings promptExecutionSettings, + Kernel kernel, + @Nullable + KernelHooks kernelHooks) { + ParsedPrompt parsedPrompt = XMLPromptParser.parse(prompt); + + if (kernelHooks == null) { + kernelHooks = new KernelHooks(); + } + + return internalChatMessageContentsAsync( + parsedPrompt.getChatRequestMessages(), + parsedPrompt.getFunctions(), + promptExecutionSettings, + kernelHooks); } @Override public Mono> getChatMessageContentsAsync(String prompt, PromptExecutionSettings promptExecutionSettings, Kernel kernel) { - ParsedPrompt parsedPrompt = XMLPromptParser.parse(prompt); - return internalChatMessageContentsAsync( - parsedPrompt.getChatRequestMessages(), - parsedPrompt.getFunctions(), - promptExecutionSettings); + return getChatMessageContentsAsync(prompt, promptExecutionSettings, kernel, null); } + private Mono> internalChatMessageContentsAsync( List chatRequestMessages, List functions, - PromptExecutionSettings promptExecutionSettings) { + PromptExecutionSettings promptExecutionSettings, + KernelHooks kernelHooks) { + ChatCompletionsOptions options = getCompletionsOptions(this, chatRequestMessages, functions, promptExecutionSettings); Mono> results = - internalChatMessageContentsAsync(options); + internalChatMessageContentsAsync(kernelHooks, options); - return results.flatMap(list -> { - boolean makeSecondCall = false; - for (ChatMessageContent messageContent : list) { - if (messageContent.getAuthorRole() == AuthorRole.TOOL) { - makeSecondCall = true; - String content = messageContent.getContent(); - String id = messageContent.getModelId(); - ChatRequestToolMessage toolMessage = new ChatRequestToolMessage(content, id); - chatRequestMessages.add(toolMessage); + return results + .flatMap(list -> { + boolean makeSecondCall = false; + for (ChatMessageContent messageContent : list) { + if (messageContent.getAuthorRole() == AuthorRole.TOOL) { + makeSecondCall = true; + String content = messageContent.getContent(); + String id = messageContent.getModelId(); + ChatRequestToolMessage toolMessage = new ChatRequestToolMessage(content, + id); + chatRequestMessages.add(toolMessage); + } } - } - if (makeSecondCall) { - return internalChatMessageContentsAsync(options); - } - return Mono.just(list); - }); + if (makeSecondCall) { + return internalChatMessageContentsAsync(kernelHooks, options); + } + return Mono.just(list); + }); } private Mono> internalChatMessageContentsAsync( + KernelHooks kernelHooks, ChatCompletionsOptions options) { + options = kernelHooks + .executeHooks(new PreChatCompletionEvent(options)) + .getOptions(); + return client .getChatCompletions(getModelId(), options) .filter(choices -> choices.getChoices() != null && !choices.getChoices().isEmpty()) diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/XMLPromptParser.java b/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/XMLPromptParser.java index 9a1e2a5cbb..08d45667fe 100644 --- a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/XMLPromptParser.java +++ b/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/chatcompletion/XMLPromptParser.java @@ -11,6 +11,7 @@ import com.microsoft.semantickernel.exceptions.SKException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -61,7 +62,7 @@ class XMLPromptParser { // If one handler does not recognize the element, it should pass it to the next handler. // In this way, we can avoid parsing the whole prompt twice and easily extend the parsing logic. List messages = new ArrayList<>(); - try (InputStream is = new ByteArrayInputStream(prompt.getBytes())) { + try (InputStream is = new ByteArrayInputStream(prompt.getBytes(StandardCharsets.UTF_8))) { XMLInputFactory factory = XMLInputFactory.newFactory(); XMLEventReader reader = factory.createXMLEventReader(is); while (reader.hasNext()) { @@ -87,7 +88,7 @@ class XMLPromptParser { // ... // List functionDefinitions = new ArrayList<>(); - try (InputStream is = new ByteArrayInputStream(prompt.getBytes())) { + try (InputStream is = new ByteArrayInputStream(prompt.getBytes(StandardCharsets.UTF_8))) { XMLInputFactory factory = XMLInputFactory.newFactory(); XMLEventReader reader = factory.createXMLEventReader(is); FunctionDefinition functionDefinition = null; diff --git a/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java b/java/aiservices/openai/src/main/java/com/microsoft/semantickernel/aiservices/openai/textcompletion/OpenAITextGenerationService.java index 13f6d1d6e2..b1fea06b4e 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 @@ -51,7 +51,7 @@ public class OpenAITextGenerationService implements TextGenerationService { @Override public Map> getAttributes() { - return attributes; + return Collections.unmodifiableMap(attributes); } @Override diff --git a/java/connectors/semantickernel-connectors-web-bing/src/main/java/com/microsoft/semantickernel/connectors/web/bing/BingConnector.java b/java/connectors/semantickernel-connectors-web-bing/src/main/java/com/microsoft/semantickernel/connectors/web/bing/BingConnector.java index 48c21115d2..83ce198ec6 100644 --- a/java/connectors/semantickernel-connectors-web-bing/src/main/java/com/microsoft/semantickernel/connectors/web/bing/BingConnector.java +++ b/java/connectors/semantickernel-connectors-web-bing/src/main/java/com/microsoft/semantickernel/connectors/web/bing/BingConnector.java @@ -21,7 +21,7 @@ import com.microsoft.semantickernel.exceptions.SKException; import reactor.core.publisher.Mono; -public class BingConnector implements WebSearchEngineConnector { +public class BingConnector implements WebSearchEngineConnector { private static String BING_SEARCH_URL; static { @@ -91,7 +91,7 @@ public class BingConnector implements WebSearchEngineConnector { @Override public String getSnippet() { return snippet; - } + } } private final String apiKey; // TODO: secure this @@ -107,7 +107,7 @@ public class BingConnector implements WebSearchEngineConnector { } @Override - public Mono> searchAsync(String query, int count, int offset) { + public Mono> searchAsync(String query, int count, int offset) { if (count <= 0 || 50 <= count) throw new IllegalArgumentException("count must be between 1 and 50"); if (offset < 0) throw new IllegalArgumentException("offset must be greater than or equal to 0"); diff --git a/java/plugins/semantickernel-plugin-core/src/main/java/com/microsoft/semantickernel/util/LocaleParser.java b/java/plugins/semantickernel-plugin-core/src/main/java/com/microsoft/semantickernel/util/LocaleParser.java index ed09d340fb..539ff74e87 100644 --- a/java/plugins/semantickernel-plugin-core/src/main/java/com/microsoft/semantickernel/util/LocaleParser.java +++ b/java/plugins/semantickernel-plugin-core/src/main/java/com/microsoft/semantickernel/util/LocaleParser.java @@ -1,9 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.util; -import java.util.Locale; - import com.microsoft.semantickernel.plugin.annotations.KernelFunctionParameter; +import java.util.Locale; /** @@ -14,7 +13,8 @@ import com.microsoft.semantickernel.plugin.annotations.KernelFunctionParameter; */ public final class LocaleParser { - private LocaleParser() {} + private LocaleParser() { + } /** * Parses the locale. If the input is "en-US", it will return a Locale based on the language @@ -26,12 +26,13 @@ public final class LocaleParser { * @param locale the locale in String text. * @return */ + @SuppressWarnings("StringSplitter") public static final Locale parseLocale(String locale) { Locale parsedLocale = null; if (locale == null - || "".equals(locale.trim()) - || KernelFunctionParameter.NO_DEFAULT_VALUE.equals(locale)) { + || "".equals(locale.trim()) + || KernelFunctionParameter.NO_DEFAULT_VALUE.equals(locale)) { return Locale.getDefault(); } else if (locale.indexOf("-") > -1) { parsedLocale = Locale.forLanguageTag(locale); diff --git a/java/pom.xml b/java/pom.xml index 96af19fab4..22f0206db7 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -15,7 +15,7 @@ 1.0.0-beta.6 10.12.2 - 0.10.10 + 0.10.21 2.19.1 1.17.0 1.6.0 @@ -60,7 +60,7 @@ UTF-8 microsoft/semantic-kernel git@github.com:${project.github.repository}.git - 4.7.3 + 4.8.3 @@ -155,7 +155,13 @@ com.microsoft.semantic-kernel semantickernel-connectors-web-bing ${project.version} - + + + + com.github.spotbugs + spotbugs-annotations + ${spotbugs.version} + @@ -685,6 +691,7 @@ + diff --git a/java/samples/pom.xml b/java/samples/pom.xml index 257f57151f..192cf6abfb 100644 --- a/java/samples/pom.xml +++ b/java/samples/pom.xml @@ -87,40 +87,26 @@ + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.23 + + + android + test + + check + + + + + true + + - - bug-check @@ -140,28 +126,16 @@ ${maven.compiler.release} ${project.build.sourceEncoding} true - - -XDcompilePolicy=simple + + - - -Xplugin:ErrorProne - -XepOpt:NullAway:AnnotatedPackages=com.microsoft.semantickernel - -Xep:AlmostJavadoc:OFF -Xep:MissingSummary:OFF - -Xep:UnusedVariable:OFF -Xep:EmptyBlockTag:OFF - - - - - com.google.errorprone - error_prone_core - ${google.errorprone.core.version} - - - com.uber.nullaway - nullaway - ${com.uber.nullaway.version} - - + + + com.github.spotbugs + spotbugs-maven-plugin + ${maven.spotbugs-plugin.version} + + true diff --git a/java/samples/sample-code/pom.xml b/java/samples/sample-code/pom.xml index 4449113969..fb8ad75d3b 100644 --- a/java/samples/sample-code/pom.xml +++ b/java/samples/sample-code/pom.xml @@ -39,17 +39,6 @@ com.microsoft.semantic-kernel semantickernel-connectors-web-bing - org.apache.logging.log4j @@ -66,18 +55,6 @@ log4j-slf4j2-impl 2.20.0 - com.fasterxml.jackson.core jackson-databind diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example17_ChatGPT.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example17_ChatGPT.java index 8f9efb6fa1..03ea02f188 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example17_ChatGPT.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example17_ChatGPT.java @@ -19,6 +19,7 @@ public class Example17_ChatGPT { .getOrDefault("MODEL_ID", "gpt-35-turbo"); public static void main(String[] args) { + System.out.println("======== Open AI - ChatGPT ========"); OpenAIAsyncClient client; @@ -67,6 +68,7 @@ public class Example17_ChatGPT { } private static void GPTReply(ChatCompletionService chatGPT, ChatHistory chatHistory) { + var reply = chatGPT.getChatMessageContentsAsync(chatHistory, null, null) .block(); 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 2b45cf3759..f6b6ac33cd 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 @@ -5,6 +5,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.ServiceNotFoundException; import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.chatcompletion.ChatHistory; import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; @@ -30,7 +31,7 @@ public class Example30_ChatWithPrompts { private static final String MODEL_ID = System.getenv() .getOrDefault("MODEL_ID", "gpt-35-turbo"); - public static void main(String[] args) throws FileNotFoundException { + public static void main(String[] args) throws FileNotFoundException, ServiceNotFoundException { OpenAIAsyncClient client; if (AZURE_CLIENT_KEY != null) { 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 f985c05d69..9abcc342e6 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 @@ -67,7 +67,8 @@ public class Example42_KernelBuilder { // using the public constructor that's available for anyone to use directly if desired. Kernel kernel = new Kernel( new OrderedAIServiceSelector(), - new KernelPluginCollection() + new KernelPluginCollection(), + null ); } } diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example57_KernelHooks.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example57_KernelHooks.java new file mode 100644 index 0000000000..c7637634d1 --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/samples/syntaxexamples/Example57_KernelHooks.java @@ -0,0 +1,417 @@ +package com.microsoft.semantickernel.samples.syntaxexamples; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.ai.openai.models.ChatCompletionsOptions; +import com.azure.ai.openai.models.ChatRequestMessage; +import com.azure.ai.openai.models.ChatRequestSystemMessage; +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.chatcompletion.OpenAIChatCompletion; +import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.hooks.FunctionInvokedEvent; +import com.microsoft.semantickernel.hooks.KernelHooks; +import com.microsoft.semantickernel.hooks.KernelHook.FunctionInvokedHook; +import com.microsoft.semantickernel.hooks.KernelHook.FunctionInvokingHook; +import com.microsoft.semantickernel.hooks.KernelHook.PreChatCompletionHook; +import com.microsoft.semantickernel.hooks.KernelHook.PromptRenderedHook; +import com.microsoft.semantickernel.hooks.KernelHook.PromptRenderingHook; +import com.microsoft.semantickernel.hooks.PreChatCompletionEvent; +import com.microsoft.semantickernel.hooks.PromptRenderedEvent; +import com.microsoft.semantickernel.orchestration.FunctionResult; +import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable; +import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromPrompt; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +public class Example57_KernelHooks { + + private static final String CLIENT_KEY = System.getenv("CLIENT_KEY"); + private static final String AZURE_CLIENT_KEY = System.getenv("AZURE_CLIENT_KEY"); + + // Only required if AZURE_CLIENT_KEY is set + private static final String CLIENT_ENDPOINT = System.getenv("CLIENT_ENDPOINT"); + private static final String MODEL_ID = System.getenv() + .getOrDefault("MODEL_ID", "gpt-35-turbo"); + + public static void main(String[] args) { + + OpenAIAsyncClient client; + + if (AZURE_CLIENT_KEY != null) { + client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(AZURE_CLIENT_KEY)) + .endpoint(CLIENT_ENDPOINT) + .buildAsyncClient(); + + } else { + client = new OpenAIClientBuilder() + .credential(new KeyCredential(CLIENT_KEY)) + .buildAsyncClient(); + } + + ChatCompletionService openAIChatCompletion = OpenAIChatCompletion.builder() + .withModelId(MODEL_ID) + .withOpenAIAsyncClient(client) + .build(); + + Builder kernelBuilder = Kernel.builder() + .withAIService(ChatCompletionService.class, openAIChatCompletion); + + getUsageAsync(kernelBuilder.build()); + getRenderedPromptAsync(kernelBuilder.build()); + changingResultAsync(kernelBuilder.build()); + beforeInvokeCancellationAsync(kernelBuilder.build()); + afterInvokeCancellationAsync(kernelBuilder.build()); + chatCompletionHook(kernelBuilder.build()); + invocationHook(kernelBuilder.build()); + } + + + /// + /// Demonstrate using kernel invocation-hooks to monitor usage: + /// + /// + /// + private static void getUsageAsync(Kernel kernel) { + System.out.println("\n======== Get Usage Data ========\n"); + + // Initialize prompt + String functionPrompt = "Write a random paragraph about: {{$input}}."; + + var excuseFunction = KernelFunctionFromPrompt.builder() + .withTemplate(functionPrompt) + .withName("Excuse") + .withDefaultExecutionSettings(PromptExecutionSettings + .builder() + .withMaxTokens(100) + .withTemperature(0.4) + .withTopP(1) + .build()) + .build(); + + FunctionInvokingHook preHook = event -> { + System.out.println( + event.getFunction().getName() + " : Pre Execution Handler - Triggered"); + return event; + }; + + FunctionInvokingHook removedPreExecutionHandler = event -> { + System.out.println( + event.getFunction().getName() + " : Pre Execution Handler - Should not trigger"); + return event; + }; + + FunctionInvokedHook postExecutionHandler = event -> { + System.out.println( + event.getFunction().getName() + " : Post Execution Handler - Usage: " + event + .getResult() + .getMetadata() + .getUsage() + .getTotalTokens()); + return event; + }; + + kernel.getHookService().addHook(preHook); + + // Demonstrate pattern for removing a handler. + kernel.getHookService().addHook("pre-invoke-removed", removedPreExecutionHandler); + kernel.getHookService().removeHook("pre-invoke-removed"); + + kernel.getHookService().addHook(postExecutionHandler); + + // Invoke prompt to trigger execution hooks. + String input = "I missed the F1 final race"; + var result = kernel.invokeAsync( + excuseFunction, + KernelArguments + .builder() + .withVariable("input", input) + .build(), + String.class) + .block(); + System.out.println("Function Result: " + result.getResult()); + } + + + /// + /// Demonstrate using kernel-hooks to around prompt rendering: + /// + /// + /// + private static void getRenderedPromptAsync(Kernel kernel) { + System.out.println("\n======== Get Rendered Prompt ========\n"); + + // Initialize prompt + String functionPrompt = "Write a random paragraph about: {{$input}} in the style of {{$style}}."; + + var excuseFunction = KernelFunctionFromPrompt.builder() + .withTemplate(functionPrompt) + .withName("Excuse") + .withDefaultExecutionSettings(PromptExecutionSettings + .builder() + .withMaxTokens(100) + .withTemperature(0.4) + .withTopP(1) + .build()) + .build(); + + PromptRenderingHook myRenderingHandler = event -> { + System.out.println( + event.getFunction().getName() + " : Prompt Rendering Handler - Triggered"); + event.getArguments().put("style", ContextVariable.of("Seinfeld")); + return event; + }; + + PromptRenderedHook myRenderedHandler = event -> { + System.out.println( + event.getFunction().getName() + " : Prompt Rendered Handler - Triggered"); + + String prompt = event.getPrompt() + "\nUSE SHORT, CLEAR, COMPLETE SENTENCES."; + + return new PromptRenderedEvent( + event.getFunction(), + event.getArguments(), + prompt + ); + }; + kernel.getHookService().addHook(myRenderingHandler); + kernel.getHookService().addHook(myRenderedHandler); + + // Invoke prompt to trigger execution hooks. + String input = "I missed the F1 final race"; + var result = kernel.invokeAsync(excuseFunction, + KernelArguments + .builder() + .withVariable("input", input) + .build(), + String.class) + .block(); + System.out.println("Function Result: " + result.getResult()); + } + + + /// + /// Demonstrate using kernel invocation-hooks to post process result: + /// + /// + private static void changingResultAsync(Kernel kernel) { + System.out.println("\n======== Changing/Filtering Function Result ========\n"); + + // Initialize function + String functionPrompt = "Write a paragraph about Handlers."; + + var writerFunction = KernelFunctionFromPrompt.builder() + .withTemplate(functionPrompt) + .withName("Writer") + .withDefaultExecutionSettings(PromptExecutionSettings + .builder() + .withMaxTokens(100) + .withTemperature(0.4) + .withTopP(1) + .build()) + .build(); + + FunctionInvokedHook hook = event -> { + String result = (String) event.getResult().getResult(); + result = result.replaceAll("[aeiouAEIOU0-9]", "*"); + + return new FunctionInvokedEvent<>( + event.getFunction(), + event.getArguments(), + new FunctionResult<>( + ContextVariable.of(result), + event.getResult().getMetadata() + ) + ); + }; + kernel.getHookService().addHook(hook); + + // Invoke prompt to trigger execution hooks. + var result = kernel.invokeAsync( + writerFunction, + KernelArguments.builder().build(), + String.class) + .block(); + System.out.println("Function Result: " + result.getResult()); + } + + + /// + /// Demonstrate using kernel invocation-hooks to cancel prior to execution: + /// + /// + /// + private static void beforeInvokeCancellationAsync(Kernel kernel) { + System.out.println("\n======== Cancelling Pipeline Execution - Invoking event ========\n"); + + // Initialize prompt + String functionPrompt = "Write a paragraph about: Cancellation."; + + var writerFunction = KernelFunctionFromPrompt.builder() + .withTemplate(functionPrompt) + .withName("Writer") + .withDefaultExecutionSettings(PromptExecutionSettings + .builder() + .withMaxTokens(1000) + .withTemperature(1) + .withTopP(0.5) + .build()) + .build(); + + FunctionInvokingHook hook = event -> { + System.out.println( + event.getFunction().getName() + + " : FunctionInvoking - Cancelling before execution"); + throw new RuntimeException("Cancelled"); + }; + + kernel.getHookService().addHook(hook); + + try { + // Invoke prompt to trigger execution hooks. + var result = kernel.invokeAsync( + writerFunction, + KernelArguments.builder().build(), + String.class) + .block(); + System.out.println("Function Result: " + result.getResult()); + } catch (Exception e) { + System.out.println("Exception: " + e.getMessage()); + } + } + + + /// + /// Demonstrate using kernel invocation-hooks to cancel post after execution: + /// + /// + /// + private static void afterInvokeCancellationAsync(Kernel kernel) { + System.out.println("\n======== Cancelling Pipeline Execution - Invoked event ========\n"); + + // Initialize prompts + var firstFunction = KernelFunctionFromPrompt.create("Write a phrase with Invoke."); + var secondFunction = KernelFunctionFromPrompt.create("Write a phrase with Cancellation."); + + AtomicInteger invokingCounter = new AtomicInteger(0); + kernel.getHookService().addHook((FunctionInvokingHook) event -> { + invokingCounter.incrementAndGet(); + return event; + }); + + AtomicInteger invokedCounter = new AtomicInteger(0); + kernel.getHookService().addHook((FunctionInvokedHook) event -> { + invokedCounter.incrementAndGet(); + throw new RuntimeException("Cancelled"); + }); + + // Invoke prompt to trigger execution hooks. + try { + var result = kernel.invokeAsync( + secondFunction, + KernelArguments.builder().build(), + String.class) + .block(); + System.out.println("Function Result: " + result.getResult()); + } catch (Exception e) { + System.out.println("Exception: " + e.getMessage()); + System.out.println("Function Invoked Times: " + invokedCounter.get()); + System.out.println("Function Invoking Times: " + invokedCounter.get()); + } + } + + private static void chatCompletionHook(Kernel kernel) { + // Initialize prompt + String functionPrompt = "Write a paragraph about hats"; + + var writerFunction = KernelFunctionFromPrompt.builder() + .withTemplate(functionPrompt) + .withName("Writer") + .withDefaultExecutionSettings(PromptExecutionSettings + .builder() + .withMaxTokens(1000) + .withTemperature(1) + .withTopP(0.5) + .build()) + .build(); + + kernel.getHookService().addPreChatCompletionHook(event -> { + ChatCompletionsOptions options = event.getOptions(); + List messages = options.getMessages(); + + messages = new ArrayList<>(messages); + messages.add( + new ChatRequestSystemMessage("Use upper case text when responding to the prompt.")); + + return new PreChatCompletionEvent( + PreChatCompletionHook.cloneOptionsWithMessages(options, messages) + ); + }); + + try { + // Invoke prompt to trigger execution hooks. + var result = kernel.invokeAsync( + writerFunction, + KernelArguments.builder().build(), + String.class) + .block(); + System.out.println("Function Result: " + result.getResult()); + } catch (Exception e) { + System.out.println("Exception: " + e.getMessage()); + } + } + + /** + * Show use of hooks added on a single invocations. + * + * @param kernel + */ + private static void invocationHook(Kernel kernel) { + // Initialize prompt + String functionPrompt = "Write a paragraph about hats"; + + var writerFunction = KernelFunctionFromPrompt.builder() + .withTemplate(functionPrompt) + .withName("Writer") + .withDefaultExecutionSettings(PromptExecutionSettings + .builder() + .withMaxTokens(1000) + .withTemperature(1) + .withTopP(0.5) + .build()) + .build(); + + KernelHooks kernelHooks = new KernelHooks(); + kernelHooks.addPreChatCompletionHook(event -> { + ChatCompletionsOptions options = event.getOptions(); + List messages = options.getMessages(); + + messages = new ArrayList<>(messages); + messages.add( + new ChatRequestSystemMessage("Use upper case text when responding to the prompt.")); + + return new PreChatCompletionEvent( + PreChatCompletionHook.cloneOptionsWithMessages(options, messages) + ); + }); + + try { + // Invoke prompt to trigger execution hooks. + var result = kernel.invokeAsync( + writerFunction, + KernelArguments.builder().build(), + kernelHooks, + String.class) + .block(); + System.out.println("Function Result: " + result.getResult()); + } catch (Exception e) { + System.out.println("Exception: " + e.getMessage()); + } + } +} 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 index 913cd910df..1ed731b17c 100644 --- 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 @@ -90,8 +90,8 @@ public class Example62_CustomAIServiceSelector { @Nullable @Override - public AIServiceSelection trySelectAIService( - Class serviceType, + public AIServiceSelection trySelectAIService( + Class serviceType, @Nullable KernelFunction function, 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 54ee48fdcd..b6506e5bd7 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 @@ -43,7 +43,6 @@ public class Main { Kernel kernel = Kernel.builder() .withAIService(ChatCompletionService.class, gpt35Turbo) - .withPromptTemplate(new HandlebarsPromptTemplate()) .build(); // Initialize the required functions and services for the kernel 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 aff3b2ead9..a4b2fab168 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 @@ -3,11 +3,10 @@ import com.microsoft.semantickernel.chatcompletion.AuthorRole; import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; import com.microsoft.semantickernel.chatcompletion.ChatHistory; import com.microsoft.semantickernel.chatcompletion.ChatMessageContent; +import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.KernelFunction; import com.microsoft.semantickernel.orchestration.KernelFunctionYaml; -import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; -import com.microsoft.semantickernel.templateengine.handlebars.HandlebarsPromptTemplate; import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; @@ -65,7 +64,6 @@ public class SimpleChatTest { throws IOException { Kernel kernel = Kernel.builder() .withAIService(ChatCompletionService.class, gpt35Turbo) - .withPromptTemplate(new HandlebarsPromptTemplate()) .build(); // Initialize the required functions and services for the kernel 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 a60bd7323f..84d975ccda 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 @@ -41,7 +41,6 @@ public class Main { Kernel kernel = Kernel.builder() .withAIService(ChatCompletionService.class, gpt35Turbo) - .withPromptTemplate(new HandlebarsPromptTemplate()) .build(); ChatHistory chatHistory = new ChatHistory(); 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 1de8095ced..1e48f45837 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 @@ -59,7 +59,6 @@ public class PersonaChatTest { Kernel kernel = Kernel.builder() .withAIService(ChatCompletionService.class, gpt35Turbo) - .withPromptTemplate(new HandlebarsPromptTemplate()) .build(); // Initialize the required functions and services for the kernel 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 88a3eb48b6..fdd75ff8a2 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 @@ -49,7 +49,6 @@ public class Main { Kernel kernel = Kernel.builder() .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 4a441bc3dc..8b24899301 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 @@ -72,7 +72,6 @@ public class SimpleRagTest { Kernel kernel = Kernel.builder() .withAIService(ChatCompletionService.class, gpt35Turbo) - .withPromptTemplate(new HandlebarsPromptTemplate()) .withPlugin(searchPlugin) .build(); diff --git a/java/semantickernel-api/pom.xml b/java/semantickernel-api/pom.xml index 4eb2bc4ccb..456c7305a9 100644 --- a/java/semantickernel-api/pom.xml +++ b/java/semantickernel-api/pom.xml @@ -66,5 +66,9 @@ junit-jupiter-api test + + com.github.spotbugs + spotbugs-annotations + \ No newline at end of file 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 a9df954780..cf07463ebd 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 @@ -2,6 +2,7 @@ package com.microsoft.semantickernel; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable; +import javax.annotation.Nullable; import java.util.Map; /** @@ -50,6 +51,7 @@ public interface AIService { /// /// Gets the specified attribute. /// + @Nullable static String getAttribute(AIService service, String key) { if (service.getAttributes() != null) { 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 3b038f81f6..1b596d8aa7 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 @@ -3,6 +3,7 @@ package com.microsoft.semantickernel; import com.microsoft.semantickernel.builders.Buildable; import com.microsoft.semantickernel.builders.SemanticKernelBuilder; +import com.microsoft.semantickernel.hooks.KernelHooks; import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.KernelFunction; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType; @@ -12,13 +13,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.services.AIServiceSelection; import com.microsoft.semantickernel.services.AIServiceSelector; import com.microsoft.semantickernel.services.OrderedAIServiceSelector; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,23 +37,37 @@ public class Kernel implements Buildable { private final AIServiceSelector serviceSelector; private final KernelPluginCollection plugins; + private final KernelHooks kernelHooks; public Kernel( AIServiceSelector serviceSelector, - @Nullable KernelPluginCollection plugins) { + @Nullable KernelPluginCollection plugins, + @Nullable KernelHooks kernelHooks) { this.serviceSelector = serviceSelector; + if (plugins != null) { - this.plugins = plugins; + this.plugins = new KernelPluginCollection(plugins.getPlugins()); } else { this.plugins = new KernelPluginCollection(); } + + this.kernelHooks = new KernelHooks(kernelHooks); + } + + public Mono> invokeAsync( + KernelFunction function, + @Nullable KernelArguments arguments, + @Nullable KernelHooks kernelHooks, + ContextVariableType resultType) { + kernelHooks = mergeInGlobalHooks(kernelHooks); + return function.invokeAsync(this, arguments, kernelHooks, resultType); } public Mono> invokeAsync( KernelFunction function, @Nullable KernelArguments arguments, ContextVariableType resultType) { - return function.invokeAsync(this, arguments, resultType); + return invokeAsync(function, arguments, null, resultType); } @@ -57,16 +75,29 @@ public class Kernel implements Buildable { String pluginName, String functionName, @Nullable KernelArguments arguments, + @Nullable KernelHooks kernelHooks, ContextVariableType resultType) { + kernelHooks = mergeInGlobalHooks(kernelHooks); return plugins.getFunction(pluginName, functionName) - .invokeAsync(this, arguments, resultType); + .invokeAsync(this, arguments, kernelHooks, resultType); + } + + public Mono> invokeAsync( + String pluginName, + String functionName, + @Nullable KernelArguments arguments, + ContextVariableType resultType) { + return invokeAsync(pluginName, functionName, arguments, null, resultType); } public Mono> invokeAsync( KernelFunction function, @Nullable KernelArguments arguments, + @Nullable KernelHooks kernelHooks, Class resultType) { + kernelHooks = mergeInGlobalHooks(kernelHooks); + ContextVariableType contextVariable; try { @@ -80,39 +111,72 @@ public class Kernel implements Buildable { throw e; } } - return function.invokeAsync(this, arguments, contextVariable); + return function.invokeAsync(this, arguments, kernelHooks, contextVariable); + } + + public Mono> invokeAsync( + KernelFunction function, + @Nullable KernelArguments arguments, + Class resultType) { + return invokeAsync(function, arguments, null, resultType); + } + + private KernelHooks mergeInGlobalHooks(@Nullable KernelHooks kernelHooks) { + KernelHooks allKernelHooks = this.kernelHooks; + if (kernelHooks != null) { + allKernelHooks = allKernelHooks.append(kernelHooks); + } + return allKernelHooks; } public List getFunctions() { - return null; + return plugins.getPlugins() + .stream() + .map(KernelPlugin::getFunctions) + .flatMap(m -> m.values().stream()) + .collect(Collectors.toList()); } public AIServiceSelector getServiceSelector() { return serviceSelector; } + @SuppressFBWarnings("EI_EXPOSE_REP") public KernelPluginCollection getPlugins() { return plugins; } - public T getService(Class clazz) { - return (T) serviceSelector + + @SuppressFBWarnings("EI_EXPOSE_REP") + public KernelHooks getHookService() { + return kernelHooks; + } + + public T getService(Class clazz) throws ServiceNotFoundException { + AIServiceSelection selector = serviceSelector .trySelectAIService( clazz, null, null - ) - .getService(); + ); + + if (selector == null) { + throw new ServiceNotFoundException("Unable to find service of type " + clazz.getName()); + } + + return selector.getService(); } public static Kernel.Builder builder() { return new Kernel.Builder(); } + public static class Builder implements SemanticKernelBuilder { private final Map, AIService> services = new HashMap<>(); private final List plugins = new ArrayList<>(); + @Nullable private Function, AIService>, AIServiceSelector> serviceSelectorProvider; public Builder withAIService(Class clazz, T aiService) { @@ -147,7 +211,8 @@ public class Kernel implements Buildable { return new Kernel( serviceSelector, - new KernelPluginCollection(plugins)); + new KernelPluginCollection(plugins), + null); } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ServiceNotFoundException.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ServiceNotFoundException.java new file mode 100644 index 0000000000..bbb127271d --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ServiceNotFoundException.java @@ -0,0 +1,10 @@ +package com.microsoft.semantickernel; + +import com.microsoft.semantickernel.exceptions.SKCheckedException; + +public class ServiceNotFoundException extends SKCheckedException { + + public ServiceNotFoundException(String s) { + super(s); + } +} 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 e878159a8f..c0f694c935 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 @@ -8,6 +8,7 @@ import com.microsoft.semantickernel.TextAIService; import com.microsoft.semantickernel.builders.Buildable; import com.microsoft.semantickernel.builders.SemanticKernelBuilder; import com.microsoft.semantickernel.builders.ServiceLoadUtil; +import com.microsoft.semantickernel.hooks.KernelHooks; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import java.util.List; import reactor.core.publisher.Mono; @@ -26,6 +27,20 @@ public interface ChatCompletionService extends Buildable, TextAIService { Kernel kernel ); + Mono> getChatMessageContentsAsync( + ChatHistory chatHistory, + PromptExecutionSettings promptExecutionSettings, + Kernel kernel, + KernelHooks kernelHooks + ); + + Mono> getChatMessageContentsAsync( + String prompt, + PromptExecutionSettings promptExecutionSettings, + Kernel kernel, + KernelHooks kernelHooks + ); + static Builder builder() { return ServiceLoadUtil.findServiceLoader(Builder.class, "com.microsoft.semantickernel.aiservices.openai.chatcompletion.OpenAIChatCompletion$Builder") diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/chatcompletion/ChatMessageContent.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/chatcompletion/ChatMessageContent.java index d91bb3fe94..62f3f98593 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/chatcompletion/ChatMessageContent.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/chatcompletion/ChatMessageContent.java @@ -3,11 +3,11 @@ package com.microsoft.semantickernel.chatcompletion; import com.microsoft.semantickernel.KernelContent; import com.microsoft.semantickernel.orchestration.FunctionResultMetadata; -import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import java.util.Map; import javax.annotation.Nullable; public class ChatMessageContent extends KernelContent { @@ -61,7 +61,7 @@ public class ChatMessageContent extends KernelContent { super(innerContent, modelId, metadata); this.authorRole = authorRole; this.encoding = encoding != null ? encoding : StandardCharsets.UTF_8; - this.items = items; + this.items = new ArrayList<>(items); } public AuthorRole getAuthorRole() { @@ -81,11 +81,11 @@ public class ChatMessageContent extends KernelContent { } public List getItems() { - return items; + return Collections.unmodifiableList(items); } public void setItems(List items) { - this.items = items; + this.items = new ArrayList<>(items); } @Nullable 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 new file mode 100644 index 0000000000..abd5e40490 --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokedEvent.java @@ -0,0 +1,31 @@ +package com.microsoft.semantickernel.hooks; + +import com.microsoft.semantickernel.orchestration.FunctionResult; +import com.microsoft.semantickernel.orchestration.KernelFunction; +import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; + +public class FunctionInvokedEvent implements KernelHookEvent { + + private final KernelFunction function; + private final KernelArguments arguments; + private final FunctionResult result; + + public FunctionInvokedEvent(KernelFunction function, KernelArguments arguments, + FunctionResult result) { + this.function = function; + this.arguments = arguments; + this.result = result; + } + + public KernelFunction getFunction() { + return function; + } + + public KernelArguments getArguments() { + return arguments; + } + + public FunctionResult getResult() { + return result; + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..4cb8f100da --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/FunctionInvokingEvent.java @@ -0,0 +1,23 @@ +package com.microsoft.semantickernel.hooks; + +import com.microsoft.semantickernel.orchestration.KernelFunction; +import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; + +public class FunctionInvokingEvent implements KernelHookEvent { + + private final KernelFunction function; + private final KernelArguments arguments; + + public FunctionInvokingEvent(KernelFunction function, KernelArguments arguments) { + this.function = function; + this.arguments = arguments; + } + + public KernelFunction getFunction() { + return function; + } + + public KernelArguments getArguments() { + return arguments; + } +} \ No newline at end of file diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHook.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHook.java new file mode 100644 index 0000000000..535727e391 --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHook.java @@ -0,0 +1,79 @@ +package com.microsoft.semantickernel.hooks; + +import com.azure.ai.openai.models.ChatCompletionsOptions; +import com.azure.ai.openai.models.ChatRequestMessage; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +public interface KernelHook extends Predicate, Function { + + interface FunctionInvokingHook extends KernelHook { + + @Override + default boolean test(KernelHookEvent arguments) { + return FunctionInvokingEvent.class.isAssignableFrom(arguments.getClass()); + } + } + + interface FunctionInvokedHook extends KernelHook { + + @Override + default boolean test(KernelHookEvent arguments) { + return FunctionInvokedEvent.class.isAssignableFrom(arguments.getClass()); + } + } + + interface PromptRenderingHook extends KernelHook { + + @Override + default boolean test(KernelHookEvent arguments) { + return PromptRenderingEvent.class.isAssignableFrom(arguments.getClass()); + } + } + + interface PromptRenderedHook extends KernelHook { + + @Override + default boolean test(KernelHookEvent arguments) { + return PromptRenderedEvent.class.isAssignableFrom(arguments.getClass()); + } + } + + interface PreChatCompletionHook extends KernelHook { + + @Override + default boolean test(KernelHookEvent arguments) { + return PreChatCompletionEvent.class.isAssignableFrom(arguments.getClass()); + } + + static ChatCompletionsOptions cloneOptionsWithMessages( + ChatCompletionsOptions options, + List messages) { + ChatCompletionsOptions newOptions = new ChatCompletionsOptions(messages) + .setPresencePenalty(options.getPresencePenalty()) + .setFrequencyPenalty(options.getFrequencyPenalty()) + .setLogitBias(options.getLogitBias()) + .setMaxTokens(options.getMaxTokens()) + .setModel(options.getModel()) + .setStop(options.getStop()) + .setTemperature(options.getTemperature()) + .setTools(options.getTools()) + .setTopP(options.getTopP()) + .setUser(options.getUser()) + .setDataSources(options.getDataSources()) + .setEnhancements(options.getEnhancements()) + .setFunctions(options.getFunctions()) + .setN(options.getN()) + .setResponseFormat(options.getResponseFormat()) + .setSeed(options.getSeed()) + .setStream(options.isStream()) + .setToolChoice(options.getToolChoice()); + + if (options.getFunctionCall() != null) { + newOptions = newOptions.setFunctionCall(options.getFunctionCall()); + } + return newOptions; + } + } +} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHookEvent.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHookEvent.java new file mode 100644 index 0000000000..ae78e8b2e6 --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHookEvent.java @@ -0,0 +1,5 @@ +package com.microsoft.semantickernel.hooks; + +public interface KernelHookEvent { + +} \ No newline at end of file diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHooks.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHooks.java new file mode 100644 index 0000000000..02151b51bb --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/KernelHooks.java @@ -0,0 +1,93 @@ +package com.microsoft.semantickernel.hooks; + +import com.microsoft.semantickernel.hooks.KernelHook.FunctionInvokedHook; +import com.microsoft.semantickernel.hooks.KernelHook.FunctionInvokingHook; +import com.microsoft.semantickernel.hooks.KernelHook.PreChatCompletionHook; +import com.microsoft.semantickernel.hooks.KernelHook.PromptRenderedHook; +import com.microsoft.semantickernel.hooks.KernelHook.PromptRenderingHook; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.Function; +import javax.annotation.Nullable; + +public class KernelHooks { + + private final Map> hooks; + + public KernelHooks() { + this.hooks = new HashMap<>(); + } + + public KernelHooks(@Nullable KernelHooks kernelHooks) { + if (kernelHooks == null) { + this.hooks = new HashMap<>(); + } else { + this.hooks = new HashMap<>(kernelHooks.getHooks()); + } + } + + public KernelHooks(Map> hooks) { + this.hooks = new HashMap<>(hooks); + } + + private Map> getHooks() { + return Collections.unmodifiableMap(hooks); + } + + public String addFunctionInvokingHook( + Function function) { + return addHook((FunctionInvokingHook) function::apply); + } + + public String addFunctionInvokedHook( + Function function) { + return addHook((FunctionInvokedHook) function::apply); + } + + public String addPreChatCompletionHook( + Function function) { + return addHook((PreChatCompletionHook) function::apply); + } + + public String addPromptRenderedHook( + Function function) { + return addHook((PromptRenderedHook) function::apply); + } + + public String addPromptRenderingHook( + Function function) { + return addHook((PromptRenderingHook) function::apply); + } + + public T executeHooks(T event) { + for (KernelHook hook : hooks.values()) { + if (hook.test(event)) { + event = ((KernelHook) hook).apply(event); + } + } + return event; + } + + public String addHook(KernelHook hook) { + return addHook(UUID.randomUUID().toString(), hook); + } + + public String addHook(String hookName, KernelHook hook) { + hooks.put(hookName, hook); + return hookName; + } + + public KernelHook removeHook(String hookName) { + return hooks.remove(hookName); + } + + public KernelHooks append(KernelHooks kernelHooks) { + Map> newHooks = new HashMap<>(this.hooks); + newHooks.putAll(this.hooks); + newHooks.putAll(kernelHooks.getHooks()); + + return new KernelHooks(newHooks); + } +} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreChatCompletionEvent.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreChatCompletionEvent.java new file mode 100644 index 0000000000..64154d67da --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PreChatCompletionEvent.java @@ -0,0 +1,16 @@ +package com.microsoft.semantickernel.hooks; + +import com.azure.ai.openai.models.ChatCompletionsOptions; + +public class PreChatCompletionEvent implements KernelHookEvent { + + private final ChatCompletionsOptions options; + + public PreChatCompletionEvent(ChatCompletionsOptions options) { + this.options = options; + } + + public ChatCompletionsOptions getOptions() { + return options; + } +} 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 new file mode 100644 index 0000000000..cb5d17662d --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderedEvent.java @@ -0,0 +1,30 @@ +package com.microsoft.semantickernel.hooks; + +import com.microsoft.semantickernel.orchestration.KernelFunction; +import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; + +public class PromptRenderedEvent implements KernelHookEvent { + + private final KernelFunction function; + private final KernelArguments arguments; + private final String prompt; + + public PromptRenderedEvent(KernelFunction function, KernelArguments arguments, + String prompt) { + this.function = function; + this.arguments = arguments; + this.prompt = prompt; + } + + public KernelFunction getFunction() { + return function; + } + + public KernelArguments getArguments() { + return arguments; + } + + public String getPrompt() { + return prompt; + } +} \ No newline at end of file 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 new file mode 100644 index 0000000000..d7af3b857b --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/hooks/PromptRenderingEvent.java @@ -0,0 +1,23 @@ +package com.microsoft.semantickernel.hooks; + +import com.microsoft.semantickernel.orchestration.KernelFunction; +import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; + +public class PromptRenderingEvent implements KernelHookEvent { + + private final KernelFunction function; + private final KernelArguments arguments; + + public PromptRenderingEvent(KernelFunction function, KernelArguments arguments) { + this.function = function; + this.arguments = arguments; + } + + public KernelFunction getFunction() { + return function; + } + + public KernelArguments getArguments() { + return arguments; + } +} 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 774b8cc801..e1f33b23ea 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 @@ -1,5 +1,6 @@ package com.microsoft.semantickernel.orchestration; +import java.util.Collections; import java.util.Map; import javax.annotation.Nullable; @@ -64,6 +65,10 @@ public abstract class DefaultKernelFunction implements KernelFunction { @Override @Nullable public Map getExecutionSettings() { - return executionSettings; + if (executionSettings != null) { + return Collections.unmodifiableMap(executionSettings); + } else { + return null; + } } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResultMetadata.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResultMetadata.java index afec88a79e..185bf6ea29 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResultMetadata.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionResultMetadata.java @@ -15,7 +15,7 @@ public class FunctionResultMetadata { private final CaseInsensitiveMap> metadata; public FunctionResultMetadata(CaseInsensitiveMap> metadata) { - this.metadata = metadata; + this.metadata = new CaseInsensitiveMap<>(metadata); } public static FunctionResultMetadata build( 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 e005f0faa6..8aff64caa2 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 @@ -3,6 +3,7 @@ package com.microsoft.semantickernel.orchestration; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.builders.Buildable; +import com.microsoft.semantickernel.hooks.KernelHooks; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType; import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; import com.microsoft.semantickernel.semanticfunctions.InputVariable; @@ -81,6 +82,12 @@ public interface KernelFunction extends Buildable { @Nullable KernelArguments arguments, ContextVariableType variableType); + Mono> invokeAsync( + Kernel kernel, + @Nullable KernelArguments arguments, + KernelHooks kernelHooks, + ContextVariableType variableType); + @Nullable Map getExecutionSettings(); @@ -92,8 +99,6 @@ public interface KernelFunction extends Buildable { FromPromptBuilder withPromptTemplate(PromptTemplate promptTemplate); - FromPromptBuilder withPluginName(String name); - FromPromptBuilder withExecutionSettings( Map executionSettings); diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunctionMetadata.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunctionMetadata.java index 5959d2e570..f48166069a 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunctionMetadata.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunctionMetadata.java @@ -2,6 +2,8 @@ package com.microsoft.semantickernel.orchestration; import com.microsoft.semantickernel.plugin.KernelParameterMetadata; import com.microsoft.semantickernel.plugin.KernelReturnParameterMetadata; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class KernelFunctionMetadata { @@ -25,7 +27,7 @@ public class KernelFunctionMetadata { KernelReturnParameterMetadata returnParameter) { this.name = name; this.description = description; - this.parameters = parameters; + this.parameters = new ArrayList<>(parameters); this.returnParameter = returnParameter; } @@ -34,7 +36,7 @@ public class KernelFunctionMetadata { } public List getParameters() { - return parameters; + return Collections.unmodifiableList(parameters); } public String getDescription() { diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunctionYaml.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunctionYaml.java index a3d8cbdd9a..d514de6a5b 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunctionYaml.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/KernelFunctionYaml.java @@ -10,6 +10,7 @@ import com.microsoft.semantickernel.templateengine.handlebars.HandlebarsPromptTe import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import javax.annotation.Nullable; @@ -26,14 +27,14 @@ public class KernelFunctionYaml { String yaml, @Nullable PromptTemplateFactory promptTemplateFactory ) throws IOException { - InputStream targetStream = new ByteArrayInputStream(yaml.getBytes()); + InputStream targetStream = new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)); return fromYaml(targetStream, promptTemplateFactory); } public static KernelFunction fromPromptYaml( String yaml ) throws IOException { - InputStream targetStream = new ByteArrayInputStream(yaml.getBytes()); + InputStream targetStream = new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)); return fromYaml(targetStream, null); } @@ -62,7 +63,6 @@ public class KernelFunctionYaml { .withName(functionModel.getName()) .withInputParameters(functionModel.getInputVariables()) .withPromptTemplate(promptTemplate) - .withPluginName(functionModel.getName()) // TODO: 1.0 add plugin name .withExecutionSettings(functionModel.getExecutionSettings()) .withDescription(functionModel.getDescription()) .withOutputVariable(functionModel.getOutputVariable()) diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/MethodDetails.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/MethodDetails.java index 183edf0b98..07e7baf837 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/MethodDetails.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/MethodDetails.java @@ -1,8 +1,10 @@ package com.microsoft.semantickernel.orchestration; -import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromMethod.ImplementationFunc; import com.microsoft.semantickernel.plugin.KernelParameterMetadata; import com.microsoft.semantickernel.plugin.KernelReturnParameterMetadata; +import com.microsoft.semantickernel.semanticfunctions.KernelFunctionFromMethod.ImplementationFunc; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class MethodDetails { @@ -22,7 +24,7 @@ public class MethodDetails { this.name = name; this.description = description; this.function = function; - this.parameters = parameters; + this.parameters = new ArrayList<>(parameters); this.returnParameter = returnParameter; } @@ -39,7 +41,7 @@ public class MethodDetails { } public List getParameters() { - return parameters; + return Collections.unmodifiableList(parameters); } public KernelReturnParameterMetadata getReturnParameter() { 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 18fcb2d831..03bd59885f 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,22 +1,20 @@ package com.microsoft.semantickernel.orchestration; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.annotation.Nullable; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; - 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_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; @@ -56,12 +54,15 @@ public class PromptExecutionSettings { private final int bestOf; private final int resultsPerPrompt; private final String user; + @Nullable private final List stopSequences; + @Nullable private final ToolCallBehavior toolCallBehavior; /// /// Modify the likelihood of specified tokens appearing in the completion. /// s + @Nullable public Map tokenSelectionBiases; @JsonCreator @@ -76,22 +77,24 @@ public class PromptExecutionSettings { @JsonProperty(RESULTS_PER_PROMPT) int resultsPerPrompt, @JsonProperty(BEST_OF) int bestOf, @JsonProperty(USER) String user, + @Nullable @JsonProperty(STOP_SEQUENCES) List stopSequences, + @Nullable @JsonProperty(TOKEN_SELECTION_BIASES) Map tokenSelectionBiases) { - this( - serviceId, - modelId, - temperature, - topP, - presencePenalty, - frequencyPenalty, - maxTokens, - resultsPerPrompt, - bestOf, - user, - stopSequences, - tokenSelectionBiases, - null); + this( + serviceId, + modelId, + temperature, + topP, + presencePenalty, + frequencyPenalty, + maxTokens, + resultsPerPrompt, + bestOf, + user, + stopSequences, + tokenSelectionBiases, + null); } public PromptExecutionSettings( @@ -108,19 +111,23 @@ public class PromptExecutionSettings { @Nullable List stopSequences, @Nullable Map tokenSelectionBiases, @Nullable ToolCallBehavior toolCallBehavior) { - this.serviceId = serviceId; - this.modelId = modelId; - this.temperature = temperature; - this.topP = topP; - this.presencePenalty = presencePenalty; - this.frequencyPenalty = frequencyPenalty; - this.maxTokens = maxTokens; - this.resultsPerPrompt = resultsPerPrompt; - this.bestOf = bestOf; - this.user = user; - this.stopSequences = stopSequences != null ? stopSequences : Collections.emptyList(); - this.tokenSelectionBiases = tokenSelectionBiases != null ? tokenSelectionBiases : Collections.emptyMap(); - this.toolCallBehavior = toolCallBehavior; + this.serviceId = serviceId; + this.modelId = modelId; + this.temperature = temperature; + this.topP = topP; + this.presencePenalty = presencePenalty; + this.frequencyPenalty = frequencyPenalty; + this.maxTokens = maxTokens; + this.resultsPerPrompt = resultsPerPrompt; + this.bestOf = bestOf; + this.user = user; + this.stopSequences = + stopSequences != null ? new ArrayList<>(stopSequences) : Collections.emptyList(); + this.tokenSelectionBiases = + tokenSelectionBiases != null ? new HashMap<>(tokenSelectionBiases) + : Collections.emptyMap(); + this.toolCallBehavior = + toolCallBehavior != null ? new ToolCallBehavior(toolCallBehavior) : null; } @JsonProperty(SERVICE_ID) @@ -143,7 +150,7 @@ public class PromptExecutionSettings { return topP; } - @JsonProperty(PRESENCE_PENALTY) + @JsonProperty(PRESENCE_PENALTY) public double getPresencePenalty() { return presencePenalty; } @@ -175,19 +182,30 @@ public class PromptExecutionSettings { } @JsonProperty(STOP_SEQUENCES) + @Nullable public List getStopSequences() { - return stopSequences; + if (stopSequences != null) { + return Collections.unmodifiableList(stopSequences); + } + return null; } - + @JsonProperty(TOKEN_SELECTION_BIASES) + @Nullable public Map getTokenSelectionBiases() { - return tokenSelectionBiases; + if (tokenSelectionBiases != null) { + return Collections.unmodifiableMap(tokenSelectionBiases); + } + return null; } @JsonIgnore @Nullable public ToolCallBehavior getToolCallBehavior() { - return toolCallBehavior; + if (toolCallBehavior != null) { + return new ToolCallBehavior(toolCallBehavior); + } + return null; } public static Builder builder() { @@ -265,7 +283,8 @@ public class PromptExecutionSettings { @SuppressWarnings("unchecked") public Builder withStopSequences(List stopSequences) { if (stopSequences != null) { - ((List)settings.computeIfAbsent(STOP_SEQUENCES, k -> new ArrayList<>())).addAll(stopSequences); + ((List) settings.computeIfAbsent(STOP_SEQUENCES, + k -> new ArrayList<>())).addAll(stopSequences); } return this; } @@ -273,7 +292,8 @@ public class PromptExecutionSettings { @SuppressWarnings("unchecked") public Builder withTokenSelectionBiases(Map tokenSelectionBiases) { if (tokenSelectionBiases != null) { - ((Map)settings.computeIfAbsent(TOKEN_SELECTION_BIASES, k -> new HashMap<>())).putAll(tokenSelectionBiases); + ((Map) settings.computeIfAbsent(TOKEN_SELECTION_BIASES, + k -> new HashMap<>())).putAll(tokenSelectionBiases); } return this; } @@ -281,19 +301,20 @@ public class PromptExecutionSettings { @SuppressWarnings("unchecked") public PromptExecutionSettings build() { return new PromptExecutionSettings( - (String)settings.getOrDefault(SERVICE_ID, ""), - (String)settings.getOrDefault(MODEL_ID, ""), - (double)settings.getOrDefault(TEMPERATURE, DEFAULT_TEMPERATURE), - (double)settings.getOrDefault(TOP_P, DEFAULT_TOP_P), - (double)settings.getOrDefault(PRESENCE_PENALTY, DEFAULT_PRESENCE_PENALTY), - (double)settings.getOrDefault(FREQUENCY_PENALTY, DEFAULT_FREQUENCY_PENALTY), - (int)settings.getOrDefault(MAX_TOKENS, DEFAULT_MAX_TOKENS), - (int)settings.getOrDefault(RESULTS_PER_PROMPT, DEFAULT_RESULTS_PER_PROMPT), - (int)settings.getOrDefault(BEST_OF, DEFAULT_BEST_OF), - (String)settings.getOrDefault(USER, ""), - (List)settings.getOrDefault(STOP_SEQUENCES, Collections.emptyList()), - (Map)settings.getOrDefault(TOKEN_SELECTION_BIASES, Collections.emptyMap()), - (ToolCallBehavior)settings.getOrDefault("toolCallBehavior", new ToolCallBehavior()) + (String) settings.getOrDefault(SERVICE_ID, ""), + (String) settings.getOrDefault(MODEL_ID, ""), + (double) settings.getOrDefault(TEMPERATURE, DEFAULT_TEMPERATURE), + (double) settings.getOrDefault(TOP_P, DEFAULT_TOP_P), + (double) settings.getOrDefault(PRESENCE_PENALTY, DEFAULT_PRESENCE_PENALTY), + (double) settings.getOrDefault(FREQUENCY_PENALTY, DEFAULT_FREQUENCY_PENALTY), + (int) settings.getOrDefault(MAX_TOKENS, DEFAULT_MAX_TOKENS), + (int) settings.getOrDefault(RESULTS_PER_PROMPT, DEFAULT_RESULTS_PER_PROMPT), + (int) settings.getOrDefault(BEST_OF, DEFAULT_BEST_OF), + (String) settings.getOrDefault(USER, ""), + (List) settings.getOrDefault(STOP_SEQUENCES, Collections.emptyList()), + (Map) settings.getOrDefault(TOKEN_SELECTION_BIASES, + Collections.emptyMap()), + (ToolCallBehavior) settings.getOrDefault("toolCallBehavior", new ToolCallBehavior()) ); } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/TextRequestContent.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/TextRequestContent.java deleted file mode 100644 index a07dce63ef..0000000000 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/TextRequestContent.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.microsoft.semantickernel.orchestration; - -import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; - -public class TextRequestContent { - - /// - /// In a scenario of multiple choices per request, this represents zero-based index of the choice in the streaming sequence - /// - public int choiceIndex; - - /// - /// The inner content representation. Use this to bypass the current abstraction. - /// - /// - /// The usage of this property is considered "unsafe". Use it only if strictly necessary. - /// - public T innerContent; - - /// - /// The model ID used to generate the content. - /// - public String modelId; - - /// - /// The metadata associated with the content. - /// - public KernelArguments metadata; - - - public TextRequestContent(T content) { - this.innerContent = content; - } -} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ToolCallBehavior.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ToolCallBehavior.java index 36398e959d..a1a603d610 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ToolCallBehavior.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ToolCallBehavior.java @@ -4,7 +4,7 @@ import java.util.HashMap; import java.util.Map; /** - * Defines the behavior of a tool call. Currently, the only tool available is function calling. + * Defines the behavior of a tool call. Currently, the only tool available is function calling. */ public class ToolCallBehavior { @@ -46,6 +46,12 @@ public class ToolCallBehavior { public ToolCallBehavior() { } + public ToolCallBehavior(ToolCallBehavior toolCallBehavior) { + flags.putAll(toolCallBehavior.flags); + functionSettings.putAll(toolCallBehavior.functionSettings); + settings.putAll(toolCallBehavior.settings); + } + public ToolCallBehavior kernelFunctions(boolean enable) { setFlag("kernelFunctions", enable); return this; @@ -61,7 +67,7 @@ public class ToolCallBehavior { String key = FunctionCallBehavior.getKey(function); functionSettings.compute( key, - (k,v) -> { + (k, v) -> { if (v == null) { return new FunctionCallBehavior(function, require, false); } else { @@ -78,7 +84,7 @@ public class ToolCallBehavior { String key = FunctionCallBehavior.getKey(function); functionSettings.compute( key, - (k,v) -> { + (k, v) -> { if (v == null) { return new FunctionCallBehavior(function, false, enable); } else { @@ -93,7 +99,7 @@ public class ToolCallBehavior { public ToolCallBehavior maximumAutoInvokeAttempts(int maximumAutoInvokeAttempts) { setSetting("maximumAutoInvokeAttempts", maximumAutoInvokeAttempts); return this; - } + } public boolean kernelFunctionsEnabled() { return getFlag("kernelFunctions"); @@ -127,7 +133,7 @@ public class ToolCallBehavior { } public boolean functionEnabled(String pluginName, String functionName) { - + String key = FunctionCallBehavior.getKey(pluginName, functionName); if (functionSettings.containsKey(key)) { return functionSettings.get(key).enabled(); diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/CaseInsensitiveMap.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/CaseInsensitiveMap.java index b4751fe2d9..134b069879 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/CaseInsensitiveMap.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/CaseInsensitiveMap.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; +import javax.annotation.Nullable; public class CaseInsensitiveMap extends HashMap { @@ -53,6 +54,7 @@ public class CaseInsensitiveMap extends HashMap { return super.containsKey(((String) key).toLowerCase(Locale.ROOT)); } + @Nullable @Override public T get(Object key) { if (key == null) { diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/ContextVariableTypeConverter.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/ContextVariableTypeConverter.java index 95dcce38fd..22160b15b5 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/ContextVariableTypeConverter.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/contextvariables/ContextVariableTypeConverter.java @@ -1,5 +1,6 @@ package com.microsoft.semantickernel.orchestration.contextvariables; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -75,7 +76,7 @@ public class ContextVariableTypeConverter { this.fromObject = fromObject; this.toPromptString = toPromptString; this.fromPromptString = fromPromptString; - this.toObjects = toObjects; + this.toObjects = new ArrayList<>(toObjects); } @@ -104,6 +105,7 @@ public class ContextVariableTypeConverter { return null; } + @Nullable public T fromObject(Object s) { if (s == null) { return null; @@ -118,6 +120,7 @@ public class ContextVariableTypeConverter { return toPromptString.apply(t); } + @Nullable public T fromPromptString(String t) { if (t == null) { return null; 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 064a07c225..3fa69cd2b1 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 @@ -2,12 +2,14 @@ package com.microsoft.semantickernel.orchestration.contextvariables; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; +import reactor.util.annotation.NonNull; + +import javax.annotation.Nullable; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; -import javax.annotation.Nullable; -import reactor.util.annotation.NonNull; /// /// Context Variables is a data structure that holds temporary data while a task is being performed. @@ -161,7 +163,7 @@ public class DefaultKernelArguments implements KernelArguments, WritableKernelAr @Nullable @Override public Map getExecutionSettings() { - return executionSettings; + return Collections.unmodifiableMap(executionSettings); } @Override 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 1294a2e1f9..f275a22fe2 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 @@ -49,7 +49,7 @@ public interface KernelArguments extends Buildable, Map> T get(String key); + ContextVariable get(String key); /** * Return the variable with the given name diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelParameterMetadata.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelParameterMetadata.java index dce48b8a31..a3f88c9dd9 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelParameterMetadata.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelParameterMetadata.java @@ -1,6 +1,6 @@ package com.microsoft.semantickernel.plugin; -public class KernelParameterMetadata { +public class KernelParameterMetadata { /// The name of the parameter. private final String name; @@ -8,18 +8,18 @@ public class KernelParameterMetadata { private final String description; private final String defaultValue; private final boolean isRequired; - private final Class parameterType; + private final Class parameterType; public KernelParameterMetadata( String name, String description, - Class parameterType, + Class parameterType, String defaultValue, boolean isRequired) { this.name = name; this.description = description; this.defaultValue = defaultValue; this.isRequired = isRequired; - this.parameterType = String.class; + this.parameterType = parameterType; } public String getName() { diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPlugin.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPlugin.java index 5e402ec1fb..6e22e632e2 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPlugin.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPlugin.java @@ -1,11 +1,10 @@ package com.microsoft.semantickernel.plugin; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - import com.microsoft.semantickernel.orchestration.KernelFunction; import com.microsoft.semantickernel.orchestration.contextvariables.CaseInsensitiveMap; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; public class KernelPlugin implements Iterable { @@ -20,17 +19,17 @@ public class KernelPlugin implements Iterable { this.description = description; this.functions = new CaseInsensitiveMap<>(); if (plugins != null) { - plugins.forEach((key,kerneFunction) -> this.functions.put(key, kerneFunction)); + this.functions.putAll(plugins); } } public Map getFunctions() { - return functions; + return Collections.unmodifiableMap(functions); } public KernelFunction get(String functionName) { return functions.get(functionName); - } + } @Override public Iterator iterator() { diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginCollection.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginCollection.java index 2dee49a28a..3b0b817f29 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginCollection.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginCollection.java @@ -1,19 +1,17 @@ package com.microsoft.semantickernel.plugin; +import com.microsoft.semantickernel.orchestration.KernelFunction; +import com.microsoft.semantickernel.orchestration.KernelFunctionMetadata; +import com.microsoft.semantickernel.orchestration.contextvariables.CaseInsensitiveMap; +import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import javax.annotation.Nullable; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.microsoft.semantickernel.orchestration.KernelFunction; -import com.microsoft.semantickernel.orchestration.KernelFunctionMetadata; -import com.microsoft.semantickernel.orchestration.contextvariables.CaseInsensitiveMap; - public class KernelPluginCollection implements Iterable { private static final Logger LOGGER = LoggerFactory.getLogger(KernelPluginCollection.class); @@ -28,12 +26,10 @@ public class KernelPluginCollection implements Iterable { plugins.forEach(plugin -> this.plugins.put(plugin.getName(), plugin)); } - @Nullable public KernelFunction getFunction(String pluginName, String functionName) { KernelPlugin plugin = plugins.get(pluginName); if (plugin == null) { - LOGGER.warn("Failed to find plugin '{}'", pluginName); - return null; + throw new IllegalArgumentException("Failed to find plugin " + pluginName); } KernelFunction function = plugin.get(functionName); @@ -56,6 +52,10 @@ public class KernelPluginCollection implements Iterable { return plugins.values().iterator(); } + public List getPlugins() { + return new ArrayList<>(plugins.values()); + } + public void add(KernelPlugin plugin) { if (plugins.containsKey(plugin.getName())) { LOGGER.warn("Plugin {} already exists, overwriting existing plugin", plugin.getName()); diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginFactory.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginFactory.java index e6547805a8..3d3da008dc 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginFactory.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/KernelPluginFactory.java @@ -1,6 +1,5 @@ package com.microsoft.semantickernel.plugin; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.microsoft.semantickernel.exceptions.SKException; import com.microsoft.semantickernel.orchestration.KernelFunction; @@ -22,7 +21,6 @@ import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -219,11 +217,13 @@ public class KernelPluginFactory { return new Builder().withName(config.getName()).withDescription(config.getDescription()) .withExecutionSettings(config.getExecutionSettings()) - .withInputParameters(config.getInputVariables()).withPromptTemplate(promptTemplate) - .withPluginName(pluginDirectoryName).withTemplate(template) + .withInputParameters(config.getInputVariables() + ).withPromptTemplate(promptTemplate) + .withTemplate(template) .withTemplateFormat(config.getTemplateFormat()) .withOutputVariable(config.getOutputVariable()) - .withPromptTemplateFactory(promptTemplateFactory).build(); + .withPromptTemplateFactory(promptTemplateFactory) + .build(); } public static KernelPlugin importPluginFromResourcesDirectory(String parentDirectory, diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/Plugin.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/Plugin.java index 82d2bbc83b..eb2163806c 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/Plugin.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/plugin/Plugin.java @@ -27,6 +27,6 @@ public class Plugin { } public List functions() { - return functions; + return Collections.unmodifiableList(functions); } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/AggregatorPromptTemplateFactory.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/AggregatorPromptTemplateFactory.java index 2abaf72c91..7b056b754a 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/AggregatorPromptTemplateFactory.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/AggregatorPromptTemplateFactory.java @@ -2,12 +2,13 @@ package com.microsoft.semantickernel.semanticfunctions; import reactor.util.annotation.NonNull; +import java.util.ArrayList; import java.util.List; public class AggregatorPromptTemplateFactory implements PromptTemplateFactory { private final List templateFactories; public AggregatorPromptTemplateFactory(List templateFactories) { - this.templateFactories = templateFactories; + this.templateFactories = new ArrayList<>(templateFactories); } @Override public PromptTemplate tryCreate(@NonNull PromptTemplateConfig templateConfig) { 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 0d5a09cce8..8856b254c0 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 @@ -5,6 +5,9 @@ import static com.microsoft.semantickernel.plugin.annotations.KernelFunctionPara import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.exceptions.AIException; import com.microsoft.semantickernel.exceptions.AIException.ErrorCodes; +import com.microsoft.semantickernel.hooks.FunctionInvokedEvent; +import com.microsoft.semantickernel.hooks.FunctionInvokingEvent; +import com.microsoft.semantickernel.hooks.KernelHooks; import com.microsoft.semantickernel.orchestration.DefaultKernelFunction; import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.KernelFunction; @@ -66,7 +69,21 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction { Kernel kernel, @Nullable KernelArguments arguments, ContextVariableType variableType) { - return function.invoke(kernel, this, arguments); + return function.invoke(kernel, this, arguments, kernel.getHookService()); + } + + @Override + public Mono> invokeAsync( + Kernel kernel, + @Nullable KernelArguments arguments, + @Nullable KernelHooks kernelHooks, + ContextVariableType variableType) { + + if (kernelHooks == null) { + kernelHooks = kernel.getHookService(); + } + + return function.invoke(kernel, this, arguments, kernelHooks); } public interface ImplementationFunc { @@ -75,7 +92,8 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction { Kernel kernel, KernelFunction function, @Nullable - KernelArguments arguments); + KernelArguments arguments, + KernelHooks kernelHooks); } @@ -138,16 +156,22 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction { return new ImplementationFunc() { @Override - public Mono> invoke(Kernel kernel, KernelFunction function, + public Mono> invoke( + Kernel kernel, + KernelFunction function, @Nullable - KernelArguments arguments) { + KernelArguments arguments, + KernelHooks kernelHooks) { - //Set inputArgs = determineInputArgs(method); + FunctionInvokingEvent updatedState = kernelHooks + .executeHooks( + new FunctionInvokingEvent(function, arguments)); + KernelArguments updatedArguments = updatedState.getArguments(); try { List args = Arrays.stream(method.getParameters()) - .map(getParameters(method, arguments, kernel)) + .map(getParameters(method, updatedArguments, kernel)) .collect(Collectors.toList()); Mono mono; @@ -169,7 +193,13 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction { }); return r - .map(it -> new FunctionResult<>(ContextVariable.of(it))); + .map(it -> new FunctionResult<>(ContextVariable.of(it))) + .map(it -> { + FunctionInvokedEvent updatedResult = kernelHooks + .executeHooks( + new FunctionInvokedEvent<>(function, updatedArguments, it)); + return updatedResult.getResult(); + }); } catch (Exception e) { return Mono.error(e); } @@ -232,22 +262,10 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction { ContextVariable arg = context == null ? null : context.get(variableName); if (arg == null) { - /* - // If this is bound to input get the input value - if (inputArgs.contains(parameter)) { - ContextVariable input = context.get(KernelArguments.MAIN_KEY); - if (input != null) { - arg = input; - } - } - - */ - - if (arg == null) { - KernelFunctionParameter annotation = - parameter.getAnnotation(KernelFunctionParameter.class); - if (annotation != null) { - // Convert from the defaultValue, which is a String to the argument type + KernelFunctionParameter annotation = + parameter.getAnnotation(KernelFunctionParameter.class); + if (annotation != null) { + // Convert from the defaultValue, which is a String to the argument type // Expectation here is that the fromPromptString method will be able to handle a null or empty string Class type = annotation.type(); ContextVariableType cvType = ContextVariableTypes.getDefaultVariableTypeForClass(type); @@ -257,22 +275,21 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction { arg = ContextVariable.of(value); } - if (NO_DEFAULT_VALUE.equals(arg)) { - if (!annotation.required()) { - return null; - } - - throw new AIException( - AIException.ErrorCodes.INVALID_CONFIGURATION, - "Attempted to invoke function " - + method.getDeclaringClass().getName() - + "." - + method.getName() - + ". The context variable \"" - + variableName - + "\" has not been set, and no default value is" - + " specified."); + if (arg != null && NO_DEFAULT_VALUE.equals(arg.getValue())) { + if (!annotation.required()) { + return null; } + + throw new AIException( + AIException.ErrorCodes.INVALID_CONFIGURATION, + "Attempted to invoke function " + + method.getDeclaringClass().getName() + + "." + + method.getName() + + ". The context variable \"" + + variableName + + "\" has not been set, and no default value is" + + " specified."); } } } @@ -281,7 +298,7 @@ public class KernelFunctionFromMethod extends DefaultKernelFunction { LOGGER.warn(formErrorMessage(method, parameter)); } - if (NO_DEFAULT_VALUE.equals(arg)) { + if (arg != null && NO_DEFAULT_VALUE.equals(arg.getValue())) { if (parameter.getName().matches("arg\\d")) { throw new AIException( AIException.ErrorCodes.INVALID_CONFIGURATION, 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 cb041612f8..6ba0d479f1 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 @@ -8,17 +8,23 @@ import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.TextAIService; import com.microsoft.semantickernel.chatcompletion.AuthorRole; import com.microsoft.semantickernel.chatcompletion.ChatCompletionService; +import com.microsoft.semantickernel.hooks.FunctionInvokedEvent; +import com.microsoft.semantickernel.hooks.FunctionInvokingEvent; +import com.microsoft.semantickernel.hooks.KernelHooks; +import com.microsoft.semantickernel.hooks.PromptRenderedEvent; +import com.microsoft.semantickernel.hooks.PromptRenderingEvent; import com.microsoft.semantickernel.orchestration.DefaultKernelFunction; +import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.KernelFunction; import com.microsoft.semantickernel.orchestration.KernelFunctionMetadata; import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariable; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableType; import com.microsoft.semantickernel.orchestration.contextvariables.ContextVariableTypes; -import com.microsoft.semantickernel.orchestration.FunctionResult; import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; import com.microsoft.semantickernel.services.AIServiceSelection; import com.microsoft.semantickernel.textcompletion.TextGenerationService; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -83,23 +89,36 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { ); } - private Flux> invokeInternalAsync( Kernel kernel, @Nullable KernelArguments arguments, + KernelHooks kernelHooks, ContextVariableType variableType) { + + PromptRenderingEvent preRenderingHookResult = kernelHooks + .executeHooks(new PromptRenderingEvent(this, arguments)); + return this .template - .renderAsync(kernel, arguments) + .renderAsync(kernel, preRenderingHookResult.getArguments()) .flatMapMany(prompt -> { + PromptRenderedEvent promptHookResult = kernelHooks + .executeHooks(new PromptRenderedEvent(this, arguments, prompt)); + prompt = promptHookResult.getPrompt(); + KernelArguments args = promptHookResult.getArguments(); + LOGGER.info("RENDERED PROMPT: \n{}", prompt); + FunctionInvokingEvent updateArguments = kernelHooks + .executeHooks(new FunctionInvokingEvent(this, args)); + args = updateArguments.getArguments(); + AIServiceSelection aiServiceSelection = kernel .getServiceSelector() .trySelectAIService( TextAIService.class, this, - arguments + args ); AIService client = aiServiceSelection.getService(); @@ -117,7 +136,8 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { .getChatMessageContentsAsync( prompt, executionSettings, - kernel) + kernel, + kernelHooks) .flatMapMany(Flux::fromIterable) .concatMap(chatMessageContent -> { if (chatMessageContent.getAuthorRole() @@ -157,8 +177,6 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { it.getMetadata() ); }); - return result; - } else if (client instanceof TextGenerationService) { result = ((TextGenerationService) client) .getTextContentsAsync( @@ -190,7 +208,18 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { } else { return Flux.error(new IllegalStateException("Unknown service type")); } - return result; + + return result + .map(it -> { + FunctionInvokedEvent updatedResult = kernelHooks + .executeHooks( + new FunctionInvokedEvent<>( + this, + arguments, + it)); + + return updatedResult.getResult(); + }); }) .doOnError( ex -> { @@ -228,12 +257,25 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { public Mono> invokeAsync( Kernel kernel, @Nullable KernelArguments arguments, + @Nullable KernelHooks kernelHooks, ContextVariableType variableType) { - return invokeInternalAsync(kernel, arguments, variableType) + if (kernelHooks == null) { + kernelHooks = kernel.getHookService(); + } + return invokeInternalAsync(kernel, arguments, kernelHooks, variableType) .take(1) .single(); } + @Override + public Mono> invokeAsync( + Kernel kernel, + @Nullable KernelArguments arguments, + ContextVariableType variableType) { + return invokeAsync(kernel, arguments, null, variableType); + } + + /* * Given a json string, invoke the tool specified in the json string. * At this time, the only tool we have is 'function'. @@ -327,12 +369,11 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { private PromptTemplate promptTemplate; private String name; - private String pluginName; private Map executionSettings; private String description; private List inputVariables; private String template; - private String templateFormat; + private String templateFormat = PromptTemplateConfig.SEMANTIC_KERNEL_TEMPLATE_FORMAT; private OutputVariable outputVariable; private PromptTemplateFactory promptTemplateFactory; @@ -345,7 +386,7 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { @Override public FromPromptBuilder withInputParameters(List inputVariables) { - this.inputVariables = inputVariables; + this.inputVariables = new ArrayList<>(inputVariables); return this; } @@ -355,12 +396,6 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { return this; } - @Override - public FromPromptBuilder withPluginName(String name) { - this.pluginName = name; - return this; - } - @Override public FromPromptBuilder withExecutionSettings( Map executionSettings) { @@ -381,7 +416,8 @@ public class KernelFunctionFromPrompt extends DefaultKernelFunction { this.executionSettings = new HashMap<>(); } - this.executionSettings.put("default", executionSettings); + this.executionSettings.put(PromptExecutionSettings.DEFAULT_SERVICE_ID, + executionSettings); if (executionSettings.getServiceId() != null) { this.executionSettings.put(executionSettings.getServiceId(), executionSettings); diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelPromptTemplateFactory.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelPromptTemplateFactory.java index 93fb1d17aa..e1ab221e10 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelPromptTemplateFactory.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/KernelPromptTemplateFactory.java @@ -5,15 +5,12 @@ import static com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfi import com.microsoft.semantickernel.templateengine.handlebars.HandlebarsPromptTemplate; import com.microsoft.semantickernel.templateengine.semantickernel.DefaultPromptTemplate; +import javax.annotation.Nonnull; import java.util.Locale; public class KernelPromptTemplateFactory implements PromptTemplateFactory { - public PromptTemplate tryCreate(PromptTemplateConfig templateConfig) { - if (templateConfig == null || templateConfig.getTemplateFormat() == null) { - return new DefaultPromptTemplate(templateConfig); - } - + public PromptTemplate tryCreate(@Nonnull PromptTemplateConfig templateConfig) { switch (templateConfig.getTemplateFormat().toLowerCase(Locale.ROOT)) { case SEMANTIC_KERNEL_TEMPLATE_FORMAT: return new DefaultPromptTemplate(templateConfig); 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 f1c2b80b70..08052e0164 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 @@ -16,11 +16,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import javax.annotation.Nullable; @JsonIgnoreProperties(ignoreUnknown = true) public class PromptTemplateConfig { public static final int CURRENT_SCHEMA = 1; + public static final String DEFAULT_CONFIG_NAME = "default"; private String name; private String template; @@ -40,7 +42,7 @@ public class PromptTemplateConfig { public PromptTemplateConfig(String template) { this( CURRENT_SCHEMA, - "default", + DEFAULT_CONFIG_NAME, template, SEMANTIC_KERNEL_TEMPLATE_FORMAT, "", @@ -58,7 +60,10 @@ public class PromptTemplateConfig { String name, @JsonProperty("template") String template, - @JsonProperty("template_format") + @Nullable + @JsonProperty( + value = "template_format", + defaultValue = SEMANTIC_KERNEL_TEMPLATE_FORMAT) String templateFormat, @JsonProperty("description") String description, @@ -70,6 +75,9 @@ public class PromptTemplateConfig { Map executionSettings) { this.name = name; this.template = template; + if (templateFormat == null) { + templateFormat = SEMANTIC_KERNEL_TEMPLATE_FORMAT; + } this.templateFormat = templateFormat; this.description = description; if (inputVariables == null) { @@ -78,7 +86,11 @@ public class PromptTemplateConfig { this.inputVariables = new ArrayList<>(inputVariables); } this.outputVariable = outputVariable; - this.executionSettings = executionSettings; + if (executionSettings == null) { + this.executionSettings = new HashMap<>(); + } else { + this.executionSettings = new HashMap<>(executionSettings); + } } /** @@ -107,6 +119,18 @@ public class PromptTemplateConfig { ); } + public PromptTemplateConfig(PromptTemplateConfig promptTemplate) { + this( + promptTemplate.name, + promptTemplate.template, + promptTemplate.templateFormat, + promptTemplate.description, + promptTemplate.inputVariables, + promptTemplate.outputVariable, + promptTemplate.executionSettings + ); + } + public List getKernelParametersMetadata() { if (inputVariables == null) { return Collections.emptyList(); @@ -134,98 +158,16 @@ public class PromptTemplateConfig { } public void addInputVariable(InputVariable inputVariable) { - if (inputVariables == null) { - inputVariables = new ArrayList<>(); - } inputVariables.add(inputVariable); } - - public static class ExecutionSettingsModel { - - @JsonProperty("model_id") - private String modelId; - - @JsonProperty("model_id_pattern") - private String modelIdPattern; - - @JsonProperty("service_id") - private String serviceId; - - @JsonProperty("temperature") - private String temperature; - - @JsonProperty("additional_properties") - private final Map additionalProperties; - - public Object get(String propertyName) { - return additionalProperties.get(propertyName); - } - - public void set(String propertyName, Object value) { - additionalProperties.put(propertyName, value); - } - - public ExecutionSettingsModel() { - this.additionalProperties = new HashMap<>(); - } - - public ExecutionSettingsModel( - String modelId, - String modelIdPattern, - String serviceId, - String temperature, - Map additionalProperties) { - this.modelId = modelId; - this.modelIdPattern = modelIdPattern; - this.serviceId = serviceId; - this.temperature = temperature; - this.additionalProperties = additionalProperties; - } - - public String getModelId() { - return modelId; - } - - public void setModelId(String modelId) { - this.modelId = modelId; - } - - public String getModelIdPattern() { - return modelIdPattern; - } - - public void setModelIdPattern(String modelIdPattern) { - this.modelIdPattern = modelIdPattern; - } - - public String getServiceId() { - return serviceId; - } - - public void setServiceId(String serviceId) { - this.serviceId = serviceId; - } - - public String getTemperature() { - return temperature; - } - - public void setTemperature(String temperature) { - this.temperature = temperature; - } - - public Map getAdditionalProperties() { - return additionalProperties; - } - } - public String getName() { return name; } - public void setName(String name) { + public PromptTemplateConfig setName(String name) { this.name = name; + return this; } public String getTemplate() { @@ -253,11 +195,11 @@ public class PromptTemplateConfig { } public List getInputVariables() { - return inputVariables; + return Collections.unmodifiableList(inputVariables); } public void setInputVariables(List inputVariables) { - this.inputVariables = inputVariables; + this.inputVariables = new ArrayList<>(inputVariables); } public OutputVariable getOutputVariable() { @@ -269,11 +211,12 @@ public class PromptTemplateConfig { } public Map getExecutionSettings() { - return executionSettings; + return Collections.unmodifiableMap(executionSettings); } + public void setExecutionSettings(Map executionSettings) { - this.executionSettings = executionSettings; + this.executionSettings = new HashMap<>(executionSettings); } public static PromptTemplateConfig parseFromJson(String json) throws SKException { diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelection.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelection.java index 54c5fd9196..715d1c8be3 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelection.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/services/AIServiceSelection.java @@ -7,32 +7,37 @@ import com.microsoft.semantickernel.orchestration.PromptExecutionSettings; /** * The result of an AI service selection. */ -public class AIServiceSelection { +public class AIServiceSelection { - private final AIService service; + private final T service; private final PromptExecutionSettings settings; /** * Creates a new AI service selection. - * @param service The selected AI service. - * @param settings The settings associated with the selected service. This may be null even if a service is selected.. + * + * @param service The selected AI service. + * @param settings The settings associated with the selected service. This may be null even if a + * service is selected.. */ - public AIServiceSelection(AIService service, PromptExecutionSettings settings) { + public AIServiceSelection(T service, PromptExecutionSettings settings) { this.service = service; this.settings = settings; } /** * Gets the selected AI service. + * * @return The selected AI service. */ - public AIService getService() { + public T getService() { return service; } /** * Gets the settings associated with the selected service. - * @return The settings associated with the selected service. This may be null even if a service is selected. + * + * @return The settings associated with the selected service. This may be null even if a service + * is selected. */ public PromptExecutionSettings getSettings() { return settings; 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 index b74b906f9f..e1045b3faf 100644 --- 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 @@ -26,8 +26,8 @@ public interface AIServiceSelector { * @return */ @Nullable - AIServiceSelection trySelectAIService( - Class serviceType, + AIServiceSelection trySelectAIService( + Class serviceType, @Nullable KernelFunction function, 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 index 540d4f3985..38da3ce09a 100644 --- 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 @@ -20,20 +20,20 @@ public abstract class BaseAIServiceSelector implements AIServiceSelector { @Override @Nullable - public AIServiceSelection trySelectAIService( - Class serviceType, - + public AIServiceSelection trySelectAIService( + Class serviceType, @Nullable KernelFunction function, @Nullable - KernelArguments arguments) { + KernelArguments arguments + ) { return trySelectAIService(serviceType, function, arguments, services); } @Nullable - public abstract AIServiceSelection trySelectAIService( - Class serviceType, + public abstract AIServiceSelection trySelectAIService( + Class serviceType, @Nullable KernelFunction function, 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 index 93e9f62ba9..2ed0391015 100644 --- 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 @@ -29,8 +29,8 @@ public class OrderedAIServiceSelector extends BaseAIServiceSelector { } @Nullable - public AIServiceSelection trySelectAIService( - Class serviceType, + public AIServiceSelection trySelectAIService( + Class serviceType, @Nullable KernelFunction function, @Nullable @@ -108,10 +108,9 @@ public class OrderedAIServiceSelector extends BaseAIServiceSelector { 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); + settings = executionSettings.values().stream().findFirst().orElseGet(() -> null); } if (service != null) { 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 b714606481..9f28ac6b5c 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 @@ -1,21 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.templateengine.handlebars; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; - -import javax.annotation.Nullable; - import com.github.jknack.handlebars.Context; import com.github.jknack.handlebars.Handlebars; +import com.github.jknack.handlebars.Options; import com.github.jknack.handlebars.ValueResolver; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.chatcompletion.ChatHistory; @@ -28,21 +16,28 @@ import com.microsoft.semantickernel.plugin.KernelPlugin; import com.microsoft.semantickernel.plugin.KernelPluginCollection; import com.microsoft.semantickernel.semanticfunctions.PromptTemplate; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; - +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import reactor.core.publisher.Mono; public class HandlebarsPromptTemplate implements PromptTemplate { - @Nullable - private PromptTemplateConfig promptTemplate; - - public HandlebarsPromptTemplate() { - this(null); - } + private final PromptTemplateConfig promptTemplate; public HandlebarsPromptTemplate( - @Nullable PromptTemplateConfig promptTemplate) { - this.promptTemplate = promptTemplate; + @Nonnull PromptTemplateConfig promptTemplate) { + this.promptTemplate = new PromptTemplateConfig(promptTemplate); } @@ -93,7 +88,7 @@ public class HandlebarsPromptTemplate implements PromptTemplate { @Override public Object resolve(Object context, String name) { - if (context instanceof KernelPluginCollection) { + if (context instanceof KernelPluginCollection) { return context; } if (context instanceof KernelFunction) { @@ -109,7 +104,7 @@ public class HandlebarsPromptTemplate implements PromptTemplate { @Override public Object resolve(Object context) { - if (context instanceof KernelPluginCollection) { + if (context instanceof KernelPluginCollection) { return ((KernelPluginCollection) context).iterator(); } return UNRESOLVED; @@ -163,98 +158,99 @@ public class HandlebarsPromptTemplate implements PromptTemplate { private final String template; private final Handlebars handlebars; + + @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")// Think this is a false positive public HandleBarsPromptTemplateHandler(Kernel kernel, String template) { this.kernel = kernel; this.template = template; this.handlebars = new Handlebars(); - this.handlebars.registerHelper( - "message", - (context, options) -> { - String role = options.hash("role"); - String content = (String) options.fn(context); + this.handlebars + .registerHelper("message", HandleBarsPromptTemplateHandler::handleMessage) + .registerHelper("each", HandleBarsPromptTemplateHandler::handleEach) + .registerHelper("functions", (context, options) -> handleFunctions(kernel, options)) + .registerHelper("function", + (context, options) -> handleFunction((KernelFunction) context)); + // TODO: 1.0 Add more helpers + } - if (context instanceof Optional) { - ChatMessageContent message = ((Optional) context).orElse(null); - if (message != null) { - if (role == null || role.isEmpty()) { - role = message.getAuthorRole().name(); - } - content = message.getContent(); - } - } + private static Handlebars.SafeString handleFunction(KernelFunction context) { + KernelFunction function = context; + String pluginName = function.getSkillName(); + String functionName = function.getName(); + String description = function.getDescription(); + StringBuilder sb = new StringBuilder( + String.format( + "", + pluginName, functionName, description)); + List parameters = function.getMetadata().getParameters(); + parameters.forEach(p -> { + sb.append(String.format( + "", + p.getName(), p.getDescription(), p.getDefaultValue(), p.isRequired(), + p.getType())); + }); + sb.append(""); + return new Handlebars.SafeString(sb.toString()); + } - if (role != null && !role.isEmpty()) { - return new Handlebars.SafeString( - String.format( - "%s", - role.toLowerCase(Locale.ROOT), content)); - } - return ""; + private static Handlebars.SafeString handleFunctions(Kernel kernel, Options options) + throws IOException { + StringBuilder sb = new StringBuilder(""); + KernelPluginCollection plugins = kernel.getPlugins(); + sb.append(options.fn(plugins)); + sb.append(""); + + return new Handlebars.SafeString(sb.toString()); + } + + private static CharSequence handleEach(Object context, Options options) + throws IOException { + if (context instanceof ChatHistory) { + StringBuilder sb = new StringBuilder(""); + for (ChatMessageContent message : (ChatHistory) context) { + sb.append(options.fn(message)); + } + sb.append(""); + return new Handlebars.SafeString(sb.toString()); + } + if (context instanceof KernelPluginCollection) { + StringBuilder sb = new StringBuilder(); + Iterator plugins = ((KernelPluginCollection) context).iterator(); + while (plugins.hasNext()) { + KernelPlugin plugin = plugins.next(); + Iterator functions = plugin.iterator(); + while (functions.hasNext()) { + KernelFunction function = functions.next(); + sb.append(options.fn(function)); } - ) + } + return new Handlebars.SafeString(sb.toString()); + } + return ""; + } - .registerHelper( - "each", - (context, options) -> { - if (context instanceof ChatHistory) { - StringBuilder sb = new StringBuilder(""); - for(ChatMessageContent message : (ChatHistory) context) { - sb.append(options.fn(message)); - } - sb.append(""); - return new Handlebars.SafeString(sb.toString()); - } - if (context instanceof KernelPluginCollection) { - StringBuilder sb = new StringBuilder(); - Iterator plugins = ((KernelPluginCollection) context).iterator(); - while (plugins.hasNext()) { - KernelPlugin plugin = plugins.next(); - Iterator functions = plugin.iterator(); - while (functions.hasNext()) { - KernelFunction function = functions.next(); - sb.append(options.fn(function)); - } - } - return new Handlebars.SafeString(sb.toString()); - } - return ""; - } - ) + private static CharSequence handleMessage(Object context, Options options) + throws IOException { + String role = options.hash("role"); + String content = (String) options.fn(context); - .registerHelper( - "functions", - (context, options) -> { - StringBuilder sb = new StringBuilder(""); - KernelPluginCollection plugins = kernel.getPlugins(); - sb.append(options.fn(plugins)); - sb.append(""); - - return new Handlebars.SafeString(sb.toString()); + if (context instanceof Optional) { + ChatMessageContent message = ((Optional) context).orElse(null); + if (message != null) { + if (role == null || role.isEmpty()) { + role = message.getAuthorRole().name(); } - ) + content = message.getContent(); + } + } - .registerHelper( - "function", - (context, options) -> { - KernelFunction function = (KernelFunction) context; - String pluginName = function.getSkillName(); - String functionName = function.getName(); - String description = function.getDescription(); - StringBuilder sb = new StringBuilder( - String.format( - "", - pluginName, functionName, description)); - List parameters = function.getMetadata().getParameters(); - parameters.forEach(p -> { - sb.append(String.format( - "", - p.getName(), p.getDescription(), p.getDefaultValue(), p.isRequired(), p.getType())); - }); - sb.append(""); - return new Handlebars.SafeString(sb.toString()); - } - ); - // TODO: 1.0 Add more helpers + if (role != null && !role.isEmpty()) { + return new Handlebars.SafeString( + String.format( + "%s", + role.toLowerCase(Locale.ROOT), content)); + } + return ""; } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/DefaultPromptTemplate.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/DefaultPromptTemplate.java index 1ff8cf8869..85787259c4 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/DefaultPromptTemplate.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/semantickernel/DefaultPromptTemplate.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -28,16 +29,11 @@ import reactor.core.publisher.Mono; /// public class DefaultPromptTemplate implements PromptTemplate { - @Nullable - private PromptTemplateConfig promptTemplate; - - public DefaultPromptTemplate() { - this(null); - } + private final PromptTemplateConfig promptTemplate; public DefaultPromptTemplate( - @Nullable PromptTemplateConfig promptTemplate) { - this.promptTemplate = promptTemplate; + @Nonnull PromptTemplateConfig promptTemplate) { + this.promptTemplate = new PromptTemplateConfig(promptTemplate); } /// 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 e3fcb48142..148a2c714b 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.FunctionResult; import com.microsoft.semantickernel.orchestration.contextvariables.KernelArguments; import com.microsoft.semantickernel.templateengine.semantickernel.TemplateException; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -123,7 +124,9 @@ public final class CodeBlock extends Block implements CodeRendering { arguments = this.enrichFunctionArguments(kernel, fBlock, arguments == null ? new DefaultKernelArguments() - : new DefaultKernelArguments(arguments, new HashMap<>())); + : new DefaultKernelArguments( + arguments, + new HashMap<>())); } return kernel diff --git a/java/spotbugs-exclude.xml b/java/spotbugs-exclude.xml index 180519797a..5fd588b2c4 100644 --- a/java/spotbugs-exclude.xml +++ b/java/spotbugs-exclude.xml @@ -1,119 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -