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 extends AIService> 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