From 152593ea03faa0113c39cbda48403ec042d0a0d2 Mon Sep 17 00:00:00 2001 From: John Oliver <1615532+johnoliver@users.noreply.github.com> Date: Wed, 5 Jul 2023 18:34:56 +0100 Subject: [PATCH] Java: Add sequential planner (#1526) ### Motivation and Context ### Description ### Contribution Checklist - [ ] The code builds clean without any errors or warnings - [ ] The PR follows SK Contribution Guidelines (https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) - [ ] The code follows the .NET coding conventions (https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/coding-conventions) verified with `dotnet format` - [ ] All unit tests pass, and I have added new tests where possible - [ ] I didn't break anyone :smile: --- .../src/main/java/InlineFunctionExample.java | 57 +- ...nlineFunctionWithPreBuiltSkillExample.java | 121 ++-- .../e2e/AbstractKernelTest.java | 29 +- .../e2e/ContextVariableFunctionTest.java | 17 +- .../semantickernel/e2e/FunSkillTest.java | 10 +- .../e2e/InlineFunctionTest.java | 8 +- .../semantickernel/e2e/KernelTest.java | 50 +- .../semantickernel/e2e/PlannerTest.java | 20 +- .../e2e/TextEmbeddingsTest.java | 239 ++++---- java/pom.xml | 7 +- java/samples/pom.xml | 109 ++++ .../com/microsoft/semantickernel/Config.java | 31 +- .../Example00_GettingStarted.java | 73 ++- .../Example02_RunnningPromptsFromFile.java | 45 +- .../Example03_SemanticFunctionInline.java | 17 +- .../Example04_ContextVariablesChat.java | 25 +- .../Example05_UsingThePlanner.java | 12 +- .../Example06_MemoryAndEmbeddings.java | 4 +- .../syntaxexamples/Example02_Pipeline.java | 2 +- .../syntaxexamples/Example03_Variables.java | 60 ++ ...mple04_CombineLLMPromptsAndNativeCode.java | 57 ++ .../Example05_InlineFunctionDefinition.java | 77 +++ .../Example06_TemplateLanguage.java | 145 ++--- .../Example08_RetryHandler.java | 137 ++--- .../Example09_FunctionTypes.java | 280 +++++++++ .../Example12_SequentialPlanner.java | 211 +++++++ .../Example13_ConversationSummarySkill.java | 10 +- .../syntaxexamples/Example17_ChatGPT.java | 2 +- .../Example28_ActionPlanner.java | 59 ++ .../semantickernel/syntaxexamples/RunAll.java | 32 + .../sample-code/src/main/resources/log4j2.xml | 20 + .../guice/SemanticKernelModule.java | 2 - .../guice/CompletionFunctionExample.java | 6 +- .../com/microsoft/semantickernel/Kernel.java | 56 +- .../semantickernel/KernelConfig.java | 41 +- .../semantickernel/SkillExecutor.java | 70 +++ .../com/microsoft/semantickernel/Verify.java | 6 + .../semantickernel/ai/AIException.java | 28 +- .../ai/embeddings/Embedding.java | 11 +- .../builders/BuildersSingleton.java | 125 ++-- .../builders/FunctionBuilders.java | 10 +- .../semantickernel/builders/SKBuilders.java | 9 +- .../builders/ServiceLoadUtil.java | 40 +- .../extensions/KernelExtensions.java | 29 +- .../semantickernel/memory/MemoryRecord.java | 2 +- .../memory/MemoryRecordMetadata.java | 2 +- .../memory/SemanticTextMemory.java | 28 +- .../orchestration/AbstractSKContext.java | 25 +- .../orchestration/AbstractSkFunction.java | 70 ++- .../orchestration/ContextVariables.java | 5 + .../FunctionNotRegisteredException.java | 14 +- .../InternalBuildersSingleton.java | 7 +- .../orchestration/SKContext.java | 54 +- .../orchestration/SKFunction.java | 120 +--- .../orchestration/SemanticSKContext.java | 5 - .../WritableContextVariables.java | 14 +- .../planner/PlanningException.java | 79 +++ .../semanticfunctions/PromptTemplate.java | 9 +- .../PromptTemplateConfig.java | 148 ++++- .../skilldefinition/FunctionView.java | 66 ++ .../skilldefinition/ParameterView.java | 4 + .../ReadOnlyFunctionCollection.java | 4 +- .../ReadOnlySkillCollection.java | 9 +- .../templateengine/PromptTemplateEngine.java | 12 + .../CompletionRequestSettings.java | 53 +- .../textcompletion/CompletionSKContext.java | 6 - .../textcompletion/CompletionSKFunction.java | 15 +- .../util/AzureOpenAISettings.java | 20 +- .../semantickernel/util/OpenAISettings.java | 12 +- java/semantickernel-bom/pom.xml | 13 +- .../chatcompletion/OpenAIChatCompletion.java | 7 +- .../textcompletion/OpenAITextCompletion.java | 5 +- .../OpenAITextEmbeddingGeneration.java | 3 +- .../AzureCognitiveSearchMemory.java | 42 +- .../AzureCognitiveSearchRecord.java | 16 +- .../coreskills/ConversationSummarySkill.java | 26 +- .../coreskills/FileIOSkill.java | 26 +- .../semantickernel/coreskills/MathSkill.java | 40 +- .../semantickernel/coreskills/TextSkill.java | 2 +- .../semantickernel/coreskills/TimeSkill.java | 18 +- .../coreskills/FileIOSkillTest.java | 17 +- .../coreskills/TimeSkillTest.java | 15 +- java/semantickernel-core/pom.xml | 5 + .../semantickernel/DefaultKernel.java | 115 +++- .../semantickernel/SkFunctionBuilders.java | 44 +- .../memory/DefaultSemanticTextMemory.java | 45 +- .../memory/VolatileMemoryStore.java | 6 +- .../DefaultCompletionSKContext.java | 36 -- .../DefaultCompletionSKFunction.java | 114 ++-- .../DefaultContextVariables.java | 29 + .../orchestration/DefaultSKContext.java | 105 ++++ .../DefaultSemanticSKContext.java | 45 -- .../DefaultSemanticSKFunction.java | 26 +- .../orchestration/NativeSKFunction.java | 134 +++-- .../DefaultPromptTemplate.java | 72 ++- .../skilldefinition/CaseInsensitiveMap.java | 30 +- .../DefaultSkillCollection.java | 27 +- .../skilldefinition/FunctionCollection.java | 30 +- .../DefaultPromptTemplateEngine.java | 2 +- .../templateengine/blocks/CodeBlock.java | 21 +- .../templateengine/blocks/CodeRendering.java | 18 +- .../blocks/FunctionIdBlock.java | 3 +- .../templateengine/blocks/VarBlock.java | 4 + ....semantickernel.memory.MemoryStore$Builder | 1 + ...el.orchestration.ContextVariables$Builder} | 0 ...ntickernel.orchestration.SKContext$Builder | 2 +- ...stration.WritableContextVariables$Builder} | 0 .../semantickernel/DefaultKernelTest.java | 92 ++- .../OpenAIChatCompletionTest.java | 10 - .../memory/VolatileMemoryStoreTests.java | 9 +- .../orchestration/NativeSKFunctionTest.java | 22 +- .../syntaxexamples/Example02PipelineTest.java | 2 +- .../Example03VariablesTest.java | 2 +- ...e04CombineLLMPromptsAndNativeCodeTest.java | 8 +- .../Example05UsingThePlannerTest.java | 17 +- .../Example06TemplateLanguageTest.java | 23 +- .../Example12SequentialPlanner.java | 196 ++++++ .../Example28ActionPlanner.java | 144 +++++ .../actionplanner-extension/pom.xml | 29 + .../actionplanner/ActionPlanResponse.java | 47 ++ .../planner/actionplanner/ActionPlanner.java | 339 +++++++++++ .../planner/actionplanner/Plan.java | 569 ++++++++++++++++++ .../planner/actionplanner/skprompt.txt | 11 + java/semantickernel-extensions-parent/pom.xml | 1 + .../sequentialplanner-extensions/pom.xml | 4 + .../planner/SequentialPlannerSKContext.java | 19 - .../DefaultSequentialPlannerSKContext.java | 46 +- .../SequentialPlanParser.java | 213 +++++++ .../sequentialplanner/SequentialPlanner.java | 97 +-- .../SequentialPlannerRequestSettings.java | 2 +- .../planner/sequentialplanner/skprompt.txt | 4 +- 131 files changed, 4670 insertions(+), 1562 deletions(-) create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example03_Variables.java create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example04_CombineLLMPromptsAndNativeCode.java create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example05_InlineFunctionDefinition.java create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example09_FunctionTypes.java create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example12_SequentialPlanner.java create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example28_ActionPlanner.java create mode 100644 java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/RunAll.java create mode 100644 java/samples/sample-code/src/main/resources/log4j2.xml create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/SkillExecutor.java delete mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SemanticSKContext.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/planner/PlanningException.java create mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/FunctionView.java delete mode 100644 java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionSKContext.java delete mode 100644 java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultCompletionSKContext.java create mode 100644 java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSKContext.java delete mode 100644 java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSemanticSKContext.java create mode 100644 java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.memory.MemoryStore$Builder rename java/semantickernel-core/src/main/resources/META-INF/services/{com.microsoft.semantickernel.orchestration.ContextVariables$Builder.java => com.microsoft.semantickernel.orchestration.ContextVariables$Builder} (100%) rename java/semantickernel-core/src/main/resources/META-INF/services/{com.microsoft.semantickernel.orchestration.WritableContextVariables$Builder.java => com.microsoft.semantickernel.orchestration.WritableContextVariables$Builder} (100%) create mode 100644 java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example12SequentialPlanner.java create mode 100644 java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example28ActionPlanner.java create mode 100644 java/semantickernel-extensions-parent/actionplanner-extension/pom.xml create mode 100644 java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/ActionPlanResponse.java create mode 100644 java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/ActionPlanner.java create mode 100644 java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/Plan.java create mode 100644 java/semantickernel-extensions-parent/actionplanner-extension/src/main/resources/com/microsoft/semantickernel/planner/actionplanner/skprompt.txt delete mode 100644 java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/SequentialPlannerSKContext.java create mode 100644 java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/SequentialPlanParser.java rename java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/{ => sequentialplanner}/SequentialPlannerRequestSettings.java (98%) diff --git a/java/api-test/api-usage-example/src/main/java/InlineFunctionExample.java b/java/api-test/api-usage-example/src/main/java/InlineFunctionExample.java index 916f3d42ee..5c83485d09 100644 --- a/java/api-test/api-usage-example/src/main/java/InlineFunctionExample.java +++ b/java/api-test/api-usage-example/src/main/java/InlineFunctionExample.java @@ -1,16 +1,4 @@ - // Copyright (c) Microsoft. All rights reserved. -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Properties; -import java.util.concurrent.CountDownLatch; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.azure.ai.openai.OpenAIAsyncClient; import com.azure.ai.openai.OpenAIClientBuilder; import com.azure.core.credential.AzureKeyCredential; @@ -21,6 +9,16 @@ import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import com.microsoft.semantickernel.textcompletion.TextCompletion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; + public class InlineFunctionExample { public static final String AZURE_CONF_PROPERTIES = "conf.properties"; private static final Logger LOGGER = LoggerFactory.getLogger(InlineFunctionExample.class); @@ -28,7 +26,8 @@ public class InlineFunctionExample { private static final String API_KEY; private static final String ENDPOINT; - private static final String TEXT_TO_SUMMARIZE = """ + private static final String TEXT_TO_SUMMARIZE = + """ Demo (ancient Greek poet) From Wikipedia, the free encyclopedia Demo or Damo (Greek: Δεμώ, Δαμώ; fl. c. AD 200) was a Greek woman of the @@ -94,28 +93,30 @@ public class InlineFunctionExample { } public static void main(String[] args) { - OpenAIAsyncClient client = new OpenAIClientBuilder() - .endpoint(ENDPOINT) - .credential(new AzureKeyCredential(API_KEY)) - .buildAsyncClient(); + OpenAIAsyncClient client = + new OpenAIClientBuilder() + .endpoint(ENDPOINT) + .credential(new AzureKeyCredential(API_KEY)) + .buildAsyncClient(); TextCompletion textCompletion = SKBuilders.textCompletionService().build(client, MODEL); String prompt = "{{$input}}\n" + "Summarize the content above."; - KernelConfig kernelConfig = new KernelConfig.Builder() - .addTextCompletionService(MODEL, kernel -> textCompletion) - .build(); + KernelConfig kernelConfig = + new KernelConfig.Builder() + .addTextCompletionService(MODEL, kernel -> textCompletion) + .build(); Kernel kernel = SKBuilders.kernel().setKernelConfig(kernelConfig).build(); - CompletionSKFunction summarize = kernel.getSemanticFunctionBuilder() - .createFunction( - prompt, - "summarize", - null, - null, - new PromptTemplateConfig.CompletionConfig( - 0.2, 0.5, 0, 0, 2000, new ArrayList<>())); + CompletionSKFunction summarize = + kernel.getSemanticFunctionBuilder() + .createFunction( + prompt, + "summarize", + null, + null, + new PromptTemplateConfig.CompletionConfig(0.2, 0.5, 0, 0, 2000)); if (summarize == null) { LOGGER.error("Null function"); diff --git a/java/api-test/api-usage-example/src/main/java/InlineFunctionWithPreBuiltSkillExample.java b/java/api-test/api-usage-example/src/main/java/InlineFunctionWithPreBuiltSkillExample.java index 5d7b75dfa6..bdf02ae5fe 100644 --- a/java/api-test/api-usage-example/src/main/java/InlineFunctionWithPreBuiltSkillExample.java +++ b/java/api-test/api-usage-example/src/main/java/InlineFunctionWithPreBuiltSkillExample.java @@ -1,16 +1,4 @@ - // Copyright (c) Microsoft. All rights reserved. -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Properties; -import java.util.concurrent.CountDownLatch; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.azure.ai.openai.OpenAIAsyncClient; import com.azure.ai.openai.OpenAIClientBuilder; import com.azure.core.credential.AzureKeyCredential; @@ -21,43 +9,55 @@ import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import com.microsoft.semantickernel.textcompletion.TextCompletion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; + public class InlineFunctionWithPreBuiltSkillExample { public static final String AZURE_CONF_PROPERTIES = "conf.properties"; - private static final Logger LOGGER = LoggerFactory.getLogger(InlineFunctionWithPreBuiltSkillExample.class); + private static final Logger LOGGER = + LoggerFactory.getLogger(InlineFunctionWithPreBuiltSkillExample.class); private static final String MODEL = "text-davinci-003"; private static final String API_KEY; private static final String ENDPOINT; - private static final String TEXT_TO_SUMMARIZE = """ - Demo (ancient Greek poet) - From Wikipedia, the free encyclopedia - Demo or Damo (Greek: Δεμώ, Δαμώ; fl. c. AD 200) was a Greek woman of the - Roman period, known for a single epigram, engraved upon the Colossus of - Memnon, which bears her name. She speaks of herself therein as a lyric - poetess dedicated to the Muses, but nothing is known of her life.[1] - Identity - Demo was evidently Greek, as her name, a traditional epithet of Demeter, - signifies. The name was relatively common in the Hellenistic world, in - Egypt and elsewhere, and she cannot be further identified. The date of her - visit to the Colossus of Memnon cannot be established with certainty, but - internal evidence on the left leg suggests her poem was inscribed there at - some point in or after AD 196.[2] - Epigram - There are a number of graffiti inscriptions on the Colossus of Memnon. - Following three epigrams by Julia Balbilla, a fourth epigram, in elegiac - couplets, entitled and presumably authored by "Demo" or "Damo" (the - Greek inscription is difficult to read), is a dedication to the Muses.[2] - The poem is traditionally published with the works of Balbilla, though the - internal evidence suggests a different author.[1] - In the poem, Demo explains that Memnon has shown her special respect. In - return, Demo offers the gift for poetry, as a gift to the hero. At the end - of this epigram, she addresses Memnon, highlighting his divine status by - recalling his strength and holiness.[2] - Demo, like Julia Balbilla, writes in the artificial and poetic Aeolic - dialect. The language indicates she was knowledgeable in Homeric - poetry—'bearing a pleasant gift', for example, alludes to the use of that - phrase throughout the Iliad and Odyssey.[a][2]; - """; + private static final String TEXT_TO_SUMMARIZE = + """ + Demo (ancient Greek poet) + From Wikipedia, the free encyclopedia + Demo or Damo (Greek: Δεμώ, Δαμώ; fl. c. AD 200) was a Greek woman of the + Roman period, known for a single epigram, engraved upon the Colossus of + Memnon, which bears her name. She speaks of herself therein as a lyric + poetess dedicated to the Muses, but nothing is known of her life.[1] + Identity + Demo was evidently Greek, as her name, a traditional epithet of Demeter, + signifies. The name was relatively common in the Hellenistic world, in + Egypt and elsewhere, and she cannot be further identified. The date of her + visit to the Colossus of Memnon cannot be established with certainty, but + internal evidence on the left leg suggests her poem was inscribed there at + some point in or after AD 196.[2] + Epigram + There are a number of graffiti inscriptions on the Colossus of Memnon. + Following three epigrams by Julia Balbilla, a fourth epigram, in elegiac + couplets, entitled and presumably authored by "Demo" or "Damo" (the + Greek inscription is difficult to read), is a dedication to the Muses.[2] + The poem is traditionally published with the works of Balbilla, though the + internal evidence suggests a different author.[1] + In the poem, Demo explains that Memnon has shown her special respect. In + return, Demo offers the gift for poetry, as a gift to the hero. At the end + of this epigram, she addresses Memnon, highlighting his divine status by + recalling his strength and holiness.[2] + Demo, like Julia Balbilla, writes in the artificial and poetic Aeolic + dialect. The language indicates she was knowledgeable in Homeric + poetry—'bearing a pleasant gift', for example, alludes to the use of that + phrase throughout the Iliad and Odyssey.[a][2]; + """; static { try { @@ -94,31 +94,30 @@ public class InlineFunctionWithPreBuiltSkillExample { } public static void main(String[] args) { - OpenAIAsyncClient client = new OpenAIClientBuilder() - .endpoint(ENDPOINT) - .credential(new AzureKeyCredential(API_KEY)) - .buildAsyncClient(); + OpenAIAsyncClient client = + new OpenAIClientBuilder() + .endpoint(ENDPOINT) + .credential(new AzureKeyCredential(API_KEY)) + .buildAsyncClient(); TextCompletion textCompletion = SKBuilders.textCompletionService().build(client, MODEL); String prompt = "{{$input}}\nSummarize the content above."; - CompletionSKFunction summarizeFunc = SKBuilders.completionFunctions() - .createFunction( - prompt, - "summarize", - null, - null, - new PromptTemplateConfig.CompletionConfig( - 0.2, 0.5, 0, 0, 2000, new ArrayList<>())); - - KernelConfig kernelConfig = new KernelConfig.Builder() - .addTextCompletionService(MODEL, kernel -> textCompletion) - .addSkill(summarizeFunc) - .build(); + KernelConfig kernelConfig = + new KernelConfig.Builder() + .addTextCompletionService(MODEL, kernel -> textCompletion) + .build(); Kernel kernel = SKBuilders.kernel().setKernelConfig(kernelConfig).build(); - CompletionSKFunction summarize = kernel.getSkills().getFunction("summarize", CompletionSKFunction.class); + CompletionSKFunction summarize = + SKBuilders.completionFunctions(kernel) + .createFunction( + prompt, + "summarize", + null, + null, + new PromptTemplateConfig.CompletionConfig(0.2, 0.5, 0, 0, 2000)); if (summarize == null) { LOGGER.error("Null function"); diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/AbstractKernelTest.java b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/AbstractKernelTest.java index 515a839e74..2fd4b1dbb7 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/AbstractKernelTest.java +++ b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/AbstractKernelTest.java @@ -11,6 +11,7 @@ import com.microsoft.semantickernel.builders.SKBuilders; import com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletion; import com.microsoft.semantickernel.memory.VolatileMemoryStore; import com.microsoft.semantickernel.textcompletion.TextCompletion; + import org.junit.jupiter.api.condition.EnabledIf; import java.io.File; @@ -29,12 +30,15 @@ public class AbstractKernelTest { final OpenAIAsyncClient openAIClient = getAzureOpenAIClient(); TextCompletion textCompletion = new OpenAITextCompletion(openAIClient, model); - KernelConfig kernelConfig = SKBuilders.kernelConfig() - .addTextCompletionService(model, kernel -> textCompletion) - .addTextEmbeddingsGenerationService( - model, - kernel -> SKBuilders.textEmbeddingGenerationService().build(openAIClient, model)) - .build(); + KernelConfig kernelConfig = + SKBuilders.kernelConfig() + .addTextCompletionService(model, kernel -> textCompletion) + .addTextEmbeddingsGenerationService( + model, + kernel -> + SKBuilders.textEmbeddingGenerationService() + .build(openAIClient, model)) + .build(); return SKBuilders.kernel() .setKernelConfig(kernelConfig) @@ -45,18 +49,17 @@ public class AbstractKernelTest { public static OpenAIAsyncClient getOpenAIClient() throws IOException { String apiKey = getToken(CONF_OPENAI_PROPERTIES); NonAzureOpenAIKeyCredential credential = new NonAzureOpenAIKeyCredential(apiKey); - return new OpenAIClientBuilder() - .credential(credential) - .buildAsyncClient(); + return new OpenAIClientBuilder().credential(credential).buildAsyncClient(); } public static OpenAIAsyncClient getAzureOpenAIClient() throws IOException { String apiKey = getToken(AZURE_CONF_PROPERTIES); - OpenAIAsyncClient client = new OpenAIClientBuilder() - .endpoint(getEndpoint(AZURE_CONF_PROPERTIES)) - .credential(new AzureKeyCredential(apiKey)) - .buildAsyncClient(); + OpenAIAsyncClient client = + new OpenAIClientBuilder() + .endpoint(getEndpoint(AZURE_CONF_PROPERTIES)) + .credential(new AzureKeyCredential(apiKey)) + .buildAsyncClient(); return client; } diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/ContextVariableFunctionTest.java b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/ContextVariableFunctionTest.java index 117998ef8a..3453861820 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/ContextVariableFunctionTest.java +++ b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/ContextVariableFunctionTest.java @@ -2,8 +2,9 @@ package com.microsoft.semantickernel.e2e; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import org.junit.jupiter.api.Test; @@ -14,7 +15,6 @@ import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; import java.io.IOException; -import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import java.util.function.Function; @@ -50,10 +50,9 @@ public class ContextVariableFunctionTest extends AbstractKernelTest { "ChatBot", null, null, - new PromptTemplateConfig.CompletionConfig( - 0.7, 0.5, 0, 0, 2000, new ArrayList<>())); + new PromptTemplateConfig.CompletionConfig(0.7, 0.5, 0, 0, 2000)); - CompletionSKContext readOnlySkContext = chat.buildContext(); + SKContext readOnlySkContext = SKBuilders.context().build(kernel); chat("Hi, I'm looking for book suggestions?", chat, readOnlySkContext) .flatMap( @@ -72,8 +71,7 @@ public class ContextVariableFunctionTest extends AbstractKernelTest { .block(); } - private Function> chat( - String input, CompletionSKFunction chat) { + private Function> chat(String input, CompletionSKFunction chat) { return (context) -> { try { return chat(input, chat, context); @@ -83,14 +81,13 @@ public class ContextVariableFunctionTest extends AbstractKernelTest { }; } - private Mono chat( - String input, CompletionSKFunction chat, CompletionSKContext context) + private Mono chat(String input, CompletionSKFunction chat, SKContext context) throws ExecutionException, InterruptedException, TimeoutException { context = context.setVariable("user_input", input); LOGGER.info("User:\n" + input); - CompletionSKContext finalContext = context; + SKContext finalContext = context; return chat.invokeAsync(context, null) .map( result -> { diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/FunSkillTest.java b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/FunSkillTest.java index 43df7a521d..b788f0e712 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/FunSkillTest.java +++ b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/FunSkillTest.java @@ -1,8 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.e2e; -import com.microsoft.semantickernel.extensions.KernelExtensions; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; +import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import org.junit.jupiter.api.Test; @@ -20,12 +19,9 @@ public class FunSkillTest extends AbstractKernelTest { @Test public void tellAJoke() throws IOException { - Mono result = + Mono result = buildTextCompletionKernel() - .importSkill( - "FunSkill", - KernelExtensions.importSemanticSkillFromDirectory( - "../../../samples/skills", "FunSkill")) + .importSkillFromDirectory("FunSkill", "../../../samples/skills") .getFunction("joke", CompletionSKFunction.class) .invokeAsync("time travel to dinosaur age"); diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/InlineFunctionTest.java b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/InlineFunctionTest.java index 87eb864459..6c941756d0 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/InlineFunctionTest.java +++ b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/InlineFunctionTest.java @@ -2,8 +2,8 @@ package com.microsoft.semantickernel.e2e; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import org.junit.jupiter.api.Test; @@ -14,7 +14,6 @@ import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; import java.io.IOException; -import java.util.ArrayList; public class InlineFunctionTest extends AbstractKernelTest { @@ -33,8 +32,7 @@ public class InlineFunctionTest extends AbstractKernelTest { "summarize", null, null, - new PromptTemplateConfig.CompletionConfig( - 0.2, 0.5, 0, 0, 2000, new ArrayList<>())); + new PromptTemplateConfig.CompletionConfig(0.2, 0.5, 0, 0, 2000)); String text = """ @@ -49,7 +47,7 @@ public class InlineFunctionTest extends AbstractKernelTest { Demo, like Julia Balbilla, writes in the artificial and poetic Aeolic dialect. The language indicates she was knowledgeable in Homeric poetry—'bearing a pleasant gift', for example, alludes to the use of that phrase throughout the Iliad and Odyssey.[a][2] """; - Mono result = summarize.invokeAsync(text); + Mono result = summarize.invokeAsync(text); LOGGER.info("Result: " + result.block().getResult()); } diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/KernelTest.java b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/KernelTest.java index 64a01c0b1c..e7524e8e5d 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/KernelTest.java +++ b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/KernelTest.java @@ -1,61 +1,61 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.e2e; -import java.io.IOException; -import java.util.ArrayList; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.EnabledIf; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.azure.ai.openai.OpenAIAsyncClient; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.KernelConfig; import com.microsoft.semantickernel.builders.SKBuilders; import com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletion; +import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import com.microsoft.semantickernel.textcompletion.TextCompletion; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import reactor.core.publisher.Mono; +import java.io.IOException; + public class KernelTest extends AbstractKernelTest { private static final Logger LOGGER = LoggerFactory.getLogger(KernelTest.class); private static void executeCompletion(Kernel kernel) { - CompletionSKFunction summarize = kernel.getSemanticFunctionBuilder() - .createFunction( - """ + CompletionSKFunction summarize = + kernel.getSemanticFunctionBuilder() + .createFunction( + """ {{$input}} One line TLDR with the fewest words.""", - null, - "", - null, - new PromptTemplateConfig.CompletionConfig( - 0, 0, 0, 0, 256, new ArrayList<>())); + null, + "", + null, + new PromptTemplateConfig.CompletionConfig(0, 0, 0, 0, 256)); - String text1 = """ + String text1 = + """ 1st Law of Thermodynamics - Energy cannot be created or destroyed. 2nd Law of Thermodynamics - For a spontaneous process, the entropy of the universe increases. 3rd Law of Thermodynamics - A perfect crystal at zero Kelvin has zero entropy"""; - Mono mono = summarize.invokeAsync(text1); - CompletionSKContext result = mono.block(); + Mono mono = summarize.invokeAsync(text1); + SKContext result = mono.block(); - if (result != null) - LOGGER.info("Result: " + result.getResult()); + if (result != null) LOGGER.info("Result: " + result.getResult()); } public static Kernel buildKernel(OpenAIAsyncClient client, String model) { TextCompletion textCompletion = new OpenAITextCompletion(client, model); - KernelConfig kernelConfig = SKBuilders.kernelConfig() - .addTextCompletionService(model, kernel -> textCompletion) - .build(); + KernelConfig kernelConfig = + SKBuilders.kernelConfig() + .addTextCompletionService(model, kernel -> textCompletion) + .build(); return SKBuilders.kernel().setKernelConfig(kernelConfig).build(); } diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/PlannerTest.java b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/PlannerTest.java index bffb2a3680..1788f4b0b8 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/PlannerTest.java +++ b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/PlannerTest.java @@ -2,9 +2,9 @@ package com.microsoft.semantickernel.e2e; import com.microsoft.semantickernel.Kernel; -import com.microsoft.semantickernel.extensions.KernelExtensions; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.planner.actionplanner.Plan; import com.microsoft.semantickernel.planner.sequentialplanner.SequentialPlanner; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; @@ -21,21 +21,19 @@ public class PlannerTest extends AbstractKernelTest { @EnabledIf("isAzureTestEnabled") public void executeInlineFunction() throws IOException { Kernel kernel = buildTextCompletionKernel(); - kernel.importSkill( - "SummarizeSkill", - KernelExtensions.importSemanticSkillFromDirectory( - "../../../samples/skills", "SummarizeSkill")); - kernel.importSkill( - "WriterSkill", - KernelExtensions.importSemanticSkillFromDirectory( - "../../../samples/skills", "WriterSkill")); + kernel.importSkillFromDirectory( + "SummarizeSkill", "../../../samples/skills", "SummarizeSkill"); + kernel.importSkillFromDirectory("WriterSkill", "../../../samples/skills", "WriterSkill"); SequentialPlanner planner = new SequentialPlanner(kernel, null, null); - CompletionSKContext result = + Plan plan = planner.createPlanAsync( "Write a poem about John Doe, then translate it into Italian.") .block(); + + SKContext result = plan.invokeAsync().block(); + LOGGER.info("Result: " + result.getResult()); } } diff --git a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/TextEmbeddingsTest.java b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/TextEmbeddingsTest.java index 2220e4efc5..5de815a0d9 100644 --- a/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/TextEmbeddingsTest.java +++ b/java/api-test/integration-tests/src/test/java/com/microsoft/semantickernel/e2e/TextEmbeddingsTest.java @@ -1,9 +1,16 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.e2e; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import com.azure.ai.openai.OpenAIAsyncClient; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.connectors.ai.openai.textembeddings.OpenAITextEmbeddingGeneration; +import com.microsoft.semantickernel.coreskills.TextMemorySkill; +import com.microsoft.semantickernel.memory.MemoryQueryResult; +import com.microsoft.semantickernel.memory.SemanticTextMemory; +import com.microsoft.semantickernel.memory.VolatileMemoryStore; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -11,155 +18,141 @@ import org.junit.jupiter.api.condition.EnabledIf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.azure.ai.openai.OpenAIAsyncClient; -import com.microsoft.semantickernel.Kernel; -import com.microsoft.semantickernel.KernelConfig; -import com.microsoft.semantickernel.builders.SKBuilders; -import com.microsoft.semantickernel.connectors.ai.openai.textembeddings.OpenAITextEmbeddingGeneration; -import com.microsoft.semantickernel.coreskills.TextMemorySkill; -import com.microsoft.semantickernel.memory.MemoryQueryResult; -import com.microsoft.semantickernel.memory.SemanticTextMemory; -import com.microsoft.semantickernel.memory.VolatileMemoryStore; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; -import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; public class TextEmbeddingsTest extends AbstractKernelTest { - private static final Logger LOGGER = LoggerFactory.getLogger(TextEmbeddingsTest.class); - private static final int EXPECTED_EMBEDDING_SIZE = 1536; + private static final Logger LOGGER = LoggerFactory.getLogger(TextEmbeddingsTest.class); + private static final int EXPECTED_EMBEDDING_SIZE = 1536; - @Test - @EnabledIf("isOpenAIComTestEnabled") - public void testEmbeddingGenerationOpenAI() throws IOException { - testEmbeddingGeneration(getOpenAIClient(), EXPECTED_EMBEDDING_SIZE); - } + @Test + @EnabledIf("isOpenAIComTestEnabled") + public void testEmbeddingGenerationOpenAI() throws IOException { + testEmbeddingGeneration(getOpenAIClient(), EXPECTED_EMBEDDING_SIZE); + } - @Test - @EnabledIf("isAzureTestEnabled") - public void testEmbeddingGenerationAzure() throws IOException { - testEmbeddingGeneration(getAzureOpenAIClient(), EXPECTED_EMBEDDING_SIZE); - } + @Test + @EnabledIf("isAzureTestEnabled") + public void testEmbeddingGenerationAzure() throws IOException { + testEmbeddingGeneration(getAzureOpenAIClient(), EXPECTED_EMBEDDING_SIZE); + } - @Test - @EnabledIf("isAzureTestEnabled") - public void testEmbeddingGeneration() throws IOException { - String model = "text-embedding-ada-002"; - OpenAITextEmbeddingGeneration embeddingGeneration = new OpenAITextEmbeddingGeneration(getOpenAIClient(), - model); + @Test + @EnabledIf("isAzureTestEnabled") + public void testEmbeddingGeneration() throws IOException { + String model = "text-embedding-ada-002"; + OpenAITextEmbeddingGeneration embeddingGeneration = + new OpenAITextEmbeddingGeneration(getOpenAIClient(), model); - List data = new ArrayList<>(); - data.add("This is just a test"); + List data = new ArrayList<>(); + data.add("This is just a test"); - LOGGER.info(String.valueOf(embeddingGeneration.generateEmbeddingsAsync(data).block())); - } + LOGGER.info(String.valueOf(embeddingGeneration.generateEmbeddingsAsync(data).block())); + } @Test @EnabledIf("isAzureTestEnabled") public void testMemory() throws IOException { - Kernel kernel = buildTextCompletionKernel(); - kernel.importSkill(new TextMemorySkill(), "aboutMe"); + Kernel kernel = buildTextCompletionKernel(); + kernel.importSkill(new TextMemorySkill(), "aboutMe"); - String skPrompt = """ + String skPrompt = + """ - ChatBot can have a conversation with you about any topic. - It can give explicit instructions or say 'I don't know' if it does not have an answer. + ChatBot can have a conversation with you about any topic. + It can give explicit instructions or say 'I don't know' if it does not have an answer. - Information about me, from previous conversations: - - {{$fact1}} {{recall $fact1}} - - {{$fact2}} {{recall $fact2}} - - {{$fact3}} {{recall $fact3}} - - {{$fact4}} {{recall $fact4}} - - {{$fact5}} {{recall $fact5}} + Information about me, from previous conversations: + - {{$fact1}} {{recall $fact1}} + - {{$fact2}} {{recall $fact2}} + - {{$fact3}} {{recall $fact3}} + - {{$fact4}} {{recall $fact4}} + - {{$fact5}} {{recall $fact5}} - Chat: - {{$history}} - User: {{$userInput}} - ChatBot:\s"""; + Chat: + {{$history}} + User: {{$userInput}} + ChatBot:\s"""; - CompletionSKFunction chat = kernel.getSemanticFunctionBuilder() - .createFunction( - skPrompt, "recall", "aboutMe", "TextEmbeddingTest#testMemory"); + CompletionSKFunction chat = + kernel.getSemanticFunctionBuilder() + .createFunction( + skPrompt, "recall", "aboutMe", "TextEmbeddingTest#testMemory"); - VolatileMemoryStore volatileMemoryStore = new VolatileMemoryStore(); - volatileMemoryStore.createCollectionAsync("aboutMe").block(); + VolatileMemoryStore volatileMemoryStore = new VolatileMemoryStore(); + volatileMemoryStore.createCollectionAsync("aboutMe").block(); - OpenAITextEmbeddingGeneration embeddingGeneration = new OpenAITextEmbeddingGeneration( - getAzureOpenAIClient(), "text-embedding-ada-002"); + OpenAITextEmbeddingGeneration embeddingGeneration = + new OpenAITextEmbeddingGeneration(getAzureOpenAIClient(), "text-embedding-ada-002"); - SemanticTextMemory memory = SKBuilders.semanticTextMemory() - .setEmbeddingGenerator(embeddingGeneration) - .setStorage(volatileMemoryStore) - .build(); + SemanticTextMemory memory = + SKBuilders.semanticTextMemory() + .setEmbeddingGenerator(embeddingGeneration) + .setStorage(volatileMemoryStore) + .build(); - CompletionSKContext context = chat.buildContext(SKBuilders.variables().build(), memory, - kernel.getSkills()); + SKContext context = + SKBuilders.context() + .with(SKBuilders.variables().build()) + .with(memory) + .with(kernel.getSkills()) + .build(); - context.getSemanticMemory() - .saveInformationAsync("aboutMe", "My name is Andrea", "fact1", null, null) - .block(); + context.getSemanticMemory() + .saveInformationAsync("aboutMe", "My name is Andrea", "fact1", null, null) + .block(); - context.getSemanticMemory() - .saveInformationAsync( - "aboutMe", "I currently work as a tour guide", "fact2", null, null) - .block(); + context.getSemanticMemory() + .saveInformationAsync( + "aboutMe", "I currently work as a tour guide", "fact2", null, null) + .block(); - context.getSemanticMemory() - .saveInformationAsync( - "aboutMe", "I've been living in Seattle since 2005", "fact3", null, - null) - .block(); + context.getSemanticMemory() + .saveInformationAsync( + "aboutMe", "I've been living in Seattle since 2005", "fact3", null, null) + .block(); - context.getSemanticMemory() - .saveInformationAsync( - "aboutMe", - "I visited France and Italy five times since 2015", - "fact4", - null, - null) - .block(); + context.getSemanticMemory() + .saveInformationAsync( + "aboutMe", + "I visited France and Italy five times since 2015", + "fact4", + null, + null) + .block(); - context.getSemanticMemory() - .saveInformationAsync("aboutMe", "My family is from New York", "fact5", null, null) - .block(); + context.getSemanticMemory() + .saveInformationAsync("aboutMe", "My family is from New York", "fact5", null, null) + .block(); - String query = "Where are you from originally?"; - List results = memory.searchAsync("aboutMe", query, 10, .5, false).block(); - results.forEach( - result -> System.out.printf( - "%s %s (relevance=%f)%n", - query, result.getMetadata().getText(), result.getRelevance())); - } + String query = "Where are you from originally?"; + List results = + memory.searchAsync("aboutMe", query, 10, .5, false).block(); + results.forEach( + result -> + System.out.printf( + "%s %s (relevance=%f)%n", + query, result.getMetadata().getText(), result.getRelevance())); + } - public void testEmbeddingGeneration(OpenAIAsyncClient client, int expectedEmbeddingSize) { - String model = "text-embedding-ada-002"; - OpenAITextEmbeddingGeneration embeddingGeneration = new OpenAITextEmbeddingGeneration(client, model); + public void testEmbeddingGeneration(OpenAIAsyncClient client, int expectedEmbeddingSize) { + String model = "text-embedding-ada-002"; + OpenAITextEmbeddingGeneration embeddingGeneration = + new OpenAITextEmbeddingGeneration(client, model); - List data = new ArrayList<>(); - data.add("This is just"); - data.add("a test"); + List data = new ArrayList<>(); + data.add("This is just"); + data.add("a test"); - embeddingGeneration - .generateEmbeddingsAsync(data) - .block() - .forEach( - embedding -> Assertions.assertEquals( - expectedEmbeddingSize, embedding.getVector().size())); - } - - private Kernel buildTextEmbeddingsKernel() throws IOException { - String model = "text-embedding-ada-002"; - OpenAITextEmbeddingGeneration embeddingGeneration = new OpenAITextEmbeddingGeneration(getOpenAIClient(), - model); - - KernelConfig kernelConfig = SKBuilders.kernelConfig() - .addTextEmbeddingsGenerationService(model, kernel -> embeddingGeneration) - .build(); - - // TODO: .WithMemoryStorage(new VolatileMemoryStore()) - - // TODO: .WithMemoryStorage(new VolatileMemoryStore()) - - return SKBuilders.kernel().setKernelConfig(kernelConfig).build(); - } + embeddingGeneration + .generateEmbeddingsAsync(data) + .block() + .forEach( + embedding -> + Assertions.assertEquals( + expectedEmbeddingSize, embedding.getVector().size())); + } } diff --git a/java/pom.xml b/java/pom.xml index 080af27679..6dab4acac4 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -12,7 +12,7 @@ https://www.github.com/microsoft/semantic-kernel - 1.0.0-beta.1 + 1.0.0-beta.2 10.11.0 0.10.10 2.19.1 @@ -131,6 +131,11 @@ sequentialplanner-extensions ${project.version} + + com.microsoft.semantickernel.extensions + actionplanner-extension + ${project.version} + diff --git a/java/samples/pom.xml b/java/samples/pom.xml index 5eb1f46b66..96d2b50856 100644 --- a/java/samples/pom.xml +++ b/java/samples/pom.xml @@ -35,4 +35,113 @@ + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler-plugin.version} + + ${maven.compiler.release} + ${maven.compiler.release} + ${maven.compiler.release} + + + + + + + + com.microsoft.semantickernel + semantickernel-api + + + com.microsoft.semantickernel.connectors + semantickernel-connectors + + + com.microsoft.semantickernel + semantickernel-core + runtime + + + com.microsoft.semantickernel + semantickernel-core-skills + ${project.version} + + + org.apache.logging.log4j + log4j-api + ${log4j2.version} + + + org.apache.logging.log4j + log4j-core + ${log4j2.version} + + + org.apache.logging.log4j + log4j-slf4j2-impl + ${log4j2.version} + + + com.microsoft.semantickernel.extensions + sequentialplanner-extensions + compile + + + com.microsoft.semantickernel.extensions + actionplanner-extension + compile + + + + + + bug-check + + + false + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven.compiler-plugin.version} + + ${maven.compiler.release} + ${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} + + + + + + + + diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Config.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Config.java index 6b3c792d7b..c53f75bb83 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Config.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Config.java @@ -16,11 +16,24 @@ public class Config { public static final String CONF_PROPERTIES = System.getProperty("CONF_PROPERTIES", "java/samples/conf.properties"); + public static final String OPENAI_CLIENT_TYPE = + System.getProperty("OPENAI_CLIENT_TYPE", "OPEN_AI"); + + public static OpenAIAsyncClient getClient() throws IOException { + return ClientType.valueOf(OPENAI_CLIENT_TYPE).getClient(); + } + public enum ClientType { OPEN_AI { @Override public OpenAIAsyncClient getClient() throws IOException { - OpenAISettings settings = AIProviderSettings.getOpenAISettingsFromFile(CONF_PROPERTIES); + return getClient(CONF_PROPERTIES); + } + + @Override + public OpenAIAsyncClient getClient(String file) throws IOException { + OpenAISettings settings = AIProviderSettings.getOpenAISettingsFromFile(file); + return new OpenAIClientBuilder() .credential(new NonAzureOpenAIKeyCredential(settings.getKey())) .buildAsyncClient(); @@ -28,9 +41,14 @@ public class Config { }, AZURE_OPEN_AI { @Override - public OpenAIAsyncClient getClient() + public OpenAIAsyncClient getClient() throws IOException { + return getClient(CONF_PROPERTIES); + } + + @Override + public OpenAIAsyncClient getClient(String file) throws IOException { - AzureOpenAISettings settings = AIProviderSettings.getAzureOpenAISettingsFromFile(CONF_PROPERTIES); + AzureOpenAISettings settings = AIProviderSettings.getAzureOpenAISettingsFromFile(file); return new OpenAIClientBuilder() .endpoint(settings.getEndpoint()) @@ -40,5 +58,12 @@ public class Config { }; public abstract OpenAIAsyncClient getClient() throws IOException; + + /** + * Returns the client that will handle AzureOpenAI or OpenAI requests. + * + * @return client to be used by the kernel. + */ + public abstract OpenAIAsyncClient getClient(String file) throws IOException; } } diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example00_GettingStarted.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example00_GettingStarted.java index 1b6d279340..641d303673 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example00_GettingStarted.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example00_GettingStarted.java @@ -4,9 +4,8 @@ import java.io.IOException; import com.azure.ai.openai.OpenAIAsyncClient; import com.microsoft.semantickernel.builders.SKBuilders; -import com.microsoft.semantickernel.extensions.KernelExtensions; +import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.skilldefinition.ReadOnlyFunctionCollection; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import reactor.core.publisher.Mono; @@ -24,54 +23,52 @@ import reactor.core.publisher.Mono; */ public class Example00_GettingStarted { - /** - * Returns a Semantic Kernel with Text Completion. - * - * @param client Client that will handle requests to AzureOpenAI or OpenAI. - * @return - */ - public static Kernel getKernel(OpenAIAsyncClient client) { - KernelConfig config = SKBuilders.kernelConfig() + /** + * Returns a Semantic Kernel with Text Completion. + * + * @param client Client that will handle requests to AzureOpenAI or OpenAI. + * @return + */ + public static Kernel getKernel(OpenAIAsyncClient client) { + KernelConfig config = SKBuilders.kernelConfig() .addTextCompletionService("davinci", kernel -> SKBuilders.textCompletionService().build(client, "text-davinci-003")) - .build(); + .build(); - Kernel kernel = SKBuilders.kernel() - .setKernelConfig(config) - .build(); + Kernel kernel = SKBuilders.kernel() + .setKernelConfig(config) + .build(); - return kernel; - } + return kernel; + } - /** + /** * Imports 'FunSkill' from directory examples and runs the 'Joke' function * within it. - * - * @param kernel Kernel with Text Completion. - */ - public static void joke(Kernel kernel) { + * + * @param kernel Kernel with Text Completion. + */ + public static void joke(Kernel kernel) { - ReadOnlyFunctionCollection skill = kernel - .importSkill("FunSkill", KernelExtensions.importSemanticSkillFromDirectory( - "samples/skills", "FunSkill")); + ReadOnlyFunctionCollection skill = kernel.importSkillFromDirectory("FunSkill", "samples/skills", "FunSkill"); - CompletionSKFunction function = skill.getFunction("Joke", - CompletionSKFunction.class); + CompletionSKFunction function = skill.getFunction("Joke", + CompletionSKFunction.class); - Mono result = function.invokeAsync("time travel to dinosaur age"); + Mono result = function.invokeAsync("time travel to dinosaur age"); - if (result != null) { - System.out.println(result.block().getResult()); + if (result != null) { + System.out.println(result.block().getResult()); + } } - } - public static void run(Config.ClientType clientType) throws IOException { - Kernel kernel = getKernel(clientType.getClient()); - joke(kernel); - } + public static void run(Config.ClientType clientType) throws IOException { + Kernel kernel = getKernel(clientType.getClient()); + joke(kernel); + } - public static void main(String args[]) throws IOException { - // Send one of Config.ClientType.OPEN_AI or Config.ClientType.AZURE_OPEN_AI - run(Config.ClientType.OPEN_AI); - } + public static void main(String args[]) throws IOException { + // Send one of Config.ClientType.OPEN_AI or Config.ClientType.AZURE_OPEN_AI + run(Config.ClientType.OPEN_AI); + } } diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example02_RunnningPromptsFromFile.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example02_RunnningPromptsFromFile.java index 506586715d..405b5c3191 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example02_RunnningPromptsFromFile.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example02_RunnningPromptsFromFile.java @@ -1,12 +1,10 @@ package com.microsoft.semantickernel; -import java.io.IOException; - -import com.azure.ai.openai.OpenAIAsyncClient; -import com.microsoft.semantickernel.extensions.KernelExtensions; import com.microsoft.semantickernel.skilldefinition.ReadOnlyFunctionCollection; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; +import java.io.IOException; + /** * Using Semantic Functions stored on disk * @@ -22,30 +20,29 @@ import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; */ public class Example02_RunnningPromptsFromFile { - /** + /** * Imports skill 'FunSkill' stored in the samples folder and then returns the * semantic function 'Joke' within it. - * - * @param kernel Kernel with Text Completion - * @return Joke function - */ - public static CompletionSKFunction getJokeFunction(Kernel kernel) { - ReadOnlyFunctionCollection skill = kernel - .importSkill("FunSkill", KernelExtensions.importSemanticSkillFromDirectory( - "samples/skills", "FunSkill")); + * + * @param kernel Kernel with Text Completion + * @return Joke function + */ + public static CompletionSKFunction getJokeFunction(Kernel kernel) { + ReadOnlyFunctionCollection skill = kernel + .importSkillFromDirectory("FunSkill", "samples/skills", "FunSkill"); - return skill.getFunction("Joke", CompletionSKFunction.class); - } + return skill.getFunction("Joke", CompletionSKFunction.class); + } - public static void run(Config.ClientType clientType) throws IOException { - Kernel kernel = Example00_GettingStarted.getKernel(clientType.getClient()); - CompletionSKFunction jokeFunction = getJokeFunction(kernel); + public static void run(Config.ClientType clientType) throws IOException { + Kernel kernel = Example00_GettingStarted.getKernel(clientType.getClient()); + CompletionSKFunction jokeFunction = getJokeFunction(kernel); - System.out.println(jokeFunction.invokeAsync("time travel to dinosaur age").block().getResult()); - } + System.out.println(jokeFunction.invokeAsync("time travel to dinosaur age").block().getResult()); + } - public static void main(String args[]) throws IOException { - // Send one of Config.ClientType.OPEN_AI or Config.ClientType.AZURE_OPEN_AI - run(Config.ClientType.OPEN_AI); - } + public static void main(String args[]) throws IOException { + // Send one of Config.ClientType.OPEN_AI or Config.ClientType.AZURE_OPEN_AI + run(Config.ClientType.OPEN_AI); + } } diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example03_SemanticFunctionInline.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example03_SemanticFunctionInline.java index db984fab72..9bdc7b8f63 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example03_SemanticFunctionInline.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example03_SemanticFunctionInline.java @@ -1,15 +1,13 @@ package com.microsoft.semantickernel; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.orchestration.SKFunction; +import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; +import reactor.core.publisher.Mono; + import java.io.IOException; import java.util.ArrayList; -import com.azure.ai.openai.OpenAIAsyncClient; -import com.microsoft.semantickernel.orchestration.SKFunction; -import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; - -import reactor.core.publisher.Mono; - /** * Define a Semantic Function inline with Java code. */ @@ -36,10 +34,9 @@ public class Example03_SemanticFunctionInline { 0.5, 0, 0, - 2000, - new ArrayList<>())); + 2000)); - Mono result = summarize.invokeAsync(text); + Mono result = summarize.invokeAsync(text); if (result != null) { System.out.println(result.block().getResult()); diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example04_ContextVariablesChat.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example04_ContextVariablesChat.java index b2efae5f94..0a773da15f 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example04_ContextVariablesChat.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example04_ContextVariablesChat.java @@ -1,17 +1,17 @@ package com.microsoft.semantickernel; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; +import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; +import reactor.core.publisher.Mono; + import java.io.IOException; import java.util.ArrayList; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; import java.util.function.Function; -import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; -import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; - -import reactor.core.publisher.Mono; - /** * Chatbot using context variables * @@ -43,10 +43,9 @@ public class Example04_ContextVariablesChat { 0.5, 0, 0, - 2000, - new ArrayList<>())); + 2000)); - CompletionSKContext readOnlySkContext = chat.buildContext(); + SKContext readOnlySkContext = SKBuilders.context().build(kernel); chat("Hi, I'm looking for book suggestions?", chat, readOnlySkContext) .flatMap( @@ -65,14 +64,14 @@ public class Example04_ContextVariablesChat { .block(); } - private static Mono chat( - String input, CompletionSKFunction chat, CompletionSKContext context) + private static Mono chat( + String input, CompletionSKFunction chat, SKContext context) throws ExecutionException, InterruptedException, TimeoutException { context = context.setVariable("user_input", input); System.out.println("User: " + input); - CompletionSKContext finalContext = context; + SKContext finalContext = context; return chat.invokeAsync(context, null) .map( result -> { @@ -87,7 +86,7 @@ public class Example04_ContextVariablesChat { }); } - private static Function> chat( + private static Function> chat( String input, CompletionSKFunction chat) { return (context) -> { try { diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example05_UsingThePlanner.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example05_UsingThePlanner.java index 05314283fe..9055ebd830 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example05_UsingThePlanner.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example05_UsingThePlanner.java @@ -1,7 +1,5 @@ package com.microsoft.semantickernel; -import com.azure.ai.openai.OpenAIAsyncClient; -import com.microsoft.semantickernel.extensions.KernelExtensions; import com.microsoft.semantickernel.planner.sequentialplanner.SequentialPlanner; import java.io.IOException; @@ -9,21 +7,19 @@ import java.io.IOException; public class Example05_UsingThePlanner { public static SequentialPlanner getPlanner(Kernel kernel) { - kernel.importSkill("SummarizeSkill", KernelExtensions.importSemanticSkillFromDirectory( - "samples/skills", "SummarizeSkill")); - kernel.importSkill("WriterSkill", KernelExtensions.importSemanticSkillFromDirectory( - "samples/skills", "WriterSkill")); + kernel.importSkillFromDirectory("SummarizeSkill", "samples/skills", "SummarizeSkill"); + kernel.importSkillFromDirectory("WriterSkill", "samples/skills", "WriterSkill"); return new SequentialPlanner(kernel, null, null); } - public static void run (Config.ClientType clientType) throws IOException { + public static void run(Config.ClientType clientType) throws IOException { Kernel kernel = Example00_GettingStarted.getKernel(clientType.getClient()); SequentialPlanner planner = getPlanner(kernel); System.out.println(planner.createPlanAsync( "Write a poem about John Doe, then translate it into Italian.") - .block().getResult()); + .block().invokeAsync().block()); // TODO: execute the plan } diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example06_MemoryAndEmbeddings.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example06_MemoryAndEmbeddings.java index c3c463ed10..052191e79d 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example06_MemoryAndEmbeddings.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/Example06_MemoryAndEmbeddings.java @@ -11,9 +11,7 @@ public class Example06_MemoryAndEmbeddings { KernelConfig config = SKBuilders.kernelConfig() .addTextCompletionService( "davinci", kernel -> SKBuilders.textCompletionService().build(client, "text-davinci-003")) - .addTextEmbeddingsGenerationService( - "embeddings", kernel -> SKBuilders.textEmbeddingGenerationService().build(client, "text-embedding-ada-002")) - .build(); + .build(); // TODO: Add Volatile memory diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example02_Pipeline.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example02_Pipeline.java index bb32dac071..6996c0ed7c 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example02_Pipeline.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example02_Pipeline.java @@ -16,7 +16,7 @@ public class Example02_Pipeline { // Load native skill ReadOnlyFunctionCollection text = kernel.importSkill(new TextSkill(), null); - Mono> result = + Mono result = kernel.runAsync( " i n f i n i t e s p a c e ", text.getFunction("LStrip"), diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example03_Variables.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example03_Variables.java new file mode 100644 index 0000000000..ba12ef73a3 --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example03_Variables.java @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.syntaxexamples; + +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.KernelConfig; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.orchestration.ContextVariables; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.skilldefinition.ReadOnlyFunctionCollection; +import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction; +import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute; +import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters; +import reactor.core.publisher.Mono; + +import java.util.Locale; + +public class Example03_Variables { + + public static class StaticTextSkill { + @DefineSKFunction(description = "Change all string chars to uppercase.", name = "Uppercase") + public static Mono uppercase( + @SKFunctionInputAttribute + @SKFunctionParameters(description = "Text to uppercase", name = "input") + String text) { + return Mono.just(text.toUpperCase(Locale.ROOT)); + } + + @DefineSKFunction(description = "Append the day variable", name = "appendDay") + public Mono appendDay( + @SKFunctionInputAttribute + @SKFunctionParameters(description = "Text to append to", name = "input") + String input, + @SKFunctionParameters(description = "Current day", name = "day") String day) { + return Mono.just(input + day); + } + } + + public static void main(String[] args) { + KernelConfig kernelConfig = SKBuilders.kernelConfig().build(); + Kernel kernel = SKBuilders.kernel().setKernelConfig(kernelConfig).build(); + + // Load native skill + ReadOnlyFunctionCollection functionCollection = + kernel.importSkill(new StaticTextSkill(), "text"); + + ContextVariables variables = + SKBuilders.variables() + .build("Today is: ") + .writableClone() + .setVariable("day", "Monday"); + + Mono result = + kernel.runAsync( + variables, + functionCollection.getFunction("AppendDay"), + functionCollection.getFunction("Uppercase")); + + System.out.println(result.block().getResult()); + } +} diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example04_CombineLLMPromptsAndNativeCode.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example04_CombineLLMPromptsAndNativeCode.java new file mode 100644 index 0000000000..6e37a0bcb8 --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example04_CombineLLMPromptsAndNativeCode.java @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.syntaxexamples; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.microsoft.semantickernel.Config; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.KernelConfig; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction; +import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute; +import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters; +import com.microsoft.semantickernel.textcompletion.TextCompletion; +import reactor.core.publisher.Mono; + +import java.io.IOException; + +public class Example04_CombineLLMPromptsAndNativeCode { + + public static class SearchEngineSkill { + @DefineSKFunction(description = "Append the day variable", name = "search") + public Mono search( + @SKFunctionInputAttribute + @SKFunctionParameters(description = "Text to search", name = "input") + String input) { + return Mono.just("Gran Torre Santiago is the tallest building in South America"); + } + } + + public static void main(String[] args) throws IOException { + OpenAIAsyncClient client = Config.ClientType.AZURE_OPEN_AI.getClient(); + + TextCompletion textCompletion = SKBuilders.textCompletionService().build(client, "text-davinci-003"); + + KernelConfig kernelConfig = new KernelConfig.Builder() + .addTextCompletionService("text-davinci-003", kernel -> textCompletion) + .build(); + + Kernel kernel = SKBuilders.kernel().setKernelConfig(kernelConfig).build(); + kernel.importSkill(new SearchEngineSkill(), null); + kernel.importSkillFromDirectory("SummarizeSkill", "./samples/skills", "SummarizeSkill"); + + // Run + String ask = "What's the tallest building in South America?"; + + Mono result = + kernel.runAsync(ask, kernel.getSkills().getFunction("Search", null)); + + result = + kernel.runAsync( + ask, + kernel.getSkills().getFunction("Search", null), + kernel.getSkill("SummarizeSkill").getFunction("Summarize", null)); + + System.out.println(result.block().getResult()); + } +} diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example05_InlineFunctionDefinition.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example05_InlineFunctionDefinition.java new file mode 100644 index 0000000000..2906daa35f --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example05_InlineFunctionDefinition.java @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft. All rights reserved. + +package com.microsoft.semantickernel.syntaxexamples; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.microsoft.semantickernel.Config; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.KernelConfig; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; +import com.microsoft.semantickernel.textcompletion.TextCompletion; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.FormatStyle; + +public class Example05_InlineFunctionDefinition { + public static void main(String[] args) throws IOException { + OpenAIAsyncClient client = Config.ClientType.AZURE_OPEN_AI.getClient(); + + TextCompletion textCompletion = SKBuilders.textCompletionService().build(client, "text-davinci-003"); + + KernelConfig kernelConfig = new KernelConfig.Builder() + .addTextCompletionService("text-davinci-003", kernel -> textCompletion) + .build(); + + Kernel kernel = SKBuilders.kernel().setKernelConfig(kernelConfig).build(); + + System.out.println("======== Inline Function Definition ========"); + + // Function defined using few-shot design pattern + String functionDefinition = """ + Generate a creative reason or excuse for the given event. + Be creative and be funny. Let your imagination run wild. + + Event: I am running late. + Excuse: I was being held ransom by giraffe gangsters. + + Event: I haven't been to the gym for a year + Excuse: I've been too busy training my pet dragon. + + Event: {{$input}} + """.stripIndent(); + + // Create function via builder + var excuseFunction = SKBuilders + .completionFunctions(kernel) + .createFunction( + functionDefinition, + new PromptTemplateConfig.CompletionConfigBuilder() + .maxTokens(100) + .temperature(0.4) + .topP(1) + .build()); + + + var result = excuseFunction.invokeAsync("I missed the F1 final race").block(); + System.out.println(result.getResult()); + + result = excuseFunction.invokeAsync("sorry I forgot your birthday").block(); + System.out.println(result.getResult()); + + // Create function via kernel + var fixedFunction = kernel. + getSemanticFunctionBuilder() + .createFunction("Translate this date " + + DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(LocalDateTime.now()) + " to French format", + new PromptTemplateConfig.CompletionConfigBuilder() + .maxTokens(100) + .temperature(0.4) + .topP(1) + .build()); + + System.out.println(fixedFunction.invokeAsync().block().getResult()); + } +} diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example06_TemplateLanguage.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example06_TemplateLanguage.java index 917e0f0d7d..e136edad4c 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example06_TemplateLanguage.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example06_TemplateLanguage.java @@ -4,91 +4,100 @@ package com.microsoft.semantickernel.syntaxexamples; import com.azure.ai.openai.OpenAIAsyncClient; import com.microsoft.semantickernel.Config; import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.KernelConfig; import com.microsoft.semantickernel.builders.SKBuilders; import com.microsoft.semantickernel.coreskills.TimeSkill; import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; -import com.microsoft.semantickernel.templateengine.PromptTemplateEngine; + import java.io.IOException; -import java.util.ArrayList; public class Example06_TemplateLanguage { - /// - /// Show how to invoke a Native Function written in C# - /// from a Semantic Function written in natural language - /// + /// + /// Show how to invoke a Native Function written in C# + /// from a Semantic Function written in natural language + /// - public static void main(String[] args) throws IOException { - System.out.println("======== TemplateLanguage ========"); + public static void main(String[] args) throws IOException { + System.out.println("======== TemplateLanguage ========"); - OpenAIAsyncClient client = Config.ClientType.AZURE_OPEN_AI.getClient(); + OpenAIAsyncClient client = Config.getClient(); + KernelConfig kernelConfig = SKBuilders + .kernelConfig() + .addTextCompletionService("text-davinci-003", kernel1 -> SKBuilders.textCompletionService() + .build(client, "text-davinci-003")) + .build(); - Kernel kernel = SKBuilders.kernel().build(); + Kernel kernel = SKBuilders.kernel() + .setKernelConfig(kernelConfig) + .build(); - // Load native skill into the kernel skill collection, sharing its functions - // with prompt templates - // Functions loaded here are available as "time.*" - kernel.importSkill(new TimeSkill(), "time"); + // Load native skill into the kernel skill collection, sharing its functions + // with prompt templates + // Functions loaded here are available as "time.*" + kernel.importSkill(new TimeSkill(), "time"); - // Semantic Function invoking time.Date and time.Time native functions - String functionDefinition = """ - Today is: {{time.Date}} - Current time is: {{time.Time}} + // Semantic Function invoking time.Date and time.Time native functions + String functionDefinition = """ + Today is: {{time.Date}} + Current time is: {{time.Time}} - Answer to the following questions using JSON syntax, including the data used. - Is it morning, afternoon, evening, or night (morning/afternoon/evening/night)? - Is it weekend time (weekend/not weekend)? - """; + Answer to the following questions using JSON syntax, including the data used. + Is it morning, afternoon, evening, or night (morning/afternoon/evening/night)? + Is it weekend time (weekend/not weekend)? + """; - // This allows to see the prompt before it's sent to OpenAI - System.out.println("--- Rendered Prompt"); + // This allows to see the prompt before it's sent to OpenAI + System.out.println("--- Rendered Prompt"); - var promptRenderer = SKBuilders.promptTemplate().build(functionDefinition, null); + var promptRenderer = SKBuilders.promptTemplate() + .withPromptTemplateConfig(new PromptTemplateConfig()) + .withPromptTemplate(functionDefinition) + .build(kernel.getPromptTemplateEngine()); - SKContext skContext = SKBuilders - .context() - .build(kernel.getSkills()); + SKContext skContext = SKBuilders + .context() + .build(kernel.getSkills()); - PromptTemplateEngine promptTemplateEngine = SKBuilders.promptTemplateEngine().build(); + var renderedPrompt = promptRenderer.renderAsync(skContext); + System.out.println(renderedPrompt.block()); - var renderedPrompt = promptRenderer.renderAsync(skContext, promptTemplateEngine); - System.out.println(renderedPrompt.block()); + // Run the prompt / semantic function + var kindOfDay = kernel + .getSemanticFunctionBuilder() + .createFunction( + functionDefinition, + null, + null, + null, + new PromptTemplateConfig.CompletionConfig( + 0, 0, 0, 0, 256)); - // Run the prompt / semantic function - var kindOfDay = kernel.getSemanticFunctionBuilder() - .createFunction( - functionDefinition, - null, - null, - null, - new PromptTemplateConfig.CompletionConfig( - 0, 0, 0, 0, 256, new ArrayList<>())); - - // Show the result - System.out.println("--- Semantic Function result"); - var result = kindOfDay.invokeAsync("").block().getResult(); - System.out.println(result); - /* - * OUTPUT: - * - * --- Rendered Prompt - * - * Today is: Friday, April 28, 2023 - * Current time is: 11:04:30 PM - * - * Answer to the following questions using JSON syntax, including the data used. - * Is it morning, afternoon, evening, or night - * (morning/afternoon/evening/night)? - * Is it weekend time (weekend/not weekend)? - * - * --- Semantic Function result - * - * { - * "date": "Friday, April 28, 2023", - * "time": "11:04:30 PM", - * "period": "night", - * "weekend": "weekend" - * } - */ - } + // Show the result + System.out.println("--- Semantic Function result"); + var result = kindOfDay.invokeAsync("").block().getResult(); + System.out.println(result); + /* + * OUTPUT: + * + * --- Rendered Prompt + * + * Today is: Friday, April 28, 2023 + * Current time is: 11:04:30 PM + * + * Answer to the following questions using JSON syntax, including the data used. + * Is it morning, afternoon, evening, or night + * (morning/afternoon/evening/night)? + * Is it weekend time (weekend/not weekend)? + * + * --- Semantic Function result + * + * { + * "date": "Friday, April 28, 2023", + * "time": "11:04:30 PM", + * "period": "night", + * "weekend": "weekend" + * } + */ + } } diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example08_RetryHandler.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example08_RetryHandler.java index f23db9e5b2..502c79f21f 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example08_RetryHandler.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example08_RetryHandler.java @@ -1,12 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.syntaxexamples; -import static com.microsoft.semantickernel.Config.CONF_PROPERTIES; - -import java.io.IOException; -import java.time.Duration; -import java.util.ArrayList; - import com.azure.ai.openai.OpenAIAsyncClient; import com.azure.ai.openai.OpenAIClientBuilder; import com.azure.core.credential.AzureKeyCredential; @@ -15,84 +9,91 @@ import com.azure.core.http.policy.RetryOptions; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.KernelConfig; import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import com.microsoft.semantickernel.textcompletion.TextCompletion; import com.microsoft.semantickernel.util.AIProviderSettings; import com.microsoft.semantickernel.util.AzureOpenAISettings; +import java.io.IOException; +import java.time.Duration; + +import static com.microsoft.semantickernel.Config.CONF_PROPERTIES; + public class Example08_RetryHandler { - public static void main(String[] args) throws IOException { - RetryOptions retryOptions = new RetryOptions(new ExponentialBackoffOptions() - .setMaxDelay(Duration.ofSeconds(180)) - .setBaseDelay(Duration.ofSeconds(60)) - .setMaxRetries(3)); + public static void main(String[] args) throws IOException { + RetryOptions retryOptions = new RetryOptions(new ExponentialBackoffOptions() + .setMaxDelay(Duration.ofSeconds(180)) + .setBaseDelay(Duration.ofSeconds(60)) + .setMaxRetries(3)); - AzureOpenAISettings settings = AIProviderSettings.getAzureOpenAISettingsFromFile(CONF_PROPERTIES); - OpenAIAsyncClient client = new OpenAIClientBuilder() - .retryOptions(retryOptions) - .endpoint(settings.getEndpoint()) - .credential(new AzureKeyCredential(settings.getKey())) - .buildAsyncClient(); + AzureOpenAISettings settings = AIProviderSettings.getAzureOpenAISettingsFromFile(CONF_PROPERTIES); + OpenAIAsyncClient client = new OpenAIClientBuilder() + .retryOptions(retryOptions) + .endpoint(settings.getEndpoint()) + .credential(new AzureKeyCredential(settings.getKey())) + .buildAsyncClient(); - String text = """ - Demo (ancient Greek poet) - From Wikipedia, the free encyclopedia - Demo or Damo (Greek: Δεμώ, Δαμώ; fl. c. AD 200) was a Greek woman of the - Roman period, known for a single epigram, engraved upon the Colossus of - Memnon, which bears her name. She speaks of herself therein as a lyric - poetess dedicated to the Muses, but nothing is known of her life.[1] - Identity - Demo was evidently Greek, as her name, a traditional epithet of Demeter, - signifies. The name was relatively common in the Hellenistic world, in - Egypt and elsewhere, and she cannot be further identified. The date of her - visit to the Colossus of Memnon cannot be established with certainty, but - internal evidence on the left leg suggests her poem was inscribed there at - some point in or after AD 196.[2] - Epigram - There are a number of graffiti inscriptions on the Colossus of Memnon. - Following three epigrams by Julia Balbilla, a fourth epigram, in elegiac - couplets, entitled and presumably authored by "Demo" or "Damo" (the - Greek inscription is difficult to read), is a dedication to the Muses.[2] - The poem is traditionally published with the works of Balbilla, though the - internal evidence suggests a different author.[1] - In the poem, Demo explains that Memnon has shown her special respect. In - return, Demo offers the gift for poetry, as a gift to the hero. At the end - of this epigram, she addresses Memnon, highlighting his divine status by - recalling his strength and holiness.[2] - Demo, like Julia Balbilla, writes in the artificial and poetic Aeolic - dialect. The language indicates she was knowledgeable in Homeric - poetry—'bearing a pleasant gift', for example, alludes to the use of that - phrase throughout the Iliad and Odyssey.[a][2]; - """; + String text = """ + Demo (ancient Greek poet) + From Wikipedia, the free encyclopedia + Demo or Damo (Greek: Δεμώ, Δαμώ; fl. c. AD 200) was a Greek woman of the + Roman period, known for a single epigram, engraved upon the Colossus of + Memnon, which bears her name. She speaks of herself therein as a lyric + poetess dedicated to the Muses, but nothing is known of her life.[1] + Identity + Demo was evidently Greek, as her name, a traditional epithet of Demeter, + signifies. The name was relatively common in the Hellenistic world, in + Egypt and elsewhere, and she cannot be further identified. The date of her + visit to the Colossus of Memnon cannot be established with certainty, but + internal evidence on the left leg suggests her poem was inscribed there at + some point in or after AD 196.[2] + Epigram + There are a number of graffiti inscriptions on the Colossus of Memnon. + Following three epigrams by Julia Balbilla, a fourth epigram, in elegiac + couplets, entitled and presumably authored by "Demo" or "Damo" (the + Greek inscription is difficult to read), is a dedication to the Muses.[2] + The poem is traditionally published with the works of Balbilla, though the + internal evidence suggests a different author.[1] + In the poem, Demo explains that Memnon has shown her special respect. In + return, Demo offers the gift for poetry, as a gift to the hero. At the end + of this epigram, she addresses Memnon, highlighting his divine status by + recalling his strength and holiness.[2] + Demo, like Julia Balbilla, writes in the artificial and poetic Aeolic + dialect. The language indicates she was knowledgeable in Homeric + poetry—'bearing a pleasant gift', for example, alludes to the use of that + phrase throughout the Iliad and Odyssey.[a][2]; + """; - String prompt = "{{$input}}\nSummarize the content above."; + String prompt = "{{$input}}\nSummarize the content above."; - TextCompletion textCompletion = SKBuilders.textCompletionService().build(client, "text-davinci-003"); + TextCompletion textCompletion = SKBuilders.textCompletionService().build(client, "text-davinci-003"); - CompletionSKFunction summarizeFunc = SKBuilders.completionFunctions() - .createFunction( - prompt, - "summarize", - null, - null, - new PromptTemplateConfig.CompletionConfig( - 0.2, 0.5, 0, 0, 2000, new ArrayList<>())); + KernelConfig kernelConfig = new KernelConfig.Builder() + .addTextCompletionService("text-davinci-003", kernel -> textCompletion) + .build(); - KernelConfig kernelConfig = new KernelConfig.Builder() - .addTextCompletionService("text-davinci-003", kernel -> textCompletion) - .addSkill(summarizeFunc) - .build(); + Kernel kernel = SKBuilders + .kernel() + .setKernelConfig(kernelConfig) + .build(); - Kernel kernel = SKBuilders.kernel().setKernelConfig(kernelConfig).build(); + CompletionSKFunction summarizeFunc = SKBuilders.completionFunctions(kernel) + .createFunction( + prompt, + "summarize", + null, + null, + new PromptTemplateConfig.CompletionConfig( + 0.2, 0.5, 0, 0, 2000)); - CompletionSKFunction summarize = kernel.getSkills().getFunction("summarize", - CompletionSKFunction.class); - CompletionSKContext context = summarize.invokeAsync(text).block(); + kernel.registerSemanticFunction(summarizeFunc); - System.out.println(context.getResult()); - } + SKContext context = summarizeFunc.invokeAsync(text).block(); + + System.out.println(context.getResult()); + } } diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example09_FunctionTypes.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example09_FunctionTypes.java new file mode 100644 index 0000000000..721aadb8ce --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example09_FunctionTypes.java @@ -0,0 +1,280 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.syntaxexamples; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.microsoft.semantickernel.Config; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.KernelConfig; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction; +import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute; +import com.microsoft.semantickernel.textcompletion.TextCompletion; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.time.Duration; + +public class Example09_FunctionTypes { + public static void main(String[] args) throws IOException { + OpenAIAsyncClient client = Config.ClientType.AZURE_OPEN_AI.getClient(); + + TextCompletion textCompletion = SKBuilders.textCompletionService().build(client, "text-davinci-003"); + + KernelConfig kernelConfig = new KernelConfig.Builder() + .addTextCompletionService("text-davinci-003", kernel -> textCompletion) + .build(); + + Kernel kernel = SKBuilders.kernel().setKernelConfig(kernelConfig).build(); + + System.out.println("======== Native function types ========"); + + // Load native skill into the kernel skill collection, sharing its functions with prompt templates + var test = kernel + .importSkill(new LocalExampleSkill(), "test"); + + kernel.importSkillsFromDirectory("./samples/skills", "SummarizeSkill"); + + + var fakeContext = SKBuilders.context() + .with(kernel.getSkills()) + .build(); + + // The kernel takes care of wiring the input appropriately + SKContext result = kernel.runAsync( + "", + test.getFunction("type01"), + test.getFunction("type02"), + test.getFunction("type03"), + test.getFunction("type04"), + test.getFunction("type05"), + test.getFunction("type06"), + test.getFunction("type07"), + test.getFunction("type08"), + test.getFunction("type09"), + test.getFunction("type10"), + test.getFunction("type11"), + test.getFunction("type12"), + test.getFunction("type13"), + test.getFunction("type14"), + test.getFunction("type15"), + test.getFunction("type16"), + test.getFunction("type17"), + test.getFunction("type18") + ) + .block(); + + kernel.getFunction("test", "type01").invokeAsync().block(); + test.getFunction("type01").invokeAsync().block(); + + kernel.getFunction("test", "type02").invokeAsync().block(); + test.getFunction("type02").invokeAsync().block(); + + kernel.getFunction("test", "type03").invokeAsync().block(); + test.getFunction("type03").invokeAsync().block(); + + kernel.getFunction("test", "type04").invokeAsync(fakeContext).block(); + test.getFunction("type04").invokeAsync(fakeContext).block(); + + kernel.getFunction("test", "type05").invokeAsync(fakeContext).block(); + test.getFunction("type05").invokeAsync(fakeContext).block(); + + kernel.getFunction("test", "type06").invokeAsync(fakeContext).block(); + test.getFunction("type06").invokeAsync(fakeContext).block(); + + kernel.getFunction("test", "type07").invokeAsync(fakeContext).block(); + test.getFunction("type07").invokeAsync(fakeContext).block(); + + kernel.getFunction("test", "type08").invokeAsync("").block(); + test.getFunction("type08").invokeAsync("").block(); + + kernel.getFunction("test", "type09").invokeAsync("").block(); + test.getFunction("type09").invokeAsync("").block(); + + kernel.getFunction("test", "type10").invokeAsync("").block(); + test.getFunction("type10").invokeAsync("").block(); + + kernel.getFunction("test", "type11").invokeAsync("").block(); + test.getFunction("type11").invokeAsync("").block(); + + kernel.getFunction("test", "type12").invokeAsync("").block(); + test.getFunction("type12").invokeAsync("").block(); + + kernel.getFunction("test", "type13").invokeAsync("").block(); + test.getFunction("type13").invokeAsync("").block(); + + kernel.getFunction("test", "type14").invokeAsync("").block(); + test.getFunction("type14").invokeAsync("").block(); + + kernel.getFunction("test", "type15").invokeAsync("").block(); + test.getFunction("type15").invokeAsync("").block(); + + kernel.getFunction("test", "type16").invokeAsync("").block(); + test.getFunction("type16").invokeAsync("").block(); + + kernel.getFunction("test", "type17").invokeAsync("").block(); + test.getFunction("type17").invokeAsync("").block(); + + kernel.getFunction("test", "type18").invokeAsync("").block(); + test.getFunction("type18").invokeAsync("").block(); + } + + + public static class LocalExampleSkill { + + @DefineSKFunction(name = "type01") + public void Type01() { + System.out.println("Running function type 1"); + } + + @DefineSKFunction(name = "type02") + public String Type02() { + System.out.println("Running function type 2"); + return ""; + } + + @DefineSKFunction(name = "type03") + public Mono Type03Async() { + return Mono.delay(Duration.ZERO) + .map(x -> { + System.out.println("Running function type 3"); + return ""; + }); + } + + + @DefineSKFunction(name = "type04") + public void Type04(SKContext context) { + System.out.println("Running function type 4"); + } + + + @DefineSKFunction(name = "type05") + public String Type05(SKContext context) { + System.out.println("Running function type 5"); + return ""; + } + + + @DefineSKFunction(name = "type06") + public Mono Type06Async(SKContext context) { + var summarizer = context.getSkills().getFunction("SummarizeSkill", "Summarize", null); + + return summarizer + .invokeAsync("blah blah blah") + .map(summary -> { + System.out.println("Running function type 6 " + summary.getResult()); + return ""; + }); + } + + @DefineSKFunction(name = "type07") + public Mono Type07Async(SKContext context) { + return Mono.delay(Duration.ZERO) + .map(x -> { + System.out.println("Running function type 7"); + return context; + }); + } + + @DefineSKFunction(name = "type08") + public void Type08( + @SKFunctionInputAttribute + String x) { + System.out.println("Running function type 8"); + } + + + @DefineSKFunction(name = "type09") + public String Type09( + @SKFunctionInputAttribute + String x) { + System.out.println("Running function type 9"); + return ""; + } + + @DefineSKFunction(name = "type10") + public Mono Type10Async( + @SKFunctionInputAttribute + String x) { + return Mono.delay(Duration.ZERO) + .map(x2 -> { + System.out.println("Running function type 10"); + return ""; + }); + } + + @DefineSKFunction(name = "type11") + public void Type11( + @SKFunctionInputAttribute + String x, + SKContext context) { + System.out.println("Running function type 11"); + } + + + @DefineSKFunction(name = "type12") + public String Type12( + @SKFunctionInputAttribute + String x, + SKContext context) { + System.out.println("Running function type 12"); + return ""; + } + + @DefineSKFunction(name = "type13") + public Mono Type13Async( + @SKFunctionInputAttribute + String x, SKContext context) { + return Mono.delay(Duration.ZERO) + .map(x2 -> { + System.out.println("Running function type 13"); + return ""; + }); + } + + @DefineSKFunction(name = "type14") + public Mono Type14Async( + @SKFunctionInputAttribute + String x, SKContext context) { + return Mono.delay(Duration.ZERO) + .ignoreElement() + .map(x2 -> { + System.out.println("Running function type 14"); + return context; + }); + } + + @DefineSKFunction(name = "type15") + public Mono Type15Async(String x) { + return Mono.delay(Duration.ZERO) + .ignoreElement() + .doFinally(ignore -> System.out.println("Running function type 15")) + .then(); + } + + @DefineSKFunction(name = "type16") + public Mono Type16Async(SKContext context) { + return Mono.delay(Duration.ZERO) + .ignoreElement() + .doFinally(ignore -> System.out.println("Running function type 16")) + .then(); + } + + @DefineSKFunction(name = "type17") + public Mono Type17Async(String x, SKContext context) { + return Mono.delay(Duration.ZERO) + .ignoreElement() + .doFinally(ignore -> System.out.println("Running function type 17")) + .then(); + } + + @DefineSKFunction(name = "type18") + public Mono Type18Async() { + return Mono.delay(Duration.ZERO) + .ignoreElement() + .doFinally(ignore -> System.out.println("Running function type 18")) + .then(); + } + } +} diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example12_SequentialPlanner.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example12_SequentialPlanner.java new file mode 100644 index 0000000000..735252a632 --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example12_SequentialPlanner.java @@ -0,0 +1,211 @@ +package com.microsoft.semantickernel.syntaxexamples;// Copyright (c) Microsoft. All rights reserved. + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.microsoft.semantickernel.Config; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.coreskills.TextSkill; +import com.microsoft.semantickernel.memory.VolatileMemoryStore; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.planner.sequentialplanner.SequentialPlanner; +import com.microsoft.semantickernel.planner.sequentialplanner.SequentialPlannerRequestSettings; +import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction; +import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute; +import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Set; + +public class Example12_SequentialPlanner { + + private static final Logger LOGGER = LoggerFactory.getLogger(Example12_SequentialPlanner.class); + + public static void main(String[] args) throws IOException { + poetrySamplesAsync(); + emailSamplesAsync(); + bookSamplesAsync(); + //MemorySampleAsync(); + } + + private static void poetrySamplesAsync() throws IOException { + System.out.println("======== Sequential Planner - Create and Execute Poetry Plan ========"); + var kernel = initializeKernel(); + + kernel.importSkill(new TextSkill(), "TextSkill"); + + var planner = new SequentialPlanner(kernel, null, null); + + var plan = planner.createPlanAsync("Write a poem about John Doe, then translate it into Italian, then convert it to uppercase.").block(); + + // Original plan: + // Goal: Write a poem about John Doe, then translate it into Italian. + + // Steps: + // - WriterSkill.ShortPoem INPUT='John Doe is a friendly guy who likes to help others and enjoys reading books.' => + // - WriterSkill.Translate language='Italian' INPUT='' => + + SKContext result = plan.invokeAsync().block(); + + System.out.println("Result:"); + System.out.println(result.getResult()); + } + + public static class EmailSkill { + @DefineSKFunction(name = "SendEmail", description = "Given an e-mail and message body, send an email") + public void sendEmail( + @SKFunctionInputAttribute() + @SKFunctionParameters(description = "The body of the email message to send.", name = "input") + String input, + @SKFunctionParameters(description = "The email address to send email to.", name = "email_address") + String emailAddress + ) { + LOGGER.info("Emailing to " + emailAddress + "\n" + input); + } + + @DefineSKFunction(name = "GetEmailAddressAsync", description = "Lookup an email address for a person given a name") + public String getEmailAddressAsync( + @SKFunctionInputAttribute() + @SKFunctionParameters(description = "The name of the person to email.", name = "input") + String input) { + return "fake@example.com"; + } + } + + private static void emailSamplesAsync() throws IOException { + System.out.println("======== Sequential Planner - Create and Execute Email Plan ========"); + var kernel = initializeKernel(); + + kernel.importSkill(new EmailSkill(), "email"); + + + SequentialPlanner planner = initPlanner(kernel, 1024); + + var plan = planner.createPlanAsync("Summarize an input, translate to french, and e-mail to John Doe").block(); + + // Original plan: + // Goal: Summarize an input, translate to french, and e-mail to John Doe + + // Steps: + // - SummarizeSkill.Summarize INPUT='' => + // - WriterSkill.Translate language='French' INPUT='' => TRANSLATED_SUMMARY + // - email.GetEmailAddress INPUT='John Doe' => EMAIL_ADDRESS + // - email.SendEmail INPUT='$TRANSLATED_SUMMARY' email_address='$EMAIL_ADDRESS' => + + System.out.println("Original plan:"); + System.out.println(plan.toPlanString()); + + var input = + "Once upon a time, in a faraway kingdom, there lived a kind and just king named Arjun. " + + "He ruled over his kingdom with fairness and compassion, earning him the love and admiration of his people. " + + "However, the kingdom was plagued by a terrible dragon that lived in the nearby mountains and terrorized the nearby villages, " + + "burning their crops and homes. The king had tried everything to defeat the dragon, but to no avail. " + + "One day, a young woman named Mira approached the king and offered to help defeat the dragon. She was a skilled archer " + + "and claimed that she had a plan to defeat the dragon once and for all. The king was skeptical, but desperate for a solution, " + + "so he agreed to let her try. Mira set out for the dragon's lair and, with the help of her trusty bow and arrow, " + + "she was able to strike the dragon with a single shot through its heart, killing it instantly. The people rejoiced " + + "and the kingdom was at peace once again. The king was so grateful to Mira that he asked her to marry him and she agreed. " + + "They ruled the kingdom together, ruling with fairness and compassion, just as Arjun had done before. They lived " + + "happily ever after, with the people of the kingdom remembering Mira as the brave young woman who saved them from the dragon."; + + System.out.println(plan.invokeAsync(input).block()); + } + + private static void bookSamplesAsync() throws IOException { + System.out.println("======== Sequential Planner - Create and Execute Book Creation Plan ========"); + var kernel = initializeKernel(); + + SequentialPlanner planner = initPlanner(kernel, 1024); + + var plan = planner.createPlanAsync("Create a book with 3 chapters about a group of kids in a club called 'The Thinking Caps.'").block(); + + // Original plan: + // Goal: Create a book with 3 chapters about a group of kids in a club called 'The Thinking Caps.' + + // Steps: + // - WriterSkill.NovelOutline chapterCount='3' INPUT='A group of kids in a club called 'The Thinking Caps' that solve mysteries and puzzles using their creativity and logic.' endMarker='' => OUTLINE + // - MiscSkill.ElementAtIndex count='3' INPUT='$OUTLINE' index='0' => CHAPTER_1_SYNOPSIS + // - WriterSkill.NovelChapter chapterIndex='1' previousChapter='' INPUT='$CHAPTER_1_SYNOPSIS' theme='Children's mystery' => RESULT__CHAPTER_1 + // - MiscSkill.ElementAtIndex count='3' INPUT='$OUTLINE' index='1' => CHAPTER_2_SYNOPSIS + // - WriterSkill.NovelChapter chapterIndex='2' previousChapter='$CHAPTER_1_SYNOPSIS' INPUT='$CHAPTER_2_SYNOPSIS' theme='Children's mystery' => RESULT__CHAPTER_2 + // - MiscSkill.ElementAtIndex count='3' INPUT='$OUTLINE' index='2' => CHAPTER_3_SYNOPSIS + // - WriterSkill.NovelChapter chapterIndex='3' previousChapter='$CHAPTER_2_SYNOPSIS' INPUT='$CHAPTER_3_SYNOPSIS' theme='Children's mystery' => RESULT__CHAPTER_3 + System.out.println(plan.invokeAsync().block().getResult()); + } + + /* + TODO + private static void memorySampleAsync() throws IOException { + System.out.println("======== Sequential Planner - Create and Execute Plan using Memory ========"); + var kernel = initializeKernel(); + + SequentialPlanner planner = initPlanner(kernel, 1024); + + kernel.importSkillsFromDirectory("samples/skills", + "CalendarSkill", + "ChatSkill", + "ChildrensBookSkill", + "ClassificationSkill", + "CodingSkill", + "FunSkill", + "IntentDetectionSkill", + "MiscSkill", + "QASkill"); + + kernel.importSkill(new EmailSkill(), "email"); + kernel.importSkill(new StaticTextSkill(), "statictext"); + kernel.importSkill(new TextSkill(), "text"); + kernel.importSkill(new Microsoft.SemanticKernel.CoreSkills.TextSkill(), "coretext"); + + var goal = "Create a book with 3 chapters about a group of kids in a club called 'The Thinking Caps.'"; + + var planner = new SequentialPlanner(kernel, new SequentialPlannerConfig { + RelevancyThreshold =0.78 + }); + + var plan = await planner.CreatePlanAsync(goal); + + System.out.println("Original plan:"); + System.out.println(plan.ToPlanString()); + } + + */ + + private static Kernel initializeKernel() throws IOException { + OpenAIAsyncClient client = Config.getClient(); + var kernel = SKBuilders.kernel() + .setKernelConfig(SKBuilders + .kernelConfig() + .addTextCompletionService("text-davinci-003", kernel1 -> SKBuilders.textCompletionService() + .build(client, "text-davinci-003")) + .build()) + .withMemory(SKBuilders + .semanticTextMemory() + .setEmbeddingGenerator( + SKBuilders.textEmbeddingGenerationService().build(client, "text-davinci-003") + ) + .setStorage(new VolatileMemoryStore()) + .build()) + .build(); + + + // Load additional skills to enable planner to do non-trivial asks. + kernel.importSkillFromDirectory("SummarizeSkill", "samples/skills", "SummarizeSkill"); + kernel.importSkillFromDirectory("WriterSkill", "samples/skills", "WriterSkill"); + kernel.importSkillFromDirectory("MiscSkill", "samples/skills", "MiscSkill"); + + return kernel; + } + + private static SequentialPlanner initPlanner(Kernel kernel, int maxTokens) { + return new SequentialPlanner(kernel, new SequentialPlannerRequestSettings( + null, + 100, + Set.of(), + Set.of(), + Set.of(), + maxTokens + ), null); + } +} diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example13_ConversationSummarySkill.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example13_ConversationSummarySkill.java index 2b4476c8a9..478d72bb86 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example13_ConversationSummarySkill.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example13_ConversationSummarySkill.java @@ -7,9 +7,9 @@ import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.KernelConfig; import com.microsoft.semantickernel.builders.SKBuilders; import com.microsoft.semantickernel.coreskills.ConversationSummarySkill; +import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.orchestration.SKFunction; import com.microsoft.semantickernel.skilldefinition.ReadOnlyFunctionCollection; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import com.microsoft.semantickernel.textcompletion.TextCompletion; import reactor.core.publisher.Mono; @@ -127,7 +127,7 @@ public class Example13_ConversationSummarySkill { """.stripIndent(); public static void main(String[] args) throws IOException { - Config.ClientType clientType = Config.ClientType.OPEN_AI; + Config.ClientType clientType = Config.ClientType.AZURE_OPEN_AI; conversationSummarySkillAsync(clientType); getConversationActionItemsAsync(clientType); @@ -141,7 +141,7 @@ public class Example13_ConversationSummarySkill { ReadOnlyFunctionCollection conversationSummarySkill = kernel.importSkill(new ConversationSummarySkill(kernel), null); - Mono summary = conversationSummarySkill + Mono summary = conversationSummarySkill .getFunction("SummarizeConversation", SKFunction.class) .invokeAsync(ChatTranscript); @@ -157,7 +157,7 @@ public class Example13_ConversationSummarySkill { ReadOnlyFunctionCollection conversationSummarySkill = kernel.importSkill(new ConversationSummarySkill(kernel), null); - Mono summary = conversationSummarySkill + Mono summary = conversationSummarySkill .getFunction("GetConversationActionItems", SKFunction.class) .invokeAsync(ChatTranscript); @@ -171,7 +171,7 @@ public class Example13_ConversationSummarySkill { ReadOnlyFunctionCollection conversationSummarySkill = kernel.importSkill(new ConversationSummarySkill(kernel), null); - Mono summary = conversationSummarySkill + Mono summary = conversationSummarySkill .getFunction("GetConversationTopics", SKFunction.class) .invokeAsync(ChatTranscript); diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example17_ChatGPT.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example17_ChatGPT.java index ece04a99b8..a851fb64b3 100644 --- a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example17_ChatGPT.java +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example17_ChatGPT.java @@ -19,7 +19,7 @@ import java.io.IOException; public class Example17_ChatGPT { public static void main(String[] args) throws IOException { - OpenAIAsyncClient client = Config.ClientType.OPEN_AI.getClient(); + OpenAIAsyncClient client = Config.ClientType.AZURE_OPEN_AI.getClient(); KernelConfig kernelConfig = SKBuilders.kernelConfig() .addChatCompletionService( diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example28_ActionPlanner.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example28_ActionPlanner.java new file mode 100644 index 0000000000..533a6b0912 --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/Example28_ActionPlanner.java @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.syntaxexamples; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.microsoft.semantickernel.Config; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.coreskills.TextSkill; +import com.microsoft.semantickernel.planner.actionplanner.ActionPlanner; + +import java.io.IOException; + +public class Example28_ActionPlanner { + public static void main(String[] args) throws IOException { + OpenAIAsyncClient client = Config.ClientType.AZURE_OPEN_AI.getClient(); + + System.out.println("======== Action Planner ========"); + + var kernel = SKBuilders.kernel() + .setKernelConfig(SKBuilders + .kernelConfig() + .addTextCompletionService("text-davinci-002", kernel1 -> SKBuilders.textCompletionService() + .build(client, "text-davinci-002")) + .build()) + .build(); + + kernel.importSkillFromDirectory("SummarizeSkill", "./samples/skills", "SummarizeSkill"); + + kernel.importSkillFromDirectory("WriterSkill", "./samples/skills", "WriterSkill"); + + kernel.importSkill(new TextSkill(), "TextSkill"); + + // Create an instance of ActionPlanner. + // The ActionPlanner takes one goal and returns a single function to execute. + var planner = new ActionPlanner(kernel, null); + + // We're going to ask the planner to find a function to achieve this goal. + var goal = "Write a poem about Cleopatra."; + + // The planner returns a plan, consisting of a single function + // to execute and achieve the goal requested. + var plan = planner.createPlanAsync(goal).block(); + + // Show the result, which should match the given goal + System.out.println(plan.getName()); + + System.out.println(plan.invokeAsync(goal).block().getResult()); + + /* Output: + * + * Cleopatra was a queen + * But she didn't act like one + * She was more like a teen + + * She was always on the scene + * And she loved to be seen + * But she didn't have a queenly bone in her body + */ + } +} diff --git a/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/RunAll.java b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/RunAll.java new file mode 100644 index 0000000000..1700dd8255 --- /dev/null +++ b/java/samples/sample-code/src/main/java/com/microsoft/semantickernel/syntaxexamples/RunAll.java @@ -0,0 +1,32 @@ +package com.microsoft.semantickernel.syntaxexamples; + +import java.util.Arrays; +import java.util.List; + +public class RunAll { + public interface MainMethod { + void run(String[] args) throws Exception; + } + + public static void main(String[] args) { + List mains = Arrays.asList( + Example01_NativeFunctions::main, + Example02_Pipeline::main, + Example06_TemplateLanguage::main, + Example08_RetryHandler::main, + Example12_SequentialPlanner::main, + Example13_ConversationSummarySkill::main, + Example14_SemanticMemory::main, + Example17_ChatGPT::main, + Example28_ActionPlanner::main + ); + + mains.forEach(mainMethod -> { + try { + mainMethod.run(args); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } +} diff --git a/java/samples/sample-code/src/main/resources/log4j2.xml b/java/samples/sample-code/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..dfe2ccca7e --- /dev/null +++ b/java/samples/sample-code/src/main/resources/log4j2.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/samples/semantickernel-guice/src/main/java/com/microsoft/semantickernel/guice/SemanticKernelModule.java b/java/samples/semantickernel-guice/src/main/java/com/microsoft/semantickernel/guice/SemanticKernelModule.java index 1778e0c8d5..d6838ae511 100644 --- a/java/samples/semantickernel-guice/src/main/java/com/microsoft/semantickernel/guice/SemanticKernelModule.java +++ b/java/samples/semantickernel-guice/src/main/java/com/microsoft/semantickernel/guice/SemanticKernelModule.java @@ -98,8 +98,6 @@ public class SemanticKernelModule extends AbstractModule { if (embeddingsGenerationServiceId != null) { OpenAITextEmbeddingGeneration embeddings = new OpenAITextEmbeddingGeneration(client, embeddingsGenerationServiceId); - kernelConfig.addTextEmbeddingsGenerationService( - embeddingsGenerationServiceId, kernel -> embeddings); bind(new TypeLiteral>() { }).toInstance(embeddings); } diff --git a/java/samples/semantickernel-guice/src/test/java/com/microsoft/semantickernel/guice/CompletionFunctionExample.java b/java/samples/semantickernel-guice/src/test/java/com/microsoft/semantickernel/guice/CompletionFunctionExample.java index 41a236f999..33a96b8d26 100644 --- a/java/samples/semantickernel-guice/src/test/java/com/microsoft/semantickernel/guice/CompletionFunctionExample.java +++ b/java/samples/semantickernel-guice/src/test/java/com/microsoft/semantickernel/guice/CompletionFunctionExample.java @@ -4,8 +4,8 @@ package com.microsoft.semantickernel.guice; import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; +import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import java.util.ArrayList; @@ -61,13 +61,13 @@ public class CompletionFunctionExample { + "poetry—'bearing a pleasant gift', for example, alludes to the use of that\n" + "phrase throughout the Iliad and Odyssey.[a][2];"; - CompletionSKContext summary = + SKContext summary = completionFunctionFactory .createFunction( prompt, "summarize", new PromptTemplateConfig.CompletionConfig( - 0.2, 0.5, 0, 0, 2000, new ArrayList<>())) + 0.2, 0.5, 0, 0, 2000)) .invokeAsync(text) .block(); diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Kernel.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Kernel.java index 2fa469bc5b..efa60eb10f 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 @@ -2,26 +2,20 @@ package com.microsoft.semantickernel; import com.microsoft.semantickernel.builders.BuildersSingleton; -import com.microsoft.semantickernel.exceptions.SkillsNotFoundException; import com.microsoft.semantickernel.memory.MemoryStore; import com.microsoft.semantickernel.memory.SemanticTextMemory; import com.microsoft.semantickernel.orchestration.ContextVariables; import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.orchestration.SKFunction; -import com.microsoft.semantickernel.semanticfunctions.SemanticFunctionConfig; -import com.microsoft.semantickernel.skilldefinition.ReadOnlyFunctionCollection; -import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; import com.microsoft.semantickernel.templateengine.PromptTemplateEngine; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import reactor.core.publisher.Mono; -import java.util.Map; - import javax.annotation.Nullable; /** Interface for the semantic kernel. */ -public interface Kernel { +public interface Kernel extends SkillExecutor { /** * Settings required to execute functions, including details about AI dependencies, e.g. @@ -49,7 +43,7 @@ public interface Kernel { * @param pipeline List of functions * @return Result of the function composition */ - Mono> runAsync(SKFunction... pipeline); + Mono runAsync(SKFunction... pipeline); /** * Run a pipeline composed of synchronous and asynchronous functions. @@ -58,7 +52,7 @@ public interface Kernel { * @param pipeline List of functions * @return Result of the function composition */ - Mono> runAsync(String input, SKFunction... pipeline); + Mono runAsync(String input, SKFunction... pipeline); /** * Run a pipeline composed of synchronous and asynchronous functions. @@ -67,42 +61,7 @@ public interface Kernel { * @param pipeline List of functions * @return Result of the function composition */ - Mono> runAsync(ContextVariables variables, SKFunction... pipeline); - - /** - * Import a set of skills - * - * @param skillName - * @param skills - * @return - * @throws SkillsNotFoundException - */ - ReadOnlyFunctionCollection importSkill( - String skillName, Map skills) - throws SkillsNotFoundException; - - /** - * Get function collection with the skill name - * - * @param skillName - * @return - * @throws SkillsNotFoundException - */ - ReadOnlyFunctionCollection getSkill(String skillName) throws SkillsNotFoundException; - - /** - * Imports the native functions annotated on the given object as a skill. - * - * @param nativeSkill - * @param skillName - * @return - */ - ReadOnlyFunctionCollection importSkill(Object nativeSkill, @Nullable String skillName); - - /** - * @return Reference to the read-only skill collection containing all the imported functions - */ - ReadOnlySkillCollection getSkills(); + Mono runAsync(ContextVariables variables, SKFunction... pipeline); CompletionSKFunction.Builder getSemanticFunctionBuilder(); @@ -110,12 +69,11 @@ public interface Kernel { T getService(@Nullable String name, Class clazz) throws KernelException; /** Registers a semantic functon on this kernel */ - < - RequestConfiguration, - ContextType extends SKContext, - FunctionType extends SKFunction> + > FunctionType registerSemanticFunction(FunctionType semanticFunctionDefinition); + SKFunction getFunction(String skill, String function); + // > T createNewContext(); class Builder { diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/KernelConfig.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/KernelConfig.java index 31a95ba8c1..39a4663d69 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/KernelConfig.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/KernelConfig.java @@ -3,17 +3,13 @@ package com.microsoft.semantickernel; import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration; import com.microsoft.semantickernel.chatcompletion.ChatCompletion; -import com.microsoft.semantickernel.orchestration.SKFunction; import com.microsoft.semantickernel.textcompletion.TextCompletion; -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.function.Function; +import javax.annotation.Nullable; + public final class KernelConfig { private static final String DEFAULT_SERVICE_ID = "__SK_DEFAULT"; @@ -22,19 +18,16 @@ public final class KernelConfig { private final Map>> textEmbeddingGenerationServices; - private final ArrayList> skills; public KernelConfig( Map> textCompletionServices, Map>> textEmbeddingGenerationServices, - Map> chatCompletionServices, - List> skills) { + Map> chatCompletionServices) { this.textCompletionServices = new HashMap<>(); this.textCompletionServices.putAll(textCompletionServices); this.textEmbeddingGenerationServices = new HashMap<>(textEmbeddingGenerationServices); this.chatCompletionServices = new HashMap<>(chatCompletionServices); - this.skills = new ArrayList<>(skills); } @Nullable @@ -42,10 +35,6 @@ public final class KernelConfig { return textCompletionServices.get(serviceId); } - public List> getSkills() { - return Collections.unmodifiableList(skills); - } - public Function getTextCompletionServiceOrDefault( @Nullable String serviceId) { if (serviceId == null) { @@ -55,7 +44,10 @@ public final class KernelConfig { if (!this.textCompletionServices.containsKey(serviceId)) { throw new KernelException( KernelException.ErrorCodes.ServiceNotFound, - "A text completion service id '" + serviceId + "' doesn't exist"); + "A text completion service id '" + + serviceId + + "' doesn't exist. This likely means a text completion service was not" + + " registered on this kernel."); } return this.textCompletionServices.get(serviceId); @@ -76,8 +68,8 @@ public final class KernelConfig { return this.chatCompletionServices.get(serviceId); } - public Function> getTextEmbeddingGenerationServiceOrDefault( - @Nullable String serviceId) { + public Function> + getTextEmbeddingGenerationServiceOrDefault(@Nullable String serviceId) { if (serviceId == null) { serviceId = DEFAULT_SERVICE_ID; @@ -96,18 +88,11 @@ public final class KernelConfig { private Map> textCompletionServices = new HashMap<>(); - private List> skillBuilders = new ArrayList<>(); - private Map>> textEmbeddingGenerationServices = new HashMap<>(); private final Map> chatCompletionServices = new HashMap<>(); - public Builder addSkill(SKFunction functionDefinition) { - skillBuilders.add(functionDefinition); - return this; - } - // TODO, is there a need for this to be a factory? public Builder addTextCompletionService( String serviceId, Function serviceFactory) { @@ -160,7 +145,8 @@ public final class KernelConfig { */ public Builder addChatCompletionService( @Nullable String serviceId, Function serviceFactory) { - if (serviceId != null && serviceId.toUpperCase().equals(DEFAULT_SERVICE_ID)) { + if (serviceId != null + && serviceId.toUpperCase(Locale.ROOT).equals(DEFAULT_SERVICE_ID)) { String msg = "The service id '" + serviceId @@ -185,8 +171,7 @@ public final class KernelConfig { return new KernelConfig( Collections.unmodifiableMap(textCompletionServices), textEmbeddingGenerationServices, - chatCompletionServices, - skillBuilders); + chatCompletionServices); } } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/SkillExecutor.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/SkillExecutor.java new file mode 100644 index 0000000000..1b83d0785a --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/SkillExecutor.java @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel; + +import com.microsoft.semantickernel.exceptions.SkillsNotFoundException; +import com.microsoft.semantickernel.semanticfunctions.SemanticFunctionConfig; +import com.microsoft.semantickernel.skilldefinition.ReadOnlyFunctionCollection; +import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; + +import java.util.Map; + +import javax.annotation.Nullable; + +public interface SkillExecutor { + + /** + * Import a set of skills + * + * @param skillName + * @param skills + * @return + * @throws SkillsNotFoundException + */ + ReadOnlyFunctionCollection importSkill( + String skillName, Map skills) + throws SkillsNotFoundException; + + /** + * Get function collection with the skill name + * + * @param skillName + * @return + * @throws SkillsNotFoundException + */ + ReadOnlyFunctionCollection getSkill(String skillName) throws SkillsNotFoundException; + + /** + * Imports the native functions annotated on the given object as a skill. + * + * @param skillName + * @return + */ + ReadOnlyFunctionCollection importSkillFromDirectory( + String skillName, String parentDirectory, String skillDirectoryName); + + /** Imports the native functions annotated on the given object as a skill. */ + void importSkillsFromDirectory(String parentDirectory, String... skillNames); + + /** + * Imports the native functions annotated on the given object as a skill. Assumes that the + * directory that contains the skill is the same as skillName + * + * @param skillName + * @return + */ + ReadOnlyFunctionCollection importSkillFromDirectory(String skillName, String parentDirectory); + + /** + * Imports the native functions annotated on the given object as a skill. + * + * @param nativeSkill + * @param skillName + * @return + */ + ReadOnlyFunctionCollection importSkill(Object nativeSkill, @Nullable String skillName); + + /** + * @return Reference to the read-only skill collection containing all the imported functions + */ + ReadOnlySkillCollection getSkills(); +} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Verify.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Verify.java index 2ff77e5a35..f03a61e481 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Verify.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/Verify.java @@ -1,8 +1,14 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel; +import javax.annotation.Nullable; + public class Verify { public static void notNull(Object object) { assert object != null; } + + public static boolean isNullOrEmpty(@Nullable String s) { + return s == null || s.isEmpty(); + } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ai/AIException.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ai/AIException.java index 226ef8847a..c4e2a5ccf1 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ai/AIException.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ai/AIException.java @@ -11,14 +11,32 @@ public class AIException extends SKException { @Nonnull private final ErrorCodes errorCode; + /** + * Initializes a new instance of the {@link AIException} class. + * + * @param error The error code. + */ public AIException(@Nonnull ErrorCodes error) { this(error, null, null); } + /** + * Initializes a new instance of the {@link AIException} class. + * + * @param errorCode The error code. + * @param message The message. + */ public AIException(@Nonnull ErrorCodes errorCode, @Nullable String message) { this(errorCode, message, null); } + /** + * Initializes a new instance of the {@link AIException} class. + * + * @param errorCode The error code. + * @param message The message. + * @param innerException The cause of the exception. + */ public AIException( @Nonnull ErrorCodes errorCode, @Nullable String message, @@ -27,19 +45,21 @@ public class AIException extends SKException { this.errorCode = errorCode; } + /** + * Gets the error code. + * + * @return The error code. + */ public ErrorCodes getErrorCode() { return errorCode; } - /* Translate the error code into a default message */ + /** Translate the error code into a default message */ private static String getDefaultMessage( @Nonnull ErrorCodes errorCode, @Nullable String message) { return String.format("%s: %s", errorCode.getMessage(), message); } - /// - /// Possible error codes for exceptions - /// public enum ErrorCodes { UnknownError("Unknown error"), diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ai/embeddings/Embedding.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ai/embeddings/Embedding.java index 05c0e78710..5abbd25d3b 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ai/embeddings/Embedding.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/ai/embeddings/Embedding.java @@ -19,14 +19,6 @@ public class Embedding { private final List vector; - // /// - // /// An empty instance. - // /// - // [SuppressMessage("Design", "CA1000:Do not declare static members on generic types", - // Justification = "Static empty struct instance.")] - // public static Embedding Empty { get; } = new - // Embedding(Array.Empty()); - private static final Embedding EMPTY = new Embedding(Collections.unmodifiableList(new ArrayList<>())); @@ -43,6 +35,7 @@ public class Embedding { // /// Type is // unsupported. // /// A null vector is passed in. + public Embedding() { this.vector = Collections.emptyList(); } @@ -62,7 +55,7 @@ public class Embedding { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof Embedding)) return false; Embedding embedding = (Embedding) o; diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/BuildersSingleton.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/BuildersSingleton.java index 749d5d6178..8232a9a238 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/BuildersSingleton.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/BuildersSingleton.java @@ -16,7 +16,12 @@ import com.microsoft.semantickernel.textcompletion.TextCompletion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; + /** Enum singleton that service loads builder implementations */ +@SuppressWarnings("ImmutableEnumChecker") public enum BuildersSingleton { INST; @@ -27,94 +32,46 @@ public enum BuildersSingleton { "com.microsoft.semantickernel.DefaultKernel$Builder"; private static final String FALLBACK_TEXT_COMPLETION_BUILDER_CLASS = "com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletion$Builder"; - private static final String FALLBACK_TEXT_EMBEDDING_GENERATION_BUILDER_CLASS = "com.microsoft.semantickernel.connectors.ai.openai.textembeddings.OpenAITextEmbeddingGeneration$Builder"; private static final String FALLBACK_SKILL_COLLECTION_BUILDER_CLASS = - "com.microsoft.semantickernel.skilldefinition.DefaultCollection$Builder"; - + "com.microsoft.semantickernel.skilldefinition.DefaultSkillCollection$Builder"; private static final String FALLBACK_PROMPT_TEMPLATE_BUILDER_CLASS = "com.microsoft.semantickernel.semanticfunctions.DefaultPromptTemplate$Builder"; - private static final String FALLBACK_VARIABLE_BUILDER_CLASS = "com.microsoft.semantickernel.orchestration.DefaultContextVariables$Builder"; - private static final String FALLBACK_CONTEXT_BUILDER_CLASS = - "com.microsoft.semantickernel.orchestration.DefaultSemanticSKContext$Builder"; + "com.microsoft.semantickernel.orchestration.DefaultSKContext$Builder"; private static final String FALLBACK_PROMPT_TEMPLATE_ENGINE_BUILDER_CLASS = "com.microsoft.semantickernel.templateengine.DefaultPromptTemplateEngine$Builder"; private static final String FALLBACK_SEMANTIC_TEXT_MEMORY_CLASS = "com.microsoft.semantickernel.memory.DefaultSemanticTextMemory$Builder"; private static final String FALLBACK_CHAT_COMPLETION_BUILDER_CLASS = "com.microsoft.semantickernel.connectors.ai.openai.chatcompletion.OpenAIChatCompletion$Builder"; - private static final String FALLBACK_MEMORY_STORE_BUILDER_CLASS = "com.microsoft.semantickernel.memory.VolatileMemoryStore$Builder"; - private final FunctionBuilders functionBuilders; - private final Kernel.InternalBuilder kernelBuilder; - private final TextCompletion.Builder textCompletionBuilder; - private final EmbeddingGeneration.Builder textEmbeddingGenerationBuilder; - private final ReadOnlySkillCollection.Builder readOnlySkillCollection; - private final PromptTemplate.Builder promptTemplate; - private final ContextVariables.Builder variables; - private final SKContext.Builder context; - private final PromptTemplateEngine.Builder promptTemplateEngine; - private final ChatCompletion.Builder chatCompletion; - private final SemanticTextMemory.Builder semanticTextMemoryBuilder; - private final MemoryStore.Builder memoryStoreBuilder; + private final Map builders = new HashMap<>(); BuildersSingleton() { try { - functionBuilders = - ServiceLoadUtil.findServiceLoader( - FunctionBuilders.class, FALLBACK_FUNCTION_BUILDER_CLASS); - - kernelBuilder = - ServiceLoadUtil.findServiceLoader( - Kernel.InternalBuilder.class, FALLBACK_KERNEL_BUILDER_CLASS); - - textCompletionBuilder = - ServiceLoadUtil.findServiceLoader( - TextCompletion.Builder.class, FALLBACK_TEXT_COMPLETION_BUILDER_CLASS); - - textEmbeddingGenerationBuilder = - ServiceLoadUtil.findServiceLoader( - EmbeddingGeneration.Builder.class, - FALLBACK_TEXT_EMBEDDING_GENERATION_BUILDER_CLASS); - - readOnlySkillCollection = - ServiceLoadUtil.findServiceLoader( - ReadOnlySkillCollection.Builder.class, - FALLBACK_SKILL_COLLECTION_BUILDER_CLASS); - - promptTemplate = - ServiceLoadUtil.findServiceLoader( - PromptTemplate.Builder.class, FALLBACK_PROMPT_TEMPLATE_BUILDER_CLASS); - - variables = - ServiceLoadUtil.findServiceLoader( - ContextVariables.Builder.class, FALLBACK_VARIABLE_BUILDER_CLASS); - semanticTextMemoryBuilder = - ServiceLoadUtil.findServiceLoader( - SemanticTextMemory.Builder.class, FALLBACK_SEMANTIC_TEXT_MEMORY_CLASS); - - context = - ServiceLoadUtil.findServiceLoader( - SKContext.Builder.class, FALLBACK_CONTEXT_BUILDER_CLASS); - - promptTemplateEngine = - ServiceLoadUtil.findServiceLoader( - PromptTemplateEngine.Builder.class, - FALLBACK_PROMPT_TEMPLATE_ENGINE_BUILDER_CLASS); - - chatCompletion = - ServiceLoadUtil.findServiceLoader( - ChatCompletion.Builder.class, FALLBACK_CHAT_COMPLETION_BUILDER_CLASS); - - memoryStoreBuilder = - ServiceLoadUtil.findServiceLoader( - MemoryStore.Builder.class, FALLBACK_MEMORY_STORE_BUILDER_CLASS); + registerBuilder(FunctionBuilders.class, FALLBACK_FUNCTION_BUILDER_CLASS); + registerBuilder(Kernel.InternalBuilder.class, FALLBACK_KERNEL_BUILDER_CLASS); + registerBuilder(TextCompletion.Builder.class, FALLBACK_TEXT_COMPLETION_BUILDER_CLASS); + registerBuilder( + EmbeddingGeneration.Builder.class, + FALLBACK_TEXT_EMBEDDING_GENERATION_BUILDER_CLASS); + registerBuilder( + ReadOnlySkillCollection.Builder.class, FALLBACK_SKILL_COLLECTION_BUILDER_CLASS); + registerBuilder(PromptTemplate.Builder.class, FALLBACK_PROMPT_TEMPLATE_BUILDER_CLASS); + registerBuilder(ContextVariables.Builder.class, FALLBACK_VARIABLE_BUILDER_CLASS); + registerBuilder(SemanticTextMemory.Builder.class, FALLBACK_SEMANTIC_TEXT_MEMORY_CLASS); + registerBuilder(SKContext.Builder.class, FALLBACK_CONTEXT_BUILDER_CLASS); + registerBuilder( + PromptTemplateEngine.Builder.class, + FALLBACK_PROMPT_TEMPLATE_ENGINE_BUILDER_CLASS); + registerBuilder(ChatCompletion.Builder.class, FALLBACK_CHAT_COMPLETION_BUILDER_CLASS); + registerBuilder(MemoryStore.Builder.class, FALLBACK_MEMORY_STORE_BUILDER_CLASS); } catch (Throwable e) { Logger LOGGER = LoggerFactory.getLogger(BuildersSingleton.class); @@ -141,51 +98,59 @@ public enum BuildersSingleton { } } + private void registerBuilder(Class clazz, String fallbackClassName) { + builders.put(clazz, ServiceLoadUtil.findServiceLoader(clazz, fallbackClassName)); + } + + private T getInstance(Class clazz) { + return (T) builders.get(clazz).get(); + } + public FunctionBuilders getFunctionBuilders() { - return functionBuilders; + return getInstance(FunctionBuilders.class); } public Kernel.InternalBuilder getKernelBuilder() { - return kernelBuilder; + return getInstance(Kernel.InternalBuilder.class); } public TextCompletion.Builder getTextCompletionBuilder() { - return textCompletionBuilder; + return getInstance(TextCompletion.Builder.class); } public EmbeddingGeneration.Builder getTextEmbeddingGenerationBuilder() { - return textEmbeddingGenerationBuilder; + return getInstance(EmbeddingGeneration.Builder.class); } public ReadOnlySkillCollection.Builder getReadOnlySkillCollection() { - return readOnlySkillCollection; + return getInstance(ReadOnlySkillCollection.Builder.class); } public PromptTemplate.Builder getPromptTemplateBuilder() { - return promptTemplate; + return getInstance(PromptTemplate.Builder.class); } public PromptTemplateEngine.Builder getPromptTemplateEngineBuilder() { - return promptTemplateEngine; + return getInstance(PromptTemplateEngine.Builder.class); } public ContextVariables.Builder variables() { - return variables; + return getInstance(ContextVariables.Builder.class); } public SKContext.Builder context() { - return context; + return getInstance(SKContext.Builder.class); } public SemanticTextMemory.Builder getSemanticTextMemoryBuilder() { - return semanticTextMemoryBuilder; + return getInstance(SemanticTextMemory.Builder.class); } public ChatCompletion.Builder getChatCompletion() { - return chatCompletion; + return getInstance(ChatCompletion.Builder.class); } public MemoryStore.Builder getMemoryStoreBuilder() { - return memoryStoreBuilder; + return getInstance(MemoryStore.Builder.class); } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/FunctionBuilders.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/FunctionBuilders.java index 0e8be1aacd..a77bcf1ee6 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/FunctionBuilders.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/FunctionBuilders.java @@ -4,18 +4,12 @@ package com.microsoft.semantickernel.builders; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; -import javax.annotation.Nullable; - /** Provides various builders for completion functions */ public interface FunctionBuilders { - static CompletionSKFunction.Builder getCompletionBuilder() { - return getCompletionBuilder(null); - } - - static CompletionSKFunction.Builder getCompletionBuilder(@Nullable Kernel kernel) { + static CompletionSKFunction.Builder getCompletionBuilder(Kernel kernel) { return BuildersSingleton.INST.getFunctionBuilders().completionBuilders(kernel); } - CompletionSKFunction.Builder completionBuilders(@Nullable Kernel kernel); + CompletionSKFunction.Builder completionBuilders(Kernel kernel); } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/SKBuilders.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/SKBuilders.java index 95c9c17853..7a0ff4f4cf 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/SKBuilders.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/SKBuilders.java @@ -16,23 +16,18 @@ import com.microsoft.semantickernel.templateengine.PromptTemplateEngine; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import com.microsoft.semantickernel.textcompletion.TextCompletion; -import javax.annotation.Nullable; - public class SKBuilders { // Prevent creating object private SKBuilders() {} - public static CompletionSKFunction.Builder completionFunctions() { - return completionFunctions(null); - } - /** * A CompletionSKFunction builder, the created function will be registered on the kernel * provided * * @param kernel The kernel to register the function on + * @return a function builder */ - public static CompletionSKFunction.Builder completionFunctions(@Nullable Kernel kernel) { + public static CompletionSKFunction.Builder completionFunctions(Kernel kernel) { return FunctionBuilders.getCompletionBuilder(kernel); } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/ServiceLoadUtil.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/ServiceLoadUtil.java index 72c1a09687..3113482753 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/ServiceLoadUtil.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/builders/ServiceLoadUtil.java @@ -4,21 +4,26 @@ package com.microsoft.semantickernel.builders; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; +import java.util.function.Supplier; public class ServiceLoadUtil { private static final Logger LOGGER = LoggerFactory.getLogger(ServiceLoadUtil.class); private ServiceLoadUtil() {} - public static T findServiceLoader(Class clazz, String alternativeClassName) { + public static Supplier findServiceLoader(Class clazz, String alternativeClassName) { List services = findAllServiceLoaders(clazz); + + T impl = null; + if (services.size() > 0) { - return services.get(0); + impl = services.get(0); } try { @@ -26,7 +31,7 @@ public class ServiceLoadUtil { Object instance = Class.forName(alternativeClassName).getDeclaredConstructor().newInstance(); if (clazz.isInstance(instance)) { - return (T) instance; + impl = (T) instance; } } catch (ClassNotFoundException | InvocationTargetException @@ -37,7 +42,34 @@ public class ServiceLoadUtil { LOGGER.error("Unable to load service " + clazz.getName() + " ", e); } - throw new RuntimeException("Service not found: " + clazz.getName()); + if (impl == null) { + throw new RuntimeException("Service not found: " + clazz.getName()); + } + + try { + Constructor constructor = impl.getClass().getConstructor(); + + // Test that we can construct the builder + if (!clazz.isInstance(constructor.newInstance())) { + throw new RuntimeException( + "Builder creates instance of the wrong type: " + clazz.getName()); + } + + return () -> { + try { + return (T) constructor.newInstance(); + } catch (InstantiationException + | IllegalAccessException + | InvocationTargetException e) { + throw new RuntimeException(e); + } + }; + } catch (NoSuchMethodException e) { + throw new RuntimeException( + "Builder requires a no args constructor: " + clazz.getName()); + } catch (InvocationTargetException | InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Builder is of wrong type: " + clazz.getName()); + } } static List findAllServiceLoaders(Class clazz) { diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/extensions/KernelExtensions.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/extensions/KernelExtensions.java index c4e3348f7b..a125c38185 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/extensions/KernelExtensions.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/extensions/KernelExtensions.java @@ -2,10 +2,12 @@ package com.microsoft.semantickernel.extensions; import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.semantickernel.KernelException; import com.microsoft.semantickernel.builders.SKBuilders; import com.microsoft.semantickernel.semanticfunctions.PromptTemplate; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; import com.microsoft.semantickernel.semanticfunctions.SemanticFunctionConfig; +import com.microsoft.semantickernel.templateengine.PromptTemplateEngine; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,7 +16,6 @@ import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; -import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -24,7 +25,9 @@ public class KernelExtensions { private KernelExtensions() {} public static Map importSemanticSkillFromDirectory( - String parentDirectory, String skillDirectoryName) { + String parentDirectory, + String skillDirectoryName, + PromptTemplateEngine promptTemplateEngine) { String CONFIG_FILE = "config.json"; String PROMPT_FILE = "skprompt.txt"; @@ -35,7 +38,9 @@ public class KernelExtensions { File[] files = skillDir.listFiles(); if (files == null) { - return Collections.unmodifiableMap(new HashMap<>()); + throw new KernelException( + KernelException.ErrorCodes.FunctionNotAvailable, + "No Skills found in directory " + skillDir.getAbsolutePath()); } HashMap skills = new HashMap<>(); @@ -64,15 +69,17 @@ public class KernelExtensions { // config.ToJson()); // Load prompt template - PromptTemplate template = - SKBuilders.promptTemplate() - .build( - new String( - Files.readAllBytes(promptPath.toPath()), - Charset.defaultCharset()), - config); + String template = + new String( + Files.readAllBytes(promptPath.toPath()), Charset.defaultCharset()); - skills.put(dir.getName(), new SemanticFunctionConfig(config, template)); + PromptTemplate promptTemplate = + SKBuilders.promptTemplate() + .withPromptTemplate(template) + .withPromptTemplateConfig(config) + .build(promptTemplateEngine); + + skills.put(dir.getName(), new SemanticFunctionConfig(config, promptTemplate)); } catch (IOException e) { LOGGER.error("Failed to read file", e); } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/MemoryRecord.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/MemoryRecord.java index dead651d72..756e94d626 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/MemoryRecord.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/MemoryRecord.java @@ -143,7 +143,7 @@ public class MemoryRecord extends DataEntryBase { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof MemoryRecord)) return false; MemoryRecord that = (MemoryRecord) o; diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/MemoryRecordMetadata.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/MemoryRecordMetadata.java index 3fcb26073a..dac959bf2b 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/MemoryRecordMetadata.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/MemoryRecordMetadata.java @@ -97,7 +97,7 @@ public class MemoryRecordMetadata { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (!(o instanceof MemoryRecordMetadata)) return false; MemoryRecordMetadata that = (MemoryRecordMetadata) o; diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/SemanticTextMemory.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/SemanticTextMemory.java index 05cca76c6a..eaa2f1c2c8 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/SemanticTextMemory.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/memory/SemanticTextMemory.java @@ -8,7 +8,6 @@ import reactor.core.publisher.Mono; import java.util.List; import javax.annotation.CheckReturnValue; -import javax.annotation.Nonnull; import javax.annotation.Nullable; /** An interface for semantic memory that creates and recalls memories associated with text. */ @@ -28,9 +27,9 @@ public interface SemanticTextMemory { * @return Unique identifier of the saved memory record. */ public Mono saveInformationAsync( - @Nonnull String collection, - @Nonnull String text, - @Nonnull String externalId, + String collection, + String text, + String externalId, @Nullable String description, @Nullable String additionalMetadata); @@ -48,10 +47,10 @@ public interface SemanticTextMemory { * @return Unique identifier of the saved memory record. */ public Mono saveReferenceAsync( - @Nonnull String collection, - @Nonnull String text, - @Nonnull String externalId, - @Nonnull String externalSourceName, + String collection, + String text, + String externalId, + String externalSourceName, @Nullable String description, @Nullable String additionalMetadata); @@ -64,8 +63,7 @@ public interface SemanticTextMemory { * @param withEmbedding Whether to return the embedding of the memory found. * @return Memory record, or null when nothing is found */ - public Mono getAsync( - @Nonnull String collection, @Nonnull String key, boolean withEmbedding); + public Mono getAsync(String collection, String key, boolean withEmbedding); /** * Remove a memory by key. For local memories the key is the "id" used when saving the record. @@ -75,7 +73,7 @@ public interface SemanticTextMemory { * @param key Unique memory record identifier. * @return Mono completion. */ - public Mono removeAsync(@Nonnull String collection, @Nonnull String key); + public Mono removeAsync(String collection, String key); /** * Find some information in memory @@ -88,8 +86,8 @@ public interface SemanticTextMemory { * @return Memories found */ public Mono> searchAsync( - @Nonnull String collection, - @Nonnull String query, + String collection, + String query, int limit, double minRelevanceScore, boolean withEmbeddings); @@ -102,10 +100,10 @@ public interface SemanticTextMemory { public Mono> getCollectionsAsync(); interface Builder { - Builder setStorage(@Nonnull MemoryStore storage); + Builder setStorage(MemoryStore storage); Builder setEmbeddingGenerator( - @Nonnull EmbeddingGeneration embeddingGenerator); + EmbeddingGeneration embeddingGenerator); SemanticTextMemory build(); } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/AbstractSKContext.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/AbstractSKContext.java index 24286fa614..1351feeaeb 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/AbstractSKContext.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/AbstractSKContext.java @@ -15,8 +15,8 @@ import javax.annotation.CheckReturnValue; /// /// Semantic Kernel context. /// memory -public abstract class AbstractSKContext> implements SKContext { - @Nullable private final ReadOnlySkillCollection skills; +public abstract class AbstractSKContext implements SKContext { + private final ReadOnlySkillCollection skills; private final WritableContextVariables variables; @Nullable private final SemanticTextMemory memory; @@ -59,15 +59,19 @@ public abstract class AbstractSKContext> implements SKCon this.memory = null; } - this.skills = skills; + if (skills == null) { + this.skills = SKBuilders.skillCollection().build(); + } else { + this.skills = skills; + } } @CheckReturnValue @Override - public T copy() { + public SKContext copy() { ReadOnlySkillCollection clonedSkill; if (skills == null) { - clonedSkill = skills; + clonedSkill = null; } else { clonedSkill = skills.copy(); } @@ -80,20 +84,19 @@ public abstract class AbstractSKContext> implements SKCon return memory != null ? memory.copy() : null; } - @Nullable @Override public ReadOnlySkillCollection getSkills() { return skills; } @Override - public T setVariable(@NonNull String key, @NonNull String content) { + public SKContext setVariable(@NonNull String key, @NonNull String content) { variables.setVariable(key, content); return getThis(); } @Override - public T appendToVariable(@NonNull String key, @NonNull String content) { + public SKContext appendToVariable(@NonNull String key, @NonNull String content) { variables.appendToVariable(key, content); return getThis(); } @@ -106,16 +109,16 @@ public abstract class AbstractSKContext> implements SKCon /// if the pipeline reached the end. /// The current instance @Override - public T update(@NonNull String content) { + public SKContext update(@NonNull String content) { variables.update(content); return getThis(); } @Override - public T update(@NonNull ContextVariables newData) { + public SKContext update(@NonNull ContextVariables newData) { variables.update(newData, true); return getThis(); } - protected abstract T getThis(); + protected abstract SKContext getThis(); } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/AbstractSkFunction.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/AbstractSkFunction.java index 87631883d2..b1d5222870 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/AbstractSkFunction.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/AbstractSkFunction.java @@ -17,9 +17,8 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; -public abstract class AbstractSkFunction< - RequestConfiguration, ContextType extends SKContext> - implements SKFunction, RegistrableSkFunction { +public abstract class AbstractSkFunction + implements SKFunction, RegistrableSkFunction { private final DelegateTypes delegateType; private final List parameters; @@ -60,36 +59,43 @@ public abstract class AbstractSkFunction< } @Override - public Mono invokeAsync( - String input, @Nullable ContextType context, @Nullable RequestConfiguration settings) { + public Mono invokeAsync( + @Nullable String input, + @Nullable SKContext context, + @Nullable RequestConfiguration settings) { if (context == null) { assertSkillSupplierRegistered(); + context = - buildContext( - SKBuilders.variables().build(), - NullMemory.getInstance(), - skillsSupplier.get()); + SKBuilders.context() + .with(NullMemory.getInstance()) + .with(skillsSupplier == null ? null : skillsSupplier.get()) + .build(); } else { context = context.copy(); } - context = context.update(input); + if (input != null) { + context = context.update(input); + } return this.invokeAsync(context, settings); } @Override - public Mono invokeAsync(String input) { + public Mono invokeAsync(String input) { return invokeAsync(input, null, null); } @Override - public Mono invokeAsync( - ContextType context, @Nullable RequestConfiguration settings) { - // return new FutureTask(() -> function.run(null, settings, context)); - + public Mono invokeAsync( + @Nullable SKContext context, @Nullable RequestConfiguration settings) { if (context == null) { - context = buildContext(SKBuilders.variables().build(), NullMemory.getInstance(), null); + context = + SKBuilders.context() + .with(SKBuilders.variables().build()) + .with(NullMemory.getInstance()) + .build(); } else { context = context.copy(); } @@ -97,8 +103,8 @@ public abstract class AbstractSkFunction< return this.invokeAsyncInternal(context, settings); } - protected abstract Mono invokeAsyncInternal( - ContextType context, @Nullable RequestConfiguration settings); + protected abstract Mono invokeAsyncInternal( + SKContext context, @Nullable RequestConfiguration settings); @Override public String getSkillName() { @@ -157,6 +163,7 @@ public abstract class AbstractSkFunction< } @Override + @Nullable public String getDescription() { return description; } @@ -205,18 +212,31 @@ public abstract class AbstractSkFunction< + inputs; } - @Override - public ContextType buildContext() { + protected SKContext buildContext() { assertSkillSupplierRegistered(); - return buildContext(SKBuilders.variables().build(), null, getSkillsSupplier().get()); + return SKBuilders.context() + .with(SKBuilders.variables().build()) + .with(skillsSupplier == null ? null : skillsSupplier.get()) + .build(); } @Override - public Mono invokeWithCustomInputAsync( + public Mono invokeWithCustomInputAsync( ContextVariables input, - SemanticTextMemory semanticMemory, - ReadOnlySkillCollection skills) { - ContextType tmpContext = buildContext(input, semanticMemory, skills); + @Nullable SemanticTextMemory semanticMemory, + @Nullable ReadOnlySkillCollection skills) { + SKContext tmpContext = + SKBuilders.context().with(input).with(semanticMemory).with(skills).build(); return invokeAsync(tmpContext, null); } + + @Override + public Mono invokeAsync() { + return invokeAsync(null, null, null); + } + + @Override + public Mono invokeAsync(SKContext context) { + return invokeAsync(context, null); + } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ContextVariables.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ContextVariables.java index 0020815c25..f7f81d07cf 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ContextVariables.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/ContextVariables.java @@ -21,6 +21,11 @@ public interface ContextVariables { @CheckReturnValue WritableContextVariables writableClone(); + @Nullable + String getInput(); + + String prettyPrint(); + interface Builder { ContextVariables build(); diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionNotRegisteredException.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionNotRegisteredException.java index 0fc8d2ef59..910aa513e7 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionNotRegisteredException.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/FunctionNotRegisteredException.java @@ -5,13 +5,13 @@ import com.microsoft.semantickernel.SKException; public class FunctionNotRegisteredException extends SKException { - private static final String format = - "It does not appear this function(%s) has been registered on a kernel.%n" - + "Register it on a kernel either by passing it to " - + "KernelConfig.Builder().addSkill() when building the kernel, or%n" - + "passing it to Kernel.registerSemanticFunction"; - public FunctionNotRegisteredException(String name) { - super(String.format(format, name)); + super( + String.format( + "It does not appear this function(%s) has been registered on a kernel.%n" + + "Register it on a kernel either by passing it to " + + "KernelConfig.Builder().addSkill() when building the kernel, or%n" + + "passing it to Kernel.registerSemanticFunction", + name)); } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InternalBuildersSingleton.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InternalBuildersSingleton.java index 059f0f974b..23062257e2 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InternalBuildersSingleton.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/InternalBuildersSingleton.java @@ -6,6 +6,9 @@ import com.microsoft.semantickernel.builders.ServiceLoadUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.function.Supplier; + +@SuppressWarnings("ImmutableEnumChecker") enum InternalBuildersSingleton { INST; @@ -14,7 +17,7 @@ enum InternalBuildersSingleton { private static final String FALLBACK_VARIABLE_BUILDER_CLASS = "com.microsoft.semantickernel.orchestration.DefaultContextVariables$WritableBuilder"; - private final WritableContextVariables.Builder variables; + private final Supplier variables; InternalBuildersSingleton() { try { @@ -49,6 +52,6 @@ enum InternalBuildersSingleton { } public static WritableContextVariables.Builder variables() { - return INST.variables; + return INST.variables.get(); } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SKContext.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SKContext.java index b32148acd6..c75fb30208 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SKContext.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SKContext.java @@ -1,18 +1,20 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.orchestration; +import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.memory.SemanticTextMemory; import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; -import reactor.util.annotation.NonNull; import reactor.util.annotation.Nullable; +import java.util.function.Supplier; + import javax.annotation.CheckReturnValue; /** Semantic Kernel context. */ -public interface SKContext> { +public interface SKContext { - Type build( + SKContext build( ContextVariables variables, @Nullable SemanticTextMemory memory, @Nullable ReadOnlySkillCollection skills); @@ -42,7 +44,6 @@ public interface SKContext> { * * @return */ - @Nullable ReadOnlySkillCollection getSkills(); /** @@ -52,7 +53,7 @@ public interface SKContext> { * @param content * @return Context for fluent calls */ - Type setVariable(@NonNull String key, @NonNull String content); + SKContext setVariable(String key, String content); /** * Appends data to the given key @@ -61,7 +62,7 @@ public interface SKContext> { * @param content * @return Context for fluent calls */ - Type appendToVariable(@NonNull String key, @NonNull String content); + SKContext appendToVariable(String key, String content); /** * Updates the input entry with the given data @@ -70,7 +71,7 @@ public interface SKContext> { * @return Context for fluent calls */ @CheckReturnValue - Type update(@NonNull String content); + SKContext update(String content); /** * Merges in the given variables. Duplicate keys provided by newData will overwrite existing @@ -80,11 +81,46 @@ public interface SKContext> { * @return Context for fluent calls */ @CheckReturnValue - Type update(@NonNull ContextVariables newData); + SKContext update(ContextVariables newData); - Type copy(); + SKContext copy(); + + interface BuilderSupplier extends Supplier {} interface Builder { SKContext build(ReadOnlySkillCollection skills); + + SKContext build(); + + SKContext build(Class clazz); + + Builder with(ContextVariables variables); + + /** + * Sets the skills + * + * @param skills null argument will be ignored + * @return Context for fluent calls + */ + Builder with(@Nullable ReadOnlySkillCollection skills); + + /** + * Sets the memory + * + * @param memory null argument will be ignored + * @return Context for fluent calls + */ + Builder with(@Nullable SemanticTextMemory memory); + + Builder clone(SKContext context); + + /** + * Builds a context from the given kernel. If not explicitly set, the skills and memory will + * be used from the kernel. + * + * @param kernel + * @return + */ + SKContext build(Kernel kernel); } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SKFunction.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SKFunction.java index 3aeb43ed43..53dd3410ba 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SKFunction.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SKFunction.java @@ -2,6 +2,7 @@ package com.microsoft.semantickernel.orchestration; import com.microsoft.semantickernel.memory.SemanticTextMemory; +import com.microsoft.semantickernel.skilldefinition.FunctionView; import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; import reactor.core.publisher.Mono; @@ -15,52 +16,14 @@ import javax.annotation.Nullable; * @param The type of the configuration argument that will be provided when * the function is invoked */ -public interface SKFunction> { - /* - /// - /// Name of the function. The name is used by the skill collection and in prompt templates e.g. {{skillName.functionName}} - /// - string Name { get; } - - /// - /// Name of the skill containing the function. The name is used by the skill collection and in prompt templates e.g. {{skillName.functionName}} - /// - string SkillName { get; } - - /// - /// Function description. The description is used in combination with embeddings when searching relevant functions. - /// - string Description { get; } - - /// - /// Whether the function is defined using a prompt template. - /// IMPORTANT: native functions might use semantic functions internally, - /// so when this property is False, executing the function might still involve AI calls. - /// - public bool IsSemantic { get; } - - /// - /// AI service settings - /// - public CompleteRequestSettings RequestSettings { get; } - - /// - /// Returns a description of the function, including parameters. - /// - /// An instance of describing the function - FunctionView Describe(); - */ - // TODO: CancellationToken - /// - /// Invoke the internal delegate with an explicit string input - /// - /// String input - /// SK context - /// LLM completion settings - /// Application logger - /// Cancellation token - /// The updated context, potentially a new one if context switching is - // implemented. +public interface SKFunction { + /** + * Returns a description of the function, including parameters. + * + * @return An instance of {@link FunctionView} describing the function + */ + @Nullable + FunctionView describe(); /** * Invokes the function with the given input, context and settings @@ -71,7 +34,9 @@ public interface SKFunction invokeAsync(String input, ContextType context, RequestConfiguration settings); + Mono invokeAsync(String input, SKContext context, RequestConfiguration settings); + + Mono invokeAsync(); /** * Invokes the function with the given input @@ -80,10 +45,20 @@ public interface SKFunction invokeAsync(String input); + Mono invokeAsync(String input); + @Nullable Class getType(); + /** + * Invokes the function with the given context and settings + * + * @param context Request context + * @return an updated context with the result of the request + */ + @CheckReturnValue + Mono invokeAsync(SKContext context); + /** * Invokes the function with the given context and settings * @@ -92,31 +67,7 @@ public interface SKFunction invokeAsync(ContextType context, @Nullable RequestConfiguration settings); - - /// - /// Set the default skill collection to use when the function is invoked - /// without a context or with a context that doesn't have a collection. - /// - /// Kernel's skill collection - /// Self instance - /* - SKFunction setDefaultSkillCollection(SkillCollection skills); - /// - /// Set the AI service used by the semantic function, passing a factory method. - /// The factory allows to lazily instantiate the client and to properly handle its disposal. - /// - /// AI service factory - /// Self instance - SKFunction setAIService(Supplier serviceFactory); - - /// - /// Set the AI completion settings used with LLM requests - /// - /// LLM completion settings - /// Self instance - SKFunction setAIConfiguration(CompleteRequestSettings settings); - */ + Mono invokeAsync(SKContext context, @Nullable RequestConfiguration settings); /** * @return The name of the skill that this function is within @@ -135,28 +86,17 @@ public interface SKFunction invokeWithCustomInputAsync( + Mono invokeWithCustomInputAsync( ContextVariables variablesClone, - SemanticTextMemory semanticMemory, - ReadOnlySkillCollection skills); + @Nullable SemanticTextMemory semanticMemory, + @Nullable ReadOnlySkillCollection skills); } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SemanticSKContext.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SemanticSKContext.java deleted file mode 100644 index d7f003a7bd..0000000000 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/SemanticSKContext.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -package com.microsoft.semantickernel.orchestration; - -/** Semantic Kernel context. */ -public interface SemanticSKContext extends SKContext {} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/WritableContextVariables.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/WritableContextVariables.java index 0dd49bf983..408775588c 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/WritableContextVariables.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/orchestration/WritableContextVariables.java @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.orchestration; -import reactor.util.annotation.NonNull; - import java.util.Map; /** @@ -16,11 +14,11 @@ public interface WritableContextVariables extends ContextVariables { * * @param key variable name * @param content value to set - * @return Contect for fluent calls + * @return Context for fluent calls */ - ContextVariables setVariable(@NonNull String key, @NonNull String content); + ContextVariables setVariable(String key, String content); - ContextVariables appendToVariable(@NonNull String key, @NonNull String content); + ContextVariables appendToVariable(String key, String content); /// /// Updates the main input text with the new value after a function is complete. @@ -29,9 +27,11 @@ public interface WritableContextVariables extends ContextVariables { // result for the user /// if the pipeline reached the end. /// The current instance - ContextVariables update(@NonNull String content); + ContextVariables update(String content); - ContextVariables update(@NonNull ContextVariables newData, boolean merge); + ContextVariables update(ContextVariables newData, boolean merge); + + ContextVariables remove(String key); interface Builder { /** diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/planner/PlanningException.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/planner/PlanningException.java new file mode 100644 index 0000000000..e225d8e8d7 --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/planner/PlanningException.java @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.planner; + +import com.microsoft.semantickernel.SKException; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** Exception thrown for errors related to planning. */ +public class PlanningException extends SKException { + @Nonnull private final ErrorCodes errorCode; + + public PlanningException(@Nonnull ErrorCodes error) { + this(error, null, null); + } + + /** + * Initializes a new instance of the PlanningException class with a provided error code and + * message + * + * @param errorCode Error code + * @param message The exception message + */ + public PlanningException(@Nonnull ErrorCodes errorCode, @Nullable String message) { + this(errorCode, message, null); + } + + /** + * Initializes a new instance of the class with a provided error + * code, message, and inner exception. + * + * @param errorCode The error code + * @param message A string that describes the error + * @param innerException The exception that is the cause of the current exception + */ + public PlanningException( + @Nonnull ErrorCodes errorCode, + @Nullable String message, + @Nullable Throwable innerException) { + super(getDefaultMessage(errorCode, message), innerException); + this.errorCode = errorCode; + } + + /** + * Gets the error code + * + * @return The error code + */ + public ErrorCodes getErrorCode() { + return errorCode; + } + + /* Translate the error code into a default message */ + private static String getDefaultMessage( + @Nonnull ErrorCodes errorCode, @Nullable String message) { + return String.format("%s: %s", errorCode.getMessage(), message); + } + + /** Error codes for PlanningException */ + public enum ErrorCodes { + UnknownError("Unknown error"), + + InvalidGoal("Invalid goal"), + + InvalidPlan("Invalid plan"), + + InvalidConfiguration("Invalid configuration"); + + private final String message; + + ErrorCodes(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + } +} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplate.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplate.java index d92e240b2e..b5e70eead0 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplate.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/semanticfunctions/PromptTemplate.java @@ -22,14 +22,17 @@ public interface PromptTemplate { * Render the template using the information in the context * * @param executionContext Kernel execution context helpers - * @param promptTemplateEngine * @return Prompt rendered to string */ - Mono renderAsync(SKContext executionContext, PromptTemplateEngine promptTemplateEngine); + Mono renderAsync(SKContext executionContext); abstract class Builder { protected Builder() {} - public abstract PromptTemplate build(String promptTemplate, PromptTemplateConfig config); + public abstract PromptTemplate build(PromptTemplateEngine promptTemplateEngine); + + public abstract Builder withPromptTemplate(String promptTemplate); + + public abstract Builder withPromptTemplateConfig(PromptTemplateConfig config); } } 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 7b75a727db..ebf9cf3ebe 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 @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import reactor.util.annotation.Nullable; import java.util.ArrayList; +import java.util.Collections; import java.util.List; /** Prompt template configuration */ @@ -44,6 +45,8 @@ public class PromptTemplateConfig { completionConfig.presencePenalty, completionConfig.frequencyPenalty, completionConfig.maxTokens, + completionConfig.bestOf, + completionConfig.user, completionConfig.stopSequences)); } @@ -55,6 +58,8 @@ public class PromptTemplateConfig { completionConfig.presencePenalty, completionConfig.frequencyPenalty, completionConfig.maxTokens, + completionConfig.bestOf, + completionConfig.user, completionConfig.stopSequences)); } @@ -66,6 +71,8 @@ public class PromptTemplateConfig { presencePenalty, completionConfig.frequencyPenalty, completionConfig.maxTokens, + completionConfig.bestOf, + completionConfig.user, completionConfig.stopSequences)); } @@ -77,6 +84,8 @@ public class PromptTemplateConfig { completionConfig.presencePenalty, frequencyPenalty, completionConfig.maxTokens, + completionConfig.bestOf, + completionConfig.user, completionConfig.stopSequences)); } @@ -88,6 +97,8 @@ public class PromptTemplateConfig { completionConfig.presencePenalty, completionConfig.frequencyPenalty, maxTokens, + completionConfig.bestOf, + completionConfig.user, completionConfig.stopSequences)); } @@ -99,6 +110,8 @@ public class PromptTemplateConfig { completionConfig.presencePenalty, completionConfig.frequencyPenalty, completionConfig.maxTokens, + completionConfig.bestOf, + completionConfig.user, stopSequences)); } @@ -107,6 +120,10 @@ public class PromptTemplateConfig { } } + public InputConfig getInput() { + return input; + } + /** Completion configuration parameters */ public static class CompletionConfig { /* @@ -166,8 +183,37 @@ public class PromptTemplateConfig { */ public final List stopSequences; // { get; set; } = new(); + /** + * The maximum number of completions to generate for each prompt. This is used by the + * CompletionService to generate multiple completions for a single prompt. + */ + private final Integer bestOf; + + /** + * A unique identifier representing your end-user, which can help OpenAI to monitor and + * detect abuse + */ + private final String user; + public CompletionConfig() { - this(0.0, 0.0, 0.0, 0.0, 256, new ArrayList<>()); + this(0.0, 0.0, 0.0, 0.0, 256, 1, "", new ArrayList<>()); + } + + public CompletionConfig( + double temperature, + double topP, + double presencePenalty, + double frequencyPenalty, + int maxTokens) { + this( + temperature, + topP, + presencePenalty, + frequencyPenalty, + maxTokens, + 1, + "", + new ArrayList<>()); } @JsonCreator @@ -177,12 +223,22 @@ public class PromptTemplateConfig { @JsonProperty("presence_penalty") double presencePenalty, @JsonProperty("frequency_penalty") double frequencyPenalty, @JsonProperty("max_tokens") int maxTokens, + @JsonProperty("best_of") int bestOf, + @JsonProperty("user") String user, @JsonProperty(value = "stop_sequences") List stopSequences) { this.temperature = temperature; this.topP = topP; this.presencePenalty = presencePenalty; this.frequencyPenalty = frequencyPenalty; this.maxTokens = maxTokens; + + // bestOf must be at least 1 + this.bestOf = Math.max(1, bestOf); + + if (user == null) { + user = ""; + } + this.user = user; if (stopSequences == null) { stopSequences = new ArrayList<>(); } @@ -208,6 +264,22 @@ public class PromptTemplateConfig { public int getMaxTokens() { return maxTokens; } + + /** + * The maximum number of completions to generate for each prompt. This is used by the + * CompletionService to generate multiple completions for a single prompt. + */ + public int getBestOf() { + return bestOf; + } + + /** + * A unique identifier representing your end-user, which can help OpenAI to monitor and + * detect abuse + */ + public String getUser() { + return user; + } } /** Input parameter for semantic functions */ @@ -226,6 +298,34 @@ public class PromptTemplateConfig { this.description = description; this.defaultValue = defaultValue; } + + /** + * Name of the parameter to pass to the function. e.g. when using "{{$input}}" the name is + * "input", when using "{{$style}}" the name is "style", etc. + * + * @return name + */ + public String getName() { + return name; + } + + /** + * Parameter description for UI apps and planner. Localization is not supported here. + * + * @return description + */ + public String getDescription() { + return description; + } + + /** + * Default value when nothing is provided + * + * @return the default value + */ + public String getDefaultValue() { + return defaultValue; + } } /** Input configuration (list of all input parameters for a semantic function). */ @@ -235,37 +335,27 @@ public class PromptTemplateConfig { @JsonCreator public InputConfig(@JsonProperty("parameters") List parameters) { - this.parameters = parameters; + this.parameters = Collections.unmodifiableList(parameters); + } + + public List getParameters() { + return parameters; } } - /* - /// - /// Schema - Not currently used. - /// - [JsonPropertyName("schema")] - [JsonPropertyOrder(1)] - public int Schema { get; set; } = 1; - - /// - /// Type, such as "completion", "embeddings", etc. - /// - /// TODO: use enum - [JsonPropertyName("type")] - [JsonPropertyOrder(2)] - */ private final int schema; private final String type; // { get; set; } = "completion"; - /* - /// - /// Description - /// - [JsonPropertyName("description")] - [JsonPropertyOrder(3)] - */ private final String description; + public PromptTemplateConfig() { + this("", "", null); + } + + public PromptTemplateConfig(CompletionConfig completionConfig) { + this(1, "", "", completionConfig, new InputConfig(new ArrayList<>())); + } + public PromptTemplateConfig( String description, String type, @Nullable CompletionConfig completionConfig) { this(1, description, type, completionConfig, new InputConfig(new ArrayList<>())); @@ -277,7 +367,7 @@ public class PromptTemplateConfig { @JsonProperty("description") String description, @JsonProperty("type") String type, @Nullable @JsonProperty("completion") CompletionConfig completionConfig, - @JsonProperty("input") InputConfig input) { + @Nullable @JsonProperty("input") InputConfig input) { if (completionConfig == null) { completionConfig = new CompletionConfig(); } @@ -285,9 +375,17 @@ public class PromptTemplateConfig { this.description = description; this.type = type; this.completionConfig = completionConfig; + if (input == null) { + input = new InputConfig(new ArrayList<>()); + } this.input = input; } + /** + * Description + * + * @return Description + */ public String getDescription() { return description; } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/FunctionView.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/FunctionView.java new file mode 100644 index 0000000000..69abb789ff --- /dev/null +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/FunctionView.java @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.skilldefinition; + +import java.util.Collections; +import java.util.List; + +/** + * Class used to copy and export data from the skill collection. The data is mutable, but changes do + * not affect the skill collection. + */ +public class FunctionView { + // Name of the function. The name is used by the skill collection and in prompt templates e.g. + // {{skillName.functionName}} + private final String name; + + /// Name of the skill containing the function. The name is used by the skill collection and in + // prompt templates e.g. {{skillName.functionName}} + private final String skillName; + + // Function description. The description is used in combination with embeddings when searching + // relevant functions. + private final String description; + + // Whether the delegate points to a semantic function + private final boolean isSemantic; + + // Whether the delegate is an asynchronous function + private final boolean isAsynchronous; + + // List of function parameters + private final List parameters; + + /** + * Create a function view. + * + * @param name Function name + * @param skillName Skill name, e.g. the function namespace + * @param description Function description + * @param parameters List of function parameters provided by the skill developer + * @param isSemantic Whether the function is a semantic one (or native is False) + * @param isAsynchronous Whether the function is async. Note: all semantic functions are async + */ + public FunctionView( + String name, + String skillName, + String description, + List parameters, + boolean isSemantic, + boolean isAsynchronous) { + this.name = name; + this.skillName = skillName; + this.description = description; + this.parameters = Collections.unmodifiableList(parameters); + this.isSemantic = isSemantic; + this.isAsynchronous = isAsynchronous; + } + + /** + * List of function parameters + * + * @return the list of function parameters + */ + public List getParameters() { + return parameters; + } +} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ParameterView.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ParameterView.java index 717dfaaef1..5c7651692a 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ParameterView.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ParameterView.java @@ -28,6 +28,10 @@ public class ParameterView { this.defaultValue = defaultValue; } + public ParameterView(String name) { + this(name, "", ""); + } + public String getName() { return name; } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ReadOnlyFunctionCollection.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ReadOnlyFunctionCollection.java index 4b5bdf9a34..a13576a8cb 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ReadOnlyFunctionCollection.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ReadOnlyFunctionCollection.java @@ -12,7 +12,7 @@ public interface ReadOnlyFunctionCollection { String getSkillName(); - SKFunction getFunction(String functionName); + SKFunction getFunction(String functionName); /** * Get function with the given SKFunction type argument. @@ -34,5 +34,5 @@ public interface ReadOnlyFunctionCollection { /** * @return An unmodifiable list of all functions */ - List> getAll(); + List> getAll(); } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ReadOnlySkillCollection.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ReadOnlySkillCollection.java index 2d1a7e52f0..ccd4e0e3b8 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ReadOnlySkillCollection.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/skilldefinition/ReadOnlySkillCollection.java @@ -35,18 +35,19 @@ public interface ReadOnlySkillCollection { /** Get function with name from the global scope */ @Nullable - > T getFunction( - String functionName, @Nullable Class functionType); + > T getFunction(String functionName, @Nullable Class functionType); @Nullable - > T getFunction( - String skillName, String funName, Class functionClazz); + > T getFunction( + String skillName, String funName, @Nullable Class functionClazz); @Nullable ReadOnlyFunctionCollection getFunctions(String skillName); boolean hasFunction(String functionName); + boolean hasFunction(String skillName, String functionName); + public interface Builder { public ReadOnlySkillCollection build(); } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/PromptTemplateEngine.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/PromptTemplateEngine.java index 220a5a1d45..0a92adeff6 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/PromptTemplateEngine.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/templateengine/PromptTemplateEngine.java @@ -2,9 +2,12 @@ package com.microsoft.semantickernel.templateengine; import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.templateengine.blocks.Block; import reactor.core.publisher.Mono; +import java.util.List; + /** Prompt template engine interface */ public interface PromptTemplateEngine { @@ -30,6 +33,15 @@ public interface PromptTemplateEngine { */ Mono renderAsync(String templateText, SKContext context); + /** + * Given a prompt template string, extract all the blocks (text, variables, function calls) + * + * @param promptTemplate Prompt template (see skprompt.txt files) + * @return A list of all the blocks, ie the template tokenized in text, variables and function + * calls + */ + List extractBlocks(String promptTemplate); + abstract class Builder { protected Builder() {} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionRequestSettings.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionRequestSettings.java index 5885a6f256..653ea2f44c 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionRequestSettings.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionRequestSettings.java @@ -40,23 +40,56 @@ public class CompletionRequestSettings { /** Sequences where the completion will stop generating further tokens. */ private final List stopSequences; // { get; set; } = Array.Empty(); + /** + * The maximum number of completions to generate for each prompt. This is used by the + * CompletionService to generate multiple completions for a single prompt. + */ + private final int bestOf; + + /** + * A unique identifier representing your end-user, which can help OpenAI to monitor and detect + * abuse + */ + private final String user; + + public CompletionRequestSettings( + double temperature, + double topP, + double presencePenalty, + double frequencyPenalty, + int maxTokens) { + this( + temperature, + topP, + presencePenalty, + frequencyPenalty, + maxTokens, + 1, + "", + Collections.emptyList()); + } + public CompletionRequestSettings( double temperature, double topP, double presencePenalty, double frequencyPenalty, int maxTokens, + int bestOf, + String user, List stopSequences) { this.temperature = temperature; this.topP = topP; this.presencePenalty = presencePenalty; this.frequencyPenalty = frequencyPenalty; this.maxTokens = maxTokens; + this.bestOf = bestOf; + this.user = user; this.stopSequences = new ArrayList<>(stopSequences); } public CompletionRequestSettings() { - this(0, 0, 0, 0, 256, new ArrayList<>()); + this(0, 0, 0, 0, 256, 1, "", new ArrayList<>()); } /// @@ -72,6 +105,8 @@ public class CompletionRequestSettings { config.getPresencePenalty(), config.getFrequencyPenalty(), config.getMaxTokens(), + config.getBestOf(), + config.getUser(), new ArrayList<>()); } @@ -117,4 +152,20 @@ public class CompletionRequestSettings { public List getStopSequences() { return Collections.unmodifiableList(stopSequences); } + + /** + * The maximum number of completions to generate for each prompt. This is used by the + * CompletionService to generate multiple completions for a single prompt. + */ + public Integer getBestOf() { + return bestOf; + } + + /** + * A unique identifier representing your end-user, which can help OpenAI to monitor and detect + * abuse + */ + public String getUser() { + return user; + } } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionSKContext.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionSKContext.java deleted file mode 100644 index 46840ec9ee..0000000000 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionSKContext.java +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -package com.microsoft.semantickernel.textcompletion; - -import com.microsoft.semantickernel.orchestration.SKContext; - -public interface CompletionSKContext extends SKContext {} diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionSKFunction.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionSKFunction.java index 886391527c..d19cf76278 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionSKFunction.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/textcompletion/CompletionSKFunction.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.textcompletion; -import com.microsoft.semantickernel.builders.BuildersSingleton; +import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.orchestration.SKFunction; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; import com.microsoft.semantickernel.semanticfunctions.SemanticFunctionConfig; @@ -12,8 +12,7 @@ import java.util.List; import javax.annotation.Nullable; -public interface CompletionSKFunction - extends SKFunction { +public interface CompletionSKFunction extends SKFunction { /** * Method to aggregate partitioned results of a semantic function. @@ -22,12 +21,8 @@ public interface CompletionSKFunction * @param context Semantic Kernel context * @return Aggregated results */ - Mono aggregatePartitionedResultsAsync( - List partitionedInput, @Nullable CompletionSKContext context); - - static CompletionSKFunction.Builder builder() { - return BuildersSingleton.INST.getFunctionBuilders().completionBuilders(null); - } + Mono aggregatePartitionedResultsAsync( + List partitionedInput, @Nullable SKContext context); abstract class Builder { @@ -40,7 +35,7 @@ public interface CompletionSKFunction @Nullable String skillName); public abstract CompletionSKFunction createFunction( - String functionName, SemanticFunctionConfig functionConfig); + String prompt, PromptTemplateConfig.CompletionConfig functionConfig); public abstract CompletionSKFunction createFunction( @Nullable String skillNameFinal, diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/util/AzureOpenAISettings.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/util/AzureOpenAISettings.java index 48a6ff63f6..e6b24d77b1 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/util/AzureOpenAISettings.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/util/AzureOpenAISettings.java @@ -3,12 +3,17 @@ package com.microsoft.semantickernel.util; import java.io.IOException; +import javax.annotation.Nullable; + public class AzureOpenAISettings extends ClientSettings { private static final String DEFAULT_CLIENT_ID = "azureopenai"; - private String key; - private String endpoint; - private String deploymentName; + + @Nullable private String key = null; + + @Nullable private String endpoint = null; + + @Nullable private String deploymentName = null; public enum Property { AZURE_OPEN_AI_KEY("key"), @@ -26,14 +31,23 @@ public class AzureOpenAISettings extends ClientSettings { } public String getKey() { + if (key == null) { + throw new RuntimeException("Azure OpenAI key is not set"); + } return key; } public String getEndpoint() { + if (endpoint == null) { + throw new RuntimeException("Azure OpenAI endpoint is not set"); + } return endpoint; } public String getDeploymentName() { + if (deploymentName == null) { + throw new RuntimeException("Azure OpenAI deployment name is not set"); + } return deploymentName; } diff --git a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/util/OpenAISettings.java b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/util/OpenAISettings.java index bf6b13875f..0f0b77ce36 100644 --- a/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/util/OpenAISettings.java +++ b/java/semantickernel-api/src/main/java/com/microsoft/semantickernel/util/OpenAISettings.java @@ -3,10 +3,12 @@ package com.microsoft.semantickernel.util; import java.io.IOException; +import javax.annotation.Nullable; + public class OpenAISettings extends ClientSettings { - private String key; - private String organizationId; private static final String DEFAULT_CLIENT_ID = "openai"; + @Nullable private String key = null; + @Nullable private String organizationId = null; private enum Property { OPEN_AI_KEY("key"), @@ -23,10 +25,16 @@ public class OpenAISettings extends ClientSettings { } public String getKey() { + if (key == null) { + throw new RuntimeException("OpenAI key is not set"); + } return key; } public String getOrganizationId() { + if (organizationId == null) { + throw new RuntimeException("OpenAI organization id is not set"); + } return organizationId; } diff --git a/java/semantickernel-bom/pom.xml b/java/semantickernel-bom/pom.xml index afa834d4d1..caab26e358 100644 --- a/java/semantickernel-bom/pom.xml +++ b/java/semantickernel-bom/pom.xml @@ -1,8 +1,8 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 com.microsoft.semantickernel @@ -44,10 +44,17 @@ semantickernel-core-skills ${project.version} + + com.microsoft.semantickernel.extensions + actionplanner-extension + ${project.version} + + + com.azure azure-ai-openai - 1.0.0-beta.1 + 1.0.0-beta.2 org.slf4j diff --git a/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/chatcompletion/OpenAIChatCompletion.java b/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/chatcompletion/OpenAIChatCompletion.java index bff9474a2d..cc9df2e36a 100644 --- a/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/chatcompletion/OpenAIChatCompletion.java +++ b/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/chatcompletion/OpenAIChatCompletion.java @@ -14,6 +14,7 @@ import com.microsoft.semantickernel.connectors.ai.openai.azuresdk.ClientBase; import reactor.core.publisher.Mono; +import java.util.HashMap; import java.util.List; import java.util.stream.Collectors; @@ -28,6 +29,7 @@ public class OpenAIChatCompletion extends ClientBase implements ChatCompletion build(OpenAIAsyncClient client, String modelId) { return new OpenAIChatCompletion(client, modelId); } @@ -40,6 +42,7 @@ public class OpenAIChatCompletion extends ClientBase implements ChatCompletion generateMessageAsync( OpenAIChatHistory chat, @Nullable ChatRequestSettings requestSettings) { @@ -98,7 +101,9 @@ public class OpenAIChatCompletion extends ClientBase implements ChatCompletion()); + options.setN(requestSettings.getBestOf()); + options.setUser(requestSettings.getUser()); return options; } diff --git a/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/textcompletion/OpenAITextCompletion.java b/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/textcompletion/OpenAITextCompletion.java index 7560f0817f..9a54c23505 100644 --- a/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/textcompletion/OpenAITextCompletion.java +++ b/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/textcompletion/OpenAITextCompletion.java @@ -15,6 +15,7 @@ import jakarta.inject.Inject; import reactor.core.publisher.Mono; import java.util.Collections; +import java.util.HashMap; import java.util.List; /// @@ -59,7 +60,9 @@ public class OpenAITextCompletion extends ClientBase implements TextCompletion { .setFrequencyPenalty(requestSettings.getFrequencyPenalty()) .setPresencePenalty(requestSettings.getPresencePenalty()) .setModel(getModelId()) - .setUser(null); + .setUser(requestSettings.getUser()) + .setBestOf(requestSettings.getBestOf()) + .setLogitBias(new HashMap<>()); return getClient() .getCompletions(getModelId(), completionsOptions) diff --git a/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/textembeddings/OpenAITextEmbeddingGeneration.java b/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/textembeddings/OpenAITextEmbeddingGeneration.java index 50b565c1d9..3e30a4fdb0 100644 --- a/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/textembeddings/OpenAITextEmbeddingGeneration.java +++ b/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/ai/openai/textembeddings/OpenAITextEmbeddingGeneration.java @@ -27,7 +27,8 @@ public class OpenAITextEmbeddingGeneration extends ClientBase } protected Mono>> internalGenerateTextEmbeddingsAsync(List data) { - EmbeddingsOptions options = new EmbeddingsOptions(data).setModel(getModelId()).setUser("default"); + EmbeddingsOptions options = + new EmbeddingsOptions(data).setModel(getModelId()).setUser("default"); return getClient() .getEmbeddings(getModelId(), options) diff --git a/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchMemory.java b/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchMemory.java index 30ef9184c2..ab5e1715b8 100644 --- a/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchMemory.java +++ b/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchMemory.java @@ -19,14 +19,12 @@ import com.azure.search.documents.models.QueryLanguage; import com.azure.search.documents.models.QueryType; import com.azure.search.documents.models.SearchOptions; import com.azure.search.documents.util.SearchPagedResponse; -import com.microsoft.semantickernel.exceptions.NotSupportedException; import com.microsoft.semantickernel.memory.MemoryQueryResult; import com.microsoft.semantickernel.memory.MemoryRecordMetadata; import com.microsoft.semantickernel.memory.SemanticTextMemory; + import reactor.core.publisher.Mono; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -40,6 +38,9 @@ import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + /** * Semantic Memory implementation using Azure Cognitive Search. For more information about Azure * Cognitive Search {@see https://learn.microsoft.com/azure/search/search-what-is-azure-search} @@ -57,7 +58,8 @@ public class AzureCognitiveSearchMemory implements SemanticTextMemory { * Create a new instance of semantic memory using Azure Cognitive Search. * * @param endpoint Azure Cognitive Search URI, e.g. {@code "https://contoso.search.windows.net"} - * @param apiKey The key used to authenticate HTTP requests sent to the Azure Cognitive Search service. + * @param apiKey The key used to authenticate HTTP requests sent to the Azure Cognitive Search + * service. */ public AzureCognitiveSearchMemory(String endpoint, String apiKey) { AzureKeyCredential credentials = new AzureKeyCredential(apiKey); @@ -72,7 +74,8 @@ public class AzureCognitiveSearchMemory implements SemanticTextMemory { * Create a new instance of semantic memory using Azure Cognitive Search. * * @param endpoint Azure Cognitive Search URI, e.g. {@code "https://contoso.search.windows.net"} - * @param credentials TokenCredential used to authorize requests sent to the Azure Cognitive Search service. + * @param credentials TokenCredential used to authorize requests sent to the Azure Cognitive + * Search service. */ public AzureCognitiveSearchMemory(String endpoint, TokenCredential credentials) { this._adminClient = @@ -255,21 +258,18 @@ public class AzureCognitiveSearchMemory implements SemanticTextMemory { PrioritizedFields prioritizedFields = new PrioritizedFields() - .setTitleField(titleField) - .setPrioritizedContentFields(prioritizedContentFields); + .setTitleField(titleField) + .setPrioritizedContentFields(prioritizedContentFields); SemanticConfiguration semanticConfiguration = new SemanticConfiguration("default", prioritizedFields); SemanticSettings semanticSettings = new SemanticSettings() - .setConfigurations( - Collections.singletonList(semanticConfiguration) - ); + .setConfigurations(Collections.singletonList(semanticConfiguration)); SearchIndex newIndex = - new SearchIndex(indexName, fields) - .setSemanticSettings(semanticSettings); + new SearchIndex(indexName, fields).setSemanticSettings(semanticSettings); return this._adminClient.createIndex(newIndex); } @@ -292,15 +292,17 @@ public class AzureCognitiveSearchMemory implements SemanticTextMemory { return response.getValue().getResults().get(0).getKey(); }; - return client.mergeOrUploadDocumentsWithResponse(Collections.singleton(record), throwOnAnyError) + return client.mergeOrUploadDocumentsWithResponse( + Collections.singleton(record), throwOnAnyError) .map(transform) - .onErrorResume(e -> - createIndexAsync(indexName) - .then(client.mergeOrUploadDocumentsWithResponse( - Collections.singleton(record), throwOnAnyError)) - .map(transform) - ); - + .onErrorResume( + e -> + createIndexAsync(indexName) + .then( + client.mergeOrUploadDocumentsWithResponse( + Collections.singleton(record), + throwOnAnyError)) + .map(transform)); } private static MemoryRecordMetadata toMemoryRecordMetadata(AzureCognitiveSearchRecord data) { diff --git a/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchRecord.java b/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchRecord.java index 58b0fa409f..6d0b3ebeaf 100644 --- a/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchRecord.java +++ b/java/semantickernel-connectors-parent/semantickernel-connectors/src/main/java/com/microsoft/semantickernel/connectors/memory/azurecognitivesearch/AzureCognitiveSearchRecord.java @@ -12,7 +12,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.util.Arrays; import java.util.List; - /** * Azure Cognitive Search record and index definition. Note: once defined, index cannot be modified. */ @@ -37,12 +36,12 @@ public final class AzureCognitiveSearchRecord { @JsonCreator public AzureCognitiveSearchRecord( - @JsonProperty("Id") String id, - @JsonProperty("Text") String text, - @JsonProperty("Description") String description, - @JsonProperty("AdditionalMetadata") String additionalMetadata, - @JsonProperty("ExternalSourceName") String externalSourceName, - @JsonProperty("Reference") boolean isReference) { + @JsonProperty("Id") String id, + @JsonProperty("Text") String text, + @JsonProperty("Description") String description, + @JsonProperty("AdditionalMetadata") String additionalMetadata, + @JsonProperty("ExternalSourceName") String externalSourceName, + @JsonProperty("Reference") boolean isReference) { this.id = id; this.text = text; this.description = description; @@ -126,8 +125,7 @@ public final class AzureCognitiveSearchRecord { new SearchField("ExternalSourceName", SearchFieldDataType.STRING) .setSearchable(true) .setAnalyzerName(LexicalAnalyzerName.EN_LUCENE), - new SearchField("Reference", SearchFieldDataType.BOOLEAN).setFilterable(true) - ); + new SearchField("Reference", SearchFieldDataType.BOOLEAN).setFilterable(true)); } // TODO: add one more field with the vector, float array, mark it as searchable diff --git a/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/ConversationSummarySkill.java b/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/ConversationSummarySkill.java index 3185659abd..de4179b916 100644 --- a/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/ConversationSummarySkill.java +++ b/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/ConversationSummarySkill.java @@ -8,7 +8,6 @@ import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute; import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters; import com.microsoft.semantickernel.text.TextChunker; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import reactor.core.publisher.Mono; @@ -25,6 +24,7 @@ public class ConversationSummarySkill { private final CompletionSKFunction summarizeConversationFunction; private final CompletionSKFunction conversationActionItemsFunction; private final CompletionSKFunction conversationTopicsFunction; + private final Kernel kernel; /** * Initializes a new instance of the ConversationSummarySkill class @@ -32,6 +32,7 @@ public class ConversationSummarySkill { * @param kernel Kernel instance */ public ConversationSummarySkill(Kernel kernel) { + this.kernel = kernel; this.summarizeConversationFunction = SKBuilders.completionFunctions(kernel) .createFunction( @@ -85,17 +86,22 @@ public class ConversationSummarySkill { @DefineSKFunction( description = "Given a long conversation transcript, summarize the conversation.", name = "SummarizeConversation") - public Mono summarizeConversationAsync( + public Mono summarizeConversationAsync( @SKFunctionInputAttribute @SKFunctionParameters( description = "A long conversation transcript.", name = "input") String input, - @Nullable SKContext context) { + @Nullable SKContext context) { List lines = TextChunker.splitPlainTextLines(input, MaxTokens); List paragraphs = TextChunker.splitPlainTextParagraphs(lines, MaxTokens); - CompletionSKContext completionContext = summarizeConversationFunction.buildContext(context); + if (context == null) { + context = + SKBuilders.context().with(kernel.getSkills()).with(kernel.getMemory()).build(); + } + + SKContext completionContext = context.copy(); return this.summarizeConversationFunction.aggregatePartitionedResultsAsync( paragraphs, completionContext); @@ -111,17 +117,17 @@ public class ConversationSummarySkill { @DefineSKFunction( description = "Given a long conversation transcript, identify action items.", name = "GetConversationActionItems") - public Mono getConversationActionItemsAsync( + public Mono getConversationActionItemsAsync( @SKFunctionInputAttribute @SKFunctionParameters( description = "A long conversation transcript.", name = "input") String input, - SKContext context) { + SKContext context) { List lines = TextChunker.splitPlainTextLines(input, MaxTokens); List paragraphs = TextChunker.splitPlainTextParagraphs(lines, MaxTokens); - CompletionSKContext completionContext = summarizeConversationFunction.buildContext(context); + SKContext completionContext = context.copy(); return this.conversationActionItemsFunction.aggregatePartitionedResultsAsync( paragraphs, completionContext); @@ -138,17 +144,17 @@ public class ConversationSummarySkill { description = "Given a long conversation transcript, identify topics worth remembering.", name = "GetConversationTopics") - public Mono getConversationTopicsAsync( + public Mono getConversationTopicsAsync( @SKFunctionInputAttribute @SKFunctionParameters( description = "A long conversation transcript.", name = "input") String input, - SKContext context) { + SKContext context) { List lines = TextChunker.splitPlainTextLines(input, MaxTokens); List paragraphs = TextChunker.splitPlainTextParagraphs(lines, MaxTokens); - CompletionSKContext completionContext = summarizeConversationFunction.buildContext(context); + SKContext completionContext = context.copy(); return this.conversationTopicsFunction.aggregatePartitionedResultsAsync( paragraphs, completionContext); diff --git a/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/FileIOSkill.java b/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/FileIOSkill.java index 9881baa612..ec43eb5bfd 100644 --- a/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/FileIOSkill.java +++ b/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/FileIOSkill.java @@ -6,6 +6,7 @@ import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputA import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters; import java.io.IOException; +import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -41,12 +42,16 @@ public class FileIOSkill { @SKFunctionInputAttribute @SKFunctionParameters(name = "path", description = "Source file") String path, - @SKFunctionParameters(name = "charset", description = "Character set to use to read the file", defaultValue="UTF-8") - String charset) throws IOException { + @SKFunctionParameters( + name = "charset", + description = "Character set to use to read the file", + defaultValue = "UTF-8") + String charset) + throws IOException { Path filePath = Paths.get(path); byte[] fileBytes; fileBytes = Files.readAllBytes(filePath); - String fileContents = new String(fileBytes, charset); + String fileContents = new String(fileBytes, Charset.forName(charset)); return fileContents; } @@ -61,22 +66,25 @@ public class FileIOSkill { * * @param path The destination file. * @param content Contains the 'content' to write - * @return An awaitable task. */ @DefineSKFunction(description = "Write a file", name = "writeAsync") public void writeFileAsync( @SKFunctionInputAttribute @SKFunctionParameters(name = "path", description = "Destination file") String path, - @SKFunctionParameters( + @SKFunctionParameters( name = "content", description = "File content", defaultValue = "", type = String.class) String content, - @SKFunctionParameters(name = "charset", description = "Character set to use to read the file", defaultValue="UTF-8") - String charset) throws IOException { - Path filePath = Paths.get(path); - Files.write(filePath, content.getBytes(charset)); + @SKFunctionParameters( + name = "charset", + description = "Character set to use to read the file", + defaultValue = "UTF-8") + String charset) + throws IOException { + Path filePath = Paths.get(path); + Files.write(filePath, content.getBytes(charset)); } } diff --git a/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/MathSkill.java b/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/MathSkill.java index 0587d2fa96..d1f15f2275 100644 --- a/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/MathSkill.java +++ b/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/MathSkill.java @@ -9,31 +9,35 @@ import java.math.BigDecimal; /** * MathSkill provides a set of functions to make Math calculations. - * + * *
    - *
  • input is the first number in the operation.
  • - *
  • amount is the second number in the operation.
  • + *
  • input is the first number in the operation. + *
  • amount is the second number in the operation. *
- * + * *

Usage: - *

+ *
+ * 
+ * + *
  * kernel.ImportSkill("math", new MathSkill());
- * 
+ *
  * {{math.add}}         => Returns the sum of input and amount
  * {{math.subtract}}    => Returns the differentce of input from amount
- * 
- *

- * + *
+ * + *
+ * * @implNote This skill uses BigDecimal to perform the number conversion and the calculations. */ public class MathSkill { /** * Returns the addition result of input and amount values provided. - * - * This skill attempts to convert the numbers to BigDecimal and then add them. - * - * If a conversion error occurs, the skill will throw an exception. + * + *

This skill attempts to convert the numbers to BigDecimal and then add them. + * + *

If a conversion error occurs, the skill will throw an exception. * * @param input Initial value as string to add the specified amount * @param amount The amount to be added to input @@ -54,11 +58,11 @@ public class MathSkill { /** * Returns the subtraction result of input and amount values provided. - * - * This skill attempts to convert the numbers to BigDecimal and then subtract them. - * - * If a conversion error occurs, the skill will throw an exception. - * + * + *

This skill attempts to convert the numbers to BigDecimal and then subtract them. + * + *

If a conversion error occurs, the skill will throw an exception. + * * @param input Initial value as string to subtract the specified amount * @param amount The amount to be subtracted from input * @return The resulting difference as a String. diff --git a/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/TextSkill.java b/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/TextSkill.java index 949afcd4b6..6ae9e392c9 100644 --- a/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/TextSkill.java +++ b/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/TextSkill.java @@ -49,6 +49,6 @@ public class TextSkill { @SKFunctionInputAttribute @SKFunctionParameters(description = "Text to lowercase", name = "input") String input) { - return input.toLowerCase(); + return input.toLowerCase(Locale.ROOT); } } diff --git a/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/TimeSkill.java b/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/TimeSkill.java index d2fc4fb445..8efa98212d 100644 --- a/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/TimeSkill.java +++ b/java/semantickernel-core-skills/src/main/java/com/microsoft/semantickernel/coreskills/TimeSkill.java @@ -7,7 +7,6 @@ import java.time.ZoneId; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.format.FormatStyle; import java.time.format.TextStyle; import java.util.Locale; @@ -55,6 +54,9 @@ import java.util.Locale; *

{{time.timeZoneName}} => Pacific Time */ public class TimeSkill { + + public static final String DAY_MONTH_DAY_YEAR = "EEEE, MMMM d, yyyy"; + /** * Get the current date. * @@ -64,7 +66,8 @@ public class TimeSkill { */ @DefineSKFunction(name = "date", description = "Get the current date") public String date() { - return DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(ZonedDateTime.now()); + // Example: Sunday, 12 January, 2025 + return DateTimeFormatter.ofPattern(DAY_MONTH_DAY_YEAR).format(ZonedDateTime.now()); } /** @@ -76,6 +79,7 @@ public class TimeSkill { */ @DefineSKFunction(name = "time", description = "Get the current time") public String time() { + // Example: 09:15:07 PM return DateTimeFormatter.ofPattern("hh:mm:ss a").format(ZonedDateTime.now()); } @@ -88,7 +92,7 @@ public class TimeSkill { */ @DefineSKFunction(name = "utcNow", description = "Get the current UTC date and time") public String utcNow() { - return DateTimeFormatter.ofPattern("EEEE, MMMM dd, yyyy h:mm a") + return DateTimeFormatter.ofPattern(DAY_MONTH_DAY_YEAR + " h:mm a") .format(ZonedDateTime.now().withZoneSameInstant(ZoneOffset.UTC)); } @@ -115,7 +119,7 @@ public class TimeSkill { name = "now", description = "Get the current date and time in the local time zone") public String now() { - return DateTimeFormatter.ofPattern("EEEE, MMMM dd, yyyy h:mm a") + return DateTimeFormatter.ofPattern(DAY_MONTH_DAY_YEAR + " h:mm a") .format(ZonedDateTime.now()); } @@ -164,7 +168,7 @@ public class TimeSkill { */ @DefineSKFunction(name = "day", description = "Get the current day of the month") public String day() { - return DateTimeFormatter.ofPattern("dd").format(ZonedDateTime.now()); + return DateTimeFormatter.ofPattern("d").format(ZonedDateTime.now()); } /** @@ -217,7 +221,7 @@ public class TimeSkill { description = "Get the date of offset from today by a provided number of days") public static String daysAgo(String days) { int offsetDays = Integer.parseInt(days); - return DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL) + return DateTimeFormatter.ofPattern(DAY_MONTH_DAY_YEAR) .format(ZonedDateTime.now().minusDays(offsetDays)); } @@ -241,7 +245,7 @@ public class TimeSkill { currentDate.getDayOfWeek().getDisplayName(TextStyle.FULL, systemLocale); if (currentDayName.equalsIgnoreCase(dayName)) { - return DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(currentDate); + return DateTimeFormatter.ofPattern(DAY_MONTH_DAY_YEAR).format(currentDate); } } throw new IllegalArgumentException("dayName is not recognized"); diff --git a/java/semantickernel-core-skills/src/test/java/com/microsoft/semantickernel/coreskills/FileIOSkillTest.java b/java/semantickernel-core-skills/src/test/java/com/microsoft/semantickernel/coreskills/FileIOSkillTest.java index 184c83db38..9fc0ef0633 100644 --- a/java/semantickernel-core-skills/src/test/java/com/microsoft/semantickernel/coreskills/FileIOSkillTest.java +++ b/java/semantickernel-core-skills/src/test/java/com/microsoft/semantickernel/coreskills/FileIOSkillTest.java @@ -1,18 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.coreskills; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import com.microsoft.semantickernel.orchestration.ContextVariables; -import com.microsoft.semantickernel.orchestration.SKContext; - -import reactor.test.StepVerifier; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; public class FileIOSkillTest { @@ -21,7 +16,7 @@ public class FileIOSkillTest { // Create a temporary file with some content String content = "Hello, World!"; Path tempFile = Files.createTempFile("test", ".txt"); - Files.write(tempFile, content.getBytes()); + Files.write(tempFile, content.getBytes(StandardCharsets.UTF_8)); // Create an instance of FileIOSkill FileIOSkill fileIOSkill = new FileIOSkill(); @@ -52,7 +47,7 @@ public class FileIOSkillTest { fileIOSkill.writeFileAsync(tempFile.toAbsolutePath().toString(), content, "UTF-8"); // Read the written file to verify the content - String fileContents = new String(Files.readAllBytes(tempFile)); + String fileContents = new String(Files.readAllBytes(tempFile), StandardCharsets.UTF_8); Assertions.assertEquals(content, fileContents); // Delete the temporary file diff --git a/java/semantickernel-core-skills/src/test/java/com/microsoft/semantickernel/coreskills/TimeSkillTest.java b/java/semantickernel-core-skills/src/test/java/com/microsoft/semantickernel/coreskills/TimeSkillTest.java index 2ccf2212c1..8ceb15c677 100644 --- a/java/semantickernel-core-skills/src/test/java/com/microsoft/semantickernel/coreskills/TimeSkillTest.java +++ b/java/semantickernel-core-skills/src/test/java/com/microsoft/semantickernel/coreskills/TimeSkillTest.java @@ -4,10 +4,13 @@ package com.microsoft.semantickernel.coreskills; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mockito.*; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; -import java.time.*; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Locale; @SuppressWarnings("ReturnValueIgnored") public class TimeSkillTest { @@ -37,7 +40,7 @@ public class TimeSkillTest { mocked.when(ZonedDateTime::now).thenReturn(mockDateTime); String result = timeSkill.time(); - assertEquals("09:15:07 AM", result); + assertEqualsIgnoreCase("09:15:07 AM", result); } } @@ -61,7 +64,7 @@ public class TimeSkillTest { mocked.when(ZonedDateTime::now).thenReturn(mockDateTime); String result = timeSkill.now(); - assertEquals("Sunday, January 12, 2025 9:15 AM", result); + assertEqualsIgnoreCase("Sunday, January 12, 2025 9:15 AM", result); } } @@ -125,6 +128,10 @@ public class TimeSkillTest { } } + public void assertEqualsIgnoreCase(String a, String b) { + Assertions.assertEquals(a.toUpperCase(Locale.ROOT), b.toUpperCase(Locale.ROOT)); + } + @Test public void testHour() { TimeSkill timeSkill = new TimeSkill(); @@ -133,7 +140,7 @@ public class TimeSkillTest { mocked.when(ZonedDateTime::now).thenReturn(mockDateTime); String result = timeSkill.hour(); - assertEquals("9 AM", result); + assertEqualsIgnoreCase("9 AM", result); } } diff --git a/java/semantickernel-core/pom.xml b/java/semantickernel-core/pom.xml index 071560572b..f17ab320c1 100644 --- a/java/semantickernel-core/pom.xml +++ b/java/semantickernel-core/pom.xml @@ -87,6 +87,11 @@ sequentialplanner-extensions test + + com.microsoft.semantickernel.extensions + actionplanner-extension + test + jakarta.inject diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/DefaultKernel.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/DefaultKernel.java index ef961886f1..f198f42896 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/DefaultKernel.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/DefaultKernel.java @@ -9,14 +9,12 @@ import com.microsoft.semantickernel.chatcompletion.ChatCompletion; import com.microsoft.semantickernel.coreskills.SkillImporter; import com.microsoft.semantickernel.exceptions.NotSupportedException; import com.microsoft.semantickernel.exceptions.SkillsNotFoundException; +import com.microsoft.semantickernel.extensions.KernelExtensions; import com.microsoft.semantickernel.memory.MemoryConfiguration; import com.microsoft.semantickernel.memory.MemoryStore; +import com.microsoft.semantickernel.memory.NullMemory; import com.microsoft.semantickernel.memory.SemanticTextMemory; -import com.microsoft.semantickernel.orchestration.ContextVariables; -import com.microsoft.semantickernel.orchestration.DefaultCompletionSKFunction; -import com.microsoft.semantickernel.orchestration.RegistrableSkFunction; -import com.microsoft.semantickernel.orchestration.SKContext; -import com.microsoft.semantickernel.orchestration.SKFunction; +import com.microsoft.semantickernel.orchestration.*; import com.microsoft.semantickernel.semanticfunctions.SemanticFunctionConfig; import com.microsoft.semantickernel.skilldefinition.DefaultSkillCollection; import com.microsoft.semantickernel.skilldefinition.FunctionNotFound; @@ -26,15 +24,18 @@ import com.microsoft.semantickernel.templateengine.DefaultPromptTemplateEngine; import com.microsoft.semantickernel.templateengine.PromptTemplateEngine; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import com.microsoft.semantickernel.textcompletion.TextCompletion; + import jakarta.inject.Inject; + import reactor.core.publisher.Mono; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Arrays; import java.util.Map; import java.util.function.Function; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + public class DefaultKernel implements Kernel { private final KernelConfig kernelConfig; @@ -46,7 +47,7 @@ public class DefaultKernel implements Kernel { public DefaultKernel( KernelConfig kernelConfig, PromptTemplateEngine promptTemplateEngine, - SemanticTextMemory memory) { + @Nullable SemanticTextMemory memoryStore) { if (kernelConfig == null) { throw new IllegalArgumentException(); } @@ -54,13 +55,16 @@ public class DefaultKernel implements Kernel { this.kernelConfig = kernelConfig; this.promptTemplateEngine = promptTemplateEngine; this.defaultSkillCollection = new DefaultSkillCollection(); - this.memory = memory; - kernelConfig.getSkills().forEach(this::registerSemanticFunction); + if (memoryStore != null) { + this.memory = memoryStore.copy(); + } else { + this.memory = new NullMemory(); + } } @Override - public T getService(String serviceId, Class clazz) { + public T getService(@Nullable String serviceId, Class clazz) { if (TextCompletion.class.isAssignableFrom(clazz)) { Function factory = kernelConfig.getTextCompletionServiceOrDefault(serviceId); @@ -82,7 +86,7 @@ public class DefaultKernel implements Kernel { return (T) factory.apply(this); } else if (EmbeddingGeneration.class.isAssignableFrom(clazz)) { - Function> factory = + Function> factory = kernelConfig.getTextEmbeddingGenerationServiceOrDefault(serviceId); if (factory == null) { throw new KernelException( @@ -91,7 +95,7 @@ public class DefaultKernel implements Kernel { } return (T) factory.apply(this); - }else { + } else { // TODO correct exception throw new NotSupportedException( "The kernel service collection doesn't support the type " + clazz.getName()); @@ -104,10 +108,7 @@ public class DefaultKernel implements Kernel { } @Override - public < - RequestConfiguration, - ContextType extends SKContext, - FunctionType extends SKFunction> + public > FunctionType registerSemanticFunction(FunctionType func) { if (!(func instanceof RegistrableSkFunction)) { throw new RuntimeException("This function does not implement RegistrableSkFunction"); @@ -117,6 +118,11 @@ public class DefaultKernel implements Kernel { return func; } + @Override + public SKFunction getFunction(String skill, String function) { + return defaultSkillCollection.getFunction(skill, function, null); + } + /* /// public SKFunction registerSemanticFunction( @@ -149,7 +155,10 @@ public class DefaultKernel implements Kernel { .map( (entry) -> { return DefaultCompletionSKFunction.createFunction( - skillName, entry.getKey(), entry.getValue()); + skillName, + entry.getKey(), + entry.getValue(), + promptTemplateEngine); }) .forEach(this::registerSemanticFunction); @@ -163,6 +172,13 @@ public class DefaultKernel implements Kernel { @Override public ReadOnlyFunctionCollection importSkill( Object skillInstance, @Nullable String skillName) { + if (skillInstance instanceof String) { + throw new KernelException( + KernelException.ErrorCodes.FunctionNotAvailable, + "Called importSkill with a string argument, it is likely the intention was to" + + " call importSkillFromDirectory"); + } + if (skillName == null || skillName.isEmpty()) { skillName = ReadOnlySkillCollection.GlobalSkill; } @@ -203,6 +219,30 @@ public class DefaultKernel implements Kernel { return functions; } + @Override + public ReadOnlyFunctionCollection importSkillFromDirectory( + String skillName, String parentDirectory, String skillDirectoryName) { + Map skills = + KernelExtensions.importSemanticSkillFromDirectory( + parentDirectory, skillDirectoryName, promptTemplateEngine); + return importSkill(skillName, skills); + } + + @Override + public void importSkillsFromDirectory(String parentDirectory, String... skillNames) { + Arrays.stream(skillNames) + .forEach( + skill -> { + importSkillFromDirectory(skill, parentDirectory, skill); + }); + } + + @Override + public ReadOnlyFunctionCollection importSkillFromDirectory( + String skillName, String parentDirectory) { + return importSkillFromDirectory(skillName, parentDirectory, skillName); + } + @Override public PromptTemplateEngine getPromptTemplateEngine() { return promptTemplateEngine; @@ -218,36 +258,47 @@ public class DefaultKernel implements Kernel { } @Override - public Mono> runAsync(SKFunction... pipeline) { + public Mono runAsync(SKFunction... pipeline) { return runAsync(SKBuilders.variables().build(), pipeline); } @Override - public Mono> runAsync(String input, SKFunction... pipeline) { + public Mono runAsync(String input, SKFunction... pipeline) { return runAsync(SKBuilders.variables().build(input), pipeline); } @Override - public Mono> runAsync(ContextVariables variables, SKFunction... pipeline) { + public Mono runAsync(ContextVariables variables, SKFunction... pipeline) { if (pipeline == null || pipeline.length == 0) { throw new SKException("No parameters provided to pipeline"); } // TODO: The SemanticTextMemory can be null, but there should be a way to provide it. // Not sure registerMemory is the right way. - Mono> pipelineBuilder = - Mono.just(pipeline[0].buildContext(variables, null, getSkills())); + Mono pipelineBuilder = + Mono.just(SKBuilders.context().with(variables).with(getSkills()).build()); for (SKFunction f : Arrays.asList(pipeline)) { pipelineBuilder = - pipelineBuilder.flatMap( - newContext -> { - SKContext context = - f.buildContext( - newContext.getVariables(), - newContext.getSemanticMemory(), - newContext.getSkills()); - return f.invokeAsync(context, null); - }); + pipelineBuilder + .switchIfEmpty( + Mono.fromCallable( + () -> { + // Previous pipeline did not produce a result + return SKBuilders.context() + .with(variables) + .with(getSkills()) + .build(); + })) + .flatMap( + newContext -> { + SKContext context = + SKBuilders.context() + .with(newContext.getVariables()) + .with(newContext.getSemanticMemory()) + .with(newContext.getSkills()) + .build(); + return f.invokeAsync(context, null); + }); } return pipelineBuilder; diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/SkFunctionBuilders.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/SkFunctionBuilders.java index 8aad398bb3..0415fde19e 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/SkFunctionBuilders.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/SkFunctionBuilders.java @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel; +import com.microsoft.semantickernel.ai.AIException; import com.microsoft.semantickernel.builders.FunctionBuilders; import com.microsoft.semantickernel.orchestration.DefaultCompletionSKFunction; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; @@ -12,13 +13,10 @@ import javax.annotation.Nullable; public class SkFunctionBuilders implements FunctionBuilders { public SkFunctionBuilders() {} - public static final CompletionSKFunction.Builder COMPLETION_BUILDERS = - new InternalCompletionBuilder(null); - private static class InternalCompletionBuilder extends CompletionSKFunction.Builder { - private final @Nullable Kernel kernel; + private final Kernel kernel; - private InternalCompletionBuilder(@Nullable Kernel kernel) { + private InternalCompletionBuilder(Kernel kernel) { this.kernel = kernel; } @@ -37,14 +35,17 @@ public class SkFunctionBuilders implements FunctionBuilders { @Nullable String skillName) { return register( DefaultCompletionSKFunction.createFunction( - promptTemplate, config, functionName, skillName)); + promptTemplate, + config, + functionName, + skillName, + kernel.getPromptTemplateEngine())); } @Override public CompletionSKFunction createFunction( - String functionName, SemanticFunctionConfig functionConfig) { - return register( - DefaultCompletionSKFunction.createFunction(functionName, functionConfig)); + String prompt, PromptTemplateConfig.CompletionConfig functionConfig) { + return createFunction(prompt, null, null, null, functionConfig); } @Override @@ -54,7 +55,10 @@ public class SkFunctionBuilders implements FunctionBuilders { SemanticFunctionConfig functionConfig) { return register( DefaultCompletionSKFunction.createFunction( - skillNameFinal, functionName, functionConfig)); + skillNameFinal, + functionName, + functionConfig, + kernel.getPromptTemplateEngine())); } @Override @@ -78,19 +82,25 @@ public class SkFunctionBuilders implements FunctionBuilders { @Nullable String skillName, @Nullable String description, PromptTemplateConfig.CompletionConfig completionConfig) { + if (kernel == null) { + throw new AIException( + AIException.ErrorCodes.InvalidConfiguration, + "Called builder to create a function that"); + } return register( DefaultCompletionSKFunction.createFunction( - prompt, functionName, skillName, description, completionConfig)); + prompt, + functionName, + skillName, + description, + completionConfig, + kernel.getPromptTemplateEngine())); } } @Override - public CompletionSKFunction.Builder completionBuilders(@Nullable Kernel kernel) { - if (kernel == null) { - return COMPLETION_BUILDERS; - } else { - return new InternalCompletionBuilder(kernel); - } + public CompletionSKFunction.Builder completionBuilders(Kernel kernel) { + return new InternalCompletionBuilder(kernel); } } diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/memory/DefaultSemanticTextMemory.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/memory/DefaultSemanticTextMemory.java index 86288d2ca6..1f94a5e583 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/memory/DefaultSemanticTextMemory.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/memory/DefaultSemanticTextMemory.java @@ -4,17 +4,19 @@ package com.microsoft.semantickernel.memory; import com.microsoft.semantickernel.ai.embeddings.Embedding; import com.microsoft.semantickernel.ai.embeddings.EmbeddingGeneration; import com.microsoft.semantickernel.exceptions.NotSupportedException; + import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + ///

/// Implementation of ./>. /// @@ -136,23 +138,28 @@ public class DefaultSemanticTextMemory implements SemanticTextMemory { @Nullable String description, @Nullable String additionalMetadata) { - return this._storage.doesCollectionExistAsync(collection) - .map(exists -> { - if (!exists) { - return this._storage.createCollectionAsync(collection); - } - return Mono.empty(); - }) - .then(this._embeddingGenerator.generateEmbeddingsAsync(Collections.singletonList(text))) - .map(embeddings -> MemoryRecord.referenceRecord( - externalId, - externalSourceName, - description, - (Embedding) embeddings.iterator().next(), - additionalMetadata, - null, - null) - ) + return this._storage + .doesCollectionExistAsync(collection) + .map( + exists -> { + if (!exists) { + return this._storage.createCollectionAsync(collection); + } + return Mono.empty(); + }) + .then( + this._embeddingGenerator.generateEmbeddingsAsync( + Collections.singletonList(text))) + .map( + embeddings -> + MemoryRecord.referenceRecord( + externalId, + externalSourceName, + description, + (Embedding) embeddings.iterator().next(), + additionalMetadata, + null, + null)) .flatMap(record -> this._storage.upsertAsync(collection, record)); } diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/memory/VolatileMemoryStore.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/memory/VolatileMemoryStore.java index 0241cccbb5..26a1a257ea 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/memory/VolatileMemoryStore.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/memory/VolatileMemoryStore.java @@ -3,11 +3,11 @@ package com.microsoft.semantickernel.memory; import com.microsoft.semantickernel.ai.EmbeddingVector; import com.microsoft.semantickernel.ai.embeddings.Embedding; + import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; import reactor.util.function.Tuples; -import javax.annotation.Nonnull; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -20,6 +20,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.ToDoubleFunction; import java.util.stream.Collectors; +import javax.annotation.Nonnull; + /** A simple volatile memory embeddings store. */ public class VolatileMemoryStore implements MemoryStore { @@ -159,6 +161,7 @@ public class VolatileMemoryStore implements MemoryStore { return new EmbeddingVector((List) list); } + @SuppressWarnings("UnnecessaryLambda") private static final ToDoubleFunction> extractSimilarity = tuple -> tuple.getT2().doubleValue(); @@ -246,6 +249,7 @@ public class VolatileMemoryStore implements MemoryStore { public static class Builder implements MemoryStore.Builder { public Builder() {} + @Override public MemoryStore build() { return new VolatileMemoryStore(); diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultCompletionSKContext.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultCompletionSKContext.java deleted file mode 100644 index 2352e98b38..0000000000 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultCompletionSKContext.java +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -package com.microsoft.semantickernel.orchestration; - -import com.microsoft.semantickernel.memory.SemanticTextMemory; -import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; - -import javax.annotation.Nullable; - -public class DefaultCompletionSKContext extends AbstractSKContext - implements CompletionSKContext { - - DefaultCompletionSKContext(ContextVariables variables) { - super(variables); - } - - public DefaultCompletionSKContext( - ContextVariables variables, - @Nullable SemanticTextMemory memory, - @Nullable ReadOnlySkillCollection skills) { - super(variables, memory, skills); - } - - @Override - protected CompletionSKContext getThis() { - return this; - } - - @Override - public CompletionSKContext build( - ContextVariables variables, - @Nullable SemanticTextMemory memory, - @Nullable ReadOnlySkillCollection skills) { - return new DefaultCompletionSKContext(variables, memory, skills); - } -} diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultCompletionSKFunction.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultCompletionSKFunction.java index 37470a82ad..2a6e6e86bb 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultCompletionSKFunction.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultCompletionSKFunction.java @@ -2,19 +2,22 @@ package com.microsoft.semantickernel.orchestration; import com.microsoft.semantickernel.Kernel; -import com.microsoft.semantickernel.memory.SemanticTextMemory; -import com.microsoft.semantickernel.semanticfunctions.DefaultPromptTemplate; +import com.microsoft.semantickernel.builders.SKBuilders; import com.microsoft.semantickernel.semanticfunctions.PromptTemplate; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; import com.microsoft.semantickernel.semanticfunctions.SemanticFunctionConfig; +import com.microsoft.semantickernel.skilldefinition.FunctionView; import com.microsoft.semantickernel.skilldefinition.KernelSkillsSupplier; import com.microsoft.semantickernel.skilldefinition.ParameterView; import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; +import com.microsoft.semantickernel.templateengine.PromptTemplateEngine; import com.microsoft.semantickernel.textcompletion.CompletionRequestSettings; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import com.microsoft.semantickernel.textcompletion.TextCompletion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -32,10 +35,12 @@ import javax.annotation.Nullable; /// with additional methods required by the kernel. ///
public class DefaultCompletionSKFunction - extends DefaultSemanticSKFunction + extends DefaultSemanticSKFunction implements CompletionSKFunction { + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultCompletionSKFunction.class); + private final SemanticFunctionConfig functionConfig; - private SKSemanticAsyncTask function; + private SKSemanticAsyncTask function; private final CompletionRequestSettings requestSettings; @Nullable private DefaultTextCompletionSupplier aiService; @@ -107,10 +112,10 @@ public class DefaultCompletionSKFunction * @return Aggregated results */ @Override - public Mono aggregatePartitionedResultsAsync( - List partitionedInput, @Nullable CompletionSKContext contextIn) { + public Mono aggregatePartitionedResultsAsync( + List partitionedInput, @Nullable SKContext contextIn) { - CompletionSKContext context; + SKContext context; if (contextIn == null) { context = buildContext(); } else { @@ -119,15 +124,15 @@ public class DefaultCompletionSKFunction // Function that takes the current context, updates it with the latest input and invokes the // function - BiFunction, String, Flux> executeNextChunk = + BiFunction, String, Flux> executeNextChunk = (contextInput, input) -> contextInput.flatMap( newContext -> { - CompletionSKContext updated = newContext.update(input); + SKContext updated = newContext.update(input); return invokeAsync(updated, null); }); - Mono> results = + Mono> results = Flux.fromIterable(partitionedInput) .reduceWith(() -> Flux.just(context), executeNextChunk) .flatMap(Flux::collectList); @@ -140,24 +145,10 @@ public class DefaultCompletionSKFunction .map(context::update); } - @Override - public CompletionSKContext buildContext( - ContextVariables variables, - @Nullable SemanticTextMemory memory, - @Nullable ReadOnlySkillCollection skills) { - return new DefaultCompletionSKContext(variables, memory, skills); - } - - @Override - public CompletionSKContext buildContext(SKContext toClone) { - return new DefaultCompletionSKContext( - toClone.getVariables(), toClone.getSemanticMemory(), toClone.getSkills()); - } - // Run the semantic function @Override - protected Mono invokeAsyncInternal( - CompletionSKContext context, @Nullable CompletionRequestSettings settings) { + protected Mono invokeAsyncInternal( + SKContext context, @Nullable CompletionRequestSettings settings) { // TODO // this.VerifyIsSemantic(); // this.ensureContextHasSkills(context); @@ -188,15 +179,17 @@ public class DefaultCompletionSKFunction this.function = (TextCompletion client, CompletionRequestSettings requestSettings, - CompletionSKContext contextInput) -> { - CompletionSKContext context = contextInput.copy(); + SKContext contextInput) -> { + SKContext context = contextInput.copy(); // TODO // Verify.NotNull(client, "AI LLM backed is empty"); - return functionConfig - .getTemplate() - .renderAsync(context, kernel.getPromptTemplateEngine()) + + PromptTemplate func = functionConfig.getTemplate(); + + return func.renderAsync(context) .flatMapMany( prompt -> { + LOGGER.debug("RENDERED PROMPT: \n" + prompt); return client.completeAsync(prompt, requestSettings); }) .single() @@ -206,15 +199,13 @@ public class DefaultCompletionSKFunction }) .doOnError( ex -> { - System.err.println(ex.getMessage()); - ex.printStackTrace(); - // TODO - /* - log ?.LogWarning(ex, - "Something went wrong while rendering the semantic function or while executing the text completion. Function: {0}.{1}. Error: {2}", - skillName, functionName, ex.Message); - context.Fail(ex.Message, ex); - */ + LOGGER.warn( + "Something went wrong while rendering the semantic" + + " function or while executing the text" + + " completion. Function: {}.{}. Error: {}", + getSkillName(), + getName(), + ex.getMessage()); }); }; @@ -236,7 +227,8 @@ public class DefaultCompletionSKFunction @Nullable String functionName, @Nullable String skillName, @Nullable String description, - PromptTemplateConfig.CompletionConfig completion) { + PromptTemplateConfig.CompletionConfig completion, + PromptTemplateEngine promptTemplateEngine) { if (functionName == null) { functionName = randomFunctionName(); @@ -249,14 +241,16 @@ public class DefaultCompletionSKFunction PromptTemplateConfig config = new PromptTemplateConfig(description, "completion", completion); - return createFunction(promptTemplate, config, functionName, skillName); + return createFunction( + promptTemplate, config, functionName, skillName, promptTemplateEngine); } public static DefaultCompletionSKFunction createFunction( String promptTemplate, PromptTemplateConfig config, String functionName, - @Nullable String skillName) { + @Nullable String skillName, + PromptTemplateEngine promptTemplateEngine) { if (functionName == null) { functionName = randomFunctionName(); } @@ -267,17 +261,23 @@ public class DefaultCompletionSKFunction // Verify.ValidSkillName(skillName); // } - PromptTemplate template = new DefaultPromptTemplate(promptTemplate, config); + PromptTemplate template = + SKBuilders.promptTemplate() + .withPromptTemplateConfig(config) + .withPromptTemplate(promptTemplate) + .build(promptTemplateEngine); // Prepare lambda wrapping AI logic SemanticFunctionConfig functionConfig = new SemanticFunctionConfig(config, template); - return createFunction(skillName, functionName, functionConfig); + return createFunction(skillName, functionName, functionConfig, promptTemplateEngine); } public static DefaultCompletionSKFunction createFunction( - String functionName, SemanticFunctionConfig functionConfig) { - return createFunction(null, functionName, functionConfig); + String functionName, + SemanticFunctionConfig functionConfig, + PromptTemplateEngine promptTemplateEngine) { + return createFunction(null, functionName, functionConfig, promptTemplateEngine); } /// @@ -291,7 +291,8 @@ public class DefaultCompletionSKFunction public static DefaultCompletionSKFunction createFunction( @Nullable String skillNameFinal, String functionName, - SemanticFunctionConfig functionConfig) { + SemanticFunctionConfig functionConfig, + PromptTemplateEngine promptTemplateEngine) { String skillName = skillNameFinal; // Verify.NotNull(functionConfig, "Function configuration is empty"); if (skillName == null) { @@ -302,9 +303,11 @@ public class DefaultCompletionSKFunction CompletionRequestSettings.fromCompletionConfig( functionConfig.getConfig().getCompletionConfig()); + PromptTemplate promptTemplate = functionConfig.getTemplate(); + return new DefaultCompletionSKFunction( DelegateTypes.ContextSwitchInSKContextOutTaskSKContext, - functionConfig.getTemplate().getParameters(), + promptTemplate.getParameters(), skillName, functionName, functionConfig.getConfig().getDescription(), @@ -312,4 +315,15 @@ public class DefaultCompletionSKFunction functionConfig, null); } + + @Override + public FunctionView describe() { + return new FunctionView( + super.getName(), + super.getSkillName(), + super.getDescription(), + super.getParameters(), + true, + false); + } } diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultContextVariables.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultContextVariables.java index 19e975af3e..9473cca069 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultContextVariables.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultContextVariables.java @@ -42,6 +42,7 @@ class DefaultContextVariables implements ContextVariables, WritableContextVariab return this; } + @Override public ContextVariables appendToVariable(@NonNull String key, @NonNull String content) { String existing = this.variables.get(key); @@ -67,6 +68,7 @@ class DefaultContextVariables implements ContextVariables, WritableContextVariab // result for the user /// if the pipeline reached the end. /// The current instance + @Override public ContextVariables update(@NonNull String content) { return setVariable(MAIN_KEY, content); } @@ -84,6 +86,7 @@ class DefaultContextVariables implements ContextVariables, WritableContextVariab // data. /// The current instance + @Override public DefaultContextVariables update(@NonNull ContextVariables newData, boolean merge) { /* // If requested, discard old data and keep only the new one. @@ -99,11 +102,37 @@ class DefaultContextVariables implements ContextVariables, WritableContextVariab return this; } + @Override + public ContextVariables remove(String key) { + variables.remove(key); + return this; + } + @Override public WritableContextVariables writableClone() { return new DefaultContextVariables(variables); } + @Override + @Nullable + public String getInput() { + return get(MAIN_KEY); + } + + @Override + public String prettyPrint() { + return variables.entrySet().stream() + .reduce( + "", + (str, entry) -> + str + + System.lineSeparator() + + entry.getKey() + + ": " + + entry.getValue(), + (a, b) -> a + b); + } + @Override @Nullable public String get(String key) { diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSKContext.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSKContext.java new file mode 100644 index 0000000000..edda3a8eb8 --- /dev/null +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSKContext.java @@ -0,0 +1,105 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.orchestration; + +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.memory.NullMemory; +import com.microsoft.semantickernel.memory.SemanticTextMemory; +import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; + +import javax.annotation.Nullable; + +public class DefaultSKContext extends AbstractSKContext { + public DefaultSKContext(ContextVariables variables) { + super(variables); + } + + public DefaultSKContext( + ContextVariables variables, + @Nullable SemanticTextMemory memory, + @Nullable ReadOnlySkillCollection skillCollection) { + super(variables, memory, skillCollection); + } + + @Override + protected SKContext getThis() { + return this; + } + + @Override + public SKContext build( + ContextVariables variables, + @Nullable SemanticTextMemory memory, + @Nullable ReadOnlySkillCollection skillCollection) { + return new DefaultSKContext(variables, memory, skillCollection); + } + + public static class Builder implements SKContext.Builder { + + private ContextVariables variables; + private ReadOnlySkillCollection skills; + private SemanticTextMemory memory = NullMemory.getInstance(); + + @Override + public SKContext build(ReadOnlySkillCollection skills) { + return new DefaultSKContext( + SKBuilders.variables().build(), NullMemory.getInstance(), skills); + } + + @Override + public SKContext build() { + if (variables == null) { + variables = SKBuilders.variables().build(); + } + return new DefaultSKContext(variables, memory, skills); + } + + @Override + public SKContext build(Class clazz) { + return null; + } + + @Override + public SKContext.Builder with(ContextVariables variables) { + this.variables = variables; + return this; + } + + @Override + public SKContext.Builder with(@Nullable ReadOnlySkillCollection skills) { + if (skills != null) { + this.skills = skills; + } + return this; + } + + @Override + public SKContext.Builder with(@Nullable SemanticTextMemory memory) { + if (memory != null) { + this.memory = memory.copy(); + } + return this; + } + + @Override + public SKContext.Builder clone(SKContext context) { + return with(context.getVariables()) + .with(context.getSkills()) + .with(context.getSemanticMemory()); + } + + @Override + public SKContext build(Kernel kernel) { + if (memory == null) { + memory = kernel.getMemory(); + } + if (skills == null) { + skills = kernel.getSkills(); + } + if (variables == null) { + variables = SKBuilders.variables().build(); + } + return build(); + } + } +} diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSemanticSKContext.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSemanticSKContext.java deleted file mode 100644 index aef950fee9..0000000000 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSemanticSKContext.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -package com.microsoft.semantickernel.orchestration; - -import com.microsoft.semantickernel.builders.SKBuilders; -import com.microsoft.semantickernel.memory.NullMemory; -import com.microsoft.semantickernel.memory.SemanticTextMemory; -import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; - -import javax.annotation.Nullable; - -public class DefaultSemanticSKContext extends AbstractSKContext - implements SemanticSKContext { - public DefaultSemanticSKContext(ContextVariables variables) { - super(variables); - } - - public DefaultSemanticSKContext( - ContextVariables variables, - @Nullable SemanticTextMemory memory, - @Nullable ReadOnlySkillCollection skillCollection) { - super(variables, memory, skillCollection); - } - - @Override - protected SemanticSKContext getThis() { - return this; - } - - @Override - public SemanticSKContext build( - ContextVariables variables, - @Nullable SemanticTextMemory memory, - @Nullable ReadOnlySkillCollection skillCollection) { - return new DefaultSemanticSKContext(variables, memory, skillCollection); - } - - public static class Builder implements SKContext.Builder { - - @Override - public SKContext build(ReadOnlySkillCollection skills) { - return new DefaultSemanticSKContext( - SKBuilders.variables().build(), NullMemory.getInstance(), skills); - } - } -} diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSemanticSKFunction.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSemanticSKFunction.java index 0fe413fc1c..89b2771756 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSemanticSKFunction.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/DefaultSemanticSKFunction.java @@ -18,10 +18,9 @@ import javax.annotation.Nullable; // cref="Action"/>, /// with additional methods required by the kernel. /// -public abstract class DefaultSemanticSKFunction< - RequestConfiguration, ContextType extends SKContext> - extends AbstractSkFunction - implements SKFunction { +public abstract class DefaultSemanticSKFunction + extends AbstractSkFunction + implements SKFunction { public DefaultSemanticSKFunction( DelegateTypes delegateType, @@ -34,20 +33,25 @@ public abstract class DefaultSemanticSKFunction< } @Override - public Mono invokeAsync( - String input, @Nullable ContextType context, @Nullable RequestConfiguration settings) { + public Mono invokeAsync( + @Nullable String input, + @Nullable SKContext context, + @Nullable RequestConfiguration settings) { if (context == null) { assertSkillSupplierRegistered(); context = - buildContext( - SKBuilders.variables().build(), - NullMemory.getInstance(), - super.getSkillsSupplier().get()); + SKBuilders.context() + .with(SKBuilders.variables().build()) + .with(NullMemory.getInstance()) + .with(super.getSkillsSupplier().get()) + .build(); } else { context = context.copy(); } - context = context.update(input); + if (input != null) { + context = context.update(input); + } return this.invokeAsync(context, settings); } diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/NativeSKFunction.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/NativeSKFunction.java index c1ec88be83..b3ccc57308 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/NativeSKFunction.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/orchestration/NativeSKFunction.java @@ -5,7 +5,7 @@ import static com.microsoft.semantickernel.skilldefinition.annotations.SKFunctio import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.ai.AIException; -import com.microsoft.semantickernel.memory.SemanticTextMemory; +import com.microsoft.semantickernel.skilldefinition.FunctionView; import com.microsoft.semantickernel.skilldefinition.KernelSkillsSupplier; import com.microsoft.semantickernel.skilldefinition.ParameterView; import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; @@ -17,6 +17,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; +import reactor.core.scheduler.Schedulers; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -32,15 +33,15 @@ import javax.annotation.Nullable; // cref="Action"/>, /// with additional methods required by the kernel. ///
-public class NativeSKFunction extends AbstractSkFunction { +public class NativeSKFunction extends AbstractSkFunction { private static final Logger LOGGER = LoggerFactory.getLogger(NativeSKFunction.class); - private final SKNativeTask function; + private final SKNativeTask function; public NativeSKFunction( AbstractSkFunction.DelegateTypes delegateType, - SKNativeTask delegateFunction, + SKNativeTask delegateFunction, List parameters, String skillName, String functionName, @@ -56,6 +57,17 @@ public class NativeSKFunction extends AbstractSkFunction getType() { return Void.class; @@ -69,7 +81,7 @@ public class NativeSKFunction extends AbstractSkFunction function; + public final SKNativeTask function; public final List parameters; public final String name; public final String description; @@ -77,7 +89,7 @@ public class NativeSKFunction extends AbstractSkFunction function, + SKNativeTask function, List parameters, String name, String description) { @@ -116,24 +128,9 @@ public class NativeSKFunction extends AbstractSkFunction invokeAsyncInternal( - SemanticSKContext context, @Nullable Void settings) { + protected Mono invokeAsyncInternal(SKContext context, @Nullable Void settings) { return this.function.run(context); } @@ -142,7 +139,6 @@ public class NativeSKFunction extends AbstractSkFunction parameters = new ArrayList<>(); boolean hasSkFunctionAttribute = methodSignature.isAnnotationPresent(DefineSKFunction.class); @@ -151,8 +147,7 @@ public class NativeSKFunction extends AbstractSkFunction function = - getFunction(methodSignature, methodContainerInstance); + SKNativeTask function = getFunction(methodSignature, methodContainerInstance); // boolean hasStringParam = // Arrays.asList(methodSignature.getGenericParameterTypes()).contains(String.class); @@ -165,18 +160,61 @@ public class NativeSKFunction extends AbstractSkFunction parameters = getParameters(methodSignature); + return new MethodDetails( hasSkFunctionAttribute, type, function, parameters, name, description); } - private static SKNativeTask getFunction(Method method, Object instance) { + private static List getParameters(Method method) { + + List params = + Arrays.stream(method.getParameters()) + .filter( + parameter -> + parameter.isAnnotationPresent(SKFunctionParameters.class) + || parameter.isAnnotationPresent( + SKFunctionInputAttribute.class)) + .map( + parameter -> { + if (parameter.isAnnotationPresent(SKFunctionParameters.class)) { + SKFunctionParameters annotation = + parameter.getAnnotation(SKFunctionParameters.class); + return new ParameterView( + annotation.name(), + annotation.description(), + annotation.defaultValue()); + } else { + return new ParameterView("input"); + } + }) + .collect(Collectors.toList()); + + HashSet out = new HashSet<>(params); + + boolean hasInput = params.stream().anyMatch(p -> p.getName().equals("input")); + + if (!hasInput) { + List inputArgs = + determineInputArgs(method).stream() + .map( + parameter -> { + return new ParameterView("input"); + }) + .collect(Collectors.toList()); + out.addAll(inputArgs); + } + + return new ArrayList<>(out); + } + + private static SKNativeTask getFunction(Method method, Object instance) { return (contextInput) -> { - SemanticSKContext context = contextInput.copy(); + SKContext context = contextInput.copy(); Set inputArgs = determineInputArgs(method); try { - List args = Arrays.stream(method.getParameters()) .map( @@ -209,11 +247,26 @@ public class NativeSKFunction extends AbstractSkFunction { + return Mono.fromCallable( + () -> { + try { + Object result = + method.invoke( + instance, + args.toArray()); + + return result; + } catch (IllegalAccessException + | InvocationTargetException e) { + throw new RuntimeException( + e.getCause()); + } + }) + .subscribeOn(Schedulers.boundedElastic()); + }); } return mono.map( @@ -231,10 +284,7 @@ public class NativeSKFunction extends AbstractSkFunction inputArgs) { + Method method, SKContext context, Parameter parameter, Set inputArgs) { String variableName = getGetVariableName(parameter); String arg = context.getVariables().get(variableName); @@ -242,12 +292,12 @@ public class NativeSKFunction extends AbstractSkFunction /// Prompt template. @@ -16,27 +22,77 @@ import java.util.List; public class DefaultPromptTemplate implements PromptTemplate { private final String promptTemplate; private final PromptTemplateConfig config; + private final PromptTemplateEngine templateEngine; - public DefaultPromptTemplate(String promptTemplate, PromptTemplateConfig config) { + public DefaultPromptTemplate( + String promptTemplate, + PromptTemplateConfig config, + PromptTemplateEngine templateEngine) { this.promptTemplate = promptTemplate; this.config = config; + this.templateEngine = templateEngine; } @Override public List getParameters() { - return new ArrayList<>(); + // Parameters from config.json + List result = + this.config.getInput().getParameters().stream() + .filter(Objects::nonNull) + .map( + p -> + new ParameterView( + p.getName(), + p.getDescription(), + p.getDefaultValue())) + .collect(Collectors.toList()); + + List seen = + result.stream().map(ParameterView::getName).collect(Collectors.toList()); + + List listFromTemplate = + templateEngine.extractBlocks(this.promptTemplate).stream() + .filter(Objects::nonNull) + .filter(x -> x.getType() == BlockTypes.Variable) + .map(x -> (VarBlock) x) + .collect(Collectors.toList()); + + List newParams = + listFromTemplate.stream() + .filter(x -> !seen.contains(x.getName())) + .map(x -> new ParameterView(x.getName())) + .collect(Collectors.toList()); + + return Stream.concat(result.stream(), newParams.stream()).collect(Collectors.toList()); } @Override - public Mono renderAsync( - SKContext executionContext, PromptTemplateEngine promptTemplateEngine) { - return promptTemplateEngine.renderAsync(this.promptTemplate, executionContext); + public Mono renderAsync(SKContext executionContext) { + return templateEngine.renderAsync(this.promptTemplate, executionContext); } public static final class Builder extends PromptTemplate.Builder { + @Nullable private String promptTemplate = null; + @Nullable private PromptTemplateConfig config = null; + @Override - public PromptTemplate build(String promptTemplate, PromptTemplateConfig config) { - return new DefaultPromptTemplate(promptTemplate, config); + public PromptTemplate build(PromptTemplateEngine promptTemplateEngine) { + if (promptTemplate == null || config == null) + throw new IllegalStateException( + "PromptTemplate and PromptTemplateConfig must be set"); + return new DefaultPromptTemplate(promptTemplate, config, promptTemplateEngine); + } + + @Override + public Builder withPromptTemplate(String promptTemplate) { + this.promptTemplate = promptTemplate; + return this; + } + + @Override + public Builder withPromptTemplateConfig(PromptTemplateConfig config) { + this.config = config; + return this; } } } diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/CaseInsensitiveMap.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/CaseInsensitiveMap.java index 2682c1ad07..3e244adcd3 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/CaseInsensitiveMap.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/CaseInsensitiveMap.java @@ -2,6 +2,7 @@ package com.microsoft.semantickernel.skilldefinition; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Function; @@ -22,7 +23,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.computeIfAbsent(null, mappingFunction); } - return super.computeIfAbsent(key.toLowerCase(), mappingFunction); + return super.computeIfAbsent(key.toLowerCase(Locale.ROOT), mappingFunction); } @Override @@ -31,7 +32,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.computeIfPresent(null, remappingFunction); } - return super.computeIfPresent(key.toLowerCase(), remappingFunction); + return super.computeIfPresent(key.toLowerCase(Locale.ROOT), remappingFunction); } @Override @@ -40,7 +41,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.compute(null, remappingFunction); } - return super.compute(key.toLowerCase(), remappingFunction); + return super.compute(key.toLowerCase(Locale.ROOT), remappingFunction); } @Override @@ -48,7 +49,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.containsKey(null); } - return super.containsKey(((String) key).toLowerCase()); + return super.containsKey(((String) key).toLowerCase(Locale.ROOT)); } @Override @@ -56,7 +57,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.get(null); } - return super.get(((String) key).toLowerCase()); + return super.get(((String) key).toLowerCase(Locale.ROOT)); } @Override @@ -64,7 +65,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.getOrDefault(null, defaultValue); } - return super.getOrDefault(((String) key).toLowerCase(), defaultValue); + return super.getOrDefault(((String) key).toLowerCase(Locale.ROOT), defaultValue); } @Override @@ -73,7 +74,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.merge(null, value, remappingFunction); } - return super.merge(key.toLowerCase(), value, remappingFunction); + return super.merge(key.toLowerCase(Locale.ROOT), value, remappingFunction); } @Override @@ -81,7 +82,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.put(null, value); } - return super.put(key.toLowerCase(), value); + return super.put(key.toLowerCase(Locale.ROOT), value); } @Override @@ -90,7 +91,8 @@ public class CaseInsensitiveMap extends HashMap { m.entrySet().stream() .collect( Collectors.toMap( - key -> key.getKey().toLowerCase(), Entry::getValue))); + key -> key.getKey().toLowerCase(Locale.ROOT), + Entry::getValue))); } @Override @@ -98,7 +100,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.putIfAbsent(null, value); } - return super.putIfAbsent(key.toLowerCase(), value); + return super.putIfAbsent(key.toLowerCase(Locale.ROOT), value); } @Override @@ -106,7 +108,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.remove(null, value); } - return super.remove(((String) key).toLowerCase(), value); + return super.remove(((String) key).toLowerCase(Locale.ROOT), value); } @Override @@ -114,7 +116,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.remove(null); } - return super.remove(((String) key).toLowerCase()); + return super.remove(((String) key).toLowerCase(Locale.ROOT)); } @Override @@ -123,7 +125,7 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.replace(null, oldValue, newValue); } - return super.replace(key.toLowerCase(), oldValue, newValue); + return super.replace(key.toLowerCase(Locale.ROOT), oldValue, newValue); } @Override @@ -131,6 +133,6 @@ public class CaseInsensitiveMap extends HashMap { if (key == null) { return super.replace(null, value); } - return super.replace(key.toLowerCase(), value); + return super.replace(key.toLowerCase(Locale.ROOT), value); } } diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/DefaultSkillCollection.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/DefaultSkillCollection.java index 80e05ac4be..8ac6158874 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/DefaultSkillCollection.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/DefaultSkillCollection.java @@ -4,11 +4,11 @@ package com.microsoft.semantickernel.skilldefinition; import com.microsoft.semantickernel.orchestration.SKFunction; import java.util.Collections; +import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; import javax.annotation.CheckReturnValue; -import javax.annotation.Nonnull; import javax.annotation.Nullable; /// @@ -77,16 +77,16 @@ public class DefaultSkillCollection implements ReadOnlySkillCollection { @Override @Nullable - public > T getFunction( - String funName, @Nonnull Class functionClazz) { + public > T getFunction( + String funName, @Nullable Class functionClazz) { return getFunction(GlobalSkill, funName, functionClazz); } @Override @Nullable - public > T getFunction( - String skillName, String funName, Class functionClazz) { - FunctionCollection skills = skillCollection.get(skillName.toLowerCase()); + public > T getFunction( + String skillName, String funName, @Nullable Class functionClazz) { + FunctionCollection skills = skillCollection.get(skillName.toLowerCase(Locale.ROOT)); if (skills == null) { return null; } @@ -96,7 +96,7 @@ public class DefaultSkillCollection implements ReadOnlySkillCollection { @Nullable @Override public FunctionCollection getFunctions(String skillName) { - return skillCollection.get(skillName.toLowerCase()); + return skillCollection.get(skillName.toLowerCase(Locale.ROOT)); } @Override @@ -104,6 +104,15 @@ public class DefaultSkillCollection implements ReadOnlySkillCollection { return getFunction(functionName, SKFunction.class) != null; } + @Override + public boolean hasFunction(String skillName, String functionName) { + FunctionCollection skills = skillCollection.get(skillName.toLowerCase(Locale.ROOT)); + if (skills == null) { + return false; + } + return skills.getFunction(functionName) != null; + } + @Override @CheckReturnValue public ReadOnlySkillCollection copy() { @@ -121,10 +130,10 @@ public class DefaultSkillCollection implements ReadOnlySkillCollection { .forEach( entry -> { FunctionCollection existing = - skillCollection.get(entry.getKey().toLowerCase()); + skillCollection.get(entry.getKey().toLowerCase(Locale.ROOT)); if (existing == null) { skillCollection.put( - entry.getKey().toLowerCase(), + entry.getKey().toLowerCase(Locale.ROOT), new FunctionCollection(entry.getValue())); } else { existing.merge(new FunctionCollection(entry.getValue())); diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/FunctionCollection.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/FunctionCollection.java index a6a6b3b586..b09c7207d9 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/FunctionCollection.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/skilldefinition/FunctionCollection.java @@ -4,10 +4,7 @@ package com.microsoft.semantickernel.skilldefinition; import com.microsoft.semantickernel.exceptions.SkillsNotFoundException; import com.microsoft.semantickernel.orchestration.SKFunction; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import javax.annotation.CheckReturnValue; @@ -15,21 +12,22 @@ import javax.annotation.Nullable; /** A collection of functions. */ public class FunctionCollection implements ReadOnlyFunctionCollection { - private final CaseInsensitiveMap> functionCollection; + private final CaseInsensitiveMap> functionCollection; private final String skillName; - private FunctionCollection(String skillName, Map> functionCollection) { + private FunctionCollection(String skillName, Map> functionCollection) { this.functionCollection = new CaseInsensitiveMap<>(functionCollection); this.skillName = skillName; } - public FunctionCollection( - String skillName, List> functionCollection) { + public FunctionCollection(String skillName, List> functionCollection) { this( skillName, functionCollection.stream() - .collect(Collectors.toMap(it -> it.getName().toLowerCase(), it -> it))); + .collect( + Collectors.toMap( + it -> it.getName().toLowerCase(Locale.ROOT), it -> it))); } public FunctionCollection(String skillName) { @@ -40,7 +38,7 @@ public class FunctionCollection implements ReadOnlyFunctionCollection { public FunctionCollection(ReadOnlyFunctionCollection value) { this.skillName = value.getSkillName(); this.functionCollection = - new CaseInsensitiveMap>( + new CaseInsensitiveMap>( Collections.unmodifiableMap( value.getAll().stream() .collect(Collectors.toMap(SKFunction::getName, it -> it)))); @@ -52,8 +50,8 @@ public class FunctionCollection implements ReadOnlyFunctionCollection { } @Override - public SKFunction getFunction(String functionName) { - SKFunction func = functionCollection.get(functionName.toLowerCase()); + public SKFunction getFunction(String functionName) { + SKFunction func = functionCollection.get(functionName.toLowerCase(Locale.ROOT)); if (func == null) { throw new FunctionNotFound(functionName); } @@ -71,7 +69,7 @@ public class FunctionCollection implements ReadOnlyFunctionCollection { */ @Override public T getFunction(String functionName, @Nullable Class clazz) { - SKFunction func = getFunction(functionName); + SKFunction func = getFunction(functionName); if (clazz == null) { return (T) func; } @@ -99,7 +97,7 @@ public class FunctionCollection implements ReadOnlyFunctionCollection { * @return An unmodifiable list of all functions */ @Override - public List> getAll() { + public List> getAll() { return Collections.unmodifiableList(new ArrayList<>(functionCollection.values())); } @@ -110,8 +108,8 @@ public class FunctionCollection implements ReadOnlyFunctionCollection { * @param functionInstance * @return Collection for fluent callse */ - public FunctionCollection put(String functionName, SKFunction functionInstance) { - this.functionCollection.put(functionName.toLowerCase(), functionInstance); + public FunctionCollection put(String functionName, SKFunction functionInstance) { + this.functionCollection.put(functionName.toLowerCase(Locale.ROOT), functionInstance); return this; } diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/DefaultPromptTemplateEngine.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/DefaultPromptTemplateEngine.java index 8da61a1f0d..8417a763bb 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/DefaultPromptTemplateEngine.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/DefaultPromptTemplateEngine.java @@ -35,11 +35,11 @@ public class DefaultPromptTemplateEngine implements PromptTemplateEngine { tokenizer = new TemplateTokenizer(); } + @Override public List extractBlocks(String templateText) { return extractBlocks(templateText, true); } - /// public List extractBlocks(String templateText, boolean validate) { // TODO // this._log.LogTrace("Extracting blocks from template: {0}", templateText); diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/CodeBlock.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/CodeBlock.java index 2f9d376830..17ab11cd0d 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/CodeBlock.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/CodeBlock.java @@ -12,12 +12,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; -import reactor.util.annotation.Nullable; import java.util.Collections; import java.util.List; import java.util.Optional; +import javax.annotation.Nullable; + public final class CodeBlock extends Block implements CodeRendering { private static final Logger LOGGER = LoggerFactory.getLogger(CodeBlock.class); @@ -28,11 +29,6 @@ public final class CodeBlock extends Block implements CodeRendering { this.tokens = Collections.unmodifiableList(tokens); } - public CodeBlock(String content) { - super(content, BlockTypes.Code); - this.tokens = null; - } - @Override public boolean isValid() { Optional invalid = tokens.stream().filter(token -> !token.isValid()).findFirst(); @@ -63,7 +59,6 @@ public final class CodeBlock extends Block implements CodeRendering { } @Override - @Nullable public Mono renderCodeAsync(SKContext context) { if (!this.isValid()) { throw new TemplateException(TemplateException.ErrorCodes.SyntaxError); @@ -79,9 +74,13 @@ public final class CodeBlock extends Block implements CodeRendering { case FunctionId: return this.renderFunctionCallAsync((FunctionIdBlock) this.tokens.get(0), context); - } - throw new RuntimeException("Unknown type"); + case Undefined: + case Text: + case Code: + default: + throw new RuntimeException("Unknown type"); + } } private Mono renderFunctionCallAsync(FunctionIdBlock fBlock, SKContext context) { @@ -115,14 +114,14 @@ public final class CodeBlock extends Block implements CodeRendering { }); } + @Nullable private SKFunction getFunctionFromSkillCollection( ReadOnlySkillCollection skills, FunctionIdBlock fBlock) { String skillName = fBlock.getSkillName(); // Function in the global skill if ((skillName == null || skillName.isEmpty()) && skills.hasFunction(fBlock.getFunctionName())) { - SKFunction function = - skills.getFunction(fBlock.getFunctionName(), SKFunction.class); + SKFunction function = skills.getFunction(fBlock.getFunctionName(), SKFunction.class); return function; } diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/CodeRendering.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/CodeRendering.java index 49347d9816..bc0088f6a9 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/CodeRendering.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/CodeRendering.java @@ -5,17 +5,13 @@ import com.microsoft.semantickernel.orchestration.SKContext; import reactor.core.publisher.Mono; -import javax.annotation.Nullable; - -/// -/// Interface of dynamic blocks that need async IO to be rendered. -/// +/** Interface of dynamic blocks that need async IO to be rendered. */ public interface CodeRendering { - /// - /// Render the block using the given context, potentially using external I/O. - /// - /// SK execution context - /// Rendered content - @Nullable + /** + * Render the block using the given context, potentially using external I/O. + * + * @param context SK execution context + * @return Rendered content + */ Mono renderCodeAsync(SKContext context); } diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/FunctionIdBlock.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/FunctionIdBlock.java index 8b9acec649..31bc46f087 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/FunctionIdBlock.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/FunctionIdBlock.java @@ -13,7 +13,7 @@ public final class FunctionIdBlock extends Block implements TextRendering { public FunctionIdBlock(String content) { super(content, BlockTypes.FunctionId); - String[] functionNameParts = this.getContent().split("\\."); + String[] functionNameParts = this.getContent().split("\\.", -1); if (functionNameParts.length > 2) { throw new RuntimeException( "A function name can contain at most one dot separating the skill name from the" @@ -36,6 +36,7 @@ public final class FunctionIdBlock extends Block implements TextRendering { return this.getContent(); } + @Override public boolean isValid() { if (!this.getContent().matches("^[a-zA-Z0-9_.]*$")) { // errorMsg = "The function identifier is empty"; diff --git a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/VarBlock.java b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/VarBlock.java index afb6c3da64..e73662b718 100644 --- a/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/VarBlock.java +++ b/java/semantickernel-core/src/main/java/com/microsoft/semantickernel/templateengine/blocks/VarBlock.java @@ -72,4 +72,8 @@ public final class VarBlock extends Block implements TextRendering { return true; } + + public String getName() { + return name; + } } diff --git a/java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.memory.MemoryStore$Builder b/java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.memory.MemoryStore$Builder new file mode 100644 index 0000000000..9fbed01682 --- /dev/null +++ b/java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.memory.MemoryStore$Builder @@ -0,0 +1 @@ +com.microsoft.semantickernel.memory.VolatileMemoryStore$Builder \ No newline at end of file diff --git a/java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.ContextVariables$Builder.java b/java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.ContextVariables$Builder similarity index 100% rename from java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.ContextVariables$Builder.java rename to java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.ContextVariables$Builder diff --git a/java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.SKContext$Builder b/java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.SKContext$Builder index 9a2bcaba7e..1d200d14da 100644 --- a/java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.SKContext$Builder +++ b/java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.SKContext$Builder @@ -1 +1 @@ -com.microsoft.semantickernel.orchestration.DefaultSemanticSKContext$Builder \ No newline at end of file +com.microsoft.semantickernel.orchestration.DefaultSKContext$Builder \ No newline at end of file diff --git a/java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.WritableContextVariables$Builder.java b/java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.WritableContextVariables$Builder similarity index 100% rename from java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.WritableContextVariables$Builder.java rename to java/semantickernel-core/src/main/resources/META-INF/services/com.microsoft.semantickernel.orchestration.WritableContextVariables$Builder diff --git a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/DefaultKernelTest.java b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/DefaultKernelTest.java index 858242a2d2..03e1470a90 100644 --- a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/DefaultKernelTest.java +++ b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/DefaultKernelTest.java @@ -7,11 +7,8 @@ import com.azure.ai.openai.models.Completions; import com.azure.ai.openai.models.CompletionsOptions; import com.microsoft.semantickernel.builders.SKBuilders; import com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletion; -import com.microsoft.semantickernel.extensions.KernelExtensions; -import com.microsoft.semantickernel.orchestration.ContextVariables; import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import com.microsoft.semantickernel.textcompletion.TextCompletion; @@ -22,12 +19,13 @@ import org.mockito.Mockito; import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; +import reactor.util.function.Tuple3; import reactor.util.function.Tuples; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import java.util.stream.Collectors; public class DefaultKernelTest { @@ -51,17 +49,15 @@ public class DefaultKernelTest { "ChatBot", null, null, - new PromptTemplateConfig.CompletionConfig( - 0.7, 0.5, 0, 0, 2000, new ArrayList<>())); + new PromptTemplateConfig.CompletionConfig(0.7, 0.5, 0, 0, 2000)); - ContextVariables variables = SKBuilders.variables().build(); - - CompletionSKContext readOnlySkContext = - chat.buildContext(variables, null, null) + SKContext readOnlySkContext = + SKBuilders.context() + .build(kernel) .setVariable("history", "") .setVariable("user_input", "A"); - CompletionSKContext result = chat.invokeAsync(readOnlySkContext, null).block(); + SKContext result = chat.invokeAsync(readOnlySkContext, null).block(); if (result == null) { Assertions.fail(); @@ -88,14 +84,11 @@ public class DefaultKernelTest { Kernel kernel = buildKernel(model, client); CompletionSKFunction function = - kernel.importSkill( - "FunSkill", - KernelExtensions.importSemanticSkillFromDirectory( - "../../samples/skills", "FunSkill")) + kernel.importSkillFromDirectory("FunSkill", "../../samples/skills") .getFunction("joke", CompletionSKFunction.class); - Mono mono = function.invokeAsync("time travel to dinosaur age"); - CompletionSKContext result = mono.block(); + Mono mono = function.invokeAsync("time travel to dinosaur age"); + SKContext result = mono.block(); String expected = "WRITE EXACTLY ONE JOKE or HUMOROUS STORY ABOUT THE TOPIC BELOW\n" @@ -150,27 +143,48 @@ public class DefaultKernelTest { * This if the client is prompted with "Tell me a joke", the mocked client would * respond with "This is a joke" */ - public static OpenAIAsyncClient mockCompletionOpenAIAsyncClient( Tuple2... responses) { - List, String>> matchers = + List, String, Consumer>> matchers = Arrays.stream(responses) .map( response -> - Tuples., String>of( - arg -> arg.contains(response.getT1()), - response.getT2())) + Tuples + ., String, Consumer> + of( + arg -> + arg.contains( + response.getT1()), + response.getT2(), + (arg) -> {})) .collect(Collectors.toList()); - return mockCompletionOpenAIAsyncClientMatchers(matchers.toArray(new Tuple2[0])); + return DefaultKernelTest.mockCompletionOpenAIAsyncClientMatchAndAssert( + matchers.toArray(new Tuple3[0])); } - public static OpenAIAsyncClient mockCompletionOpenAIAsyncClientMatchers( + public static OpenAIAsyncClient mockCompletionOpenAIAsyncClientMatch( Tuple2, String>... responses) { + + Tuple3[] withAssert = + Arrays.stream(responses) + .map( + spec -> + Tuples + ., String, Consumer> + of(spec.getT1(), spec.getT2(), arg -> {})) + .collect(Collectors.toList()) + .toArray(new Tuple3[0]); + + return mockCompletionOpenAIAsyncClientMatchAndAssert(withAssert); + } + + public static OpenAIAsyncClient mockCompletionOpenAIAsyncClientMatchAndAssert( + Tuple3, String, Consumer>... responses) { OpenAIAsyncClient openAIAsyncClient = Mockito.mock(OpenAIAsyncClient.class); - for (Tuple2, String> response : responses) { + for (Tuple3, String, Consumer> response : responses) { Choice choice = Mockito.mock(Choice.class); Mockito.when(choice.getText()).thenReturn(response.getT2()); @@ -184,12 +198,30 @@ public class DefaultKernelTest { Mockito.any(String.class), Mockito.argThat( it -> response.getT1().matches(it.getPrompt().get(0))))) + .then( + (arg) -> { + response.getT3() + .accept( + ((CompletionsOptions) arg.getArgument(1)) + .getPrompt() + .get(0)); + return Mono.just(completions); + }) .thenReturn(Mono.just(completions)); Mockito.when( openAIAsyncClient.getCompletions( Mockito.any(String.class), Mockito.argThat(it -> response.getT1().matches(it)))) + .then( + (arg) -> { + response.getT3() + .accept( + ((CompletionsOptions) arg.getArgument(1)) + .getPrompt() + .get(0)); + return Mono.just(completions); + }) .thenReturn(Mono.just(completions)); } return openAIAsyncClient; @@ -211,18 +243,16 @@ public class DefaultKernelTest { String prompt = "{{$input}}\n" + "Summarize the content above."; CompletionSKFunction summarize = - SKBuilders.completionFunctions() + SKBuilders.completionFunctions(kernel) .createFunction( prompt, "summarize", null, null, - new PromptTemplateConfig.CompletionConfig( - 0.2, 0.5, 0, 0, 2000, new ArrayList<>())); - kernel.registerSemanticFunction(summarize); + new PromptTemplateConfig.CompletionConfig(0.2, 0.5, 0, 0, 2000)); - Mono mono = summarize.invokeAsync(text); - CompletionSKContext result = mono.block(); + Mono mono = summarize.invokeAsync(text); + SKContext result = mono.block(); String expected = "A block of text\n\nSummarize the content above."; diff --git a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/connectors/ai/openai/chatcompletion/OpenAIChatCompletionTest.java b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/connectors/ai/openai/chatcompletion/OpenAIChatCompletionTest.java index fb6b5d5c9c..22aeaa65be 100644 --- a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/connectors/ai/openai/chatcompletion/OpenAIChatCompletionTest.java +++ b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/connectors/ai/openai/chatcompletion/OpenAIChatCompletionTest.java @@ -63,16 +63,6 @@ public class OpenAIChatCompletionTest { return chatGPT.generateMessageAsync(chatHistory, null).block(); } - private static class runExample { - public final OpenAIChatHistory chatHistory; - public final String reply; - - public runExample(OpenAIChatHistory chatHistory, String reply) { - this.chatHistory = chatHistory; - this.reply = reply; - } - } - @Test public void emptyResponseThrowsError() { OpenAIAsyncClient client = diff --git a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/memory/VolatileMemoryStoreTests.java b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/memory/VolatileMemoryStoreTests.java index ce583d99fc..8538f45ce2 100644 --- a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/memory/VolatileMemoryStoreTests.java +++ b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/memory/VolatileMemoryStoreTests.java @@ -10,12 +10,9 @@ import org.junit.jupiter.api.Test; import reactor.util.function.Tuple2; +import java.time.ZoneId; import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; class VolatileMemoryStoreTests { @@ -196,7 +193,7 @@ class VolatileMemoryStoreTests { new Embedding(Arrays.asList(1f, 2f, 3f)), NULL_ADDITIONAL_METADATA, NULL_KEY, - ZonedDateTime.now()); + ZonedDateTime.now(ZoneId.of("UTC"))); String collection = "test_collection" + this._collectionNum; this._collectionNum++; diff --git a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/orchestration/NativeSKFunctionTest.java b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/orchestration/NativeSKFunctionTest.java index eae849156c..974d18e7d8 100644 --- a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/orchestration/NativeSKFunctionTest.java +++ b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/orchestration/NativeSKFunctionTest.java @@ -17,21 +17,23 @@ import org.mockito.Mockito; import reactor.core.publisher.Mono; +import java.util.Objects; + public class NativeSKFunctionTest { @Test public void singleStringIsBoundToInput() { - class Test { + class SinleStringSkill { @DefineSKFunction public String doSomething(String anInput) { return ""; } } - Test skill = Mockito.spy(new Test()); + SinleStringSkill skill = Mockito.spy(new SinleStringSkill()); FunctionCollection skills = SkillImporter.importSkill(skill, "test", DefaultSkillCollection::new); - Mono ignore = skills.getFunction("doSomething").invokeAsync("foo"); + SKContext ignore = skills.getFunction("doSomething").invokeAsync("foo").block(); Mockito.verify(skill, Mockito.times(1)).doSomething("foo"); } @@ -57,7 +59,7 @@ public class NativeSKFunctionTest { @Test public void annotatedWithNonInputIsBound() { - class With2Inputs { + class With2InputParameters { @DefineSKFunction public String doSomething( @SKFunctionParameters(name = "anInput") String anInput, @@ -65,7 +67,7 @@ public class NativeSKFunctionTest { return ""; } } - With2Inputs skill = Mockito.spy(new With2Inputs()); + With2InputParameters skill = Mockito.spy(new With2InputParameters()); FunctionCollection skills = SkillImporter.importSkill(skill, "test", DefaultSkillCollection::new); ContextVariables variables = SKBuilders.variables().build(); @@ -100,13 +102,13 @@ public class NativeSKFunctionTest { @Test public void noAnnotationCreatesError() { - class DoesNotExist { + class AnnotationDoesNotExist { public String doSomething(String anInput) { return ""; } } - DoesNotExist skill = Mockito.spy(new DoesNotExist()); + AnnotationDoesNotExist skill = Mockito.spy(new AnnotationDoesNotExist()); Assertions.assertThrows( FunctionNotFound.class, () -> { @@ -129,7 +131,7 @@ public class NativeSKFunctionTest { WithContext skill = Mockito.spy(new WithContext()); FunctionCollection skills = SkillImporter.importSkill(skill, "test", DefaultSkillCollection::new); - Mono ignore = skills.getFunction("doSomething").invokeAsync("foo"); + SKContext ignore = skills.getFunction("doSomething").invokeAsync("foo").block(); Mockito.verify(skill, Mockito.only()).doSomething(Mockito.notNull()); } @@ -145,7 +147,7 @@ public class NativeSKFunctionTest { WithMono skill = Mockito.spy(new WithMono()); FunctionCollection skills = SkillImporter.importSkill(skill, "test", DefaultSkillCollection::new); - SKContext result = skills.getFunction("doSomething").invokeAsync("foo").block(); - Assertions.assertEquals("A-RESULT", result.getResult()); + SKContext result = skills.getFunction("doSomething").invokeAsync("foo").block(); + Assertions.assertEquals("A-RESULT", Objects.requireNonNull(result).getResult()); } } diff --git a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example02PipelineTest.java b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example02PipelineTest.java index edf7615834..45862f9ec0 100644 --- a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example02PipelineTest.java +++ b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example02PipelineTest.java @@ -22,7 +22,7 @@ public class Example02PipelineTest { // Load native skill ReadOnlyFunctionCollection text = kernel.importSkill(new TextSkill(), null); - Mono> result = + Mono result = kernel.runAsync( " i n f i n i t e s p a c e ", text.getFunction("LStrip"), diff --git a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example03VariablesTest.java b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example03VariablesTest.java index 5293087e9c..f8964e1eb5 100644 --- a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example03VariablesTest.java +++ b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example03VariablesTest.java @@ -31,7 +31,7 @@ public class Example03VariablesTest { .writableClone() .setVariable("day", "Monday"); - Mono> result = + Mono result = kernel.runAsync( variables, functionCollection.getFunction("AppendDay"), diff --git a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example04CombineLLMPromptsAndNativeCodeTest.java b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example04CombineLLMPromptsAndNativeCodeTest.java index 396e9f970f..d4cc86f6bf 100644 --- a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example04CombineLLMPromptsAndNativeCodeTest.java +++ b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example04CombineLLMPromptsAndNativeCodeTest.java @@ -8,7 +8,6 @@ import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.KernelConfig; import com.microsoft.semantickernel.builders.SKBuilders; import com.microsoft.semantickernel.connectors.ai.openai.textcompletion.OpenAITextCompletion; -import com.microsoft.semantickernel.extensions.KernelExtensions; import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.syntaxexamples.skills.SearchEngineSkill; @@ -41,15 +40,12 @@ public class Example04CombineLLMPromptsAndNativeCodeTest { Kernel kernel = SKBuilders.kernel().setKernelConfig(kernelConfig).build(); kernel.importSkill(new SearchEngineSkill(), null); - kernel.importSkill( - "SummarizeSkill", - KernelExtensions.importSemanticSkillFromDirectory( - "../../samples/skills", "SummarizeSkill")); + kernel.importSkillFromDirectory("SummarizeSkill", "../../samples/skills", "SummarizeSkill"); // Run String ask = "What's the tallest building in South America?"; - Mono> result = + Mono result = kernel.runAsync(ask, kernel.getSkills().getFunction("Search", null)); Assertions.assertEquals( diff --git a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example05UsingThePlannerTest.java b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example05UsingThePlannerTest.java index fac15c2c24..57e4e2b82c 100644 --- a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example05UsingThePlannerTest.java +++ b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example05UsingThePlannerTest.java @@ -1,13 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.syntaxexamples; -import static com.microsoft.semantickernel.DefaultKernelTest.mockCompletionOpenAIAsyncClientMatchers; +import static com.microsoft.semantickernel.DefaultKernelTest.mockCompletionOpenAIAsyncClientMatch; import com.azure.ai.openai.OpenAIAsyncClient; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.KernelConfig; import com.microsoft.semantickernel.builders.SKBuilders; -import com.microsoft.semantickernel.extensions.KernelExtensions; import com.microsoft.semantickernel.planner.sequentialplanner.SequentialPlanner; import org.junit.jupiter.api.Test; @@ -18,14 +17,8 @@ import reactor.util.function.Tuples; public class Example05UsingThePlannerTest { public static SequentialPlanner getPlanner(Kernel kernel) { - kernel.importSkill( - "SummarizeSkill", - KernelExtensions.importSemanticSkillFromDirectory( - "../../samples/skills", "SummarizeSkill")); - kernel.importSkill( - "WriterSkill", - KernelExtensions.importSemanticSkillFromDirectory( - "../../samples/skills", "WriterSkill")); + kernel.importSkillFromDirectory("SummarizeSkill", "../../samples/skills", "SummarizeSkill"); + kernel.importSkillFromDirectory("WriterSkill", "../../samples/skills", "WriterSkill"); return new SequentialPlanner(kernel, null, null); } @@ -39,7 +32,7 @@ public class Example05UsingThePlannerTest { }; OpenAIAsyncClient client = - mockCompletionOpenAIAsyncClientMatchers(Tuples.of(matcher, "A-PLAN")); + mockCompletionOpenAIAsyncClientMatch(Tuples.of(matcher, "A-PLAN")); KernelConfig config = SKBuilders.kernelConfig() @@ -57,6 +50,6 @@ public class Example05UsingThePlannerTest { planner.createPlanAsync( "Write a poem about John Doe, then translate it into Italian.") .block() - .getResult()); + .toEmbeddingString()); } } diff --git a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example06TemplateLanguageTest.java b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example06TemplateLanguageTest.java index 54623c0b58..08f43deb89 100644 --- a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example06TemplateLanguageTest.java +++ b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example06TemplateLanguageTest.java @@ -11,7 +11,6 @@ import com.microsoft.semantickernel.coreskills.TimeSkill; import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.semanticfunctions.PromptTemplate; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; -import com.microsoft.semantickernel.templateengine.PromptTemplateEngine; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; import org.junit.jupiter.api.Assertions; @@ -20,8 +19,6 @@ import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; import reactor.util.function.Tuples; -import java.util.ArrayList; - public class Example06TemplateLanguageTest { @Test @@ -33,14 +30,9 @@ public class Example06TemplateLanguageTest { SKBuilders.kernelConfig() .addTextCompletionService( "text-davinci-003", - kernel -> + kernel1 -> SKBuilders.textCompletionService() .build(client, "text-davinci-003")) - .addTextEmbeddingsGenerationService( - "text-embedding-ada-002", - kernel -> - SKBuilders.textEmbeddingGenerationService() - .build(client, "text-embedding-ada-002")) .build(); Kernel kernel = SKBuilders.kernel().setKernelConfig(kernelConfig).build(); @@ -63,13 +55,15 @@ public class Example06TemplateLanguageTest { + " (morning/afternoon/evening/night)?\n" + "Is it weekend time (weekend/not weekend)?"; - PromptTemplate promptRenderer = SKBuilders.promptTemplate().build(functionDefinition, null); + PromptTemplate promptRenderer = + SKBuilders.promptTemplate() + .withPromptTemplateConfig(new PromptTemplateConfig()) + .withPromptTemplate(functionDefinition) + .build(kernel.getPromptTemplateEngine()); SKContext skContext = SKBuilders.context().build(kernel.getSkills()); - PromptTemplateEngine promptTemplateEngine = SKBuilders.promptTemplateEngine().build(); - - Mono renderedPrompt = promptRenderer.renderAsync(skContext, promptTemplateEngine); + Mono renderedPrompt = promptRenderer.renderAsync(skContext); // Check that it has been rendered Assertions.assertTrue(!renderedPrompt.block().contains("time.Date")); @@ -82,8 +76,7 @@ public class Example06TemplateLanguageTest { null, null, null, - new PromptTemplateConfig.CompletionConfig( - 0, 0, 0, 0, 256, new ArrayList<>())); + new PromptTemplateConfig.CompletionConfig(0, 0, 0, 0, 256)); Assertions.assertEquals("A-RESULT", kindOfDay.invokeAsync("").block().getResult()); } diff --git a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example12SequentialPlanner.java b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example12SequentialPlanner.java new file mode 100644 index 0000000000..08fbc56274 --- /dev/null +++ b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example12SequentialPlanner.java @@ -0,0 +1,196 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.syntaxexamples; // Copyright (c) Microsoft. All rights + +// reserved. + +import static com.microsoft.semantickernel.DefaultKernelTest.mockCompletionOpenAIAsyncClient; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.coreskills.TextSkill; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.planner.PlanningException; +import com.microsoft.semantickernel.planner.actionplanner.Plan; +import com.microsoft.semantickernel.planner.sequentialplanner.SequentialPlanner; +import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction; +import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionInputAttribute; +import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import reactor.util.function.Tuples; + +import java.io.IOException; + +public class Example12SequentialPlanner { + @Test + public void poetrySamplesAsync() { + OpenAIAsyncClient client = + mockCompletionOpenAIAsyncClient( + Tuples.of( + "Create an XML plan step by step", + "\n" + + "\n" + + "\n" + + "\n" + + ""), + Tuples.of("Generate a short funny poem or limerick", "A POEM"), + Tuples.of( + "Translate the input below into Italian", + "a poem translated to italian")); + + SequentialPlanner planner = getSequentialPlanner(client); + Plan plan = + planner.createPlanAsync( + "Write a poem about John Doe, then translate it into Italian, then" + + " convert it to uppercase.") + .block(); + SKContext result = plan.invokeAsync().block(); + Assertions.assertEquals("A POEM TRANSLATED TO ITALIAN", result.getResult()); + } + + @Test + public void resultIsInvalidXml() { + OpenAIAsyncClient client = + mockCompletionOpenAIAsyncClient( + Tuples.of("Create an XML plan step by step", "")); + + SequentialPlanner planner = getSequentialPlanner(client); + Assertions.assertThrows( + PlanningException.class, + () -> { + planner.createPlanAsync( + "Write a poem about John Doe, then translate it into Italian," + + " then convert it to uppercase.") + .block(); + }); + } + + private static SequentialPlanner getSequentialPlanner(OpenAIAsyncClient client) { + Kernel kernel = + SKBuilders.kernel() + .setKernelConfig( + SKBuilders.kernelConfig() + .addTextCompletionService( + "text-davinci-002", + kernel1 -> + SKBuilders.textCompletionService() + .build(client, "text-davinci-002")) + .build()) + .build(); + + kernel.importSkillFromDirectory("SummarizeSkill", "../../samples/skills", "SummarizeSkill"); + kernel.importSkillFromDirectory("WriterSkill", "../../samples/skills", "WriterSkill"); + kernel.importSkill(new TextSkill(), "TextSkill"); + kernel.importSkill(new EmailSkill(), "email"); + + return new SequentialPlanner(kernel, null, null); + } + + public static class EmailSkill { + @DefineSKFunction( + name = "SendEmail", + description = "Given an e-mail and message body, send an email") + public void sendEmail( + @SKFunctionInputAttribute() + @SKFunctionParameters( + description = "The body of the email message to send.", + name = "input") + String input, + @SKFunctionParameters( + description = "The email address to send email to.", + name = "email_address") + String emailAddress) { + System.out.println("Emailing to " + emailAddress + "\n" + input); + } + + @DefineSKFunction( + name = "GetEmailAddressAsync", + description = "Lookup an email address for a person given a name") + public String getEmailAddressAsync( + @SKFunctionInputAttribute() + @SKFunctionParameters( + description = "The name of the person to email.", + name = "input") + String input) { + return "fake@example.com"; + } + } + + @Test + public void emailSamplesAsync() throws IOException { + OpenAIAsyncClient client = + mockCompletionOpenAIAsyncClient( + Tuples.of( + "Create an XML plan step by step", + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""), + Tuples.of( + "Summarize the following text in two sentences or less", + "Once upon a time, in a faraway kingdom, there lived a kind and" + + " just king named Arjun. He ruled over his kingdom with" + + " fairness and compassion, earning him the love and" + + " admiration of his people. However, the kingdom was plagued" + + " by a terrible dragon that lived in the nearby mountains and" + + " terrorized the nearby villages, burning their crops and" + + " homes. The king had tried everything to defeat the dragon," + + " but to no avail. One day, a young woman named Mira" + + " approached the king and offered to help defeat the dragon." + + " She was a skilled archer and claimed that she had a plan to" + + " defeat the dragon once and for all. The king was skeptical," + + " but desperate for a solution, so he agreed to let her try." + + " Mira set out for the dragon's lair and, with the help of" + + " her trusty bow and arrow, she was able to strike the dragon" + + " with a single shot through its heart, killing it instantly." + + " The people rejoiced and the kingdom was at peace once" + + " again. The king was so grateful to Mira that he asked her" + + " to marry him and she agreed. They ruled the kingdom" + + " together, ruling with fairness and compassion, just as" + + " Arjun had done before. They lived happily ever after, with" + + " the people of the kingdom remembering Mira as the brave" + + " young woman who saved them from the dragon."), + Tuples.of("Translate the input below", "a poem translated to french")); + + SequentialPlanner planner = getSequentialPlanner(client); + + Plan plan = + planner.createPlanAsync( + "Summarize an input, translate to french, and e-mail to John Doe") + .block(); + + System.out.println("Original plan:"); + System.out.println(plan.toPlanString()); + + String input = + "Once upon a time, in a faraway kingdom, there lived a kind and just king named" + + " Arjun. He ruled over his kingdom with fairness and compassion, earning him" + + " the love and admiration of his people. However, the kingdom was plagued by" + + " a terrible dragon that lived in the nearby mountains and terrorized the" + + " nearby villages, burning their crops and homes. The king had tried" + + " everything to defeat the dragon, but to no avail. One day, a young woman" + + " named Mira approached the king and offered to help defeat the dragon. She" + + " was a skilled archer and claimed that she had a plan to defeat the dragon" + + " once and for all. The king was skeptical, but desperate for a solution, so" + + " he agreed to let her try. Mira set out for the dragon's lair and, with the" + + " help of her trusty bow and arrow, she was able to strike the dragon with a" + + " single shot through its heart, killing it instantly. The people rejoiced" + + " and the kingdom was at peace once again. The king was so grateful to Mira" + + " that he asked her to marry him and she agreed. They ruled the kingdom" + + " together, ruling with fairness and compassion, just as Arjun had done" + + " before. They lived happily ever after, with the people of the kingdom" + + " remembering Mira as the brave young woman who saved them from the dragon."; + + plan.invokeAsync(input).block(); + } +} diff --git a/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example28ActionPlanner.java b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example28ActionPlanner.java new file mode 100644 index 0000000000..70a3f29ea4 --- /dev/null +++ b/java/semantickernel-core/src/test/java/com/microsoft/semantickernel/syntaxexamples/Example28ActionPlanner.java @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.syntaxexamples; + +import static com.microsoft.semantickernel.DefaultKernelTest.mockCompletionOpenAIAsyncClient; + +import com.azure.ai.openai.OpenAIAsyncClient; +import com.microsoft.semantickernel.DefaultKernelTest; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.planner.PlanningException; +import com.microsoft.semantickernel.planner.actionplanner.ActionPlanner; +import com.microsoft.semantickernel.planner.actionplanner.Plan; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import reactor.util.function.Tuples; + +public class Example28ActionPlanner { + + @Test + public void functionsArePassedToRequest() { + OpenAIAsyncClient client = + DefaultKernelTest.mockCompletionOpenAIAsyncClientMatchAndAssert( + Tuples.of( + arg -> arg.contains("Generate a short funny"), + "A-RESULT", + arg -> {}), + Tuples.of( + arg -> arg.contains("plan"), + "{\"plan\":{\n" + + "\"rationale\": \"the list contains a function that can turn" + + " a scenario into a short and entertaining poem\",\n" + + "\"function\": \"WriterSkill.ShortPoem\",\n" + + "\"parameters\": {\n" + + "\"input\": \"Cleopatra\"\n" + + "}}}", + request -> { + Assertions.assertTrue( + request.contains("WriterSkill.NovelOutline")); + Assertions.assertTrue( + request.contains("WriterSkill.Brainstorm")); + Assertions.assertTrue(request.contains("WriterSkill.Acronym")); + Assertions.assertTrue( + request.contains("WriterSkill.ShortPoem")); + Assertions.assertTrue(request.contains("WriterSkill.EmailTo")); + Assertions.assertTrue(request.contains("WriterSkill.EmailGen")); + Assertions.assertTrue(request.contains("WriterSkill.Rewrite")); + Assertions.assertTrue( + request.contains("WriterSkill.Translate")); + + Assertions.assertTrue( + request.contains("SummarizeSkill.Topics")); + Assertions.assertTrue( + request.contains( + "SummarizeSkill.MakeAbstractReadable")); + Assertions.assertTrue( + request.contains("SummarizeSkill.Notegen")); + Assertions.assertTrue( + request.contains("SummarizeSkill.Summarize")); + Assertions.assertTrue( + request.contains("SummarizeSkill.Topics")); + })); + + ActionPlanner planner = createPlanner(client); + String goal = "Write a poem about Cleopatra."; + Plan plan = planner.createPlanAsync(goal).block(); + String planResult = plan.invokeAsync(goal).block().getResult(); + Assertions.assertEquals("A-RESULT", planResult); + } + + @Test + public void runActionPlan() { + OpenAIAsyncClient client = + mockCompletionOpenAIAsyncClient( + Tuples.of("Generate a short funny", "A-RESULT"), + Tuples.of( + "plan", + "{\"plan\":{\n" + + "\"rationale\": \"the list contains a function that can turn" + + " a scenario into a short and entertaining poem\",\n" + + "\"function\": \"WriterSkill.ShortPoem\",\n" + + "\"parameters\": {\n" + + "\"input\": \"Cleopatra\"\n" + + "}}}")); + + ActionPlanner planner = createPlanner(client); + String goal = "Write a poem about Cleopatra."; + Plan plan = planner.createPlanAsync(goal).block(); + String planResult = plan.invokeAsync(goal).block().getResult(); + Assertions.assertEquals("A-RESULT", planResult); + } + + @Test + public void resultIsAnonExistentFunction() { + OpenAIAsyncClient client = + mockCompletionOpenAIAsyncClient( + Tuples.of( + "plan", + "{\n" + + " \"plan\" : {\n" + + " \"function\" : \"TextSkill.md5sum\",\n" + + " \"parameters\" : {\n" + + " \"input\" : \"Cleopatra\"\n" + + " },\n" + + " \"rationale\" : \"the list contains a function that" + + " can calculate the md5sum of a string\"\n" + + " }\n" + + "}\n")); + + ActionPlanner planner = createPlanner(client); + String goal = "Write a poem about Cleopatra."; + + Assertions.assertThrows( + PlanningException.class, + () -> { + Plan plan = planner.createPlanAsync(goal).block(); + String planResult = plan.invokeAsync(goal).block().getResult(); + Assertions.assertEquals("A-RESULT", planResult); + }); + } + + private static ActionPlanner createPlanner(OpenAIAsyncClient client) { + Kernel kernel = + SKBuilders.kernel() + .setKernelConfig( + SKBuilders.kernelConfig() + .addTextCompletionService( + "text-davinci-002", + kernel1 -> + SKBuilders.textCompletionService() + .build(client, "text-davinci-002")) + .build()) + .build(); + + kernel.importSkillFromDirectory("SummarizeSkill", "../../samples/skills", "SummarizeSkill"); + kernel.importSkillFromDirectory("WriterSkill", "../../samples/skills", "WriterSkill"); + + // Create an instance of ActionPlanner. + // The ActionPlanner takes one goal and returns a single function to execute. + ActionPlanner planner = new ActionPlanner(kernel, null); + return planner; + } +} diff --git a/java/semantickernel-extensions-parent/actionplanner-extension/pom.xml b/java/semantickernel-extensions-parent/actionplanner-extension/pom.xml new file mode 100644 index 0000000000..90fc3333e8 --- /dev/null +++ b/java/semantickernel-extensions-parent/actionplanner-extension/pom.xml @@ -0,0 +1,29 @@ + + + + 4.0.0 + + + com.microsoft.semantickernel.extensions + semantickernel-extensions-parent + 0.2.0-SNAPSHOT + ../pom.xml + + + actionplanner-extension + + + + + com.microsoft.semantickernel + semantickernel-api + + + com.fasterxml.jackson.core + jackson-databind + compile + + + + diff --git a/java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/ActionPlanResponse.java b/java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/ActionPlanResponse.java new file mode 100644 index 0000000000..fabcbecaf5 --- /dev/null +++ b/java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/ActionPlanResponse.java @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.planner.actionplanner; + +/// +/// Plan data structure returned by the basic planner semantic function +/// +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + +public class ActionPlanResponse { + + public static class PlanData { + /// + /// Rationale given by the LLM for choosing the function + /// + public final String rationale; + + /// + /// Name of the function chosen + /// + public final String function; + + /// + /// Parameter values + /// + public final Map parameters; + + @JsonCreator + public PlanData( + @JsonProperty("plan") String rationale, + @JsonProperty("function") String function, + @JsonProperty("parameters") Map parameters) { + this.rationale = rationale; + this.function = function; + this.parameters = parameters; + } + } + + public final PlanData plan; + + @JsonCreator + public ActionPlanResponse(@JsonProperty("plan") PlanData plan) { + this.plan = plan; + } +} diff --git a/java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/ActionPlanner.java b/java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/ActionPlanner.java new file mode 100644 index 0000000000..cac6d1c328 --- /dev/null +++ b/java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/ActionPlanner.java @@ -0,0 +1,339 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.planner.actionplanner; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.planner.PlanningException; +import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; +import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; +import com.microsoft.semantickernel.skilldefinition.annotations.DefineSKFunction; +import com.microsoft.semantickernel.skilldefinition.annotations.SKFunctionParameters; +import com.microsoft.semantickernel.textcompletion.CompletionRequestSettings; +import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import javax.annotation.Nullable; + +/// +/// Action Planner allows to select one function out of many, to achieve a given goal. +/// The planner implement the Intent Detection pattern, uses the functions registered +/// in the kernel to see if there's a relevant one, providing instructions to call the +/// function and the rationale used to select it. The planner can also return +/// "no function" is nothing relevant is available. +/// The rationale is currently available only in the prompt, we might include it in +/// the Plan object in future. +/// +public class ActionPlanner { + private static final Logger LOGGER = LoggerFactory.getLogger(ActionPlanner.class); + + private static final String StopSequence = "#END-OF-PLAN"; + private static final String SkillName = "this"; + + // Planner semantic function + private final CompletionSKFunction plannerFunction; + + // Context used to access the list of functions in the kernel + private SKContext context; + private Kernel kernel; + + // TODO: allow to inject skill store + + /** + * Initialize a new instance of the ActionPlanner class + * + * @param kernel The semantic kernel instance + * @param prompt prompt override + */ + public ActionPlanner(Kernel kernel, @Nullable String prompt) { + // Verify.NotNull(kernel); + String promptTemplate; + if (prompt == null) { + promptTemplate = read("skprompt.txt"); + } else { + promptTemplate = prompt; + } + + this.plannerFunction = + SKBuilders.completionFunctions(kernel) + .createFunction( + promptTemplate, + null, + SkillName, + null, + new PromptTemplateConfig.CompletionConfig( + 0.0, 0.0, 0.0, 0.0, 1024)); + + kernel.importSkill(this, SkillName); + + this.kernel = kernel; + this.context = SKBuilders.context().with(kernel.getSkills()).build(); + } + + public static String read(String file) { + try (InputStream stream = ActionPlanner.class.getResourceAsStream(file)) { + byte[] buffer = new byte[stream.available()]; + stream.read(buffer); + return new String(buffer, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public Mono createPlanAsync(String goal) { + if (goal == null || goal.isEmpty()) { + throw new PlanningException( + PlanningException.ErrorCodes.InvalidGoal, "The goal specified is empty"); + } + + context = this.context.update(goal); + + return this.plannerFunction + .invokeAsync(this.context, new CompletionRequestSettings(0, 0, 0, 0, 2048)) + .handle( + (result, sink) -> { + try { + sink.next(parsePlan(goal, result)); + } catch (Exception e) { + sink.error(e); + } + }); + } + + private Plan parsePlan(String goal, SKContext result) { + ActionPlanResponse planData; + + try { + planData = new ObjectMapper().readValue(result.getResult(), ActionPlanResponse.class); + } catch (Exception e) { + throw new PlanningException( + PlanningException.ErrorCodes.InvalidPlan, + "Plan parsing error, invalid JSON", + e); + } + + if (planData == null) { + throw new PlanningException( + PlanningException.ErrorCodes.InvalidPlan, + "The plan deserialized to a null object"); + } + + CompletionSKFunction function = getPlanFunction(planData); + + if (function == null) { + // No function was found - return a plan with no steps. + return new Plan(goal, () -> kernel.getSkills()); + } else { + return new Plan(goal, () -> kernel.getSkills(), function); + } + } + + public CompletionSKFunction getPlanFunction(ActionPlanResponse planData) { + + if (planData.plan.function.contains(".")) { + String[] parts = planData.plan.function.split("\\.", -1); + CompletionSKFunction skill = + context.getSkills().getFunction(parts[0], parts[1], CompletionSKFunction.class); + + if (skill == null) { + throw new PlanningException( + PlanningException.ErrorCodes.InvalidPlan, + "Unknown skill " + planData.plan.function); + } + return skill; + + } else if (!planData.plan.function.isEmpty()) { + CompletionSKFunction function = + context.getSkills() + .getFunction(planData.plan.function, CompletionSKFunction.class); + + if (function == null) { + throw new PlanningException( + PlanningException.ErrorCodes.InvalidPlan, + "Unknown skill " + planData.plan.function); + } + + return function; + } + return null; + } + + // TODO: use goal to find relevant functions in a skill store + /// + /// Native function returning a list of all the functions in the current context, + /// excluding functions in the planner itself. + /// + /// Currently unused. Will be used to handle long lists of functions. + /// Function execution context + /// List of functions, formatted accordingly to the prompt + + @DefineSKFunction( + name = "listOfFunctions", + description = "List all functions available in the kernel") + @Nullable + public String listOfFunctions( + @SKFunctionParameters( + name = "goal", + description = "The current goal processed by the planner", + defaultValue = "") + String goal, + SKContext context) { + // Verify.NotNull(context.Skills); + ReadOnlySkillCollection skills = context.getSkills(); + + return this.populateList(skills); + } + + // TODO: generate string programmatically + // TODO: use goal to find relevant examples + @DefineSKFunction( + name = "GoodExamples", + description = "List a few good examples of plans to generate") + public String goodExamples( + @SKFunctionParameters( + name = "goal", + description = "The current goal processed by the planner", + defaultValue = "") + String goal, + SKContext context) { + + return "\n" + + "[EXAMPLE]\n" + + "- List of functions:\n" + + "// Read a file.\n" + + "FileIOSkill.ReadAsync\n" + + "Parameter \"path\": Source file.\n" + + "// Write a file.\n" + + "FileIOSkill.WriteAsync\n" + + "Parameter \"path\": Destination file. (default value: sample.txt)\n" + + "Parameter \"content\": File content.\n" + + "// Get the current time.\n" + + "TimeSkill.Time\n" + + "No parameters.\n" + + "// Makes a POST request to a uri.\n" + + "HttpSkill.PostAsync\n" + + "Parameter \"body\": The body of the request.\n" + + "- End list of functions.\n" + + "Goal: create a file called \"something.txt\".\n" + + "{\"plan\":{\n" + + "\"rationale\": \"the list contains a function that allows to create files\",\n" + + "\"function\": \"FileIOSkill.WriteAsync\",\n" + + "\"parameters\": {\n" + + "\"path\": \"something.txt\",\n" + + "\"content\": null\n" + + "}}}\n" + + "#END-OF-PLAN\n"; + } + + @DefineSKFunction( + name = "EdgeCaseExamples", + description = "List a few edge case examples of plans to handle") + public String edgeCaseExamples( + @SKFunctionParameters( + name = "goal", + description = "The current goal processed by the planner", + defaultValue = "") + String goal, + SKContext context) { + return "\n" + + "[EXAMPLE]\n" + + "- List of functions:\n" + + "// Get the current time.\n" + + "TimeSkill.Time\n" + + "No parameters.\n" + + "// Write a file.\n" + + "FileIOSkill.WriteAsync\n" + + "Parameter \"path\": Destination file. (default value: sample.txt)\n" + + "Parameter \"content\": File content.\n" + + "// Makes a POST request to a uri.\n" + + "HttpSkill.PostAsync\n" + + "Parameter \"body\": The body of the request.\n" + + "// Read a file.\n" + + "FileIOSkill.ReadAsync\n" + + "Parameter \"path\": Source file.\n" + + "- End list of functions.\n" + + "Goal: tell me a joke.\n" + + "{\"plan\":{\n" + + "\"rationale\": \"the list does not contain functions to tell jokes or" + + " something funny\",\n" + + "\"function\": \"\",\n" + + "\"parameters\": {\n" + + "}}}\n" + + "#END-OF-PLAN\n"; + } + + private String populateList(ReadOnlySkillCollection skills) { + return skills.getAllFunctions().getAll().stream() + .filter(skill -> !SkillName.equalsIgnoreCase(skill.getSkillName())) + .map( + func -> { + StringBuilder result = new StringBuilder(); + // Function description + if (func.getDescription() != null) { + result.append("// " + addPeriod(func.getDescription()) + "\n"); + } else { + LOGGER.warn( + "{0}.{1} is missing a description", + func.getSkillName(), func.getName()); + result.append( + "// Function " + + func.getSkillName() + + "." + + func.getName() + + ".\n"); + } + + // Function name + result.append(func.getSkillName() + "." + func.getName() + "\n"); + + func.describe() + .getParameters() + .forEach( + parameter -> { + String description; + if (parameter.getDescription() == null + || parameter.getDefaultValue().isEmpty()) { + description = parameter.getName(); + } else { + description = parameter.getDescription(); + } + + String defaultValueString; + if (parameter.getDefaultValue() == null + || parameter.getDefaultValue().isEmpty()) { + defaultValueString = ""; + } else { + defaultValueString = + " (default value: " + + parameter.getDefaultValue() + + ")"; + } + + result.append( + "Parameter \"" + + parameter.getName() + + "\": " + + addPeriod(description) + + " " + + defaultValueString + + "\n"); + }); + + return result.toString(); + }) + .reduce("", (a, b) -> a + b, (a, b) -> a + b); + } + + private static String addPeriod(String x) { + return x.endsWith(".") ? x : x + "."; + } +} diff --git a/java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/Plan.java b/java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/Plan.java new file mode 100644 index 0000000000..547f3a2c39 --- /dev/null +++ b/java/semantickernel-extensions-parent/actionplanner-extension/src/main/java/com/microsoft/semantickernel/planner/actionplanner/Plan.java @@ -0,0 +1,569 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.planner.actionplanner; + +import com.microsoft.semantickernel.Kernel; +import com.microsoft.semantickernel.Verify; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.memory.SemanticTextMemory; +import com.microsoft.semantickernel.orchestration.*; +import com.microsoft.semantickernel.planner.PlanningException; +import com.microsoft.semantickernel.skilldefinition.FunctionView; +import com.microsoft.semantickernel.skilldefinition.KernelSkillsSupplier; +import com.microsoft.semantickernel.skilldefinition.ParameterView; +import com.microsoft.semantickernel.textcompletion.CompletionRequestSettings; +import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; + +import reactor.core.publisher.Mono; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import javax.annotation.Nullable; + +/** Standard Semantic Kernel callable plan. Plan is used to create trees of SKFunctions. */ +public class Plan extends AbstractSkFunction { + + private static final Pattern s_variablesRegex = Pattern.compile("\\$(\\w+)", Pattern.MULTILINE); + + private static final String DefaultResultKey = "PLAN.RESULT"; + + // State of the plan + private ContextVariables state; + + // Steps of the plan + private final List steps = new ArrayList<>(); + + // Parameters for the plan, used to pass information to the next step + @Nullable private ContextVariables parameters; + + // Outputs for the plan, used to pass information to the caller + private List outputs = new ArrayList<>(); + + @Nullable private SKFunction function = null; + + // Gets the next step index. + public int nextStepIndex; + + public Plan( + String goal, + ContextVariables state, + @Nullable KernelSkillsSupplier kernelSkillsSupplier) { + super( + DelegateTypes.ContextSwitchInSKContextOutTaskSKContext, + new ArrayList<>(), + Plan.class.getName(), + "", + goal, + kernelSkillsSupplier); + this.state = state; + } + + public Plan(String goal, @Nullable KernelSkillsSupplier kernelSkillsSupplier) { + this(goal, SKBuilders.variables().build(), kernelSkillsSupplier); + } + + public Plan( + CompletionSKFunction function, + ContextVariables state, + List functionOutputs, + KernelSkillsSupplier kernelSkillsSupplier) { + super( + DelegateTypes.ContextSwitchInSKContextOutTaskSKContext, + function.describe().getParameters(), + function.getSkillName(), + function.getName(), + function.getDescription(), + kernelSkillsSupplier); + + this.parameters = null; + this.function = function; + this.outputs = new ArrayList<>(functionOutputs); + this.state = state; + } + + public Plan( + SKFunction function, + ContextVariables parameters, + ContextVariables state, + List functionOutputs, + KernelSkillsSupplier kernelSkillsSupplier) { + super( + DelegateTypes.ContextSwitchInSKContextOutTaskSKContext, + function.describe().getParameters(), + function.getSkillName(), + function.getName(), + function.getDescription(), + kernelSkillsSupplier); + this.parameters = parameters; + this.function = function; + this.outputs = new ArrayList<>(functionOutputs); + this.state = state; + } + + public Plan( + CompletionSKFunction function, + List functionOutputs, + KernelSkillsSupplier kernelSkillsSupplier) { + this(function, SKBuilders.variables().build(), functionOutputs, kernelSkillsSupplier); + } + + public Plan(CompletionSKFunction function, KernelSkillsSupplier kernelSkillsSupplier) { + this(function, SKBuilders.variables().build(), new ArrayList<>(), kernelSkillsSupplier); + } + + public Plan( + String goal, KernelSkillsSupplier kernelSkillsSupplier, CompletionSKFunction... steps) { + this(goal, kernelSkillsSupplier); + this.addSteps(steps); + } + + @Override + public void registerOnKernel(Kernel kernel) { + kernel.registerSemanticFunction(this); + } + + @Override + @Nullable + public FunctionView describe() { + if (function == null) { + return null; + } + return function.describe(); + } + + @Override + @Nullable + public Class getType() { + if (function == null) { + return null; + } + return function.getType(); + } + + /** Gets whether the plan has a next step */ + public boolean hasNextStep() { + return this.nextStepIndex < this.steps.size(); + } + + /** + * Adds one or more existing plans to the end of the current plan as steps. When you add a plan + * as a step to the current plan, the steps of the added plan are executed after the steps of + * the current plan have completed. + * + * @param steps The plans to add as steps to the current plan. + */ + public void addSteps(Plan... steps) { + this.steps.addAll(Arrays.asList(steps)); + } + + /** + * Adds one or more new steps to the end of the current plan. When you add a new step to the + * current plan, it is executed after the previous step in the plan has completed. Each step can + * be a function call or another plan. + * + * @param steps The steps to add to the current plan. + */ + public void addSteps(CompletionSKFunction... steps) { + List plans = + Arrays.stream(steps) + .map(step -> new Plan(step, getSkillsSupplier())) + .collect(Collectors.toList()); + this.steps.addAll(plans); + } + + public void setParameters(ContextVariables functionVariables) { + this.parameters = functionVariables; + } + + public void addOutputs(List outputs) { + this.outputs.addAll(outputs); + } + + /** + * Runs the next step in the plan using the provided kernel instance and variables. + * + *

This method executes the next step in the plan using the specified kernel instance and + * context variables. The context variables contain the necessary information for executing the + * plan, such as the memory, skills, and logger. The method returns a task representing the + * asynchronous execution of the plan's next step. + * + * @param kernel The kernel instance to use for executing the plan. + * @param variables The variables to use for the execution of the plan. + * @return A task representing the asynchronous execution of the plan's next step. + */ + public Mono runNextStepAsync(Kernel kernel, ContextVariables variables) { + SKContext context = + SKBuilders.context() + .with(variables) + .with(kernel.getSkills()) + .with(kernel.getMemory()) + .build(); + return this.invokeNextStepAsync(context, null); + } + + /** + * Invoke the next step of the plan + * + * @param context Context to use + * @param settings Settings to use + * @return The updated plan + */ + public Mono invokeNextStepAsync( + SKContext context, @Nullable CompletionRequestSettings settings) { + if (this.hasNextStep()) { + Plan step = this.steps.get(this.nextStepIndex); + this.nextStepIndex++; + + // Merge the state with the current context variables for step execution + ContextVariables functionVariables = + this.getNextStepVariables(context.getVariables(), step); + + // Execute the step + SKContext functionContext = + SKBuilders.context() + .with(functionVariables) + .with(context.getSemanticMemory()) + .with(context.getSkills()) + .build(); + + return step.invokeAsync(functionContext, settings) + .handle( + (result, sink) -> { + String resultValue = result.getResult(); + if (resultValue == null) { + sink.error(new RuntimeException("No result returned")); + return; + } + + resultValue = resultValue.trim(); + + // Update state with result + this.state = this.state.writableClone().update(resultValue); + + // Update Plan Result in State with matching outputs (if any) + + ArrayList intersection = new ArrayList<>(outputs); + intersection.retainAll(step.outputs); + // Update Plan Result in State with matching outputs (if any) + if (intersection.size() > 0) { + String currentPlanResult = this.state.get(DefaultResultKey); + + if (currentPlanResult != null) { + String newVal = + String.join( + "\n", + currentPlanResult.trim(), + resultValue); + this.state = + this.state + .writableClone() + .setVariable(DefaultResultKey, newVal); + } else { + this.state = + this.state + .writableClone() + .setVariable(DefaultResultKey, resultValue); + } + } + + String finalResultValue = resultValue; + step.outputs.forEach( + item -> { + if (result.getVariables().asMap().containsKey(item)) { + String variable = result.getVariables().get(item); + if (variable != null) { + this.state = + this.state + .writableClone() + .setVariable(item, variable); + } + } else { + this.state = + this.state + .writableClone() + .setVariable( + item, finalResultValue); + } + }); + + sink.next(this); + }); + } + return Mono.just(this); + } + + public Mono invokeAsync( + @Nullable String input, + @Nullable CompletionRequestSettings settings, + @Nullable SemanticTextMemory memory) { + + WritableContextVariables variables = state.writableClone(); + + if (input != null) { + variables.update(input); + } + + KernelSkillsSupplier skillsSupplier = getSkillsSupplier(); + + SKContext context = + SKBuilders.context() + .with(variables) + .with(memory) + .with(skillsSupplier == null ? null : skillsSupplier.get()) + .build(); + + return this.invokeAsync(context, settings); + } + + @Override + public Mono invokeAsync( + @Nullable String input, + @Nullable SKContext context, + @Nullable CompletionRequestSettings settings) { + if (input != null) { + this.state = state.writableClone().update(input); + } + return super.invokeAsync(input, context, settings); + } + + @Override + protected Mono invokeAsyncInternal( + SKContext contextIn, @Nullable CompletionRequestSettings settings) { + if (function != null) { + return ((SKFunction) function).invokeAsync(contextIn, settings); + } else { + return Mono.from( + (subscriber) -> { + // loop through steps and execute until completion + SKContext c = addVariablesToContext(this.state, contextIn); + this.invokeNextStepAsync(c, null) + .map(plan -> this.updateContextWithOutputs(contextIn)) + .subscribe(subscriber); + }) + .repeat(this::hasNextStep) + .last(); + } + } + + /** + * Expand variables in the input string. + * + * @param variables Variables to use for expansion. + * @param input Input string to expand. + * @return Expanded string. + */ + private String expandFromVariables(ContextVariables variables, String input) { + String result = input; + Matcher matches = s_variablesRegex.matcher(input); + while (matches.find()) { + + String varName = matches.group(1); + + String value = variables.get(varName); + + if (value == null) { + value = state.get(varName); + } + + if (value == null) { + value = ""; + } + + result = result.replaceAll("\\$" + varName, value); + } + + return result; + } + + /** Add any missing variables from a plan state variables to the context. */ + private static SKContext addVariablesToContext(ContextVariables vars, SKContext context) { + WritableContextVariables clone = context.getVariables().writableClone(); + vars.asMap() + .entrySet() + .forEach( + entry -> { + if (Verify.isNullOrEmpty(clone.get(entry.getKey()))) { + clone.setVariable(entry.getKey(), entry.getValue()); + } + }); + + return SKBuilders.context().clone(context).with(clone).build(); + } + + /** + * Update the context with the outputs from the current step. + * + * @param context The context to update. + * @return The updated context. + */ + private SKContext updateContextWithOutputs(SKContext context) { + String resultString = this.state.get(DefaultResultKey); + if (Verify.isNullOrEmpty(resultString)) { + resultString = state.getInput(); + } + + if (resultString == null) { + throw new PlanningException( + PlanningException.ErrorCodes.UnknownError, "Step did not produce an output"); + } + + context = context.update(resultString); + + WritableContextVariables variables = context.getVariables().writableClone(); + + String result = context.getResult(); + + if (result != null) { + outputs.forEach( + item -> { + variables.setVariable(item, result); + }); + } + + return SKBuilders.context().clone(context).with(variables).build(); + } + + /** + * Get the variables for the next step in the plan. + * + * @param variables The current context variables. + * @param step The next step in the plan. + * @return The context variables for the next step in the plan. + */ + private ContextVariables getNextStepVariables(ContextVariables variables, Plan step) { + // Priority for Input + // - Parameters (expand from variables if needed) + // - SKContext.Variables + // - Plan.State + // - Empty if sending to another plan + // - Plan.Description + + String input = ""; + if (step.parameters != null && !Verify.isNullOrEmpty(step.parameters.getInput())) { + input = + this.expandFromVariables( + variables, Objects.requireNonNull(step.parameters.getInput())); + } else if (!Verify.isNullOrEmpty(variables.getInput())) { + input = Objects.requireNonNull(variables.getInput()); + } else if (!Verify.isNullOrEmpty(this.state.getInput())) { + input = Objects.requireNonNull(this.state.getInput()); + } else if (steps.size() > 0) { + input = ""; + } else if (!Verify.isNullOrEmpty(this.getDescription())) { + input = this.getDescription(); + } + + WritableContextVariables stepVariables = + SKBuilders.variables().build(Objects.requireNonNull(input)).writableClone(); + + // Priority for remaining stepVariables is: + // - Function Parameters (pull from variables or state by a key value) + // - Step Parameters (pull from variables or state by a key value) + FunctionView functionParameters = step.describe(); + if (functionParameters != null) { + for (ParameterView param : functionParameters.getParameters()) { + if (param.getName().equals(ContextVariables.MAIN_KEY)) { + continue; + } + + if (!Verify.isNullOrEmpty(variables.get(param.getName()))) { + String variable = Objects.requireNonNull(variables.get(param.getName())); + stepVariables.setVariable(param.getName(), variable); + } else if (!Verify.isNullOrEmpty(this.state.get(param.getName()))) { + String variable = Objects.requireNonNull(this.state.get(param.getName())); + stepVariables.setVariable(param.getName(), variable); + } + } + } + + if (step.parameters != null) { + step.parameters + .asMap() + .entrySet() + .forEach( + item -> { + // Don't overwrite variable values that are already set + if (!Verify.isNullOrEmpty(stepVariables.get(item.getKey()))) { + return; + } + + String expandedValue = + this.expandFromVariables(variables, item.getValue()); + if (expandedValue != null + && !expandedValue.equalsIgnoreCase(item.getValue())) { + stepVariables.setVariable(item.getKey(), expandedValue); + } else if (!Verify.isNullOrEmpty(variables.get(item.getKey()))) { + String variable = variables.get(item.getKey()); + if (variable != null) { + stepVariables.setVariable(item.getKey(), variable); + } + } else if (!Verify.isNullOrEmpty(state.get(item.getKey()))) { + String variable = state.get(item.getKey()); + if (variable != null) { + stepVariables.setVariable(item.getKey(), variable); + } + } else { + if (expandedValue != null) { + stepVariables.setVariable(item.getKey(), expandedValue); + } + } + }); + } + + return stepVariables; + } + + public String toPlanString() { + return toPlanString(" "); + } + + private String toPlanString(String indent) { + String goalHeader = indent + "Goal: " + getDescription() + "\n\n" + indent + "}Steps:\n"; + + String stepItems = + String.join( + "\n", + steps.stream() + .map(step -> stepToString(indent, step)) + .collect(Collectors.toList())); + + return goalHeader + stepItems; + } + + private static String stepToString(String indent, Plan step) { + if (step.steps.size() == 0) { + String skillName = step.getSkillName(); + String stepName = step.getName(); + + String parameters = + String.join( + " ", + step.getParameters().stream() + .map(param -> param.getName() + " " + param.getDefaultValue()) + .collect(Collectors.toList())); + + if (!Verify.isNullOrEmpty(parameters)) { + parameters = " " + parameters; + } + + String outputs = step.outputs.stream().findFirst().orElse(""); + if (!Verify.isNullOrEmpty(outputs)) { + outputs = " => " + outputs; + } + + return indent + + indent + + "- " + + String.join(".", skillName, stepName) + + parameters + + outputs; + } + + return step.toPlanString(indent + indent); + } +} diff --git a/java/semantickernel-extensions-parent/actionplanner-extension/src/main/resources/com/microsoft/semantickernel/planner/actionplanner/skprompt.txt b/java/semantickernel-extensions-parent/actionplanner-extension/src/main/resources/com/microsoft/semantickernel/planner/actionplanner/skprompt.txt new file mode 100644 index 0000000000..969262d556 --- /dev/null +++ b/java/semantickernel-extensions-parent/actionplanner-extension/src/main/resources/com/microsoft/semantickernel/planner/actionplanner/skprompt.txt @@ -0,0 +1,11 @@ +A planner takes a list of functions, a goal, and chooses which function to use. +For each function the list includes details about the input parameters. +[START OF EXAMPLES] +{{this.GoodExamples}} +{{this.EdgeCaseExamples}} +[END OF EXAMPLES] +[REAL SCENARIO STARTS HERE] +- List of functions: +{{this.ListOfFunctions}} +- End list of functions. +Goal: {{ $input }} \ No newline at end of file diff --git a/java/semantickernel-extensions-parent/pom.xml b/java/semantickernel-extensions-parent/pom.xml index 036ff946f5..6668cfa313 100644 --- a/java/semantickernel-extensions-parent/pom.xml +++ b/java/semantickernel-extensions-parent/pom.xml @@ -18,5 +18,6 @@ sequentialplanner-extensions + actionplanner-extension diff --git a/java/semantickernel-extensions-parent/sequentialplanner-extensions/pom.xml b/java/semantickernel-extensions-parent/sequentialplanner-extensions/pom.xml index 41baac6db1..add087a82d 100644 --- a/java/semantickernel-extensions-parent/sequentialplanner-extensions/pom.xml +++ b/java/semantickernel-extensions-parent/sequentialplanner-extensions/pom.xml @@ -19,6 +19,10 @@ com.microsoft.semantickernel semantickernel-api + + com.microsoft.semantickernel.extensions + actionplanner-extension + diff --git a/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/SequentialPlannerSKContext.java b/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/SequentialPlannerSKContext.java deleted file mode 100644 index 3d4c132a3b..0000000000 --- a/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/SequentialPlannerSKContext.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -package com.microsoft.semantickernel.planner; - -import com.microsoft.semantickernel.orchestration.SKContext; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; - -import reactor.core.publisher.Mono; - -import javax.annotation.Nullable; - -public interface SequentialPlannerSKContext extends SKContext { - - public static final String PlannerMemoryCollectionName = "Planning.SKFunctionsManual"; - - public static final String PlanSKFunctionsAreRemembered = "Planning.SKFunctionsAreRemembered"; - - public Mono getFunctionsManualAsync( - @Nullable String semanticQuery, @Nullable SequentialPlannerRequestSettings config); -} diff --git a/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/DefaultSequentialPlannerSKContext.java b/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/DefaultSequentialPlannerSKContext.java index 89bbe9b584..80a8d50351 100644 --- a/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/DefaultSequentialPlannerSKContext.java +++ b/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/DefaultSequentialPlannerSKContext.java @@ -1,16 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. package com.microsoft.semantickernel.planner.sequentialplanner; -import static com.microsoft.semantickernel.planner.SequentialPlannerSKContext.PlanSKFunctionsAreRemembered; -import static com.microsoft.semantickernel.planner.SequentialPlannerSKContext.PlannerMemoryCollectionName; - import com.microsoft.semantickernel.memory.MemoryQueryResult; import com.microsoft.semantickernel.memory.NullMemory; import com.microsoft.semantickernel.memory.SemanticTextMemory; +import com.microsoft.semantickernel.orchestration.SKContext; import com.microsoft.semantickernel.orchestration.SKFunction; -import com.microsoft.semantickernel.planner.SequentialPlannerRequestSettings; import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -21,9 +17,12 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; public class DefaultSequentialPlannerSKContext { - private final CompletionSKContext delegate; + public static final String PlannerMemoryCollectionName = "Planning.SKFunctionsManual"; - public DefaultSequentialPlannerSKContext(CompletionSKContext delegate) { + public static final String PlanSKFunctionsAreRemembered = "Planning.SKFunctionsAreRemembered"; + private final SKContext delegate; + + public DefaultSequentialPlannerSKContext(SKContext delegate) { this.delegate = delegate; } @@ -41,7 +40,7 @@ public class DefaultSequentialPlannerSKContext { config = new SequentialPlannerRequestSettings(); } - Mono>> functions = + Mono>> functions = getAvailableFunctionsAsync(config, semanticQuery); return functions.map( @@ -61,7 +60,7 @@ public class DefaultSequentialPlannerSKContext { // functions /// A list of functions that are available to the user based on the semantic query and // the excluded skills and functions. - public Mono>> getAvailableFunctionsAsync( + public Mono>> getAvailableFunctionsAsync( SequentialPlannerRequestSettings config, @Nullable String semanticQuery) { Set excludedSkills = config.getExcludedSkills(); Set excludedFunctions = config.getExcludedFunctions(); @@ -70,14 +69,14 @@ public class DefaultSequentialPlannerSKContext { // context.ThrowIfSkillCollectionNotSet(); ReadOnlySkillCollection skills = delegate.getSkills(); - List> functions; + List> functions; if (skills != null) { functions = skills.getAllFunctions().getAll(); } else { functions = Collections.emptyList(); } - List> availableFunctions = + List> availableFunctions = functions.stream() /* // Is there any function that should be excluded? @@ -93,8 +92,8 @@ public class DefaultSequentialPlannerSKContext { && !excludedFunctions.contains(s.getName())) .collect(Collectors.toList()); - Comparator> comparator = - Comparator., String>comparing(SKFunction::getSkillName) + Comparator> comparator = + Comparator., String>comparing(SKFunction::getSkillName) .thenComparing(SKFunction::getName); if (semanticQuery == null @@ -104,7 +103,7 @@ public class DefaultSequentialPlannerSKContext { || config.getRelevancyThreshold() == null) { // If no semantic query is provided, return all available functions. // If a Memory provider has not been registered, return all available functions. - TreeSet> result = new TreeSet<>(comparator); + TreeSet> result = new TreeSet<>(comparator); result.addAll(availableFunctions); return Mono.just(result); } else { @@ -115,8 +114,7 @@ public class DefaultSequentialPlannerSKContext { SemanticTextMemory updatedMemory = updatedContext.getSemanticMemory(); if (updatedMemory == null) { - return Mono.just( - new ArrayList>()); + return Mono.just(new ArrayList>()); } // Search for functions that match the semantic query. return updatedMemory @@ -138,26 +136,26 @@ public class DefaultSequentialPlannerSKContext { .map(SKFunction::getName) .collect(Collectors.toList()); - List> missingFunctions = + List> missingFunctions = getMissingFunctions( includedFunctions, availableFunctions, added); - ArrayList> res = new ArrayList<>(memories); + ArrayList> res = new ArrayList<>(memories); res.addAll(missingFunctions); return res; }) .map( res -> { - TreeSet> result = new TreeSet<>(comparator); + TreeSet> result = new TreeSet<>(comparator); result.addAll(availableFunctions); return result; }); } } - private static List> getMissingFunctions( + private static List> getMissingFunctions( Set includedFunctions, - List> availableFunctions, + List> availableFunctions, List added) { return includedFunctions.stream() .filter(func -> !added.contains(func)) @@ -168,8 +166,8 @@ public class DefaultSequentialPlannerSKContext { .collect(Collectors.toList()); } - public Mono>> getRelevantFunctionsAsync( - List> availableFunctions, List memories) { + public Mono>> getRelevantFunctionsAsync( + List> availableFunctions, List memories) { return Flux.fromIterable(memories) .map( memoryEntry -> @@ -190,7 +188,7 @@ public class DefaultSequentialPlannerSKContext { ///

/// The SKContext to save the functions to. /// The available functions to save. - Mono rememberFunctionsAsync(List> availableFunctions) { + Mono rememberFunctionsAsync(List> availableFunctions) { // Check if the functions have already been saved to memory. if (delegate.getVariables().asMap().containsKey(PlanSKFunctionsAreRemembered)) { return Mono.just(delegate); diff --git a/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/SequentialPlanParser.java b/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/SequentialPlanParser.java new file mode 100644 index 0000000000..618f96db1c --- /dev/null +++ b/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/SequentialPlanParser.java @@ -0,0 +1,213 @@ +// Copyright (c) Microsoft. All rights reserved. +package com.microsoft.semantickernel.planner.sequentialplanner; + +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.orchestration.SKFunction; +import com.microsoft.semantickernel.orchestration.WritableContextVariables; +import com.microsoft.semantickernel.planner.PlanningException; +import com.microsoft.semantickernel.planner.actionplanner.Plan; +import com.microsoft.semantickernel.skilldefinition.FunctionView; +import com.microsoft.semantickernel.skilldefinition.ReadOnlySkillCollection; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Objects; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +/** Parse sequential plan text into a plan. */ +public class SequentialPlanParser { + private static final Logger LOGGER = LoggerFactory.getLogger(SequentialPlanParser.class); + + // The tag name used in the plan xml for the user's goal/ask. + private static final String GoalTag = "goal"; + + // The tag name used in the plan xml for the solution. + private static final String SolutionTag = "plan"; + + // The tag name used in the plan xml for a step that calls a skill function. + private static final String FunctionTag = "function."; + + // The attribute tag used in the plan xml for setting the context variable name to set the + // output of a function to. + private static final String SetContextVariableTag = + "setContextVariable".toLowerCase(Locale.ROOT); + + // The attribute tag used in the plan xml for appending the output of a function to the final + // result for a plan. + private static final String AppendToResultTag = "appendToResult".toLowerCase(Locale.ROOT); + + /** + * Convert a plan xml string to a plan + * + * @param xmlString The plan xml string + * @param goal The goal for the plan + * @param context The semantic kernel context + * @return The plan + * @throws PlanningException + */ + public static Plan toPlanFromXml(String xmlString, String goal, SKContext context) + throws PlanningException { + + try { + DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + + Document doc = + db.parse( + new ByteArrayInputStream( + ("" + xmlString + "") + .getBytes(StandardCharsets.UTF_8))); + + NodeList solution = doc.getElementsByTagName(SolutionTag); + + Plan plan = new Plan(goal, context::getSkills); + + for (int i = 0; i < solution.getLength(); i++) { + Node solutionNode = solution.item(i); + String parentNodeName = solutionNode.getNodeName(); + + for (int j = 0; j < solutionNode.getChildNodes().getLength(); j++) { + Node childNode = solutionNode.getChildNodes().item(j); + if (childNode.getNodeName().equals("#text")) { + if (childNode.getNodeValue() != null + && !childNode.getNodeValue().trim().isEmpty()) { + plan.addSteps( + new Plan(childNode.getNodeValue().trim(), context::getSkills)); + } + continue; + } + + if (childNode.getNodeName().toLowerCase(Locale.ROOT).startsWith(FunctionTag)) { + String[] skillFunctionNameParts = + childNode.getNodeName().split(FunctionTag, -1); + String skillFunctionName = ""; + + if (skillFunctionNameParts.length > 1) { + skillFunctionName = skillFunctionNameParts[1]; + } + + String skillName = getSkillName(skillFunctionName); + String functionName = getFunctionName(skillFunctionName); + + ReadOnlySkillCollection skills = context.getSkills(); + if (functionName != null + && !functionName.isEmpty() + && skills.hasFunction(skillName, functionName)) { + SKFunction skillFunction = + Objects.requireNonNull( + context.getSkills().getFunctions(skillName)) + .getFunction(functionName, SKFunction.class); + + WritableContextVariables functionVariables = + getFunctionVariables(skillFunction, childNode); + List functionOutputs = getFunctionOutputs(childNode); + List functionResults = getFunctionResults(childNode); + + Plan planStep = + new Plan( + skillFunction, + functionVariables, + SKBuilders.variables().build(), + functionOutputs, + context::getSkills); + + plan.addOutputs(functionResults); + plan.addSteps(planStep); + } else { + LOGGER.trace( + "{}: appending function node {}", + parentNodeName, + skillFunctionName); + plan.addSteps(new Plan(childNode.getTextContent(), context::getSkills)); + } + + continue; + } + + plan.addSteps(new Plan(childNode.getTextContent(), context::getSkills)); + } + } + return plan; + + } catch (RuntimeException | ParserConfigurationException | IOException | SAXException e) { + throw new PlanningException( + PlanningException.ErrorCodes.InvalidPlan, "Failed to parse plan xml.", e); + } + } + + private static WritableContextVariables getFunctionVariables( + SKFunction skillFunction, Node node) { + WritableContextVariables functionVariables = SKBuilders.variables().build().writableClone(); + + FunctionView description = skillFunction.describe(); + + if (description != null) { + description + .getParameters() + .forEach( + p -> { + functionVariables.setVariable(p.getName(), p.getDefaultValue()); + }); + } + + if (node.getAttributes() != null) { + for (int k = 0; k < node.getAttributes().getLength(); k++) { + Node attr = node.getAttributes().item(k); + String nodeName = attr.getNodeName().toLowerCase(Locale.ROOT); + if (!nodeName.equals(SetContextVariableTag) + && !nodeName.equals(AppendToResultTag)) { + functionVariables.setVariable(attr.getNodeName(), attr.getTextContent()); + } + } + } + return functionVariables; + } + + private static List getFunctionOutputs(Node node) { + List functionOutputs = new ArrayList<>(); + for (int k = 0; k < node.getAttributes().getLength(); k++) { + Node attr = node.getAttributes().item(k); + String nodeName = attr.getNodeName().toLowerCase(Locale.ROOT); + if (nodeName.equals(SetContextVariableTag) || nodeName.equals(AppendToResultTag)) { + functionOutputs.add(attr.getTextContent()); + } + } + return functionOutputs; + } + + private static List getFunctionResults(Node node) { + List functionResults = new ArrayList<>(); + for (int k = 0; k < node.getAttributes().getLength(); k++) { + Node attr = node.getAttributes().item(k); + String nodeName = attr.getNodeName().toLowerCase(Locale.ROOT); + if (nodeName.equals(AppendToResultTag)) { + functionResults.add(attr.getTextContent()); + } + } + return functionResults; + } + + private static String getSkillName(String skillFunctionName) { + String[] skillFunctionNameParts = skillFunctionName.split("\\.", -1); + return skillFunctionNameParts.length > 0 ? skillFunctionNameParts[0] : ""; + } + + private static String getFunctionName(String skillFunctionName) { + String[] skillFunctionNameParts = skillFunctionName.split("\\.", -1); + return skillFunctionNameParts.length > 1 ? skillFunctionNameParts[1] : skillFunctionName; + } +} diff --git a/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/SequentialPlanner.java b/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/SequentialPlanner.java index 45dea1d4fc..3735ed1eb3 100644 --- a/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/SequentialPlanner.java +++ b/java/semantickernel-extensions-parent/sequentialplanner-extensions/src/main/java/com/microsoft/semantickernel/planner/sequentialplanner/SequentialPlanner.java @@ -3,43 +3,36 @@ package com.microsoft.semantickernel.planner.sequentialplanner; import com.microsoft.semantickernel.Kernel; import com.microsoft.semantickernel.builders.FunctionBuilders; -import com.microsoft.semantickernel.planner.SequentialPlannerRequestSettings; +import com.microsoft.semantickernel.builders.SKBuilders; +import com.microsoft.semantickernel.orchestration.SKContext; +import com.microsoft.semantickernel.planner.PlanningException; +import com.microsoft.semantickernel.planner.actionplanner.Plan; import com.microsoft.semantickernel.semanticfunctions.PromptTemplateConfig; -import com.microsoft.semantickernel.textcompletion.CompletionSKContext; import com.microsoft.semantickernel.textcompletion.CompletionSKFunction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import reactor.core.publisher.Mono; -import java.util.ArrayList; - import javax.annotation.Nullable; -/// -/// A planner that uses semantic function to create a sequential plan. -/// +/** A planner that uses semantic function to create a sequential plan. */ public class SequentialPlanner { + + private static final Logger LOGGER = LoggerFactory.getLogger(SequentialPlanner.class); private static final String StopSequence = "